锁(Locks)

时间: 2023-07-09 admin 互联网

锁(Locks)

锁(Locks)

锁(Locks)

1 ReentrantLock

应用demo

可重入锁,是一种使用递归无堵塞的同步机制

synchronized 更强大、更灵活的锁机制,可以减少死锁发生的概率

默认为非公平锁,可以自定义为公平锁

底层采用 AQS 实现,通过内部 Sync 集成 AQS

简单应用:

/*** @author kenewstar* @version 1.0* @date 2021/5/14*/
public class Concurrent07 {private int count;private final Lock lock = new ReentrantLock();public static void main(String[] args) throws InterruptedException {Concurrent07 c = new Concurrent07();CountDownLatch count = new CountDownLatch(2);ExecutorService service = Executors.newCachedThreadPool();service.execute(() -> {// 加锁c.lock.lock();try {for (int i = 0; i < 100000000; i++) {c.count ++;}count.countDown();} finally {// 释放锁c.lock.unlock();}});service.execute(() -> {c.lock.lock();try {for (int i = 0; i < 100000000; i++) {c.count ++;}count.countDown();} finally {c.lock.unlock();}});count.await();service.shutdown();System.out.println("计算结果:" + c.count);}}

运行结果:

如果不对两个线程进行加锁操作,那么最终结果将不是预期的。

公平锁与非公平锁

公平锁的简单应用

/*** @author kenewstar* @version 1.0* @date 2021/5/15*/
public class Concurrent08 {/*** 公平锁,fair参数设置为true* 非公平锁,fair参数设置为false(默认)*/private static final Lock lock = new ReentrantLock(true);public static void runTest(String name, int time) {lock.lock();try {System.out.println("线程 " + name + "获取了锁");TimeUnit.SECONDS.sleep(time);} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();System.out.println("线程 " + name + "释放了锁");}}public static void main(String[] args) {ExecutorService executorService = Executors.newCachedThreadPool();for (int i = 0; i < 2; i++) {executorService.execute(() -> runTest("A", 1));executorService.execute(() -> runTest("B", 1));executorService.execute(() -> runTest("C", 1));}executorService.shutdown();}
}

运行结果:

第二次获取锁的顺序和第一次一致,也就是等待锁的时间最长的优先获取锁。

非公平锁的结果可能如下(也可能与公平锁一致,具有不确定性):

响应中断

我们创建两个线程造成死锁,然后使用其中一个线程中断,然后另一个线程就可以正常获取锁了。

案例如下:

/*** @author kenewstar* @version 1.0* @date 2021/5/15*/
public class Concurrent09 {private static Lock lock1 = new ReentrantLock();private static Lock lock2 = new ReentrantLock();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {try {lock1.lockInterruptibly();System.out.println("t1 获取了 lock1");lock2.lockInterruptibly();System.out.println("t1 获取了 lock2");TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();} finally {lock1.unlock();lock2.unlock();}});Thread t2 = new Thread(() -> {try {lock2.lockInterruptibly();System.out.println("t2 获取了 lock2");lock1.lockInterruptibly();System.out.println("t2 获取了 lock1");TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();} finally {lock2.unlock();lock1.unlock();}});t1.start();t2.start();Thread.sleep(2000);t1.interrupt();}
}

运行结果:

两个线程都在相互等待对方释放锁导致死锁,此时 t1 线程中断,然后t1线程中的lock1释放锁,t2线程就能成功获取到t1锁了。

限时等待

/*** @author kenewstar* @version 1.0* @date 2021/5/15*/
public class Concurrent10 {private static Lock lock = new ReentrantLock();public static void main(String[] args) {ExecutorService executorService = Executors.newCachedThreadPool();executorService.execute(() -> {boolean flag = false;try {flag = lock.tryLock(1000, TimeUnit.MILLISECONDS);if (flag) {System.out.println("线程A获取锁成功");TimeUnit.SECONDS.sleep(2);} else {System.out.println("线程A获取锁失败");}} catch (InterruptedException e) {e.printStackTrace();} finally {if (flag) {lock.unlock();}}});executorService.execute(() -> {boolean flag = false;try {flag = lock.tryLock(1000, TimeUnit.MILLISECONDS);if (flag) {System.out.println("线程B获取锁成功");TimeUnit.SECONDS.sleep(2);} else {System.out.println("线程B获取锁失败");}} catch (InterruptedException e) {e.printStackTrace();} finally {// ReentrantLock的一个方法:isHeldByCurrentThread()// 可用于判断当前线程是否获取到Lock锁if (flag) {lock.unlock();}}});executorService.shutdown();}
}

运行结果:

线程B会尝试 1s 后打印获取锁失败信息。

ReentrantLock与Synchronized

synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。

synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。

synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以响应中断。

相比于synchronized,ReentrantLock 在功能上更加丰富,它具有 可重入、可中断、可限时、公平锁 等特点

2 ReentrantReadWriteLock

上一节我们使用了ReentrantLock锁,该锁效果与synchronized的关键字的功能差不多,如果在写多读少的环境下,毋庸置疑的使用它也是比较合适的,但是在写少读多的环境下进行加锁,势必造成不必要的性能浪费。在只读的情况下并不存在线程安全问题,其他线程也可读取,但是此时加上了互斥锁,那么会大大降低性能,因此我们需要一个能够在读线程的情况下,其他线程也可以获取锁。我们可以使用ReentrantReadWriteLock。

读写锁,两把锁:共享锁-读锁,排它锁:写锁

支持公平性、非公平性、可重入、锁降级

锁降级:遵循获取写锁、获取读锁在释放写锁的次序,写锁能够降级成为读锁

读写锁的应用

简单应用如下:

/*** @author kenewstar* @version 1.0* @date 2021/5/16*/
public class Concurrent11 {private static ReadWriteLock lock = new ReentrantReadWriteLock();private static Lock readLock =  lock.readLock();private static Lock writeLock = lock.writeLock();private static Random rand = new Random(100);private static int pc;public void read() {readLock.lock();try {System.out.println("线程 " + Thread.currentThread() + " 读取数据 ----> " + pc);TimeUnit.MILLISECONDS.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();} finally {readLock.unlock();}}public void write(String name) {writeLock.lock();try {int s = rand.nextInt(99);System.out.println("线程" + name + " 写入数据 ---> " + s);pc = s;} finally {writeLock.unlock();}}public static void main(String[] args) {Concurrent11 c = new Concurrent11();ExecutorService service = Executors.newFixedThreadPool(20);for (int i = 0; i < 10; i++) {service.execute(c::read);}service.execute(() -> c.write("E"));service.execute(() -> c.write("D"));service.shutdown();}}

运行结果如下:

运行过程,前面十个线程是几乎同时打印数据,然后等待大约 1s 后面两个线程才开始打印数据。由此可知,读取数据时,其他读取线程也可以获取读锁,而无法获取写锁。写数据时,读写锁都无法获取,即读锁是共享锁,写锁是排它锁。

锁降级

即是由写锁降级为读锁

/*** @author kenewstar* @version 1.0* @date 2021/5/16*/
public class Concurrent12 {private static ReadWriteLock lock = new ReentrantReadWriteLock();private static Lock readLock = lock.readLock();private static Lock writeLock = lock.writeLock();private int num;public static void main(String[] args) {Concurrent12 c = new Concurrent12();ExecutorService service = Executors.newFixedThreadPool(10);for (int i = 0; i < 5; i++) {service.execute(c::read);}service.execute(c::write);for (int i = 0; i < 8; i++) {service.execute(c::read);}service.shutdown();}public void read() {readLock.lock();try {System.out.println(Thread.currentThread() + " 读取 ---> " + num);Thread.sleep(800);} catch (InterruptedException e) {e.printStackTrace();} finally {readLock.unlock();}}public void write() {writeLock.lock();try {num = 12;Thread.sleep(600);} catch (Exception e) {e.printStackTrace();}readLock.lock();try {writeLock.unlock();System.out.println(Thread.currentThread() + " 锁降级 ---> " + num);Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();} finally {readLock.unlock();}}
}

运行结果如下:

首先获取读锁,此时无法获取写锁,等待锁释放,而后write获取写锁,修改数据后降级为读锁时,其他的读锁也能获取读锁,结果中锁降级与下面的读取结果同时打印。

ReentrantReadWriteLock不支持锁升级。

3 Condition

Lock提供条件Condition,对线程的等待和唤醒更加详细和灵活

内部维护一个Condition队列。当前线程调用await方法后,将会以当前线程构造为一个结点Node,并将该节点放到该队列的尾部

Condition是个接口,基本的方法就是await()和signal()方法;
Condition依赖于Lock接口,生成一个Condition的方式是lock.newCondition()

我们来看一个使用Lock + Condition多线程顺序打印A B C …的例子

应用demo

/*** @author kenewstar* @version 1.0* @date 2021/5/16*/
public class Concurrent03 {private int count;private final Lock lock = new ReentrantLock();private final Condition condition = lock.newCondition();public static void main(String[] args) {Concurrent03 c = new Concurrent03();ExecutorService service = Executors.newCachedThreadPool();service.execute(c::printA);service.execute(c::printB);service.execute(c::printC);service.shutdown();}public void printA() {lock.lock();try {while (true){if (count % 3 != 0) {condition.await();}Thread.sleep(300);System.out.print("A ");count ++;condition.signalAll();}} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}public void printB() {lock.lock();try {while (true){if (count % 3 != 1) {condition.await();}Thread.sleep(300);System.out.print("B ");count ++;condition.signalAll();}} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}public void printC() {lock.lock();try {while (true){if (count % 3 != 2) {condition.await();}Thread.sleep(300);System.out.print("C ");count ++;condition.signalAll();}} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}
}

运行结果如下:

4 LockSupport

当需要阻塞或唤醒一个线程的时候,都会使用LockSupport工具类来完成相应工作,LockSupport为构建同步组件的基础工具。 LockSupport定义了一组 以park开头的方法用来阻塞当前线程,以及unpark(Thread thread)方法来唤醒一个被阻塞的线程。 Park有停车的意思,假设线程为车辆,那么park方法代表着停车,而unpark方法则是指车辆启动离开

简单应用

/*** @author kenewstar* @version 1.0* @date 2021/5/16*/
public class Concurrent13 {static class LockSupportTest extends Thread {@Overridepublic void run() {System.out.println("thread......");LockSupport.park();System.out.println("执行逻辑......");}}public static void main(String[] args) throws InterruptedException {Thread thread = new LockSupportTest();thread.start();Thread.sleep(1000);LockSupport.unpark(thread);System.out.println("main end.....");}
}

运行结果如下:

当调用park()方法时会阻塞当前线程,调用unpark(Thread t)时,会唤醒指定线程。