Java基础-锁
1、Lock接口
- 锁是一种工具,用于控制对共享资源的访问
- Lock和synchronize,这两个是最常见的锁,他们都可以达到线程安全的目的,但是在使用上和功能上又有较大的不同
- Lock并不能代替synchronize,而是使用synchronize不合适或无法满足要求时,来提供高级功能的
synchronize效率低,不够灵活,无法知道是否成功获取到锁。
- lock()就是最普通的获取锁。如果锁被其他线程获取,则进行等待
- Lock不会像synchronize一样,异常自动释放锁
- 最佳实践是,在finally中释放锁,以保证发送异常的时候,锁一定会被释放
- lock()方法不能被中短,这会带来隐患,一旦陷入死锁,lock()就会陷入永久等待
- tryLock()用来尝试获取锁
2、锁的分类
分类,是从不同的角度出发,一种锁可能对应多个类型,一个类型可能对应多个锁
- 线程要不要锁住同步资源
- 锁住(悲观锁)
- 不锁住(乐观锁)
- 多线程能否共享一把锁
- 可以(共享锁)
- 不可以(独占锁)
- 多线程竞争时,是否排队
- 排队(公平锁)
- 先尝试插队,插队失败再排队(非公平锁)
- 同一个线程是否可以重复获取同一把锁
- 可以(可重入)
- 不可以(不可重入锁)
- 是否可中断
- 可以(可中断锁)
- 不可以(非可中断锁)
- 等所的过程
- 自旋(自旋锁)
- 阻塞(非自旋锁)
3、乐观锁和悲观锁
3.1、互斥同步锁的劣势
- 阻塞和唤醒会有性能消耗,上下文切换
- 可能陷入永久等待,如果持有锁的线程陷入死锁
3.2、什么是乐观锁和悲观锁
悲观锁:
- 如果我不锁住这个资源,别人就会来争抢,就会造成结果错误,所以每次悲观锁为了确保结果的正确性,会在每次获取并修改数据时,把数据锁住,让别人无法访问该数据,这样可以确保数据万无一失
- java中悲观锁的实现就是synchronize和Lock相关
乐观锁:
- 认为自己在处理操作的时候不会有其他人来干扰,所以并不会锁住被操作的对象
- 如果数据和我一开始拿到的不一样,说明其他人在这段时间内改过数据,那我就不能继续更新刚才的数据,我会选择放弃、报错、 重试等策略
- 乐观锁的实现一般都是利用CAS算法来实现
- 乐观锁的典型例子就是原子类、并发容器等
/*** @Classname PessimismOptimismLock* @Description 乐观锁和悲观锁* @Date 2021/4/19 21:56* @Created by WangXiong*/ public class PessimismOptimismLock {int a ;//悲观锁public synchronized void testMethod(){a++;}public static void main(String[] args) {AtomicInteger atomicInteger = new AtomicInteger();//乐观锁atomicInteger.getAndIncrement();} }3.3、适合使用的场景
悲观锁:
- 临界区有IO操作
- 临界区代码复杂或者循环量大
- 临界区竞争非常激烈
乐观锁:
- 适用于写入少,大部分是读取的场景,不加锁能让读取性能大幅度提高
- git版本管理工具,数据库版本号
4、可重入锁和非可重入锁
以ReentrantLock举例
public class LockDemo {static class Outputer {Lock lock = new ReentrantLock();//字符串打印方法,一个个字符的打印public void output(String name){int len = name.length();// lock.lock();try{for (int i = 0; i < len; i++) {System.out.print(name.charAt(i));}System.out.println("");}finally {// lock.unlock();}}}private void init(){final Outputer outputer = new Outputer();new Thread(new Runnable() {public void run() {while (true){try {Thread.sleep(5);}catch (InterruptedException e){e.printStackTrace();}outputer.output("悟空");}}}).start();new Thread(new Runnable() {public void run() {while (true){try {Thread.sleep(5);}catch (InterruptedException e){e.printStackTrace();}outputer.output("大师兄");}}}).start();}public static void main(String[] args) {new LockDemo().init();}}
4.1、可重入
同一个线程可以多次获取同一把锁,这样的好处是:避免死锁,提高了封装性
public class GetHoldCount {private static ReentrantLock lock = new ReentrantLock();public static void main(String[] args) {System.out.println(lock.getHoldCount());lock.lock();System.out.println(lock.getHoldCount());lock.lock();System.out.println(lock.getHoldCount());lock.lock();System.out.println(lock.getHoldCount());lock.unlock();System.out.println(lock.getHoldCount());lock.unlock();System.out.println(lock.getHoldCount());lock.unlock();System.out.println(lock.getHoldCount());} }
5、公平锁和非公平锁
5.1、什么是公平和非公平
公平指的是安装线程请求的顺序,来分配锁;非公平指的是,不完全安装请求的顺序,在一定情况下,可以插队。
设置非公平,是为了提高效率,避免唤醒带来的空档期
ReentrantLock默认是非公平锁,如果要设置为公平锁,需要传递一个参数
5.2、代码演示公平和不公平
public class FairLock {public static void main(String[] args) {PrintQueue printQueue = new PrintQueue();Thread[] thread = new Thread[10];for (int i = 0; i < thread.length; i++) {thread[i] = new Thread(new Job(printQueue));}for (int i = 0; i < thread.length; i++) {thread[i].start();try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}} }class Job implements Runnable{PrintQueue printQueue;public Job(PrintQueue printQueue) {this.printQueue = printQueue;}public void run() {System.out.println(Thread.currentThread().getName() + "开始打印");printQueue.printJob(new Object());System.out.println(Thread.currentThread().getName() + "打印完毕");} }class PrintQueue{// private Lock queueLock = new ReentrantLock();private Lock queueLock = new ReentrantLock(true);public void printJob(Object document){queueLock.lock();try {int duration = new Random().nextInt(10) + 1;System.out.println(Thread.currentThread().getName() + "正在打印,需要" + duration);Thread.sleep(duration * 1000);} catch (InterruptedException e) {e.printStackTrace();} finally {queueLock.unlock();}queueLock.lock();try {int duration = new Random().nextInt(10) + 1;System.out.println(Thread.currentThread().getName() + "正在打印,需要" + duration);Thread.sleep(duration * 1000);} catch (InterruptedException e) {e.printStackTrace();} finally {queueLock.unlock();}} }
5.3、优缺点
公平锁:
- 优点:线程之间公平,每个线程在等待一段时间后,总有执行的机会。
- 缺点:更慢,吞吐量小
不公平锁:
- 更快,吞吐量大
- 有可能产生线程饥饿,也就是某些线程在长时间内,始终得不到执行
6、共享锁和排它锁
以ReentrantReadWriteLock读写锁为例
- 排它锁,有称为独占锁,独享锁
- 共享锁,又称为读锁,获得共享锁之后,可以查看但无法修改和删除数据,其他线程此时获取到共享锁,也可以查看但无法修改和删除数据
- 共享锁和排它锁的典型是读写锁R嗯嗯陶然亭ReentrantReadWriteLock,其中读锁是共享锁,写锁是独享锁
- 在没有读写锁之前,我们假设使用ReentrantLock,那么虽然我们保证了线程安全,但是也浪费了一定的资源:多个读操作同时进行,并没有线程安全问题
- 在读的地方使用读锁,在写的地方使用写锁,灵活控制,如果没有写锁的情况下,读是无阻塞的,提高了程序的执行效率
- 一句话总结:要么是一个或多个线程同时有读锁,要么是一个线程有写锁,但是二者不会同时出现(要么多读,要么一写)
代码演示
public class CinemaReadWrite {private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();private static void read(){readLock.lock();try{System.out.println(Thread.currentThread().getName() + "获取到了读锁,正在读取");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}finally {System.out.println(Thread.currentThread().getName() + "释放了读锁");readLock.unlock();}}private static void write(){writeLock.lock();try{System.out.println(Thread.currentThread().getName() + "获取到了写锁,正在写入");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}finally {System.out.println(Thread.currentThread().getName() + "释放了写锁");writeLock.unlock();}}public static void main(String[] args) {new Thread(new Runnable() {public void run() {CinemaReadWrite.read();}}, "Thread1").start();new Thread(new Runnable() {public void run() {CinemaReadWrite.read();}}, "Thread2").start();new Thread(new Runnable() {public void run() {CinemaReadWrite.write();}}, "Thread3").start();new Thread(new Runnable() {public void run() {CinemaReadWrite.write();}}, "Thread4").start();} }测试
6.1、读写锁插队策略
- 公平锁:不允许插队
- 非公平锁:
- 写锁可以随时插队(如果拿不到锁,就排队)
- 读锁仅在等待头结点不是想获取写锁的时候可以插队(读锁还是可以插队的)
测试队列的头结点是写,读就不会插队
public static void main(String[] args) {new Thread(new Runnable() {public void run() {CinemaReadWrite.write();}}, "Thread1").start();new Thread(new Runnable() {public void run() {CinemaReadWrite.read();}}, "Thread2").start();new Thread(new Runnable() {public void run() {CinemaReadWrite.read();}}, "Thread3").start();new Thread(new Runnable() {public void run() {CinemaReadWrite.write();}}, "Thread4").start();new Thread(new Runnable() {public void run() {CinemaReadWrite.read();}}, "Thread5").start();}
头结点是读锁,也是可以插队的
public class NonfairBargeDemo {//非公平的,其实是可以插队的private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(false);private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();private static void read(){System.out.println(Thread.currentThread().getName() + "开始尝试获取读锁");readLock.lock();try {System.out.println(Thread.currentThread().getName() + "得到读锁,正在读取");Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();} finally {System.out.println(Thread.currentThread().getName() + "释放读锁");readLock.unlock();}}private static void write(){System.out.println(Thread.currentThread().getName() + "开始尝试获取写锁");writeLock.lock();try {System.out.println(Thread.currentThread().getName() + "得到写锁,正在写入");Thread.sleep(40);} catch (InterruptedException e) {e.printStackTrace();} finally {System.out.println(Thread.currentThread().getName() + "释放写锁");writeLock.unlock();}}public static void main(String[] args) {new Thread(()->write(), "Thread1").start();new Thread(()->read(), "Thread2").start();new Thread(()->read(), "Thread3").start();new Thread(()->write(), "Thread4").start();new Thread(()->read(), "Thread5").start();new Thread(new Runnable() {@Overridepublic void run() {Thread thread[] = new Thread[1000];for (int i = 0; i < thread.length; i++) {thread[i] = new Thread(()->read(), "子线程创建的Threaad"+i);}for (int i = 0; i < thread.length; i++) {thread[i].start();}}}).start();} }
如果是公平的
public class NonfairBargeDemo {//设置为公平,完全按照排队的顺序来执行private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(true);private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();private static void read(){System.out.println(Thread.currentThread().getName() + "开始尝试获取读锁");readLock.lock();try {System.out.println(Thread.currentThread().getName() + "得到读锁,正在读取");Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();} finally {System.out.println(Thread.currentThread().getName() + "释放读锁");readLock.unlock();}}private static void write(){System.out.println(Thread.currentThread().getName() + "开始尝试获取写锁");writeLock.lock();try {System.out.println(Thread.currentThread().getName() + "得到写锁,正在写入");Thread.sleep(40);} catch (InterruptedException e) {e.printStackTrace();} finally {System.out.println(Thread.currentThread().getName() + "释放写锁");writeLock.unlock();}}public static void main(String[] args) {new Thread(()->write(), "Thread1").start();new Thread(()->read(), "Thread2").start();new Thread(()->read(), "Thread3").start();new Thread(()->write(), "Thread4").start();new Thread(()->read(), "Thread5").start();new Thread(new Runnable() {@Overridepublic void run() {Thread thread[] = new Thread[1000];for (int i = 0; i < thread.length; i++) {thread[i] = new Thread(()->read(), "子线程创建的Threaad"+i);}for (int i = 0; i < thread.length; i++) {thread[i].start();}}}).start();} }
6.2锁的升级和降级
public class Upgrading {private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(true);private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();private static void readUpgrading(){System.out.println(Thread.currentThread().getName() + "开始尝试获取读锁");readLock.lock();try {System.out.println(Thread.currentThread().getName() + "得到读锁,正在读取");Thread.sleep(1000);System.out.println("升级会带来阻塞");writeLock.lock();System.out.println(Thread.currentThread().getName() + "获取到了写锁,升级成功");} catch (InterruptedException e) {e.printStackTrace();} finally {System.out.println(Thread.currentThread().getName() + "释放读锁");readLock.unlock();}}private static void writeUpgrading(){System.out.println(Thread.currentThread().getName() + "开始尝试获取写锁");writeLock.lock();try {System.out.println(Thread.currentThread().getName() + "得到写锁,正在写入");Thread.sleep(1000);readLock.lock();System.out.println("在不释放写锁的情况下,直接 获取读锁,成功降级");} catch (InterruptedException e) {e.printStackTrace();} finally {readLock.unlock();System.out.println(Thread.currentThread().getName() + "释放写锁");writeLock.unlock();}}public static void main(String[] args) throws InterruptedException {System.out.println("先演示降级是可以的");Thread thread1 = new Thread(()->writeUpgrading(), "Thread1");thread1.start();thread1.join();System.out.println("-------------------------");System.out.println("演示升级是不行的");Thread thread2 = new Thread(()-> readUpgrading(), "Thread2");thread2.start();} }
为什么不支持锁的升级?避免死锁
6.3、小结
- ReentrantReadWriteLock实现了ReadWriteLock接口,最主要有两个方法:readLock()和writeLock()用来获取读锁和写锁
- 要么多读,要么一写
- 插队策略:为了防止饥饿,读锁不能插队
- 升降级策略:只能降级,不能升级
- ReentrantReadWriteLock适用于读多写少的情况,合理使用可以提高并发效率
7、自旋锁和阻塞锁
- 自旋锁:不停的请求,尝试获取锁,没有获取锁的时候不阻塞
- 如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长。为了这一段时间,切换线程,是得不偿失的。
- 阻塞锁:如果没有获取到锁,就会陷入阻塞状态
自旋其实是使用do..while循环来完成的
//AtomicInteger原子类的方法public final int getAndIncrement() {return unsafe.getAndAddInt(this, valueOffset, 1);}public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {var5 = this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));return var5;}实现一个简单的自旋锁
public class SpinLock {private AtomicReferencesign = new AtomicReference<>();public void loock(){Thread currentThread = Thread.currentThread();while (!sign.compareAndSet(null, currentThread)){System.out.println("自旋获取失败,再次尝试");}}public void unlock(){Thread currentThread = Thread.currentThread();sign.compareAndSet(currentThread, null);}public static void main(String[] args) {SpinLock spinLock = new SpinLock();Runnable runnable = new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "尝试获取自旋锁");spinLock.loock();System.out.println(Thread.currentThread().getName() + "获取到了自旋锁");try {Thread.sleep(300);} catch (InterruptedException e) {e.printStackTrace();} finally {spinLock.unlock();System.out.println(Thread.currentThread().getName() + "释放了自旋锁");}}};new Thread(runnable).start();new Thread(runnable).start();} }
适用于并发度不是特别高的情况
8、可中断锁
在Java中,synchronize就是不是可中断锁,而Lock是可中断锁,因为tryLock(time)和InckInterruptibly都能响应中断
public class LockInterruptibly implements Runnable {private static Lock lock = new ReentrantLock();public void run() {System.out.println(Thread.currentThread().getName() + "尝试获取锁");try{//lockInterruptibly相当于无限期等待,等锁期间,可以被打断lock.lockInterruptibly();try {System.out.println(Thread.currentThread().getName() + "获取到了锁");Thread.sleep(5000);}catch (InterruptedException e) {System.out.println(Thread.currentThread().getName() + "睡眠期间被中断");}finally {lock.unlock();System.out.println(Thread.currentThread().getName() + "释放了锁");}}catch (InterruptedException e) {System.out.println(Thread.currentThread().getName() + "等锁期间被中断");}}public static void main(String[] args) {LockInterruptibly r1 = new LockInterruptibly();Thread thread0 = new Thread(r1);Thread thread1 = new Thread(r1);thread0.start();thread1.start();try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}// thread0.interrupt();thread1.interrupt();} }
9、锁优化
- 缩小同步代码块
- 尽量不要锁住方法
- 减少请求锁的次数
- 锁中尽量不要再包含锁
- 选择合适的锁或者工具类
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!



5.3、优缺点






