什么是多线程

线程
进程

为什么要有多线程

应用场景

多线程的两个概念

并发 在同一时刻 有多个指令在单个CPU上交替执行
并行 在同一时刻 有多个指令在多个CPU上同时执行

多线程的实现方式

  1. 继承Thread类的方式进行实现
  2. 实现Runnable接口的方式进行实现
  3. 利用Callable接口和Future接口方式实现

继承Thread类的方式进行实现

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
public class MyThread1 extends Thread{
@Override
public void run() {
// 书写线程要执行的代码
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "hello world");
}
}
}



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Main {
public static void main(String[] args) {
/**
* 多线程的第一种启动方式:
* 1. 定义一个类继承Thread
* 2. 重写run方法
* 3. 创建子类的对象 并启动线程
*/

MyThread1 t1 = new MyThread1();
MyThread1 t2 = new MyThread1();
t1.setName("线程1");
t2.setName("线程2");
// 开启线程
t1.start();
t2.start();
}
}

实现Runnable接口的方式进行实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14

public class MyRun implements Runnable{
@Override
public void run() {

// 书写线程要执行的代码
for (int i = 0; i < 100; i++) {
// 获取到当前线程的对象
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + "hello");
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

public class Main {
public static void main(String[] args) {
/**
* 多线程的第二种启动方式:
* 1. 自己定义一个类实现Runnable接口
* 2,重写里面的run方法
* 3. 创建自己的类的对象
* 4. 创建一个Thread类的对象 并开启线程
*/

// 创建MyRun对象
MyRun myRun = new MyRun();
// 表示多线程要执行的任务
Thread t3 = new Thread(myRun);
Thread t4 = new Thread(myRun);
t3.setName("线程3");
t4.setName("线程4");
// 开启线程
t3.start();
t4.start();

}
}

实现Callable接口

1
2
3
4
5
6
7
8
9
10
11
12
13

public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 求1~100之间的和
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {

/**
* 多线程的第三种实现方式
* 特点: 可以获取到多线程运行的结果
* 1. 创建一个类MyCallable 实现Callable接口
* 2. 重写call (是有返回值的, 表示多线程运行的结果)
* 3. 创建MyCallable的对象(表示多线程要执行的任务)
* 4,创建FutureTask的对象(作用管理多线程运行的结果)
* 5.创建Thread类的对象 并启动(表示线程)
*/

MyCallable myCallable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
Thread t5 = new Thread(futureTask);
t5.start();

// 获取多线程运行的结果
Integer ans = futureTask.get();
System.out.println(ans);

}
}


常见的成员方法

基础方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyThread2 extends Thread{
public MyThread2() {

}

public MyThread2(String name) {
super(name);
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

System.out.println(getName() + "@" + i);
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class Main {
public static void main(String[] args) throws InterruptedException {
/**
* String getName()
* void setName(String name)
* 细节:
* 如果没有给线程设置名字 线程也是有默认的名字
* 格式: Thread-X (X序号 , 从0开始)
* 如果要给线程设置名字 可以用set方法 也可以构造方法设置
*
* static Thread currentThread() 获取当前线程对象
* 细节:
* 当JVM虚拟机启动之后 会自动的启动多条线程
* 其中有一条就叫main方法 并执行里面的代码
*
* static void sleep(long time) 让线程休眠指定的时间 单位毫秒
* 细节:
* 哪条线程执行到这个方法 那么哪条线程就会在这里停留对应的时间
* 时间到了之后 线程会自动醒来 继续执行下面的其他代码
*
*/

MyThread2 t1 = new MyThread2("aa");
MyThread2 t2 = new MyThread2("bb");

t1.start();
t2.start();

// 哪条线程执行到这个方法 此时获取的就是哪条线程的对象
Thread t = Thread.currentThread();
String name = t.getName();
System.out.println(name); // main

Thread.sleep(5000);

}
}

线程优先级

java采取的线程调度 是抢占式调度

1
2
3
4
5
6
7
8
9
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Main {
public static void main(String[] args) {
/*
* setPriority(int newPriority) 设置线程的优先级
* final int getPriority() 获取线程的优先级
* */

MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread("aa");
Thread t2 = new Thread("bb");

// 默认为5 最小为1 最大为10
t1.setPriority(1);
t2.setPriority(10);

t1.start();
t2.start();
}
}

守护线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

public class Main {
public static void main(String[] args) {
/*
* public final void setDaemon(boolean on) 设置为守护线程
*
* 细节:
* 当其他的非守护线程执行完毕后 守护线程会陆续结束(不是马上结束)
* 即 当非守护线程结束后 那么守护线程也没有存在的必要了
*
* */

MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();

t1.setName("aa");
t2.setName("bb");

// 把第二个线程设置为守护线程
t2.setDaemon(true);
}
}

礼让线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Main {
public static void main(String[] args) {
/**
* public static void yield() 出让线程/礼让线程
*
* 尽可能让结果均匀一点 但不是绝对的 用的比较少
*/
MyThread1 t1 = new MyThread1();
MyThread1 t2 = new MyThread1();

t1.setName("aa");
t2.setName("bb");

t1.start();
t2.start();
}
}

插入线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Main {
public static void main(String[] args) {
/**
* public static void yield() 出让线程/礼让线程
*
* 尽可能让结果均匀一点 但不是绝对的 用的比较少
*/
MyThread1 t1 = new MyThread1();
t1.setName("土豆");
t1.start();

// 表示把t这个线程 插入到当前线程之前
// t: 土豆
// 当前线程 main线程

for (int i = 0; i < 10; i++) {
System.out.println("main线程" + i);
}
}
}

线程的生命周期

问: sleep方法会让线程睡眠 睡眠时间到了之后 立马就会执行下面的代码吗?
不会因为线程在进入就绪状态后还要有CPU资源才行

线程安全

示例:
买票引发的安全问题:

  1. 相同的票出现了多次
  2. 出现了超出范围的票
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MyThread extends Thread{
// 表示这个类所有的对象 都共享ticket数据
static int ticket = 0;

@Override
public void run() {
while (true){
if(ticket < 100){
try {
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}

ticket++;
System.out.println(getName() + "正在卖第" + ticket );
}else {
break;
}

}
}
}

原因:
线程执行的随机性

同步代码块

把操作共享数据的代码锁起来

格式

1
2
3
synchronized (锁){
操作共享数据的代码
}

特点1: 锁默认打开 有一个线程进去了 锁自动关闭
特点2: 里面的代码全部执行完毕 线程出来 锁自动打开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class MyThread extends Thread{
// 表示这个类所有的对象 都共享ticket数据
static int ticket = 0;

// 锁对象 一定要是唯一的
// static Object obj = new Object();

@Override
public void run() {

while (true){
// synchronized 要写在循环的内部
// 通常会使用当前类的字节码文件 作为锁 (字节码文件符合唯一的特性)
synchronized (MyThread.class){
// 同步代码块
if(ticket < 100){
try {
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}

ticket++;
System.out.println(getName() + "正在卖第" + ticket );
}else {
break;
}
}

}
}
}

同步方法

就是把synchronized 关键字加到方法上

格式:
修饰符就是把synchronized 返回值类型 方法名(方法参数){…}

特点1: 同步方法是锁住方法里面所有的代码
特点2: 锁对象不能自己指定
非静态: this (当前方法的调用者)
静态: 当前类的字节码文件对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class MyRunnable implements Runnable{
// 共享数据 使用了同步方法的方法 没有必要加static
int ticket = 0;
@Override
public void run() {
/*
* 1.循环
* 2.同步代码块(同步方法)
* 3.判断共享数据是否到了末尾 如果到了末尾
* 4.判断共享数据是否到了末尾 如果到了末尾
* */
while (true){
if (method()){
break;
}
}
}

private synchronized boolean method(){
if (ticket == 100){
return true;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}

ticket++;
System.out.println(Thread.currentThread().getName() + " " + ticket);
}
return false;
}
}

lock锁

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

public class MyThread extends Thread{
// 表示这个类所有的对象 都共享ticket数据
static int ticket = 0;
static Lock lock = new ReentrantLock();

@Override
public void run() {
while (true){
lock.lock();

try {
if(ticket == 100){
lock.unlock();
break;
}else {
Thread.sleep(10);
ticket++;
System.out.println(getName() + "在卖第" + ticket);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
lock.unlock();
}

}
}
}

死锁

等待唤醒机制

生产者和消费者(等待唤醒机制)

等待唤醒机制 基础实现

代码示例

消费者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 消费者
public class Foodie extends Thread{

@Override
public void run() {
/**
* 1. 循环
* 2. 同步代码块
* 3. 判断共享数据是否到了末尾(到了末尾)
* 4. 判断共享数据是否到了末尾(没有到末尾 执行核心逻辑)
*/

while (true){
synchronized (Desk.lock){
if(Desk.count == 0){
break;
}else {
// 先判断桌子上是否有面条
if(Desk.foodFlag == 0){
try {
Desk.lock.wait(); // 让当前线程与锁进行绑定
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else{
// 把吃的总数 -1
Desk.count--;
// 如果有 就开吃
System.out.println("正在吃 还能吃" + Desk.count + "碗!" );
// 吃完后 唤醒厨师继续做
Desk.lock.notifyAll();
// 修改桌子的状态
Desk.foodFlag = 0;
}

}
}
}
}
}


生产者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Cook extends Thread{
@Override
public void run() {
while (true){
synchronized (Desk.lock){
if (Desk.count == 0){
break;
}else {
// 判断桌子上是否有食物
if (Desk.foodFlag == 1){
// 如果有 就等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
// 如果没有 就制作食物
System.out.println("厨房做了一碗面条");
// 修改桌子上的食物状态
Desk.foodFlag = 1;
// 唤醒等待的消费者开吃
Desk.lock.notifyAll();
}


}
}
}
}
}


Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

public class ThreadDemo {
public static void main(String[] args) {
/**
*
* 需求: 完成生产者和消费者(等待唤醒机制)的代码
* 实现线程轮流交替执行的效果
*
*/

Cook cook = new Cook();
Foodie foodie = new Foodie();

// 线程设置名字
cook.setName("厨师");
foodie.setName("吃货");

// 开启线程
cook.start();
foodie.start();
}
}


等待唤醒机制 阻塞队列实现

消费者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.concurrent.ArrayBlockingQueue;

// 消费者
public class Foodie extends Thread{

ArrayBlockingQueue<String>queue;

public Foodie(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}

@Override
public void run() {
while (true){
// 不断从阻塞队列中获取面条
try {
String food = queue.take();
// 输出语句在锁的外面 但不会影响数据的安全性
System.out.println(food);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}


生产者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.util.concurrent.ArrayBlockingQueue;

public class Cook extends Thread{

ArrayBlockingQueue<String>queue;

public Cook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}

@Override
public void run() {
while (true){
// 不断的把面放到阻塞队列当中
try {
queue.put("面条");
// 输出语句在锁的外面 但不会影响数据的安全性
System.out.println("厨师放了一碗面条");
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}
}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.util.concurrent.ArrayBlockingQueue;

public class ThreadDemo {
public static void main(String[] args) {
/**
*
* 需求: 利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码
* 细节:
* 生产者和消费者必须使用同一个阻塞队列
*/

// 1,创建阻塞队列
// 有界阻塞队列
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);

// 2. 创建线程的对象 并把阻塞队列传递过去
Cook cook = new Cook(queue);
Foodie foodie = new Foodie(queue);

// 3. 开启线程
cook.start();
foodie.start();

}
}

线程的状态

线程池

代码实现

代码示例

1
2
3
4
5
6
7
// 创建一个没有上限的线程池
ExecutorService pool1 = Executors.newCachedThreadPool();

// 方法2
// 创建一个有上限的线程池
ExecutorService pool2 = Executors.newFixedThreadPool(3);

自定义线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.concurrent.*;

public class MyThreadPoolDemo2 {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, //核心线程数量
6, //最大线程数量 不能小于0 且 最大数量 >= 核心线程数量
60, // 空闲线程最大存活时间
TimeUnit.SECONDS,// 时间单位
new ArrayBlockingQueue<>(3), // 任务队列
Executors.defaultThreadFactory(), // 创建线程工厂
new ThreadPoolExecutor.AbortPolicy() // 任务的拒绝策略
);

}
}

线程池多大合适呢?

1
2
3
4
5
6
7
8
public class MyThreadPoolDemo3 {
public static void main(String[] args) {
// 向Java虚拟机返回可用处理器的数目
int count = Runtime.getRuntime().availableProcessors();
System.out.println(count);
}
}