目录

[TOC]

JUC概述

什么是JUC

image-20220715161332655

JUC实际上就是我们对于jdk中java.util.concurrent工具包的简称。这个包下都是Java处理线程相关的类,自jdk1.5后出现。

线程和进程概念

进程

例如运行电脑管家,就开启了一个进程

线程

案例

在电脑管家里面的运行空间清理,就开启了一个线程。

如果同时开启空间清理和杀毒,则是多线程

如果同时开启多个功能,只能等上一个执行完成才能执行下一个,则为单线程

wati和sleep

  1. sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用。
  2. sleep不会释放锁,它也不需要占用锁。wait会释放锁,但调用它的前提是当前线程占有锁A(即代码要在synchronized中)。
  3. 它们都可以被interrupted方法中断。

并发和并行

串行模式

串行是一次只能取得一个任务,并执行这个任务。

并行模式

同时执行多个任务

并发模式

并发(concurrent)指的是多个程序可以同时运行的现象,更细化的是多进程可以同时运行或者多指令可以同时运行

并行和并发区别

并发:同一时刻多个线程在访问同一个资源,多个线程对一个点

  • 例子:春运抢票电商秒杀…

并行:多项工作一起执行,之后再汇总

  • 例子:泡方便面,电水壶烧水,一边撕调料倒入桶中

管程

所说锁

是一种同步机制,保证同一个时间,只有一个线程访问被保护数据或者代码

JVM同步基于进入和退出,使用管程对象实现的

用户线程和守护线程

用户线程:自定义线程。主线程结束了,用户线程还在运行,JVM存活

守护线程:运行在后台,是一种特殊的线程,比如垃圾回收。

当主线程结束后,用户线程还在运行,JVM存活

如果没有用户线程,都是守护线程,JVM结束

Lock接口

创建线程的多种方式

继承Thread

继承Thread类,重写run方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 启动线程
*/
class ThreadTest {
public static void main(String[] args) {
//创建类,调用底层的start方法,start方法会自动调用run方法
new ExtendThread().start();
}
}

/**
* 继承Thread重写run方法
*/
class ExtendThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ": 继承Thread线程啦");
}
}

实现Runnable接口

注:Thread其实也是实现了Runnable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ThreadTest {
public static void main(String[] args) {
//new一个Thread,将实现了Runnable的类创建传入Thread中,并通过Thread调用start
new Thread(new ImplRunnable()).start();
}
}

/**
* 继承Runnable重写run方法
*/
class ImplRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ": 继承Thread线程啦");
}
}

Runnable接口匿名内部类

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
class SaleTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
//线程a
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
ticket.sale();
}
}
}, "线程a").start();

//线程b:使用lambda
new Thread(() -> {
for (int i = 0; i < 100; i++) {
ticket.sale();
}
}, "线程b").start();

//线程c:使用lambda
new Thread(ticket::sale, "线程c").start();
}
}

class Ticket {
private Integer num = 30;

public synchronized void sale() {
if (num > 0) {
System.out.println(Thread.currentThread().getName() +
"卖出:" + (num--) +
"剩下:" + num);
}
}
}

实现Callable接口

Java5开始提供Callable接口,提供call方法作为线程的执行体,可以看成是Runnable接口的增强版本,增强点在于call()方法可以有返回值,并且可以抛出异常,由于Callable是新增的接口,不能作为Thread的target使用,所以Java5里提供了Future接口,该接口实现了Runnable,Future的实现类FutureTask类用来包装Callable对象

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
class ThreadTest {
public static void main(String[] args) {
//创建Callable接口实现类的对象
ImplCallable implCallable = new ImplCallable();

//将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask<String> futureTask = new FutureTask<>(implCallable);

//将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();

try {
//获取Callable中call方法的返回值,
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
String resp = futureTask.get();
System.out.println("返回值为:" + resp);
} catch (Exception e) {
e.printStackTrace();
}
}
}

/**
* 继承Thread重写run方法
* Callable<V> : V的类型即为call方法返回值的类型
*/
class ImplCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "返回值类型为:String类型";
}
}

使用线程池

线程池具体使用放在:CompletableFuture中

Synchronized

synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:

  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
    • 虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。
  3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  4. 修改一个类,其作用的范围是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
31
32
33
34
public class SaleTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
//线程a
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
ticket.sale();
}
}
}, "线程a").start();

//线程b:使用lambda
new Thread(() -> {
for (int i = 0; i < 100; i++) {
ticket.sale();
}
}, "线程b").start();

//线程c:使用lambda
new Thread(ticket::sale, "线程c").start();
}
}

class Ticket {
private Integer num = 30;

public synchronized void sale() {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "卖出:" + (num--) + "剩下:" + num);
}
}
}

synchronized实现同步的基础

synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现为以下3种形式。

  • 对于普通同步方法,锁是当前实例对象。
  • 对于静态同步方法,锁是当前类的class对象。
  • 对于同步方法块,锁是synchonized括号里配置的对象

什么是Lock

Lock锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。Lock提供了比synchronized更多的功能。

Lock与的Synchronized区别

Synchronized自动放锁,Lock手动解锁

  • Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
  • Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

Lock接口

ReentrantLock

ReentrantLock,意思是“可重入锁”,关于可重入锁的概念将在后面讲述。ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。

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
43
44
public class LSaleTicket {
public static void main(String[] args) {
LTicket lTicket = new LTicket();
//创建三个线程
new Thread(() -> {
for (int i = 0; i < 30; i++) {
lTicket.sale();
}
}, "线程一").start();
new Thread(() -> {
for (int i = 0; i < 30; i++) {
lTicket.sale();
}
}, "线程二").start();
new Thread(() -> {
for (int i = 0; i < 30; i++) {
lTicket.sale();
}
}, "线程三").start();
}
}

class LTicket {
//票数量
private int number = 30;

//创建可重复锁
private final ReentrantLock lock = new ReentrantLock();

//卖票方法
public void sale() {
//上锁
lock.lock();
//判断是否有票
try {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出:" + (number--) + " 剩余:" + number);
}
} finally {
//解锁。避免程序出现错误,无法解锁,导致死锁,这里使用finally,可以使解锁最终执行
lock.unlock();
}
}
}

ReadWriteLock

具体后续补充

Synchronized和Lock锁有以下几点不同

  1. Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
  2. synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
  3. Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
  4. 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
  5. Lock可以提高多个线程进行读操作的效率。

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。

线程间通信

线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模型来实现的。
场景—-两个线程,一个线程对当前数值加1,另一个线程对当前数值减1,要求用线程间通信

03-多线程编程步骤

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class ThreadCommunication {
public static void main(String[] args) {
Share share = new Share();
//创建线程
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
share.incr(); //+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "AA").start();

new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
share.decr(); //-1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "BB").start();
}
}

class Share {
//初始值
private int number = 0;

//+1的方法
public synchronized void incr() throws InterruptedException {
//第二步 判断 干活 通知
while (number != 0) { //判断number值是否是0,如果不是0,等待
this.wait(); //在哪里睡,就在哪里醒
}
//如果number值是0,就+1操作
number++;
System.out.println(Thread.currentThread().getName() + " :: " + number);
//通知其他线程
this.notifyAll();
}

//-1的方法
public synchronized void decr() throws InterruptedException {
//判断
while (number != 1) {
this.wait();
}
//干活
number--;
System.out.println(Thread.currentThread().getName() + " :: " + number);
//通知其他线程
this.notifyAll();
}
}

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
//第一步 创建资源类,定义属性和操作方法
class Share {
private int number = 0;

//创建Lock
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();

//+1
public void incr() throws InterruptedException {
//上锁
lock.lock();
try {
//判断
while (number != 0) {
condition.await();
}
//干活
number++;
System.out.println(Thread.currentThread().getName()+" :: "+number);
//通知
condition.signalAll();
}finally {
//解锁
lock.unlock();
}
}

//-1
public void decr() throws InterruptedException {
lock.lock();
try {
while(number != 1) {
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+" :: "+number);
condition.signalAll();
}finally {
lock.unlock();
}
}
}

public class ThreadDemo2 {
public static void main(String[] args) {
Share share = new Share();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
}
}

线程间定制化通信

实现:A线程打印5次A,B线程打印10次B,C线程打印15次C,按照此顺序循环10轮

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//第一步 创建资源类
class ShareResource {
//定义标志位
private int flag = 1; // 1 AA 2 BB 3 CC

//创建Lock锁
private Lock lock = new ReentrantLock();

//创建三个condition
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();

//打印5次,参数第几轮
public void print5(int loop) throws InterruptedException {
//上锁
lock.lock();
try {
//判断
while(flag != 1) {
//等待
c1.await();
}
//干活
for (int i = 1; i <=5; i++) {
System.out.println(Thread.currentThread().getName()+" :: "+i+" :轮数:"+loop);
}
//通知
flag = 2; //修改标志位 2
c2.signal(); //通知BB线程
}finally {
//释放锁
lock.unlock();
}
}

//打印10次,参数第几轮
public void print10(int loop) throws InterruptedException {
lock.lock();
try {
while(flag != 2) {
c2.await();
}
for (int i = 1; i <=10; i++) {
System.out.println(Thread.currentThread().getName()+" :: "+i+" :轮数:"+loop);
}
//修改标志位
flag = 3;
//通知CC线程
c3.signal();
}finally {
lock.unlock();
}
}

//打印15次,参数第几轮
public void print15(int loop) throws InterruptedException {
lock.lock();
try {
while(flag != 3) {
c3.await();
}
for (int i = 1; i <=15; i++) {
System.out.println(Thread.currentThread().getName()+" :: "+i+" :轮数:"+loop);
}
//修改标志位
flag = 1;
//通知AA线程
c1.signal();
}finally {
lock.unlock();
}
}
}

public class ThreadDemo3 {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
shareResource.print5(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();

new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
shareResource.print10(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();

new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
shareResource.print15(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
}
}

集合的线程安全

ArrayList

ArrayList集合线程不安全演示

程序会报错:java.util.ConcurrentModificationException

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
//向集合添加内容
list.add(UUID.randomUUID().toString().substring(0,8));
//从集合中获取
System.out.println(list);
},String.valueOf(i)).start();
}
}

CopyOnWriteArrayList解决(⭐)

使用JUC中提供的CopyOnWriteArrayList来解决ArrayList线程不安全问题

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
//将new ArrayList<>()创建变为使用CopyOnWriteArrayList进行创建
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 50; i++) {
new Thread(()->{
//向集合添加内容
list.add(UUID.randomUUID().toString().substring(0,8));
//从集合中获取
System.out.println(list);
},String.valueOf(i)).start();
}
}

Vector解决

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
//将new ArrayList<>()创建变为使用Vector进行创建
List<String> list = new Vector<>();
for (int i = 0; i < 50; i++) {
new Thread(()->{
//向集合添加内容
list.add(UUID.randomUUID().toString().substring(0,8));
//从集合中获取
System.out.println(list);
},String.valueOf(i)).start();
}
}

Collections解决

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
//将new ArrayList<>()创建变为使用Collections进行创建
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 50; i++) {
new Thread(()->{
//向集合添加内容
list.add(UUID.randomUUID().toString().substring(0,8));
//从集合中获取
System.out.println(list);
},String.valueOf(i)).start();
}
}

HashSet

HashSet集合线程不安全演示

程序会报错:java.util.ConcurrentModificationException

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for (int i = 0; i < 50; i++) {
new Thread(()->{
//向集合添加内容
set.add(UUID.randomUUID().toString().substring(0,8));
//从集合中获取
System.out.println(set);
},String.valueOf(i)).start();
}
}

CopyOnWriteArraySet解决(⭐)

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
//将new ArrayList<>()创建变为使用CopyOnWriteArraySet进行创建
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 50; i++) {
new Thread(()->{
//向集合添加内容
set.add(UUID.randomUUID().toString().substring(0,8));
//从集合中获取
System.out.println(set);
},String.valueOf(i)).start();
}
}

HashMap

HashMap集合线程不安全演示

程序会报错:java.util.ConcurrentModificationException

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
for (int i = 0; i < 50; i++) {
new Thread(() -> {
//向集合添加内容
String uuid = UUID.randomUUID().toString().substring(0, 8);
map.put(uuid, uuid);
//从集合中获取
System.out.println(map);
}, String.valueOf(i)).start();
}
}

ConcurrentHashMap解决(⭐)

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
//将new ArrayList<>()创建变为使用ConcurrentHashMap进行创建
Map<String, String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 50; i++) {
new Thread(() -> {
//向集合添加内容
String uuid = UUID.randomUUID().toString().substring(0, 8);
map.put(uuid, uuid);
//从集合中获取
System.out.println(map);
}, String.valueOf(i)).start();
}
}

多线程锁

锁的八个问题演示

synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现为以下3种形式。

  • 对于普通同步方法,锁是当前实例对象。
  • 对于静态同步方法,锁是当前类的class对象。
  • 对于同步方法块,锁是synchonized括号里配置的对象
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
43
44
45
46
47
48
49
/**
* 1 标准访问,先打印短信还是邮件
* ------sendSMS
* ------sendEmail
*
* 2 停4秒在短信方法内,先打印短信还是邮件
* ------sendSMS
* ------sendEmail
*
* 3 新增普通的hello方法,是先打短信还是hello
* ------getHello
* ------sendSMS
*
* 4 现在有两部手机,先打印短信还是邮件
* ------sendEmail
* ------sendSMS
*
* 5 两个静态同步方法,1部手机,先打印短信还是邮件
* ------sendSMS
* ------sendEmail
*
* 6 两个静态同步方法,2部手机,先打印短信还是邮件
* ------sendSMS
* ------sendEmail
*
* 7 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
* ------sendEmail
* ------sendSMS
*
* 8 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
* ------sendEmail
* ------sendSMS
*/
public class Phone {

public static synchronized void sendSMS() throws Exception {
//停留4秒
TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS");
}

public synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail");
}

public void getHello() {
System.out.println("------getHello");
}
}

公平锁和非公平锁

公平锁

每个线程平均执行,但是效率较低。如买票案例中,多个线程同时进行买票,使用公平锁,每个窗口平均把剩余的票卖出

非公平锁

可能一个线程直接把全部任务执行完成,其他线程可能出现线程饿死,但是效率较高。如买票案例中,多个线程同时进行买票,使用非公平锁,可能一个窗口就能直接把票全部卖出去

可重入锁

可重入锁也称为递归锁

死锁

Callable$Future接口