Java-Thread,多线程

须知少时拏云志,曾许人间第一流

基本概念

并发和并行

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

进程和线程

  • 线程是操作系统能够进行运算调度的最小单位。它被包含在进程中,是进程中的实际运作单位。简单理解为:应用软件中相互独立,可以同时运行的功能
  • 进程:是正在运行的程序独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
    动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的
    并发性:任何进程都可以同其他进程一起并发执行
  • 线程:是进程中的单个顺序控制流,是一条执行路径 单线程:一个进程如果只有一条执行路径,则称为单线程程序 多线程:一个进程如果有多条执行路径,则称为多线程程序

多线程的实现方式

  • 继承Thread类
  • 实现Runnable接口
  • 实现Callable接口

继承Thread类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package thread.case1;

public class MyThread extends Thread{
/**
* 重写run方法
*/
@Override
public void run() {
// 书写线程要执行的代码
for (int i = 0; i < 10; i++) {
System.out.println(this.getName()+":hello world!");
}
}
}

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

public class ThreadDemo1 {
public static void main(String[] args) {
/**
*多线程的第一种启动方式:
* 1.自己定义一个类继承Thread
* 2.重写run方法
* 3.创建子类对象,启动线程
*/

MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();

//设置名称
myThread1.setName("线程1");
myThread2.setName("线程2");

//开启线程
myThread1.start();
myThread2.start();

}
}

实现Runnable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package thread.case2;

public class MyRun implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//不能直接调用getname,getname方法实在Thread类中
// 获取当前线程的对象,哪条线程执行到这个方法,就会返回哪条线程的对象
Thread t = Thread.currentThread();
System.out.println(t.getName()+"hello world!");

}
}
}
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
package thread.case2;

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

// 创建MyRun的对象
// 表示多线程要执行的任务
MyRun my1 = new MyRun();
MyRun my2 = new MyRun();

// 创建多线程对象,将要执行的任务放入线程中
Thread thread1 = new Thread(my1);
Thread thread2 = new Thread(my2);

thread1.setName("1:");
thread2.setName("2:");

// 开启线程
thread1.start();
thread2.start();

}
}

实现Callable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package thread.case3;

import java.util.concurrent.Callable;

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
27
28
29
30
31
32
33
package thread.case3;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/**
* 可以获取到多线程运行的结果
* 1、创建一个类MyCallable实现Callable接口
* 2、重写call(是有返回值的,表示多线程的运行结果)
*
* 3、创建MyCallable的对象(表示多线程要执行的任务)
* 4、创建FutureTask的对象(管理多线程的运行结果)
* 5、创建Thread类的对象(表示线程)
*/

MyCallable mc = new MyCallable();

FutureTask<Integer> ft = new FutureTask<>(mc);

Thread t = new Thread(ft);

t.start();

//获取多线程的结果
Integer sum = ft.get();

System.out.println(sum);

}

}

三种实现方式的对比

常见的成员方法


线程的优先级

优先级不是绝对的,而是概率问题

1
2
3
4
5
6
7
8
9
10
package thread.method;

public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package thread.method;

public class ThreadDemo {
public static void main(String[] args) {
MyRunnable mr1 = new MyRunnable();
MyRunnable mr2 = new MyRunnable();

Thread t1 = new Thread(mr1);
Thread t2 = new Thread(mr2);

t1.setName("线程1");
t2.setName("线程2");

// 设置优先级,但优先级并非绝对优先,是概率问题
t1.setPriority(1);
t2.setPriority(10);

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

}
}

守护线程

守护线程,当其他非守护线程结束,守护线程也会陆续结束


出让线程

执行结果会出现某一线程执行完后,执行另一个线程。或者是某一个线程多次连续执行,此时出让线程,会将执行结果尽可能均匀一些,并非绝对。

插入线程

主线程和t线程争夺执行权,使用join方法,将t线程插入到主线程之前

线程的生命周期

线程的安全问题

售票问题,简易的售票存在的问题:
  • 相同的票出现了多次
  • 出现了超出范围的票
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package thread.ThreadSafe1;

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) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(this.getName()+"正在卖第"+ticket+"张票");
}else{
break;
}
}
}
}

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

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) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(this.getName()+"正在卖第"+ticket+"张票");
}else{
break;
}
}
}
}


出现问题一的原因:

一开始,线程1、2、3都会抢夺CPU资源,其中线程1抢到后会睡眠、此时不会执行代码,也不会抢夺CPU资源。这时,线程2会抢夺CPU资源,线程3同理。当线程1睡醒,票号++,还未输出票号,CPU资源倍线程2抢夺,票号++,线程3也如此,这导致票号就会出现重复。

出现问题二的原因:


原理与问题一相同,三条线程抢夺资源,都未执行到break语句,票号++,超出了票号。

解决思路:将操作共享数据的代码锁起来,让线程轮流执行代码块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package thread.ThreadSafe1;

public class ThreadDemo {

public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();

t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");

t1.start();
t2.start();
t3.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
28
29
30
package thread.ThreadSafe1;

public class MyThread extends Thread {

//将变量设置为静态变量,表示这个类的所有对象都共享ticket数据
static int ticket = 0;

//创建锁对象,需要是唯一的,设置为静态变量,共享一个锁对象
static Object o = new Object();

@Override
public void run() {
while (true) {
//同步代码块
synchronized (o) { //MyThread.class
if (ticket < 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(this.getName() + "正在卖第" + ticket + "张票");
} else {
break;
}
}
}
}
}

细节:通常将锁对象设置为当前类的字节码文件,字节码文件对象唯一,因此可以当做锁对象

同步方法

使用同步方法,实现多线程售票

按照同步代码块的方式书写代码,然后将同步代码块抽取成一个方法,将同步代码块标识删除,在方法上添加synchronized,标注方法类型

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
package thread.ThreadSafe2;

public class MyRunnable implements Runnable {

//因为使用Runnable接口实现多线程的,该对象只会创建一次,使用线程去执行该对象,ticket不需要设置为静态变量
int ticket = 0;

@Override
public void run() {
while (true) {
if (sellTicket()) break;
}
}

private synchronized boolean sellTicket() {
if (ticket == 100) {
return true;
} else {
try {
Thread.sleep(100); //sleep方法返回到就绪状态会使3条线程重新争抢CPU,但由于锁的存在就算T2、T3线程抢到了线程,也必须在锁外面排队,等T1执行完毕才能执行,所以说增加了机会
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
}
return false;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package thread.ThreadSafe2;

public class ThreadDemo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();

Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
Thread t3 = new Thread(mr);

t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");

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

如果是单线程,不考虑数据安全,使用StringBuilder

如果是多线程,需要考虑数据安全,使用StringBuffer

lock锁

虽然可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

使用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
31
32
33
34
35
36
37
package thread.lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyThread extends Thread {

//将变量设置为静态变量,表示这个类的所有对象都共享ticket数据
static int ticket = 0;

//使用锁对象
Lock lock = new ReentrantLock();

@Override
public void run() {
while (true) {
//同步代码块
// synchronized (o) {
// 使用锁
lock.lock();
if (ticket < 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(this.getName() + "正在卖第" + ticket + "张票");
} else {
break;
}
// }
// 关锁
lock.unlock();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package thread.lock;


public class ThreadDemo {

public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();

t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");

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

此时会出现的问题是,票号重复、票号超出上限,导致错误的原因是,继承Thread类创建多个MyThread对象,使得锁对象不唯一。


将锁对象设置唯一以后

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
package thread.lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyThread extends Thread {

//将变量设置为静态变量,表示这个类的所有对象都共享ticket数据
static int ticket = 0;

//使用锁对象,将锁对象设置唯一
static Lock lock = new ReentrantLock();

@Override
public void run() {
while (true) {
//同步代码块
// synchronized (o) {
// 使用锁
lock.lock();
if (ticket < 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(this.getName() + "正在卖第" + ticket + "张票");
} else {
break;
}
// }
// 关锁
lock.unlock();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package thread.lock;


public class ThreadDemo {

public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();

t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");

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

此时会出现的问题是:程序未结束运行。原因在于当票号为100时,直接执行到break代码跳出while循环,会将解锁的代码跳过不执行,此时另外两个线程扔在等待执行。解决方法是利用Finally代码块的特性,将上锁的部分用try代码块包围,程序一定会执行finally代码块,必定会解锁。

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
package thread.lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyThread extends Thread {

//将变量设置为静态变量,表示这个类的所有对象都共享ticket数据
static int ticket = 0;

//使用锁对象
static Lock lock = new ReentrantLock();

@Override
public void run() {
while (true) {
// 使用锁
lock.lock();
//将上锁的部分用try代码块包围
try {
if (ticket < 100) {
Thread.sleep(100);
ticket++;
System.out.println(this.getName() + "正在卖第" + ticket + "张票");
} else {
break;
}
} 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
package thread.lock;


public class ThreadDemo {

public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();

t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");

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

死锁

线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
  • 什么情况下会产生死锁?
  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
public class Demo {
public static void main(String[] args) {
Object objA = new Object();
Object objB = new Object();

new Thread(()->{
while(true){
synchronized (objA){
//线程一
synchronized (objB){
System.out.println("小康同学正在走路");
}
}
}
}).start();

new Thread(()->{
while(true){
synchronized (objB){
//线程二
synchronized (objA){
System.out.println("小薇同学正在走路");
}
}
}
}).start();
}
}

注意避免锁的嵌套

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

wait和notify

概述

  • 生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够对多线程编程的理解更加深刻。
  • 所谓生产者消费者问题,实际上主要是包含了两类线程:
    • 一类是生产者线程用于生产数据
    • 一类是消费者线程用于消费数据
  • 为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为


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
package thread.waitandnodify;

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 {
System.out.println(this.getName()+":已经做了一碗了,吃货来吃");
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
//没有,就做
System.out.println(this.getName()+":做好了一碗面");
Desk.foodFlag = 1;
//唤醒和锁绑定的所有 线程 叫吃货 吃
Desk.lock.notifyAll();
}
}
}
}


}
}
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
package thread.waitandnodify;

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 {
System.out.println(this.getName()+":已经做了一碗了,吃货来吃");
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
//没有,就做
System.out.println(this.getName()+":做好了一碗面");
Desk.foodFlag = 1;
//唤醒和锁绑定的所有 线程 叫吃货 吃
Desk.lock.notifyAll();
}
}
}
}


}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package thread.waitandnodify;

import java.util.concurrent.locks.ReentrantLock;

public class Desk {
/**
* 作用:控制生产者和消费者的执行
* 均为共享数据,设置为静态变量
*/

// 是否有面条?有为1,没有为0
public static int foodFlag = 0;

// 总个数 吃货可以吃的个数
public static int count = 10;

// 锁对象
public static Object lock = new Object();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package thread.waitandnodify;

public class ThreadDemo {
public static void main(String[] args) {
//创建两个线程 吃货 厨师
Foodie f = new Foodie();
Cook c = new Cook();

c.setName("厨师");
f.setName("吃货");

f.start();
c.start();
}
}

Desk可以理解为公共资源类,控制生产者、消费者的执行

生产者、消费者中只有一个锁,两个线程由一个锁限制,这使得生产者、消费者线程只能同时执行一个,另外一个只有等待锁释放以后才能执行,实现类互斥访问

当一个线程调用wait方法,它暂时停止运行,静茹等待状态,同时释放锁,等待其他线程唤醒,并重新竞争获取对象的锁

阻塞队列


  • BlockingQueue的核心方法:
    • put(anObject): 将参数放入队列,如果放不进去会阻塞
    • take(): 取出第一个数据,取不到会阻塞
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 Cooker extends Thread {

private ArrayBlockingQueue<String> bd;

public Cooker(ArrayBlockingQueue<String> bd) {
this.bd = bd;
}
// 生产者步骤:
// 1,判断桌子上是否有汉堡包
// 如果有就等待,如果没有才生产。
// 2,把汉堡包放在桌子上。
// 3,叫醒等待的消费者开吃。

@Override
public void run() {
while (true) {
try {
bd.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
27
28
29
30
31
32
public class Foodie extends Thread {
private ArrayBlockingQueue<String> bd;

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

@Override
public void run() {
// 1,判断桌子上是否有汉堡包。
// 2,如果没有就等待。
// 3,如果有就开吃
// 4,吃完之后,桌子上的汉堡包就没有了
// 叫醒等待的生产者继续生产
// 汉堡包的总数量减一

//套路:
//1. while(true)死循环
//2. synchronized 锁,锁对象要唯一
//3. 判断,共享数据是否结束. 结束
//4. 判断,共享数据是否结束. 没有结束
while (true) {
try {
String take = bd.take();
System.out.println("吃货将" + take + "拿出来吃了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}

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

public class Demo {
public static void main(String[] args) {
ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<>(1);

Foodie f = new Foodie(bd);
Cooker c = new Cooker(bd);

f.start();
c.start();
}
}

阻塞队列的底层put、take方法中内置了一个锁,这个锁实现了一直put放入直到满后wait,释放锁。take一直拿直到拿完后wait,释放锁。

同一个阻塞队列底层是同一把锁。

线程的状态


java虚拟机中没有运行状态

线程状态 具体含义
NEW 一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread()只有线程象,没有线程特征。
RUNNABLE 当我们调用线程对象的start方法,那么此时线程对象进入了RUNNABLE状态。那么此时才是真正的在JVM进程中创建了一个线程,线程一经启动并不是立即得到执行,线程的运行与否要听令与CPU的调度,那么我们把这个中间状态称之为可执行状态(RUNNABLE)也就是说它具备执行的资格,但是并没有真正的执行起来而是在等待CPU的度。
BLOCKED 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
WAITING 一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种,分别是调用Object.wait()、join()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。
TIMED_WAITING 一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long)、join(long)。
TERMINATED 一个完全运行完成的线程的状态。也称之为终止状态、结束状态

当线程抢夺到CPU的执行权的时候,虚拟机会将当前线程交给操作系统管理,因此没有定义此状态

线程池

线程池存在的意义:

系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互,当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程对系统的资源消耗有可能大于业务处理是对系统资源的消耗。为了提高性能,我们就可以采用线程池。线程池在启动的时,会创建大量空闲线程,当向线程池提交任务的时候,线程池就会启动一个线程来执行该任务。等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中称为空闲状态。等待下一次任务的执行。

Executors默认线程池

  • 概述 : JDK对线程池也进行了相关的实现,在真实企业开发中我们也很少去自定义线程池,而是使用JDK中自带的线程池。
  • 我们可以使用Executors中所提供的静态方法来创建线程池
    • static ExecutorService newCachedThreadPool() 创建一个默认的线程池
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package ThreadPool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo1 {
public static void main(String[] args) {
//获取线程池对象
ExecutorService pool = Executors.newCachedThreadPool();

//提交任务 可以添加Runnable 和 Callable的实现类
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(() -> System.out.println(Thread.currentThread().getName() + "在执行了"));

//销毁线程池 线程池一般不销毁
// pool.shutdown();
}
}

1
2
3
4
5
6
7
8
9
10
11
package ThreadPool;

public class MyRunnable implements Runnable {

@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "----" + i);
}
}
}
**Runnable和Callable都是函数式接口, 可以使用lambda表达式**

Executors创建指定上限的线程池

  • static ExecutorService newFixedThreadPool(int nThreads) : 创建一个指定最多线程数量的线程池
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
package com.itheima.mythreadpool;

//static ExecutorService newFixedThreadPool(int nThreads)
//创建一个指定最多线程数量的线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

public class MyThreadPoolDemo2 {
public static void main(String[] args) {
//参数不是初始值而是最大值
ExecutorService executorService = Executors.newFixedThreadPool(10);

ThreadPoolExecutor pool = (ThreadPoolExecutor) executorService;
System.out.println(pool.getPoolSize());//0

executorService.submit(()->{
System.out.println(Thread.currentThread().getName() + "在执行了");
});

executorService.submit(()->{
System.out.println(Thread.currentThread().getName() + "在执行了");
});

System.out.println(pool.getPoolSize());//2
// executorService.shutdown();
}
}

ThreadPoolExecutor自定义线程池

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
package com.itheima.mythreadpool;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MyThreadPoolDemo3 {
// 参数一:核心线程数量
// 参数二:最大线程数
// 参数三:空闲线程最大存活时间
// 参数四:时间单位
// 参数五:任务队列
// 参数六:创建线程工厂
// 参数七:任务的拒绝策略
// corePoolSize: 核心线程的最大值,不能小于0
// maximumPoolSize:最大线程数,不能小于等于0,maximumPoolSize >= corePoolSize
// keepAliveTime: 空闲线程最大存活时间,不能小于0
// unit: 时间单位
// workQueue: 任务队列,不能为null
// threadFactory: 创建线程工厂,不能为null
// handler: 任务的拒绝策略,不能为null
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(2,
5,
2,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());

pool.shutdown();
}
}

当所有核心线程执行任务,任务队伍排满任务,才会创建临时线程处理剩余任务

如果任务数量大于核心线程、临时线程、队伍长度之和,就会触发任务拒绝策略

线程池合理大小

额外扩展内容

额外内容