JUC并发编程学习(一)
一、线程
1、wait和sleep区别
1)、来自不同的类
wait 是 object类的
sleep是 Thread类的
2)、关于锁的释放
wait会释放锁,sleep不会释放锁。
3)、使用的范围不同
wait 必须在同步代码块中使用
sleep 可以在任何地方使用
二、Lock锁
1、synchronized
略
2、Lock
1)、ReentrantLock
ReentrantLock–>可重入锁,使用new创建的时候如下:
默认无参构造为非公平锁,有参构造传true为公平锁。
公平锁:线程获取锁的顺序和调用lock的顺序一样,先来后到;
非公平锁:线程获取锁的顺序和调用lock的顺序无关,全凭运气
3、synchronized 和 Lock的区别
1、synchronized 是内置的关键字,Lock 是一个Java类;
2、synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁;
3、synchronized 会自动释放锁,Lock 必须要手动释放锁,否则会死锁;
4、synchronized 对 线程1 加锁(这时线程1阻塞),线程2就会一直等待,Lock锁就不一定会等待下去;
5、synchronized 可重入、不可中断、非公平锁;Lock 可重入、可判断锁、非公平(可以自己设置);
6、synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码。
三、生产者和消费者
1、Synchronized 版
大致流程为:判断等待=》业务逻辑=》通知其他线程
配套的方法为:
1)、实现案例
资源类:
package com.zhan.juc.lock.producerAndconsumer;
/**
* @Author Zhanzhan
* @Date 2020/11/28 13:12
* 资源类
*/
public class Data {
private int number = 0;
/**
* 增量方法,number + 1
*/
public synchronized void increment() throws InterruptedException {
if (number != 0){
// 等待
this.wait();
}
// 开始具体的业务逻辑
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 加完后,通知其他线程 +1 操作完毕了
this.notifyAll();
}
/**
* 减量操作,number - 1
*/
public synchronized void decrement() throws InterruptedException {
if (number == 0){
// 等待
this.wait();
}
// 开始具体的业务逻辑
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 减完后,通知其他线程 -1 操作完毕了
this.notifyAll();
}
}
线程操作类:
package com.zhan.juc.lock.producerAndconsumer;
/**
* @Author Zhanzhan
* @Date 2020/11/28 13:11
* 线程之间的通信问题
* 线程交替执行
* A线程 和 B线程 操作 同一个变量 num,当 A 将 num+1 时,通知 B 将 num-1
*/
public class Demo1 {
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();
}
}
运行结果:
2)、存在的问题
目前只是模拟了A和B两个线程,那如果扩展到三个或者四个线程呢,会有什么情况?
我们将上面的线程操作类稍加变换,扩展到四个线程(A、B、C、D),A和C线程执行增量的increment(),B和D执行减量的decrement(),如下:
package com.zhan.juc.lock.producerAndconsumer;
/**
* @Author Zhanzhan
* @Date 2020/11/28 13:11
* 线程之间的通信问题
* 线程交替执行
* A线程 和 B线程 操作 同一个变量 num,当 A 将 num+1 时,通知 B 将 num-1
*/
public class Demo1 {
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();
}
}
结果如下:
我们很神奇的发现,结果和我们的预期严重不符。那么问题在哪儿呢?
这里的判断需要使用while进行判断,可以防止虚假唤醒,所以我们需要将资源类稍加修改,如下:
package com.zhan.juc.lock.producerAndconsumer;
/**
* @Author Zhanzhan
* @Date 2020/11/28 13:12
* 资源类
*/
public class Data {
private int number = 0;
/**
* 增量方法,number + 1
*/
public synchronized void increment() throws InterruptedException {
while (number != 0){
// 等待
this.wait();
}
// 开始具体的业务逻辑
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 加完后,通知其他线程 +1 操作完毕了
this.notifyAll();
}
/**
* 减量操作,number - 1
*/
public synchronized void decrement() throws InterruptedException {
while (number == 0){
// 等待
this.wait();
}
// 开始具体的业务逻辑
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 减完后,通知其他线程 -1 操作完毕了
this.notifyAll();
}
}
再次运行,结果如下:
我们发现,这次完美的符合预期。
2、Lock版
1)、代码实现
资源类:
package com.zhan.juc.producerAndConsumer.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author Zhanzhan
* @Date 2020/11/28 14:27
*/
public class Data2 {
private int number = 0;
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
/**
* 增量方法,number + 1
*/
public void increment() {
lock.lock();// 加锁
try {
while (number != 0){
// 等待
condition.await();
}
// 开始具体的业务逻辑
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 加完后,通知其他线程 +1 操作完毕了
condition.signalAll();// 唤醒全部等待线程
} catch (Exception e){
e.printStackTrace();
} finally {
lock.unlock();// 释放锁
}
}
/**
* 减量操作,number - 1
*/
public void decrement() {
lock.lock();// 加锁
try {
while (number == 0){
// 等待
condition.await();
}
// 开始具体的业务逻辑
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 减完后,通知其他线程 -1 操作完毕了
condition.signalAll();// 唤醒全部等待线程
} catch (Exception e){
e.printStackTrace();
} finally {
lock.unlock();// 释放锁
}
}
}
线程操作类:
package com.zhan.juc.producerAndConsumer.lock;
/**
* @Author Zhanzhan
* @Date 2020/11/28 14:38
*/
public class Demo2 {
public static void main(String[]args){
Data2 data = new Data2();
new Thread(()->{
for (int i = 0;i<10;i++){
data.increment();
}
}, "A").start();
new Thread(()->{
for (int i = 0;i<10;i++){
data.decrement();
}
}, "B").start();
new Thread(()->{
for (int i = 0;i<10;i++){
data.increment();
}
}, "C").start();
new Thread(()->{
for (int i = 0;i<10;i++){
data.decrement();
}
}, "D").start();
}
}
运行结果:
显示结果和预期一样。
2)、功能进阶
上面的实例中,我们看到结果,发现A B C D四个线程不是按照我们写出的顺序有序执行,而是四个线程间随机执行,那么我们如果想要多个线程间有序执行,或者要精准的通知和唤醒指定线程,能实现吗?
我们这里可以使用Condition类的signal()来实现精准唤醒,上代码:
package com.zhan.juc.producerAndConsumer.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author Zhanzhan
* @Date 2020/11/28 14:46
* 实现多个线程间的精确唤醒和通知
* A 执行完 调用 B, B 执行完 调用 C, C 执行完 调用 A
*/
public class Demo3 {
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 final Lock lock = new ReentrantLock();
private final Condition condition1 = lock.newCondition();
private final Condition condition2 = lock.newCondition();
private final Condition condition3 = lock.newCondition();
private int number = 1;// 1 -> A, 2 -> B, 3 -> C
/**
* A线程执行
*/
public void printA(){
lock.lock();
try {
// 业务, 判断 -> 执行 -> 通知
while (number != 1){
// 等待
condition1.await();// 将同步代码块 printA() 关入 小黑屋 condition1
}
System.out.println(Thread.currentThread().getName()+ "运行---");
number = 2;
// 通知 指定的线程
condition2.signal();// 小黑屋 condition2 开门,放出里面关的
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
/**
* B线程执行
*/
public void printB(){
lock.lock();
try {
// 业务, 判断 -> 执行 -> 通知
while (number != 2){
// 等待
condition2.await();// 将同步代码块 printB() 关入 小黑屋 condition2
}
System.out.println(Thread.currentThread().getName()+ "运行---");
number = 3;
// 通知 指定的线程
condition3.signal();// 小黑屋 condition3 开门,放出里面关的
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
/**
* C线程执行
*/
public void printC(){
lock.lock();
try {
// 业务, 判断 -> 执行 -> 通知
while (number != 3){
// 等待
condition3.await();// 将同步代码块 printC() 关入 小黑屋 condition3
}
System.out.println(Thread.currentThread().getName()+ "运行---");
number = 1;
// 通知 指定的线程
condition1.signal();// 小黑屋 condition1 开门,放出里面关的
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
运行结果如下:
完美符合我们的预期。