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.程序优化
缩小同步代码块
尽量不要锁住方法
减少锁的次数
避免人为制造热点
锁中尽量不要包含锁
选择合适锁类型或者合适的工具类