JUC并发编程系列(一)

会写四篇多线程和JUC相关的知识,干活满满,手把手调试,保姆级待遇!

1.什么是JUC?

有着多线程的基础,就进一步学习Java关于并发操作封装的工具类,查看JDK1.8帮助文档可知:

2.回顾多线程知识

创建线程的方法

1.继承Thread类,重写run()方法,调用start()

通过继承Thread实现的线程类,多个线程间无法共享线程类的实例变量。(需要创建不同Thread对象,自然不共享)

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
/**
*创建线程的方法:1.继承Thread类,重写run()方法,调用start()
* @author 路飞
* @create 2021/1/23
*/
public class Demo01 extends Thread{
@Override
public void run() {
//run方法 线程体
for (int i = 0; i < 20; i++) {
System.out.println("我在写博客----->"+i);
}
}

public static void main(String[] args) {
//main 主线程
//创建一个线程对象,调用start(),开启线程
Demo01 demo01 = new Demo01();
//demo01.run(); //只有主线程一条执行路径
demo01.start(); //多线程,主线程和子线程交替执行

for (int i = 0; i < 20; i++) {
System.out.println("我在复习多线程--->"+i);
}
}
}

2.实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法

此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
/**
* 创建线程的方法2:实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法
* @author 路飞
* @create 2021/1/23
*/
public class Demo02 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("我还是在写代码---->"+i);
}
}

public static void main(String[] args) {
//创建Runnable接口实现类的对象
Demo02 demo02 = new Demo02();
//创建线程对象,通过线程对象来开启我们的线程,---->代理
Thread thread = new Thread(demo02);
thread.start();

for (int i = 0; i < 20; i++) {
System.out.println("我还是在写博客--->"+i);
}


}
}

3.创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。

Callable与Runnable的功能大致相似,Callable中有一个call()函数,但是call()函数有返回值,而Runnable的run()函数不能将结果返回给客户程序。

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
/**
* 创建线程的方法3:创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
* @author 路飞
* @create 2021/1/18
*/
public class CallableTets {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// new Thread().start(); //怎么启动Callable

MyThread thread = new MyThread();
FutureTask futureTask = new FutureTask(thread);

for (int i = 0; i < 20; i++) {
new Thread(futureTask,String.valueOf(i)).start();
System.out.println(Thread.currentThread().getName()+"---->进来了");
}
Integer o = (Integer) futureTask.get(); //get 可能会产生阻塞 放到最后
// 或者使用异步

System.out.println(o);
}
}

class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("call()");
return 1024;
}
}

创建线程的三种方式的对比:

1、采用实现Runnable、Callable接口的方式创建多线程时,

优势是:

线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。

在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

劣势是:

编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

2、使用继承Thread类的方式创建多线程时,

优势是:

编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

劣势是:

线程类已经继承了Thread类,所以不能再继承其他父类。

3、Runnable和Callable的区别

(1) Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。

(2) Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。

(3) call方法可以抛出异常,run方法不可以。

(4) 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

3.线程和进程

  • 进程:进程在操作系统中可以独立运行,作为资源分配的基本单位。表示运行中的程序。
  • 线程:线程是进程中的一个实例,作为系统调度和分派的基本单位。是进程中的一段序列,能够完成进程中的一个功能。

进程和线程的区别:

(1)同一个进程可以包含多个线程,一个进程中至少包含一个线程,一个线程只能存在于一个进程中。

(2)同一个进程下的所有线程能够共享该进程下的资源。(系统运行时会为每个进程分配不同的内存区域,但不会为线程分配内存。线程只能共享它所属进程的资源。)

(3)进程结束后,该进程下的所有线程将销毁,而一个线程的结束不会影响同一进程下的其他线程。

(4)线程是轻量级的进程,它的创建和销毁所需要的时间比进程小得多,所有操作系统的执行功能都是通过创建线程去完成的。

(5)线程在执行时是同步和互斥的,因为他们共享同一个进程下的资源。

(6)在操作系统中,进程是拥有系统资源的独立单元,它可以拥有自己的资源。一般而言,线程不能拥有自己的资源,但是它能够访问其隶属进程的资源。

举个栗子:

进程:一个程序,QQ,QQ游戏,lol等程序的集合

一个进程往往可以包含多个线程,至少包含一个!

Java默认有几个线程?2个 Main、GC

线程:QQ发信息,传文件

对于Java而言:Thread、Runnable、Callable

Java是不能操作线程的,底层调用C++

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 synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();

/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);

boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}

private native void start0();

并行和并发

并发编程:并发、并行

并发(多个线程操作同一个资源)

老师让两个同学去办公室谈话。如果这两同学(进程)是并列跨过办公室门(CPU)的,那么就是并行。如果同学A先进同学B后进入(或者先B后A),或者两人并列同时进入,但是在办公室外的路人甲(用户)看来,同学A和同学B同时都在办公室内,这是并发。

如果举例要精确一点,那么大概是这样的:进办公室有两个门(两CPU),如果两同学分别从不同的门进入,不管先后性,两者互相独立,那么是并行;如果两同学不管以什么方式进入,在路人甲看来,他两同时都在办公室内,就是并发。

这篇博客讲的很清楚

并发编程的本质:充分利用CPU资源

线程的状态

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
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
//看的出来,这是属于一个已经创建的线程,但是还没有调用start方法启动的线程所处的状态。

/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
//正如JDK中介绍,该状态包含两种可能。有可能正在运行,或者正在等待CPU资源。总体上就是当我们创建线程并且启动之后,就属于Runnable状态。

/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
//阻塞状态,当线程准备进入synchronized同步块或同步方法的时候,需要申请一个监视器锁而进行的等待,会使线程进入BLOCKED状态。

/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
//该状态的出现是因为调用了Object.wait()或者Thread.join()或者LockSupport.park()。处于该状态下的线程在等待另一个线程 执行一些其余action来将其唤醒。

/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
//该状态和上一个状态其实是一样的,是不过其等待的时间是明确的。

/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
// 消亡状态比较容易理解,那就是线程执行结束了,run方法执行结束表示线程处于消亡状态了。
}

多线程编程中常用的函数比较

sleep 和 wait 的区别:

  • sleep方法:是Thread类的静态方法,当前线程将睡眠n毫秒,线程进入阻塞状态。当睡眠时间到了,会解除阻塞,进入可运行状态,等待CPU的到来。睡眠不释放锁(如果有的话)。
  • wait方法:是Object的方法,必须与synchronized关键字一起使用,线程进入阻塞状态,当notify或者notifyall被调用后,会解除阻塞。但是,只有重新占用互斥锁之后才会进入可运行状态。睡眠时,会释放互斥锁。
  • join方法:当前线程调用,则其它线程全部停止,等待当前线程执行完毕,接着执行。
  • yield方法:该方法使得线程放弃当前分得的 CPU 时间。但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。

主要考察: sleep和wait方法所处的类是哪个,并且考察其在休眠的时候对于互斥锁的处理。

上面是牛客的面试题总结,下面自己分析两个方法的区别:

1、来自不同的类

wait => Object

sleep => Thread

2、关于锁的释放:

wait 会释放锁,sleep 睡觉了,抱着锁睡觉,不会释放!

3、使用的范围不同

wait必须在同步代码块中,在sync(){}中

4.Lock锁

传统Synchronized (后面为简化书写,统一写为sync)

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
/**
* 卖票
*
* @author 路飞
* @create 2021/1/17
*/
public class SaleTicketDemo01 {
public static void main(String[] args) {
//并发多个线程 操作同一个资源
Ticket ticket = new Ticket();

// @FunctionalInterface 函数式接口,jdk1.8 lambda表达式 (参数)->{ 代码 }
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.saleTicket();
}
}, "A").start();

new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.saleTicket();
}
}, "B").start();

new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.saleTicket();
}
}, "C").start();
}
}

class Ticket {
//属性
private Integer tikcketNums = 50;

//加入同步锁
public synchronized void saleTicket() {
if (tikcketNums > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了" + (tikcketNums--) + "还剩" + tikcketNums);
}
}
}

两个地方需要注意:

  • 在售票的方法里加入sync同步方法,否则会出现线程不安全的问题;

  • 创建线程使用的lambda表达式简化操作;

  • oop的思想,线程是一个单独的资源类(属性和方法),不会写在一起,降低耦合。

Lock接口

JDK1.8文档对它的定义:

Interface Lock

所有已知实现类:

ReentrantLock ReentrantReadWriteLock.WriteRead ReentrantReadWriteLock.WriteLock

public interface Lock

Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。

它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象Condition 。

锁是用于通过多个线程控制对共享资源的访问的工具。 通常,锁提供对共享资源的独占访问:一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。 但是,一些锁可能允许并发访问共享资源,如ReadWriteLock的读锁。

​ 使用synchronized方法或语句提供对与每个对象相关联的隐式监视器锁的访问,但是强制所有锁获取和释放以块结构的方式发生:当获取多个锁时,它们必须以相反的顺序被释放,并且所有的锁都必须被释放在与它们相同的词汇范围内

​ 虽然synchronized方法和语句的范围机制使得使用监视器锁更容易编程,并且有助于避免涉及锁的许多常见编程错误,但是有时您需要以更灵活的方式处理锁。 例如,用于遍历并发访问的数据结构的一些算法需要使用“手动”或“链锁定”:您获取节点A的锁定,然后获取节点B,然后释放A并获取C,然后释放B并获得D等。 所述的实施方式中Lock接口通过允许获得并在不同的范围释放的锁,并允许获得并以任何顺序释放多个锁使得能够使用这样的技术。

​ 随着这种增加的灵活性,额外的责任。 没有块结构化锁定会删除使用synchronized方法和语句发生的锁的自动释放。 在大多数情况下,应使用以下惯用语

1
Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); } 

当在不同范围内发生锁定和解锁时,必须注意确保在锁定时执行的所有代码由try-finally或try-catch保护,以确保在必要时释放锁定。

Lock实现提供了使用synchronized方法和语句的附加功能,通过提供非阻塞尝试来获取锁 tryLock(),尝试获取可被中断的锁lockInterruptibly() ,以及尝试获取可以超时(tryLock(long, TimeUnit)。

  一个`Lock`类还可以提供与隐式监视锁定的行为和语义完全不同的行为和语义,例如保证排序,非重入使用或死锁检测。 如果一个实现提供了这样的专门的语义,那么实现必须记录这些语义。 

​ 请注意, Lock实例只是普通对象,它们本身可以用作synchronized语句中的目标。 获取Lock实例的监视器锁与调用该实例的任何lock()方法没有特定关系。 建议为避免混淆,您不要以这种方式使用Lock实例,除了在自己的实现中。

除非另有说明,传递任何参数的null值将导致NullPointerException被抛出。

文档已经说的相当清楚了,sync在获取锁时是获取所有锁以及释放以块的形式释放,并且当多个锁需要被释放时,是以相反的顺序,且都要被sync的代码块{}包裹。

Lock相比sync更灵活,手动加锁和释放锁,并且可以提供更多操作等等,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
/**
* @author 路飞
* @create 2021/1/17
*/
public class SaleTicketDemo02 {
public static void main(String[] args) {
//并发多个线程 操作同一个资源
Ticket2 ticket = new Ticket2();

new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.saleTicket();
}
}, "A").start();

new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.saleTicket();
}
}, "B").start();

new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.saleTicket();
}
}, "C").start();
}
}

//lock三部曲
//1 new ReentrantLock();
//2 Lock.lock(); 加锁
//3 finally=> lock.unlock(); 解锁
class Ticket2 {
private Integer tikcketNums = 50;

Lock lock = new ReentrantLock();

public void saleTicket() {
lock.lock(); //加锁
try {
if (tikcketNums > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了" + (tikcketNums--) + "还剩" + tikcketNums);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); //解锁
}
}
}

可以看到创建Lock锁是先new了一个ReentrantLock() 可重入锁,翻阅源码有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
//可重入锁默认是非公平锁
sync = new NonfairSync();
}

/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

可以看到new ReentrantLock() ,不传参数,走的无参构造,默认是NonfairSync() 非公平锁,若要指定为公平锁需要new ReentrantLock()的有参构造,传入true即可。

问题来了,什么是公平锁和非公平锁?

公平锁:十分公平,遵守先来后到

非公平锁:十分不公平,线程可以插队(默认)

可重入锁涉及的知识很多,后面再讲。

sync 和 Lock区别

1、Synchronized 内置的Java关键字, Lock 是一个Java类
2、Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
3、Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁
4、Synchronized 线程 1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下
去;
5、Synchronized 可重入锁,不可以中断的,非公平;Lock ,可重入锁,可以 判断锁,非公平(可以
自己设置);
6、Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!

5.生产者和消费者问题

生产者和消费者问题 sync版

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
/**
* 线程之间的通信问题:生产者和消费者问题
* 线程交替执行 A B 操作同一变量 num = 0
*A num+1
*B num-1
*
* @author 路飞
* @create 2021/1/18
*/
public class A {
public static void main(String[] args) {

//线程资源类
Data data = new Data();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();

new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();

new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();

new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();


}
}

//等待,业务,通知
class Data {
private int number = 0;

//+1
public synchronized void increment() throws InterruptedException {
//用if判断 会产生虚假唤醒
if (number != 0) {
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,+1完毕
this.notifyAll();
}

//-1
public synchronized void decrement() throws InterruptedException {
if (number == 0) {
//等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,-1完毕
this.notifyAll();
}
}

这段代码的意思是定义了两个方法,一个负责num+1,一个负责num-1,由于加入sync,并且加了num是否为0的判断,所以按道理应该是线程进来,一个生成+1,一个消费-1,但运行结合出现:

D=>0
C=>1
A=>2
C=>3

很明显还是出现线程不安全的问题,查阅JDK帮助文档的wait方法,发现:

wait存在虚假唤醒,需要用把if改成while,实现类似自旋锁的功能,始终去判断!

故原来的代码可以改成:

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
//等待,业务,通知
class Data {
private int number = 0;

//+1
public synchronized void increment() throws InterruptedException {
//用if判断 会产生虚假唤醒
while (number != 0) {
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,+1完毕
this.notifyAll();
}

//-1
public synchronized void decrement() throws InterruptedException {
while (number == 0) {
//等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,-1完毕
this.notifyAll();
}
}

测试通过!

图解生产者和消费者

通过Lock找到Codition

可以发现,Lock版的生产者和消费者问题可以new 一个ReentrantLock实例,用它的实例去创建condition,然后再去实现线程之间的通信(生产者和消费者问题的本质)

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
/**
* JUC的Lock替代Sync
*
* @author 路飞
* @create 2021/1/18
*/
public class B {
public static void main(String[] args) {
Data2 data = new Data2();

new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();

new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();

new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();

new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
}

//
class Data2 {
private int number = 0;

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

//+1
public void increment() throws InterruptedException {
lock.lock();
try {
//业务代码
//用if判断 会产生虚假唤醒
while (number != 0) {
//等待
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,+1完毕
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

//-1
public void decrement() throws InterruptedException {
lock.lock();
try {
//用if判断 会产生虚假唤醒
while (number == 0) {
//等待
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,-1完毕
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

那么到这,可能你会问?sync就可以实现生产者和消费者问题,为啥还要用Lock呢?代码明显增多了

Condition 实现精准的通知和唤醒线程

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
/**
* JUC的精准通知和唤醒线程
*
* @author 路飞
* @create 2021/1/18
*/
public class C {
public static void main(String[] args) {
Data3 data = new Data3();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printA();
}
}, "A").start();

new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printB();
}
}, "B").start();

new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printC();
}
}, "C").start();


}
}

//资源类
class Data3 {
private Lock lock = new ReentrantLock();

private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1;

public void printA() {
lock.lock();
try {
//业务
while (number != 1) {
condition1.await();
}
number = 2;
System.out.println(Thread.currentThread().getName() + "BBBBB");
//精准唤醒
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void printB() {
lock.lock();
try {
//业务
while (number != 2) {
condition2.await();
}
number = 3;
System.out.println(Thread.currentThread().getName() + "CCCCCC");
//精准唤醒
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void printC() {
lock.lock();
try {
//业务
while (number != 3) {
condition3.await();
}
number = 1;
System.out.println(Thread.currentThread().getName() + "AAAAAA");
//精准唤醒
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}