目录
一、Lock接口和核心方法
显示锁和synchronized一样,都是用来做线程同步的操作。
既然显示锁和synchronized关键字起到的效果一样,为什么要用显示锁呢?在之前的总结过程中我们了解了相关synchronized关键字的弊端,在这里就不多说了,有兴趣的朋友可以看看之前多线层的文章。现在介绍一下显示锁Lock接口,以及Lock的核心方法,Lock接口提供的主要方法有:
如图所示:
我们先来讲下列三种方法:
1、lock();
2、unlock();
3、tryLock();
/**
*
* @author xgx
*
* 显示锁的范式
*/
public class LockDemo {
private Lock lock = new ReentrantLock();
private int count = 0;
/**
* 利用显示锁进行count的累加
*/
public void increamentByLock() {
// 显示锁的范式
// 1、开启锁;
lock.lock();
try {
count++;
} catch (Exception e) {
e.printStackTrace();
} finally {
// 一定要在finally中将锁进行释放,防止出现异常后锁不释放造成的线程阻塞
lock.unlock();
}
System.out.println(count);
}
/**
* 利用synchronized进行count的累加
*/
public synchronized void increamentBySyn() {
count++;
System.out.println(count);
}
}
通过以上我们可以看到:
1、显示锁有其书写范式,一定要遵从该范式进行书写,避免出现异常造成线程阻塞。
2、synchronized内置锁要比显示锁书写要更加简洁
问题:在什么时候用显示锁和synchronized内置锁呢?
1、从Lock接口提供的方法来看,显示锁可以被中断lockInterruptibly(),超时获取锁tryLock(long time, TimeUnit unit),尝试获取锁tryLock(),因此在线程可以被中断,超时获取锁,尝试获取锁时,建议用Lock
2、除1以外的情况,都建议用synchronized内置锁
二、Lock接口和synchronized关键字的比较
1、Lock是Jdk1.5以后新增的接口。
2、synchronized是语言层面的操作,是JVM层面上的实现,在执行操作出现异常时,JVM会自动释放锁。Lock是语法层面上的操作,在执行操作出现异常时,在代码层面进行锁的释放,一定要在finally中调用unlock()来释放锁。
3、synchronized可以修饰整个方法(对象锁,类锁),也可以修饰代码块。Lock可以在任何地方调用lock()方法,并在finally中调用unlock()释放锁。
4、调用synchronized关键字,一定要等待线程释放锁,如果线程不释放锁,那么后面的线程将一直等待下去。调用Lock显示锁,在等待一段时间后,可以中断去继续做其他的事情。
5、Lock可以提高多个线程读操作的效率。
三、可重入锁ReentrantLock、公平锁、非公平锁
1、如果锁具备可重入性,则称作为可重入锁。像synchronized和 ReentrantLock都是可重入锁,可重入性实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一 个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法 method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。
2、公平锁:在时间单位上,先对锁进行获取的请求,一定先被满足,那么称这个锁为公平锁。
3、非公平锁:与公平锁相反。synchronized和ReentrantLock默认都是非公平锁。
一般来讲,非公平锁的效率会更高,举个例子:
非公平锁:当三个线程A,B,C。其中当线程A在获取锁进行操作时,执行操作时间长,此时线程B正在等待线程A的释放,那么线程B将被挂起,在上下文中被移出线程队列;此时当线程A完成了,线程C正好进入队列,那么线程C将先获得线程A释放的锁;线程B正在由挂起状态转为就绪状态的过程中,因此被线程C先获得到锁,进入锁进行操作。如果当线程C在执行完成时,线程B的状态正好已经就绪,那么正好获得了线程C释放的锁,这样几乎没有造成时间上的影响达到所谓的共赢。(这只是最理想的情况)
公平锁:当三个线程A,B,C。其中线程A,B,C依次进入线程队列,依次等待上一个线程的释放,获得锁继续进行。这样就造成了长时间的等待。
ReentrantLock和synchronized关键字都是排它锁。
四、读写锁
读写锁,顾名思义,在读的过程中,上读锁;写的过程中,上写锁。引用读写锁,可以巧妙的解决synchronized关键字带来的一个性能问题:读与读之间互斥。
先来看看读写锁接口在JDK中的定义:
public interface ReadWriteLock {
/**
* Returns the lock used for reading.
*
* @return the lock used for reading.
*/
Lock readLock();
/**
* Returns the lock used for writing.
*
* @return the lock used for writing.
*/
Lock writeLock();
}
我们发现读写锁ReadWriteLock接口很简单,只有两个抽象方法readLock() 、 writeLock()。
同一时刻,允许多个读线程同时访问,但是写线程访问的时候,所有的读和写都将被阻塞。因此,该场景最适合读多写少的情况下用读写锁。
几种状态:
1、读与读之间互不影响。
2、读与写之间互斥。
3、写与写之间互斥。
读写锁场景举例:当需要读取多个文件时,如果采用synchronized关键字进行同步,那么读与读之间也互斥,只有等待一个线程执行完成之后,后一个线程才会获得锁继续进行读操作。当采用读写锁机制时,在读与读过程中可以同时进行,不收锁的影响。在效率上也会比synchronized更高。
先用synchronized关键字进行读操作:
**
*
* @author xgx
*
* synchronized读操作
*/
public class ReadAndWriteLockSyn {
public static synchronized void read() {
System.out.println("线程开始:" + System.currentTimeMillis());
System.out.println(Thread.currentThread().getName() + " 线程开始进行读操作了...");
for (int i = 0; i < 5; i++) {
// 休眠50毫秒
try {
Thread.sleep(50);
System.out.println(Thread.currentThread().getName() + " 线程正在进行读操作...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 读操作进行完毕!!");
System.out.println("线程结束:" + System.currentTimeMillis());
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
ReadAndWriteLockSyn.read();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
ReadAndWriteLockSyn.read();
}
}).start();
}
}
控制台输出结果:
线程开始:1541692904643 ---- 这里输出第一个时间
Thread-0 线程开始进行读操作了...
Thread-0 线程正在进行读操作...
Thread-0 线程正在进行读操作...
Thread-0 线程正在进行读操作...
Thread-0 线程正在进行读操作...
Thread-0 线程正在进行读操作...
Thread-0 读操作进行完毕!!
线程结束:1541692904894
线程开始:1541692904894
Thread-1 线程开始进行读操作了...
Thread-1 线程正在进行读操作...
Thread-1 线程正在进行读操作...
Thread-1 线程正在进行读操作...
Thread-1 线程正在进行读操作...
Thread-1 线程正在进行读操作...
Thread-1 读操作进行完毕!!
线程结束:1541692905145 ---- 这输出最后一个时间
1541692905145 - 1541692904643 = 502 毫秒
接下来看读写锁进行读操作:
/**
*
* @author xgx
*
* ReentrantLock读写锁
*/
public class ReadAndWriteLockReentrant {
private static ReentrantReadWriteLock readAndWriteLock = new ReentrantReadWriteLock();
public static void read() {
System.out.println("线程开始:" + System.currentTimeMillis());
// 加锁
readAndWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " 线程开始进行读操作了...");
for (int i = 0; i < 5; i++) {
// 休眠50毫秒
try {
Thread.sleep(50);
System.out.println(Thread.currentThread().getName() + " 线程正在进行读操作...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 读操作进行完毕!!");
System.out.println("线程结束:" + System.currentTimeMillis());
} finally {
// 释放锁
readAndWriteLock.readLock().unlock();
}
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
ReadAndWriteLockReentrant.read();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
ReadAndWriteLockReentrant.read();
}
}).start();
}
}
控制台输出结果:
线程开始:1541693000398 ---- 这里是第一个输出时间
Thread-0 线程开始进行读操作了...
线程开始:1541693000399
Thread-1 线程开始进行读操作了...
Thread-0 线程正在进行读操作...
Thread-1 线程正在进行读操作...
Thread-0 线程正在进行读操作...
Thread-1 线程正在进行读操作...
Thread-1 线程正在进行读操作...
Thread-0 线程正在进行读操作...
Thread-1 线程正在进行读操作...
Thread-0 线程正在进行读操作...
Thread-1 线程正在进行读操作...
Thread-1 读操作进行完毕!!
线程结束:1541693000649
Thread-0 线程正在进行读操作...
Thread-0 读操作进行完毕!!
线程结束:1541693000649 ---- 这里是最后一个输出时间
1541693000649 - 1541693000398 = 251毫秒
由此可见:
利用synchronized进行读操作耗时:502毫秒
利用读写锁进行读操作耗时:251毫秒
因此,读写锁的在读的效率上远远高出synchronized的。
下面写一个商品的读写锁操作,先定义用synchronized方式进行实现
1、先定义商品实体类
/**
*
* @author xgx
*
* 商品实体类
*/
public class GoodsInfo {
// 商品名称
private String goodsName;
// 销售总额
private double totalMoney;
// 库存数
private Integer storeNum;
public GoodsInfo(String goodsName, Double totalMoney, Integer storeNum) {
this.goodsName = goodsName;
this.totalMoney = totalMoney;
this.storeNum = storeNum;
}
public String getGoodsName() {
return goodsName;
}
public void setGoodsName(String goodsName) {
this.goodsName = goodsName;
}
public double getTotalMoney() {
return totalMoney;
}
public void setTotalMoney(double totalMoney) {
this.totalMoney = totalMoney;
}
public Integer getStoreNum() {
return storeNum;
}
public void setStoreNum(Integer storeNum) {
this.storeNum = storeNum;
}
// 设置销售出去商品,利润和库存数的变化
public void changeNumber(int sellNumber) {
this.totalMoney += sellNumber * 25;
this.storeNum -= storeNum;
}
}
2、定义商品服务的接口
/**
*
* @author Administrator
*
* 商品服务接口
*/
public interface GoodsService {
// 获得商品信息
public GoodsInfo getNum();
// 写商品信息
public void setNum(int nextInt);
}
3、定义商品接口的实现类(用synchronized内置锁实现)
/**
*
* @author Administrator
*
* 用synchronized内置锁实现商品服务接口
*/
public class UseSynchronizedImpl implements GoodsService {
private GoodsInfo goodsInfo;
public UseSynchronizedImpl(GoodsInfo goodsInfo) {
this.goodsInfo = goodsInfo;
}
@Override
public synchronized GoodsInfo getNum() {
SleepTools.ms(5);
return this.goodsInfo;
}
@Override
public synchronized void setNum(int number) {
SleepTools.ms(5);
goodsInfo.changeNumber(number);
}
}
4、商品的实现Controller
/**
*
* @author Administrator
*
* 商品操作类的Controller
*/
public class BusinessAppController {
// 读写线程的比例
private static final int readAndWriteRatio = 10;
// 最少线程数
private static final int minThreadNum = 3;
// 读操作
private static class GetGoodsThread implements Runnable {
private GoodsService goodsService;
public GetGoodsThread(GoodsService goodsService) {
this.goodsService = goodsService;
}
@Override
public void run() {
// 记录初始时间
long start = System.currentTimeMillis();
// 进行100次读操作
for (int i = 0; i < 100; i++) {
goodsService.getNum();
}
System.out.println(
Thread.currentThread().getName() + " -线程读取商品共耗时: " + (System.currentTimeMillis() - start) + "ms");
}
}
// 写操作
private static class SetGoodsThread implements Runnable {
private GoodsService goodsService;
public SetGoodsThread(GoodsService goodsService) {
this.goodsService = goodsService;
}
@Override
public void run() {
// 记录初始时间
long start = System.currentTimeMillis();
Random rand = new Random();
// 进行10次操作
for (int i = 0; i < 10; i++) {
// 休眠50毫秒
SleepTools.ms(50);
goodsService.setNum(rand.nextInt(10));
}
System.out.println(
Thread.currentThread().getName() + " -线程写商品数据共耗时: " + (System.currentTimeMillis() - start) + "ms");
}
}
public static void main(String[] args) {
// 初始化商品信息
GoodsInfo goodsInfo = new GoodsInfo("华为", 100000D, 10000);
// 调用商品操作接口类,先用synchronized实现
GoodsService goodsService = new UseSynchronizedImpl(goodsInfo);
// 设置最小线程数
for (int i = 0; i < minThreadNum; i++) {
// 初始化写线程
Thread setThread = new Thread(new SetGoodsThread(goodsService));
// 设置读写比例10:1
for (int j = 0; j < readAndWriteRatio; j++) {
// 初始化读线程
Thread getThread = new Thread(new GetGoodsThread(goodsService));
getThread.start();
}
// 休眠100毫秒
SleepTools.ms(100);
// 启动写线程
setThread.start();
}
}
}
控制台输出结果:
Thread-8 -线程读取商品共耗时: 1091ms
Thread-5 -线程读取商品共耗时: 1957ms
Thread-4 -线程读取商品共耗时: 2457ms
Thread-3 -线程读取商品共耗时: 2957ms
Thread-2 -线程读取商品共耗时: 3458ms
Thread-6 -线程读取商品共耗时: 3637ms
Thread-7 -线程读取商品共耗时: 4092ms
Thread-9 -线程读取商品共耗时: 4492ms
Thread-10 -线程读取商品共耗时: 4652ms
Thread-30 -线程读取商品共耗时: 5660ms
Thread-29 -线程读取商品共耗时: 6160ms
Thread-28 -线程读取商品共耗时: 6660ms
Thread-27 -线程读取商品共耗时: 7160ms
Thread-26 -线程读取商品共耗时: 7660ms
Thread-24 -线程读取商品共耗时: 8420ms
Thread-23 -线程读取商品共耗时: 8920ms
Thread-1 -线程读取商品共耗时: 9478ms
Thread-21 -线程读取商品共耗时: 9875ms
Thread-19 -线程读取商品共耗时: 10595ms
Thread-18 -线程读取商品共耗时: 11097ms
Thread-15 -线程读取商品共耗时: 12012ms
Thread-14 -线程读取商品共耗时: 12512ms
Thread-13 -线程读取商品共耗时: 13012ms
Thread-16 -线程读取商品共耗时: 13852ms
Thread-17 -线程读取商品共耗时: 14062ms
Thread-20 -线程读取商品共耗时: 14341ms
Thread-31 -线程读取商品共耗时: 14555ms
Thread-32 -线程读取商品共耗时: 14690ms
Thread-25 -线程读取商品共耗时: 14796ms
Thread-12 -线程读取商品共耗时: 14937ms
Thread-0 -线程写商品数据共耗时: 15287ms
Thread-22 -线程写商品数据共耗时: 15125ms
Thread-11 -线程写商品数据共耗时: 15231ms
下面写一个商品的读写锁操作,用读写锁方式进行实现
只需要再上面第3步实现商品接口的类换成读写操作即可
3、定义商品接口的实现类(用读写锁实现)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import com.xiangxue.tools.SleepTools;
public class UseReadAndWriteLockImpl implements GoodsService {
private GoodsInfo goodsInfo;
// 初始化读写锁的接口实例,参数为null,默认非公平锁
private final ReadWriteLock lock = new ReentrantReadWriteLock();
// 读锁
private final Lock getLock = lock.readLock();
// 写锁
private final Lock setLock = lock.writeLock();
@Override
public GoodsInfo getNum() {
// 启用锁
getLock.lock();
try {
// 休眠5毫秒
SleepTools.ms(5);
return this.goodsInfo;
} finally {
// 释放锁
getLock.unlock();
}
}
@Override
public void setNum(int number) {
// 启用锁
setLock.lock();
try {
SleepTools.ms(5);
goodsInfo.changeNumber(number);
} finally {
// 释放锁
setLock.unlock();
}
}
}
4、商品的实现Controller
import java.util.Random;
import com.xiangxue.tools.SleepTools;
/**
*
* @author Administrator
*
* 商品操作类的Controller
*/
public class BusinessAppController {
// 读写线程的比例
private static final int readAndWriteRatio = 10;
// 最少线程数
private static final int minThreadNum = 3;
// 读操作
private static class GetGoodsThread implements Runnable {
private GoodsService goodsService;
public GetGoodsThread(GoodsService goodsService) {
this.goodsService = goodsService;
}
@Override
public void run() {
// 记录初始时间
long start = System.currentTimeMillis();
// 进行100次读操作
for (int i = 0; i < 100; i++) {
goodsService.getNum();
}
System.out.println(
Thread.currentThread().getName() + " -线程读取商品共耗时: " + (System.currentTimeMillis() - start) + "ms");
}
}
// 写操作
private static class SetGoodsThread implements Runnable {
private GoodsService goodsService;
public SetGoodsThread(GoodsService goodsService) {
this.goodsService = goodsService;
}
@Override
public void run() {
// 记录初始时间
long start = System.currentTimeMillis();
Random rand = new Random();
// 进行10次操作
for (int i = 0; i < 10; i++) {
// 休眠50毫秒
SleepTools.ms(50);
goodsService.setNum(rand.nextInt(10));
}
System.out.println(
Thread.currentThread().getName() + " -线程写商品数据共耗时: " + (System.currentTimeMillis() - start) + "ms");
}
}
public static void main(String[] args) {
// 初始化商品信息
GoodsInfo goodsInfo = new GoodsInfo("华为", 100000D, 10000);
// 调用商品操作接口类,先用synchronized实现
GoodsService goodsService = new UseReadAndWriteLockImpl(goodsInfo);
// 设置最小线程数
for (int i = 0; i < minThreadNum; i++) {
// 初始化写线程
Thread setThread = new Thread(new SetGoodsThread(goodsService));
// 设置读写比例10:1
for (int j = 0; j < readAndWriteRatio; j++) {
// 初始化读线程
Thread getThread = new Thread(new GetGoodsThread(goodsService));
getThread.start();
}
// 休眠100毫秒
SleepTools.ms(100);
// 启动写线程
setThread.start();
}
}
}
控制台输出结果:
Thread-8 -线程读取商品共耗时: 591ms
Thread-9 -线程读取商品共耗时: 590ms
Thread-2 -线程读取商品共耗时: 591ms
Thread-5 -线程读取商品共耗时: 596ms
Thread-6 -线程读取商品共耗时: 606ms
Thread-10 -线程读取商品共耗时: 605ms
Thread-7 -线程读取商品共耗时: 606ms
Thread-4 -线程读取商品共耗时: 606ms
Thread-1 -线程读取商品共耗时: 611ms
Thread-3 -线程读取商品共耗时: 626ms
Thread-0 -线程写商品数据共耗时: 600ms
Thread-15 -线程读取商品共耗时: 630ms
Thread-16 -线程读取商品共耗时: 630ms
Thread-13 -线程读取商品共耗时: 635ms
Thread-14 -线程读取商品共耗时: 645ms
Thread-21 -线程读取商品共耗时: 649ms
Thread-20 -线程读取商品共耗时: 654ms
Thread-18 -线程读取商品共耗时: 655ms
Thread-17 -线程读取商品共耗时: 660ms
Thread-19 -线程读取商品共耗时: 660ms
Thread-12 -线程读取商品共耗时: 665ms
Thread-11 -线程写商品数据共耗时: 599ms
Thread-24 -线程读取商品共耗时: 649ms
Thread-23 -线程读取商品共耗时: 654ms
Thread-25 -线程读取商品共耗时: 654ms
Thread-27 -线程读取商品共耗时: 654ms
Thread-31 -线程读取商品共耗时: 653ms
Thread-29 -线程读取商品共耗时: 654ms
Thread-26 -线程读取商品共耗时: 659ms
Thread-30 -线程读取商品共耗时: 659ms
Thread-32 -线程读取商品共耗时: 658ms
Thread-28 -线程读取商品共耗时: 674ms
Thread-22 -线程写商品数据共耗时: 593ms
对比很明显,读写锁的读写操作,完全高于synchronized内置锁的读写操作。
因此读写锁非常适合读多写少的流程。
五、Condition接口
来看一下JDK提供了Condition的接口方法
在这里其实和前面讲的wait、notify、notifyAll是相同的,有点区别就是
1、在用wait、notify、notifyAll进行等待通知处理时,我们建议是用notifyAll来释放所有的处于等待状态的通知
2、在用Condition时,我们建议用signal、signalAll的signal来进行释放等待的通知
关于Condition简单的进行介绍,显示锁的学习就到这。
更多精彩敬请关注公众号
Java极客思维
微信扫一扫,关注公众号