1.大纲
Lock接口
锁的分类
乐观锁和悲观锁
可重入锁与非可重入锁
公平锁与非公平锁
共享锁与排它锁
自旋锁与阻塞锁
可中断锁
锁优化
一:Lock接口
1.锁
是一种工具,用于控制对共享资源的访问
Lock和synchronized,是常见的锁,都可以达到线程安全的目的
Lock最常见的实现类是ReenTrantLock
2.为啥用Lock
synchronized不够用
效率低:锁的释放情况少,试图获得锁时不能设定超时,不能中断一个正在尝试获得锁的线程
不够灵活:加锁与释放锁单一,每个锁仅有单一的条件
无法知道是否成功获取锁
3.Lock的主要方法
lock()
tryLock()
tryLock(long time, TimeUnit unit)
lockInterruptibly()
4.lock
获取最普通的获取锁,如果被其他线程获取,则进行等待
不会像synchronized一样在异常的时候自动释放锁
lock方法不能被中断,一旦陷入死锁,lock就会永久等待
package com.jun.juc.lock.lock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Lock必须手动释放锁 */ public class MustUnLock { private static Lock lock = new ReentrantLock(); public static void main(String[] args) { lock.lock(); try{ // System.out.println(Thread.currentThread().getName()+"-run"); }finally { lock.unlock(); } } }
5.tryLock
用来尝试获取锁,如果被其他线程占用,则获取成功,返回true,否则返回false,代表获取锁失败
功能比lock强大了,可以根据是否获取到锁,决定后续程序的行为
立刻返回
6.tryLock(long time, TimeUnit unit)
超时就放弃
可以避免死锁
package com.jun.juc.lock.lock; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * tryLock避免死锁 */ public class TryLockDeadLock implements Runnable { int flag = 1; static Lock lock1 = new ReentrantLock(); static Lock lock2 = new ReentrantLock(); public static void main(String[] args) { TryLockDeadLock tryLockDeadLock1 = new TryLockDeadLock(); TryLockDeadLock tryLockDeadLock2 = new TryLockDeadLock(); tryLockDeadLock1.flag = 1; tryLockDeadLock2.flag = 0; new Thread(tryLockDeadLock1).start(); new Thread(tryLockDeadLock2).start(); } @Override public void run() { if (flag == 1) { try { for (int i = 0; i < 100; i++) { if (lock1.tryLock(800, TimeUnit.MILLISECONDS)) { try { System.out.println("1获取了lock1"); Thread.sleep(new Random().nextInt(1000)); // 获取第二把锁 if (lock2.tryLock(800, TimeUnit.MILLISECONDS)) { try { System.out.println("1获取了lock2"); System.out.println("1成功获取两把锁"); break; } finally { lock2.unlock(); Thread.sleep(new Random().nextInt(1000)); } } else { System.out.println("1获取lock2失败,在重试"); } } finally { // 因为上面获取到了锁,需要释放 lock1.unlock(); Thread.sleep(new Random().nextInt(1000)); } } else { System.out.println("1获取lock1失败,在重试"); } } } catch (Exception e) { // 防止800ms内被中断 e.printStackTrace(); } } if (flag == 0) { try { for (int i = 0; i < 100; i++) { if (lock2.tryLock(3000, TimeUnit.MILLISECONDS)) { try { System.out.println("2获取了lock2"); Thread.sleep(new Random().nextInt(1000)); // 获取第二把锁 if (lock1.tryLock(800, TimeUnit.MILLISECONDS)) { try { System.out.println("2获取了lock1"); System.out.println("2成功获取两把锁"); break; } finally { lock1.unlock(); Thread.sleep(new Random().nextInt(1000)); } } else { System.out.println("2获取lock1失败,在重试"); } } finally { // 因为上面获取到了锁,需要释放 lock2.unlock(); Thread.sleep(new Random().nextInt(1000)); } } else { System.out.println("2获取lock2失败,在重试"); } } } catch (Exception e) { // 防止800ms内被中断 e.printStackTrace(); } } } }
效果:
Connected to the target VM, address: '127.0.0.1:57057', transport: 'socket' 1获取了lock1 2获取了lock2 1获取lock2失败,在重试 2获取了lock1 2成功获取两把锁 1获取了lock1 1获取了lock2 1成功获取两把锁 Disconnected from the target VM, address: '127.0.0.1:57057', transport: 'socket'
7.lockInterruptipy
把超时时间设置为无限,在过程中,线程可以被中断
package com.jun.juc.lock.lock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockInterruptibly implements Runnable { private Lock lock = new ReentrantLock(); public static void main(String[] args) { LockInterruptibly lockInterruptibly = new LockInterruptibly(); Thread thread = new Thread(lockInterruptibly); Thread thread1 = new Thread(lockInterruptibly); thread.start(); thread1.start(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } thread.interrupt(); } @Override public void run() { System.out.println(Thread.currentThread().getName()+"尝试获取锁"); try{ lock.lockInterruptibly(); try { System.out.println(Thread.currentThread().getName()+"拿到了锁"); Thread.sleep(5000); }catch (Exception e){ System.out.println("睡眠时间被打断"); }finally { lock.unlock(); System.out.println(Thread.currentThread().getName()+"释放了锁"); } }catch (Exception e){ System.out.println("等待锁时被打断"); e.printStackTrace(); } } }
效果:
Connected to the target VM, address: '127.0.0.1:62438', transport: 'socket' Thread-0尝试获取锁 Thread-1尝试获取锁 Thread-0拿到了锁 睡眠时间被打断 Thread-0释放了锁 Thread-1拿到了锁 Disconnected from the target VM, address: '127.0.0.1:62438', transport: 'socket' Thread-1释放了锁 Process finished with exit code 0
8.可见性
happens-before
lock拥有可见性保障的
二:锁的分类
1.分类
三:乐观锁与悲观锁
1.悲观锁的劣势
也叫互斥同步锁
劣势:
阻塞和唤醒带来的性能劣势
永久阻塞,如果持有锁的线程被永久阻塞,那么等待该线程释放的线程,永远都得不到执行
优先级反转
2.悲观锁
3.乐观锁
典型的案例就是原子类,并发容器等
4.乐观锁例子
package com.jun.juc.lock.lock; import java.util.concurrent.atomic.AtomicInteger; public class PessimismOptimismLock { public static void main(String[] args) { // 内部是乐观锁,也是安全的 AtomicInteger atomicInteger = new AtomicInteger(); int i = atomicInteger.incrementAndGet(); System.out.println("===:"+i); } }
5.悲观锁与乐观锁的使用场景
悲观锁:适合并发写入多的情况,适用于临界区持锁时间较长的情况,悲观锁可以避免大量的无用的自旋消耗,典型情况:
临界区有IO操作
临界区代码复杂或者循环量大
临界区竞争激烈
乐观锁:适合并发写入少,大部分是读取的场景,不加锁的能让读取性能大幅度提高
四:重入锁与非可重入锁
1.可重入锁与非可重入锁,ReentrantLock
package com.jun.juc.lock.reentrantlock; import java.util.PrimitiveIterator; import java.util.concurrent.locks.ReentrantLock; public class CinemaBookSeat { private static ReentrantLock lock = new ReentrantLock(); private static void bookSeat(){ lock.lock(); try{ System.out.println(Thread.currentThread().getName()+"开始预订座位"); Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+"完成预订"); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } public static void main(String[] args) { new Thread(()->bookSeat()).start(); new Thread(()->bookSeat()).start(); new Thread(()->bookSeat()).start(); new Thread(()->bookSeat()).start(); } }
效果:
Connected to the target VM, address: '127.0.0.1:50292', transport: 'socket' Thread-0开始预订座位 Thread-0完成预订 Thread-1开始预订座位 Thread-1完成预订 Thread-2开始预订座位 Thread-2完成预订 Thread-3开始预订座位 Disconnected from the target VM, address: '127.0.0.1:50292', transport: 'socket' Thread-3完成预订
2.另一个示例
package com.jun.juc.lock.reentrantlock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockDemo { static class Outpurer{ Lock lock = new ReentrantLock(); public void output(String name){ int length = name.length(); lock.lock(); try{ for (int i=0;i<length;i++){ System.out.print(name.charAt(i)); } System.out.println("打印完成"); }finally { lock.unlock(); } } } private void init(){ final Outpurer outpurer = new Outpurer(); new Thread(new Runnable() { @Override public void run() { while (true){ try{ Thread.sleep(500); }catch (Exception e){ e.printStackTrace(); } outpurer.output("abcdefg"); } } }).start(); new Thread(new Runnable() { @Override public void run() { while (true){ try{ Thread.sleep(500); }catch (Exception e){ e.printStackTrace(); } outpurer.output("123456"); } } }).start(); } public static void main(String[] args) { LockDemo lockDemo = new LockDemo(); lockDemo.init(); } }
3.演示可重入
一个线程可以多次拿到这把锁,无需释放锁,就可以直接获取到。
也叫递归锁。
好处:
避免死锁:如果有两个方法,都被一把锁锁住,运行到第一个方法拿到这把锁,运行到第二个方法,如果不是可重入锁,就需要等锁释放才可以获取,就会造成死锁。
提升封装性:避免一次次加锁解锁
package com.jun.juc.lock.reentrantlock; import javax.swing.*; import java.util.concurrent.locks.ReentrantLock; /** * 可重入锁实验 */ 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()); } }
效果:
Disconnected from the target VM, address: '127.0.0.1:51619', transport: 'socket' 0 1 2 3 2 1 0 Process finished with exit code 0
结论:
很明显,继续加锁,前面没有解锁。是可重入锁。
4.另一个示例
package com.jun.juc.lock.reentrantlock; import java.util.concurrent.locks.ReentrantLock; /** * 递归锁 */ public class RecursionDemo { private static ReentrantLock lock = new ReentrantLock(); private static void accessResource() { lock.lock(); try { System.out.println("已经对资源进行了处理"); if (lock.getHoldCount() < 5) { System.out.println("before:"+lock.getHoldCount()); accessResource(); System.out.println("after:"+lock.getHoldCount()); } }finally { lock.unlock(); } } public static void main(String[] args) { accessResource(); } }
效果:
Disconnected from the target VM, address: '127.0.0.1:52207', transport: 'socket' 已经对资源进行了处理 before:1 已经对资源进行了处理 before:2 已经对资源进行了处理 before:3 已经对资源进行了处理 before:4 已经对资源进行了处理 after:4 after:3 after:2 after:1 Process finished with exit code 0
5.ReentrantLock的其他方法
isHeldByCurrentThread:看出锁是否被当前的线程持有
getQueueLength:返回当前正在等待这把锁的队列有多长,一般这按两个方法是开发和调试时候使用
五:公平锁与非公平锁
1.公平锁与非公平锁
公平是指按照线程请求的顺序,来分配锁
非公平指的是,不完全按照请求的顺序,在一定的情况下,可以插队
2.为什么需要非公平锁
为了提高效率
避免唤醒带来的空档期
ReentrantLock,默认为非公平锁,true为公平锁,false为非公平锁
3.公平锁
package com.jun.juc.lock.reentrantlock; import java.util.Random; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 演示公平锁与非公平锁 */ public class FairLock { public static void main(String[] args) { PrintQueue printQueue = new PrintQueue(); Thread[] threads = new Thread[10]; for (int i = 0; i < 10; i++) { threads[i] = new Thread(new Job(printQueue)); } for (int i=0;i<10;i++){ //依次执行 threads[i].start(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Job implements Runnable { PrintQueue printQueue; public Job(PrintQueue printQueue) { this.printQueue = printQueue; } // 如果一次全部打印完成,则是不公平;如果一次搞定了,则是不公平 @Override public void run() { System.out.println(Thread.currentThread().getName() + "开始打印"); printQueue.printJob(); System.out.println(Thread.currentThread().getName() + "开始完毕"); } } class PrintQueue { private Lock queueLock = new ReentrantLock(true); public void printJob() { queueLock.lock(); try { int duration = new Random().nextInt(10) +1; System.out.println(Thread.currentThread().getName() + "正在打印,需要时间:" + duration); Thread.sleep(duration * 1000); } catch (Exception 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 (Exception e) { e.printStackTrace(); } finally { queueLock.unlock(); } } }
效果:
Connected to the target VM, address: '127.0.0.1:62693', transport: 'socket' Thread-0开始打印 Thread-0正在打印,需要时间:2 Thread-1开始打印 Thread-2开始打印 Thread-3开始打印 Thread-4开始打印 Thread-5开始打印 Thread-6开始打印 Thread-7开始打印 Thread-8开始打印 Thread-9开始打印 Thread-1正在打印,需要时间:2 Thread-2正在打印,需要时间:2 Thread-3正在打印,需要时间:5 Thread-4正在打印,需要时间:3 Thread-5正在打印,需要时间:9 Thread-6正在打印,需要时间:10 Thread-7正在打印,需要时间:3 Thread-8正在打印,需要时间:4 Thread-9正在打印,需要时间:5 Thread-0正在打印,需要时间:8 Thread-0开始完毕 Thread-1正在打印,需要时间:3 Thread-2正在打印,需要时间:6 Thread-1开始完毕 Thread-2开始完毕 Thread-3正在打印,需要时间:6 Thread-3开始完毕 Thread-4正在打印,需要时间:9 Thread-4开始完毕 Thread-5正在打印,需要时间:5 Thread-5开始完毕 Thread-6正在打印,需要时间:7 Thread-6开始完毕 Thread-7正在打印,需要时间:7 Thread-7开始完毕 Thread-8正在打印,需要时间:1 Thread-8开始完毕 Thread-9正在打印,需要时间:2 Thread-9开始完毕 Disconnected from the target VM, address: '127.0.0.1:62693', transport: 'socket'
结论:
都是顺序的。重要的是,线程0执行完第一次锁的时候,想马上拿锁,是拿不到的,需要让线程9执行完,才能让线程0继续拿。
4.非公平锁
将true修改为false
package com.jun.juc.lock.reentrantlock; import java.util.Random; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 演示公平锁与非公平锁 */ public class FairLock { public static void main(String[] args) { PrintQueue printQueue = new PrintQueue(); Thread[] threads = new Thread[10]; for (int i = 0; i < 10; i++) { threads[i] = new Thread(new Job(printQueue)); } for (int i=0;i<10;i++){ //依次执行 threads[i].start(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Job implements Runnable { PrintQueue printQueue; public Job(PrintQueue printQueue) { this.printQueue = printQueue; } // 如果一次全部打印完成,则是不公平;如果一次搞定了,则是不公平 @Override public void run() { System.out.println(Thread.currentThread().getName() + "开始打印"); printQueue.printJob(); System.out.println(Thread.currentThread().getName() + "开始完毕"); } } class PrintQueue { private Lock queueLock = new ReentrantLock(false); public void printJob() { queueLock.lock(); try { int duration = new Random().nextInt(5) +1; System.out.println(Thread.currentThread().getName() + "正在打印,需要时间:" + duration); Thread.sleep(duration * 1000); } catch (Exception e) { e.printStackTrace(); } finally { queueLock.unlock(); } //再次打印 queueLock.lock(); try { int duration = new Random().nextInt(5) + 1; System.out.println(Thread.currentThread().getName() + "正在打印,需要时间:" + duration); Thread.sleep(duration * 1000); } catch (Exception e) { e.printStackTrace(); } finally { queueLock.unlock(); } } }
效果:
Connected to the target VM, address: '127.0.0.1:62894', transport: 'socket' Thread-0开始打印 Thread-0正在打印,需要时间:1 Thread-1开始打印 Thread-2开始打印 Thread-3开始打印 Thread-4开始打印 Thread-5开始打印 Thread-6开始打印 Thread-7开始打印 Thread-8开始打印 Thread-9开始打印 Thread-0正在打印,需要时间:2 Thread-0开始完毕 Thread-1正在打印,需要时间:3 Thread-1正在打印,需要时间:1 Thread-1开始完毕 Thread-2正在打印,需要时间:4 Thread-2正在打印,需要时间:1 Thread-2开始完毕 Thread-3正在打印,需要时间:4 Thread-3正在打印,需要时间:3 Thread-3开始完毕 Thread-4正在打印,需要时间:2 Thread-4正在打印,需要时间:5 Thread-4开始完毕 Thread-5正在打印,需要时间:1 Thread-5正在打印,需要时间:4 Thread-5开始完毕 Thread-6正在打印,需要时间:2 Thread-6正在打印,需要时间:5 Thread-6开始完毕 Thread-7正在打印,需要时间:2 Thread-7正在打印,需要时间:5 Thread-7开始完毕 Thread-8正在打印,需要时间:1 Thread-8正在打印,需要时间:3 Thread-8开始完毕 Thread-9正在打印,需要时间:3 Thread-9正在打印,需要时间:4 Disconnected from the target VM, address: '127.0.0.1:62894', transport: 'socket' Thread-9开始完毕 Process finished with exit code 0
结论:
在第二个线程还没唤醒前,当前马上又获取到锁,继续执行
5.特例
tryLock方法,不遵守设定的公平的规则
当执行的时候,一旦有了线程释放锁,则可以直接获取到
6.公平锁与非公平锁的优缺点
六:共享锁与排它锁
1.共享锁与排它锁
以ReentractReadWriteLock为例
读锁的作用:
多个读操作,不会有线程安全问题。在读的地方使用读锁,在写的地方使用写锁,灵活控制,如果没有写锁,读是无阻塞的,提高了效率
读写的规则
多个线程只申请读锁,都可以申请到
如果有一个线程占用了读锁,如果要申请写锁,则申请写锁的线程会一直等待释放读锁
如果,一个线程占用了写锁,其他线程申请写锁或者读锁,都是只能等待写锁的释放
2.读写锁示例
package com.jun.juc.lock.readwritelock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * 读写锁 */ public class CinemalLock { private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock(); private static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock(); private static void read(){ readLock.lock(); try { System.out.println(Thread.currentThread().getName()+ "得到了读锁,在读取"); 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()+ "得到了写锁,在写"); 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(()->read(),"t1").start(); new Thread(()->read(),"t2").start(); new Thread(()->write(),"t3").start(); new Thread(()->write(), "t4").start(); } }
效果:
Connected to the target VM, address: '127.0.0.1:51054', transport: 'socket' t1得到了读锁,在读取 t2得到了读锁,在读取 t1释放读锁 t2释放读锁 t3得到了写锁,在写 t3释放写锁 t4得到了写锁,在写 Disconnected from the target VM, address: '127.0.0.1:51054', transport: 'socket' t4释放写锁 Process finished with exit code 0
3 读锁插队策略
通过加true或者false判断是公平锁还是非公锁
结论:
4.演示非公平锁的插队
package com.jun.juc.lock.readwritelock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class NonfairDemo { private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock(); private static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock(); private static void read(){ readLock.lock(); try { System.out.println(Thread.currentThread().getName()+ "得到了读锁,在读取"); 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()+ "得到了写锁,在写"); 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(()->write(),"t0").start(); new Thread(()->read(),"t1").start(); new Thread(()->read(),"t2").start(); new Thread(()->write(),"t3").start(); new Thread(()->read(), "t4").start(); } }
效果:
Connected to the target VM, address: '127.0.0.1:58198', transport: 'socket' t0得到了写锁,在写 t0释放写锁 t1得到了读锁,在读取 t2得到了读锁,在读取 t1释放读锁 t2释放读锁 t3得到了写锁,在写 t3释放写锁 t4得到了读锁,在读取 Disconnected from the target VM, address: '127.0.0.1:58198', transport: 'socket' t4释放读锁 Process finished with exit code 0
5.演示读锁的插队
什么意思呢?
在队列的头结点为写锁,则需要排队。要是读锁,则直接开始了读锁。
怎么演示读锁是可以插队的呢,可以写一个子线程,不断的进行读插队,当开始读锁的时候,可以发现,每个读锁之间是有空闲期的,就可以出现了插队的出现了
演示代码:
package com.jun.juc.lock.readwritelock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class NonfairBargeDemo { private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock(); private static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock(); private static void read(){ System.out.println(Thread.currentThread().getName()+ "尝试获取读锁"); readLock.lock(); try { System.out.println(Thread.currentThread().getName()+ "得到了读锁,在读取"); Thread.sleep(10); } 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(100); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println(Thread.currentThread().getName()+"释放写锁"); writeLock.unlock(); } } public static void main(String[] args) { new Thread(()->write(),"t0").start(); new Thread(()->read(),"t1").start(); new Thread(()->read(),"t2").start(); // new Thread(()->write(),"t3").start(); new Thread(()->read(), "t4").start(); new Thread(()->read(), "t5").start(); new Thread(()->read(), "t6").start(); new Thread(()->read(), "t7").start(); new Thread(new Runnable() { @Override public void run() { Thread[] threads = new Thread[2000]; for (int i=0; i<2000; i++){ threads[i] = new Thread(()->read(),"子线程创建的线程" + i); } for (int i=0; i< 2000; i++){ threads[i].start(); } } }).start(); } }
说明:
如果还没开始进行t2,t3的获取锁,子线程就执行完成了抢锁,就演示不出效果了
6.锁的升级与降级
支持降级
什么意思呢?
在获取写锁,且不释放的情况下,可以接着获取读锁。
不支持升级,只要是避免死锁。
假设有两个线程,都在读,然后同时想进行升级到写锁,这个时候,就需要对方释放锁,然后,都不释放的时候,就陷入了死锁。所以,不支持升级。
保证每一次只有一个锁在升级,才可以实现。
七:自旋锁与阻塞锁
1.说明
2.源码说明
3.实现一个自旋锁
package com.jun.juc.lock.spinlock; import java.util.concurrent.atomic.AtomicReference; /** * 自旋锁 */ public class SpinLock { private AtomicReference<Thread> sign = new AtomicReference<Thread>(); public void lock(){ Thread current = Thread.currentThread(); while (!sign.compareAndSet(null, current)){ System.out.println(Thread.currentThread().getName() + "获取失败,再次尝试"); } } public void unlock(){ Thread current = Thread.currentThread(); sign.compareAndSet(current, null); } public static void main(String[] args) { SpinLock spinLock = new SpinLock(); Runnable runnable = new Runnable(){ public void run(){ System.out.println(Thread.currentThread().getName() + "尝试获取自旋锁"); spinLock.lock(); System.out.println(Thread.currentThread().getName() +"获取到了自旋锁"); try{ Thread.sleep(300); }catch (Exception e){ e.printStackTrace(); }finally { System.out.println(Thread.currentThread().getName() +"释放到了自旋锁"); spinLock.unlock(); } } }; Thread thread1 = new Thread(runnable); Thread thread2 = new Thread(runnable); thread1.start(); thread2.start(); } }
4.适用场景
自旋锁一般用于多核的服务器,在并发度不是很高的情况下,比阻塞锁的效率高
自旋锁适用于临界区比较短小的情况
八:可中断锁与不可中断锁
1.说明
synchronized是不可中断锁,而lock是可中断锁,因为trylock(time)与lockInterruptibly都可以相应中断
九:锁优化
1.jvm
自旋锁和自适应
锁消除
锁粗化
2.程序优化
缩小同步代码块
尽量不要锁住方法
减少锁的次数
避免人为制造热点
锁中尽量不要包含锁
选择合适锁类型或者合适的工具类