多线程-day-10显示锁

10 篇文章 0 订阅
1 篇文章 0 订阅

目录

显示锁

Lock接口和核心方法

Lock和synchronized关键字的比较

可重入锁ReentrantLock、公平锁、非公平锁

读写锁

Condition接口

用Lock和Condition实现等待和通知


一、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极客思维

微信扫一扫,关注公众号

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值