线程池-常用设计模式

第一章 线程池Thread Pool(重要)

线程池原理

1
2
线程属于系统的宝贵资源,频繁的创建和销毁线程,会降低效率
所以需要使用线程池

1601987457740

线程池

在JDK5版本中提供了代表线程池的接口ExecutorService,而这个接口下有一个实现类叫ThreadPoolExecutor类,使用ThreadPoolExecutor类就可以用来创建线程池对象。

null

下面是它的构造器,参数比较多,不要怕,干就完了。
null

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
JDK中内置的线程池

java.util.concurrent.Executor接口: 与线程池相关的接口,所有线程池必须实现这个接口
抽象方法:
public abstract void execute(Runnable command):
执行方法参数指定的Runnable接口类型的任务

Executor接口规定的功能比较少,使用常用子接口:
java.util.concurrent.ExecutorService接口: 规定了线程池的很多功能

要使用ExecutorService接口中规定的方法,必然要获取到ExecutorService接口的实现类对象
实现类:
java.util.concurrent.ThreadPoolExecutor类:
可以创建对象,发现构造方法参数太多,不方便

解决方案:
使用工具类java.util.concurrent.Executors调用静态方法创建线程池对象
静态方法:
public static ExecutorService newFixedThreadPool(int nThreads)
创建一个可重用固定线程数的线程池
返回值:
ExecutorService接口: 方法内部必然返回实现类对象

接下来,用这7个参数的构造器来创建线程池的对象。代码如下

1
2
3
4
5
6
7
8
9
10
// 一般如果是 io 密集性,设计为 cpu核数的2倍,如果是 计算 密集型,设计为 1.5倍
ExecutorService pool = new ThreadPoolExecutor(
3, //核心线程数有3个
5, //最大线程数有5个。 临时线程数=最大线程数-核心线程数=5-3=2
8, //临时线程存活的时间8秒。 意思是临时线程8秒没有任务执行,就会被销毁掉。
TimeUnit.SECONDS,//时间单位(秒)
new ArrayBlockingQueue<>(4), //任务阻塞队列,没有来得及执行的任务在,任务队列中等待
Executors.defaultThreadFactory(), //用于创建线程的工厂对象
new ThreadPoolExecutor.CallerRunsPolicy() //拒绝策略
);

关于线程池,我们需要注意下面的两个问题

null

  • 临时线程什么时候创建?
1
新任务提交时,发现核心线程都在忙、任务队列满了、并且还可以创建临时线程,此时会创建临时线程。
  • 什么时候开始拒绝新的任务?
1
核心线程和临时线程都在忙、任务队列也满了、新任务过来时才会开始拒绝任务。

线程池执行的任务可以有两种,

一种是Runnable任务;
一种是callable任务。

下面的execute方法可以用来执行Runnable任务。
null

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
/*
练习线程池执行runnable任务
*/
public class MyPool_2 {
public static void main(String[] args) {
// 1: 直接创建对象
// 一般如果是 io 密集性 ,设计为 cpu核数的2倍,如果是 计算 密集型,设计为 1.5倍
ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 8, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy ());
// 循环,给线程池添加任务
for (int i = 1; i <= 19; i++) {
// 使用lambda 作为 runnbale的实现类使用
final int a = i;
pool.execute(()->{
String name = Thread.currentThread().getName();
System.out.println(name+"即将执行任务:"+a);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(name+"执行任务完成.....................:"+a);
});
}
// 输出线程池对象
System.out.println(pool);
pool.shutdown();
}
}

执行上面的代码,结果输出如下
null

线程池执行Callable任务

接下来,我们学习使用线程池执行Callable任务。callable任务相对于Runnable任务来说,就是多了一个返回值。

执行Callable任务需要用到下面的submit方法
null

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
/*
练习线程池执行callable任务
*/
public class MyPool_3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1: 直接创建对象
// 一般如果是 io 密集性 ,设计为 cpu核数的2倍,如果是 计算 密集型,设计为 1.5倍
ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 3, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(6), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy ());
// 循环,给线程池添加任务
for (int i = 1; i <= 10; i++) {
// 使用lambda 作为 runnbale的实现类使用
final int a = i;
Future f= pool.submit(()->{
String name = Thread.currentThread().getName();
System.out.println(name+"即将执行任务:"+a);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(name+"执行任务完成.....................:"+a);
return a*2;
});
//Object o = f.get();
//System.out.println(a+"号任务返回的结果是:"+o);
}
// 输出线程池对象
System.out.println(pool);
pool.shutdown();

}
}

执行后,结果如下图所示
null

线程池工具类(Executors)

Java为开发者提供了一个创建线程池的工具类,叫做Executors,它提供了方法可以创建各种不能特点的线程池。如下图所示

null

接下来,我们演示一下创建固定线程数量的线程池。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ThreadPoolTest3 {
public static void main(String[] args) throws Exception {
// 1、通过Executors创建一个线程池对象。
ExecutorService pool = Executors.newFixedThreadPool(17);
// 老师:核心线程数量到底配置多少呢???
// 计算密集型的任务:核心线程数量 = CPU的核数 + 1
// IO密集型的任务:核心线程数量 = CPU核数 * 2

// 2、使用线程处理Callable任务。
Future<String> f1 = pool.submit(new MyCallable(100));
Future<String> f2 = pool.submit(new MyCallable(200));
Future<String> f3 = pool.submit(new MyCallable(300));
Future<String> f4 = pool.submit(new MyCallable(400));

System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
}
}

Executors创建线程池这么好用,为什么不推荐同学们使用呢?

原因在这里:看下图,这是《阿里巴巴Java开发手册》提供的强制规范要求。

null

null

第一章 设计模式

1.1 概念

1
2
3
4
5
6
7
8
9
10
11
设计模式(Design pattern),是一套被反复使用、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、保证代码可靠性、程序的重用性。

1995 年,GoF(Gang of Four,四人组)合作出版了《设计模式:可复用面向对象软件的基础》一书,共收录了 23 种设计模式。

总体来说设计模式分为三大类:

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。-->创建对象

结构型模式,共七种:[适配器模式]、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。-->对功能进行增强

行为型模式,共十一种:策略模式、模板方法模式、[观察者模式]、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、[中介者模式]、解释器模式。

1.2 单例设计模式

1
2
单例模式
目的:保证一个类只能new一个对象出来,给外界使用

1.2.1 饿汉式

1
2
饿汉式:迫不及待的想使用对象
为了达到目的:类一加载到内存,先加载静态成员,我们只需要new对象的时候将其变成static的就可以了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class A {
// 1: 私有构造方法
private A(){
if(a != null){
throw new RuntimeException("不能创建多个对象...");
}
}

// 2: 静态变量
private static final A a = new A();

// 3: 提供静态方法
public static A getInstance(){
return a;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TestA {
public static void main(String[] args) {
// 10个线程,每个线程获取3次对象
Runnable r = ()->{
String name = Thread.currentThread().getName();
for (int i = 0; i < 3; i++) {
System.out.println(name+"获取的A="+A.getInstance());
}
};

for (int i = 0; i < 10; i++) {
new Thread(r).start();
}
}
}

1.2.2 懒汉式

1
2
3
4
5
6
7
懒汉式:先不要new对象,什么时候使用,什么时候再new,但是还得保证只能是一个对象

问题:线程安全问题
一条线程进了if,第二条线程也可能进if,这样由于两个线程都进了if,那么在执行就会出现new两次对象的情况

解决:
上锁
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 B {
// 1: 私有构造方法
private B(){
if(a != null){
throw new RuntimeException("不能创建多个对象...");
}
}

// 2: 静态变量 volatile 瞬时态的修饰符,可以避免虚拟机的指令重排
private static volatile B a;

// 3: 提供静态方法
public static B getInstance(){
if(a == null){
synchronized (B.class){
if(a == null){
a= new B(); // 创建对象的对象,可能会发生指令重排
// new 申请空间,B() 执行构造方法初始化, = 是赋值,这是3个动作
}
}
}
return a;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TestB {
public static void main(String[] args) {
// 10个线程,每个线程获取3次对象
Runnable r = ()->{
String name = Thread.currentThread().getName();
for (int i = 0; i < 3; i++) {
System.out.println(name+"获取的B="+ B.getInstance());
}
};

for (int i = 0; i < 10; i++) {
new Thread(r).start();
}
}
}
1
2
3
4
5
单例模式可以保证系统中一个类只有一个对象产生
实现单例模式的步骤:
1.将构造私有化,使其不能再类的外部通过new关键字创建对象
2.在该类内部产生一个唯一的对象,并且将其封装为private static 类型的成员变量
3.定义一个静态方法返回这个唯一的对象

1.2.3 volatile

练习 volatile 关键字的特点

1: 在多线程的情况下,保证数据可见
2: 防止jvm对代码进行指令重排
i++
不是原子性操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
模拟一个银行账户, 让 张三和李四同时拥有这个账户,张三负责查看余额,李四负责修改余额
*/
public class Account {
private volatile int money = 1000;

public void show(){
String name = Thread.currentThread().getName();
while (true){
if(money != 1000){
System.out.println(name+"发现余额改变了........"+money);
break;
}
}
}
public void setMoney(){
String name = Thread.currentThread().getName();
money = 100;
System.out.println(name+"对账户进行了修改...修改后余额为:"+money);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
验证 volatile 不能保证原子性
*/
public class Account2 {
private volatile int i;

public void inrc(){
//synchronized (this){ 加锁不会出问题,既能保证数据的可见性,也能保证数据的原子性,同时也能避免指令重排的影响
i++;
//}
}

public void show(){
System.out.println(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
/*
练习 volatile 关键字的特点

1: 在多线程的情况下,保证数据可见
2: 防止jvm对代码进行指令重排

i++

不是原子性操作

*/
public class Test01 {
public static void main(String[] args) {
// 1: 创建2个线程对象,分别调用 show和 setMoney方法
Account a = new Account();
new Thread(()->a.show(),"张三").start();
new Thread(()->{
System.out.println("李四准备休眠...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
a.setMoney();
},"李四").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
/*
练习 volatile 关键字的特点

1: 在多线程的情况下,保证数据可见
2: 防止jvm对代码进行指令重排
i++

不是原子性操作
*/
public class Test02 {
public static void main(String[] args) throws InterruptedException {
// 1: 创建2个线程对象,分别调用 show和 setMoney方法
Account2 a = new Account2();
// 创建100个线程,每个线程对i增加100次; 10000
for (int i = 0; i < 100; i++) {
new Thread(()->{
for (int j = 0; j < 10000; j++) {
a.inrc();
}
}).start();
}
Thread.sleep(3000);
a.show();
}
}

1.2.4 枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class A {
// 1: 私有构造方法
private A(){
if(a != null){
throw new RuntimeException("不能创建多个对象...");
}
}

// 2: 静态变量
private static final A a = new A();

// 3: 提供静态方法
public static A getInstance(){
return a;
}
}
1
2
3
4
5
6
7
public enum C {
MyC;

public void abc(){
System.out.println("abc执行了...");
}
}
1
2
3
4
5
6
7
8
9
10
11
public class TestA {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 使用暴力反射,创建 A 对象
Class<A> aClass = A.class;
Constructor<A> constructor = aClass.getDeclaredConstructor();
constructor.setAccessible(true);
A a = constructor.newInstance();
System.out.println(a);
System.out.println(A.getInstance());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class TestC {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 使用10个线程,每个线程获取10次对象
for (int i = 0; i < 10; i++) {
new Thread(()->{
String name = Thread.currentThread().getName();
for (int i1 = 1; i1 <= 10; i1++) {
System.out.println(name+"第"+i1+"次获取对象的哈希值是:"+C.MyC.hashCode());
}
}).start();
}

// 尝试暴力反射
Class<C> cClass = C.class;
// 枚举天生 克制 暴力反射!!!
Constructor<C> constructor = cClass.getDeclaredConstructor();
C c = constructor.newInstance();
System.out.println(c);
}
}

1.3 工厂设计模式

1.3.1 什么是工厂设计模式

1
2
1.之前我们创建类对象时, 都是使用new对象的形式创建,在很多业务场景下也提供了不直接new的方式 。
2.工厂模式(Factory Pattern)是Java中最常用的设计模式之一,这种类型的设计模式属于创建型模式,它提供了一种获取对象的方式。

1.3.2 工厂设计模式的作用

1
2
工厂的方法可以封装对象的创建细节,比如:为该对象进行加工和数据注入。
可以实现类与类之间的解耦操作(核心思想)。

1.3.3 案例

1
2
3
4
5
6
7
8
9
10
11
//抽象父类
public abstract class Car {
private String brand;
private String color;
private int price;
//跑
public abstract void run();

//自己生成空参/满参/get/set/toString方法
}

1
2
3
4
5
6
7
//奥迪车
public class AudiCar extends Car {
@Override
public void run() {
System.out.println(getBrand() + "::" + getColor() + "::" + getPrice() + "....奥迪车...正在冒着狼烟的跑....");
}
}
1
2
3
4
5
6
7
//宝马车
public class BmwCar extends Car {
@Override
public void run() {
System.out.println(getBrand() + "::" + getColor() + "::" + getPrice() + "....宝马车...正在疯狂的跑....");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//工厂
public class CarFactory {
//静态方法: 创建抽象父类Car的子类对象的
public static Car createCar(String msg) {
switch (msg){
case "bmw":
Car car = new BmwCar();
car.setBrand("BMW750Li");
car.setColor("宝强绿");
car.setPrice(999999);
return car;
case "audi":
Car car2 = new AudiCar();
car2.setBrand("奥迪A8L");
car2.setColor("骚红");
car2.setPrice(888888);
return car2;
default:
return null;
}
}
}
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
/**
目标:工厂模式。
什么是工厂设计模式?
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。
这种类型的设计模式属于创建型模式,它提供了一种创建对象的方式。
之前我们创建类对象时, 都是使用new 对象的形式创建, 除new 对象方式以外,工厂模式也可以创建对象。
工厂设计模式的作用:
1.对象通过工厂的方法创建返回,工厂的方法可以为该对象进行加工和数据注入。
2.可以实现类与类之间的解耦操作(核心思想,重点)
小结:
工厂模式的思想是提供一个工厂方法返回对象!
*/
public class DemoFactory {
public static void main(String[] args) {
//多态的方式创建对象
/*Car car = new BmwCar();
car.setBrand("BMW750Li");
car.setColor("宝强绿");
car.setPrice(999999);
car.run();

Car car2 = new AudiCar();
car2.setBrand("奥迪A8L");
car2.setColor("骚红");
car2.setPrice(888888);
car2.run();*/
//通过工厂创建Car类的对象
Car car = CarFactory.createCar("bmw");
car.run();

Car car2 = CarFactory.createCar("audi");
car2.run();

}
}