JUC并发编程详解(通俗易懂)
一、JUC简介
在Java5.0提供了java.util.concurrent
包,简称JUC,即Java并发编程工具包。JUC更好的支持高并发任务。
具体的有以下三个包:
java.util.concurrent
java.util.concurrent.atomic
java.util.concurrent.locks
二、Lock锁
1、传统的synchronized锁
/**
* synchronized售票例子
*/
public class SynSaleTicket {
//真正在公司开发,遵守oop思想,降低耦合性
//线程就是一个单独的资源类,没有任何附属操作,里面只包含属性、方法
public static void main(String[] args) {
//并发:多个线程操作同一个资源
Ticket ticket = new Ticket();
//lambda表达式
new Thread(() -> {
for (int i = ; i < 40; i++) {
ticket.sale();
}
}, "A").start();
new Thread(() -> {
for (int i = ; i < 40; i++) {
ticket.sale();
}
}, "B").start();
new Thread(() -> {
for (int i = ; i < 40; i++) {
ticket.sale();
}
}, "C").start();
}
}
//资源类
class Ticket {
//票数
private int number = 30;
//买票方法
//synchronized本质就是队列+锁
public synchronized void sale() {
if (number > ) {
System.out.println(Thread.currentThread().getName() + "抢到了第" + (number--) + "张票,剩下" + number);
}
}
}
2、JUC包下的Lock接口
查看jdk1.8官方文档可以看到有三个实现类:
- ReentrantLock:可重入锁(常用)
- ReentrantReadWriteLock.ReadLock:可重入读锁
- ReentrantReadWriteLock.WriteLock:可重入写锁
//lock锁用法:
//1. 创建锁对象 Lock l = ...;
//2. 加锁 l.lock();
//3. 解锁 try {} finally { l.unlock(); }
1. 公平锁和非公平锁
通俗的说公平锁其实就是买票都需要排队按队伍顺序遵循先来后到的原则获得锁,非公平锁就是有人开VIP可以插队获得锁。
- 公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的位才能得到锁。
- 优点:所有的线程都能得到资源,不会饿死在队列中。
- 缺点:吞吐量会下降很多,队列里面除了个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
- 非公平锁:多个线程获取锁的时候,不会按照申请锁的顺序去获得锁,会直接尝试获取锁,如果能获取到锁,就直接获得锁,如果获取不到,再进入等待队列乖乖等待。
- 优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
- 缺点:可能导致队列中排队的线程一直获取不到锁或者长时间获取不到锁,活活饿死。
//ReentrantLock无参构造,相当于ReentrantLock(false)
public ReentrantLock() {
sync = new NonfairSync();//默认是非公平锁
}
//ReentrantLock有参构造,fair参数决定是否选为公平锁,true是,false否
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync()/*公平锁*/ : new NonfairSync()/*非公平锁/*;
}
package com.hao.demo01;
//使用juc包下的锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Lock售票例子
*/
public class LockSaleTicket {
//使用Lock锁来解决买票问题
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(() -> {for (int i = ; i < 40; i++) ticket.sale();}, "A").start();
new Thread(() -> {for (int i = ; i < 40; i++) ticket.sale();}, "B").start();
new Thread(() -> {for (int i = ; i < 40; i++) ticket.sale();}, "C").start();
}
}
class Ticket2 {
//票数
private int number = 30;
//Lock锁
Lock lock = new ReentrantLock();
//买票方法
public void sale() {
lock.lock();//加锁
try {
if (number > ) {
System.out.println(Thread.currentThread().getName() + "抢到了第" + (number--) + "张票,剩下" + number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//解锁
}
}
}
3、Synchronized 和 Lock锁的区别
区别 | Synchronized | Lock |
---|---|---|
是否关键字 | Synchronized是Java内置关键字 | Lock类是一个接口 |
是否可尝试获取锁 | Synchronized无法判断是否获取锁的状态 | Lock可以判断是否获取到锁 |
是否自动释放锁 | Synchronized会自动释放锁(a 线程执行完同步代码会释放锁;b 线程执行过程中发生异常会释放锁) | Lock需在finally中手工释放锁,否则容易造成线程死锁 |
是否一直阻塞 | 用Synchronized关键字修饰的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去 | Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了 |
是否可重入、中断、公平锁 | Synchronized的锁可重入、不可中断、非公平 | Lock锁可重入、可中断、可公平(也可非公平) |
使用场合 | Synchronized锁适合代码少量的同步问题 | Lock锁适合大量同步的代码的同步问题 |
相关文章