小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
JUC
JUC 是 java.util .concurrent 工具包的简称。这是一个处理线程的工具包。
并发包中包含了许多并发编程中需要用到的类。
JUC 是从JDK 1.5开始出现。
JUC 目的就是为了更好的支持高并发任务
并发:可以理解为多个线程操作同一个资源
1、传统的 Synchronized 锁
不加 Synchronize 锁
以卖票为例:
package com.cheng.lock;
public class LockDemo1 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
//并发:把资源类放入每一个线程中,多个线程操作同一个资源 lambda表达式:(参数)->{代码}
new Thread(()-> {
for (int i = 0; i < 50; i++) {
ticket.sale();
}
},"小明").start();
new Thread(()-> {
for (int i = 0; i < 50; i++) {
ticket.sale();
} },"小红").start();
new Thread(()-> {
for (int i = 0; i < 50; i++) {
ticket.sale();
} },"小白").start();
}
}
//资源类 Ticket,属性 + 方法
class Ticket{
private int nums = 50;
public void sale(){
if (nums > 0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(nums--)+"张票,剩余"+nums+"张票");
}
}
}
运行查看结果:
当操作资源的方法不加 Synchronize 锁时,多个线程操作资源会出现数据紊乱。
加上 Synchronize 锁
给方法加上 Synchronize 锁
public synchronized void sale(){
if (nums > 0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(nums--)+"张票,剩余"+nums+"张票");
}
}
运行后,数据有序,不发生紊乱。
2、LOCK 锁
Lock是一个接口,在JUC包下,Lock接口是控制多个线程对共享资源进行访问的工具。每次只能有一个线程拿到Lock对象的锁。
线程开始访问共享资源前应先获得Lock对象。
Lock 的实现类
ReentrantLock:可重入、独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活
ReentrantReadWriteLock.ReadLock:读锁
ReentrantReadWriteLock.WriteLock:写锁
ReentrantLock 实现公平锁和非公平锁
公平锁和非公平锁:
公平锁是指当锁可用时,在锁上等待时间最长的线程将获得锁的使用权,先来后到。
非公平锁则随机分配这种使用权,可以插队。
ReentrantLock 源码:
通过上面源码分析得知:
默认的 ReentrantLock 实现是非公平锁(因为非公平锁性能更好)
在创建ReentrantLock的时候通过传进参数 true 创建公平锁,传入的是false或没传参数则创建的是非公平锁
LOCK 锁的使用实例
1、new ReentrantLock();
2、加锁 lock.lock()
3、解锁 lock.unlock()
package com.cheng.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockDemo2 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
//并发:把资源类放入每一个线程中,多个线程操作同一个资源 lambda表达式:(参数)->{代码}
new Thread(()-> { for (int i = 0; i < 20; i++) ticket.sale(); },"小明").start();
new Thread(()-> { for (int i = 0; i < 20; i++) ticket.sale(); },"小红").start();
new Thread(()-> { for (int i = 0; i < 20; i++) ticket.sale(); },"小白").start();
}
}
//资源类 Ticket,属性 + 方法
class Ticket1{
private int nums = 20;
//创建 ReentrantLock 对象
Lock lock = new ReentrantLock();
public void sale(){
lock.lock(); //加锁
try {
//业务代码
if (nums > 0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(nums--)+"张票,剩余"+nums+"张票");
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock(); //解锁
}
}
}
3、 Synchronized 和 Lock 的区别
1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
2.synchronized无法判断是否获取到锁,Lock可以判断是否获取到锁;
3.synchronized在线程发生异常时会自动释放锁,因此不会发生异常死锁,Lock需在finally中手动释放锁;
4.用synchronized关键字,如果当前线A程阻塞,线程B会一直等待下去,而Lock锁不会等待下去,使用tryLock()尝试获取锁,如果获取不到,线程可以不用一直等待;
5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
7.在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。
4、生产者消费者问题
4.1、synchronized 版本生产者消费者问题
package com.cheng.PC;
public class PCSynchronize {
public static void main(String[] args) {
Test1 test1 = new Test1();
//创建两个线程操作资源
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
test1.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
test1.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
class Test1{
private int nums = 0;
public synchronized void increment() throws InterruptedException {
if (nums != 0){
this.wait();//线程等待
}
nums++;
System.out.println(Thread.currentThread().getName()+"-->"+nums);
this.notifyAll();//nums++后,通知唤醒其他线程来num--
}
public synchronized void decrement() throws InterruptedException {
if (nums == 0){
this.wait();
}
nums--;
System.out.println(Thread.currentThread().getName()+"-->"+nums);
this.notifyAll();//nums--后,通知唤醒其他线程来num++
}
}
启动测试:
上面代码看起来没问题,但如果我们把两个线程增加到四个线程呢?
新增两个线程 C,D:
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
test1.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
test1.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
再次启动测试:
线程之间通信出现问题,这是因为我们使用 if 判断是否需要等待,这样会造成虚假唤醒,等待应该总是出现在循环中。
虚假唤醒:线程可以被唤醒,但不会被通知、中断或超时。
解决虚假唤醒方法:把上面的 if 改成 while:
public synchronized void increment() throws InterruptedException {
while (nums != 0){
this.wait();//线程等待
}
nums++;
System.out.println(Thread.currentThread().getName()+"-->"+nums);
this.notifyAll();//nums++后,通知唤醒其他线程来num--
}
public synchronized void decrement() throws InterruptedException {
while (nums == 0){
this.wait();
}
nums--;
System.out.println(Thread.currentThread().getName()+"-->"+nums);
this.notifyAll();//nums--后,通知唤醒其他线程来num++
}
再次启动测试:OK!
4.2、Lock 版本生产者消费者问题
Contition 介绍
关键字synchronized与wait()/notify()这两个方法一起使用可以实现等待/通知模式, Lock对象的newContition()方法返回Condition实例,Condition类也可以实现等待/通知模式。
Condition 常用的方法:
-
await() :会使当前线程等待,同时会释放锁,当其他线程调用signal()或signalAll()时,线程会重新获得锁并继续执行。
-
signal() :唤醒一个等待的线程。如果任何线程正在等待此条件,则选择一个线程进行唤醒,那个线程必须在从 await 之前重新获取锁。
-
signalAll() :唤醒所有等待的线程。如果任何线程正在等待此条件,那么它们都被唤醒,每个线程必须在从 await 之前重新获取锁。
Condition 通知与等待
代码测试:
package com.cheng.PC;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class PCLock {
public static void main(String[] args) {
Test2 test2 = new Test2();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
test2.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
test2.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
test2.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
test2.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
class Test2{
private int nums = 0;
Lock lock = new ReentrantLock();//获得锁的对象
Condition condition = lock.newCondition();//获得Condtion对象
public void increment() throws InterruptedException {
lock.lock();
try {
while (nums != 0){
//线程等待
condition.await();
}
nums++;
System.out.println(Thread.currentThread().getName()+"-->"+nums);
//nums++后,通知唤醒其他线程来num--
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() throws InterruptedException {
lock.lock();
try {
while (nums == 0){
condition.await();
}
nums--;
System.out.println(Thread.currentThread().getName()+"-->"+nums);
condition.signalAll();//nums--后,通知唤醒其他线程来num++
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
运行结果:随机唤醒,执行顺序混乱
用notify()通知时,JVM会随机唤醒某个等待的线程, 使用Condition类可以进行选择性通知。
Condition 实现选择性通知
多个Condition实现通知部分线程, 使用更灵活,
创建三个线程A B C, 执行顺序为:A->B->C
package com.cheng.PC;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class PCLockPlus {
public static void main(String[] args) {
Test3 test3 = new Test3();
//创建三个线程A B C, 执行顺序为:A->B->C
new Thread(() -> {
for (int i = 0; i < 10; i++) {
test3.TestA();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
test3.TestB();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
test3.TestC();
}
}, "C").start();
}
}
class Test3 {
private Lock lock = new ReentrantLock();
//创建三个 Condition 实例,每个Condition实例监视一个线程
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int nums = 1; //nums=1,执行线程A;nums=2,执行线程B;nums=3,执行线程C
public void TestA() {
lock.lock();
try {
while (nums != 1) {
condition1.await();
}
nums = 2;
System.out.println(Thread.currentThread().getName() + "->AAAAA");
condition2.signal();//执行完后,指定通知condition2
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void TestB() {
lock.lock();
try {
while (nums != 2) {
condition2.await();
}
nums = 3;
System.out.println(Thread.currentThread().getName() + "->BBBBB");
condition3.signal();//指定通知condition3
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void TestC() {
lock.lock();
try {
while (nums != 3) {
condition3.await();
}
nums = 1;
System.out.println(Thread.currentThread().getName() + "->CCCCC");
condition1.signal();//指定通知condition1
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}