显式锁和AQS
Posted lys-lyy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了显式锁和AQS相关的知识,希望对你有一定的参考价值。
首先我们需要知道的是:锁可以分为公平锁和不公平锁,重入锁和非重入锁;
一、Lock接口
Lock是java 1.5中引入的线程同步工具,它主要用于多线程下共享资源的控制。本质上Lock仅仅是一个接口(位于源码包中的java\util\concurrent\locks中),
Lock有三个实现类,一个是ReentrantLock,另两个是ReentrantReadWriteLock类中的两个静态内部类ReadLock和WriteLock。
Lock它包含以下方法
//尝试获取锁,获取成功则返回,否则阻塞当前线程 void lock(); //尝试获取锁,线程在成功获取锁之前被中断,则放弃获取锁,抛出异常 void lockInterruptibly() throws InterruptedException; //尝试获取锁,获取锁成功则返回true,否则返回false boolean tryLock(); //尝试获取锁,若在规定时间内获取到锁,则返回true,否则返回false,未获取锁之前被中断,则抛出异常 boolean tryLock(long time, TimeUnit unit) throws InterruptedException; //释放锁 void unlock(); //返回当前锁的条件变量,通过条件变量可以实现类似notify和wait的功能,一个锁可以有多个条件变量 Condition newCondition();
下面我们简单的写一个关于Lock的demo入门;
package com.youyou.ch4; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockDemo private Lock lock = new ReentrantLock(); private int count; public void increament() lock.lock(); try if(count > 30) return; count++; increament(); finally lock.unlock(); public synchronized void incre() count++; if(count > 30) return; incre();
二、Lock与synchronized的不同:
1.synchronized的缺点
1)当一个代码块被synchronized修饰的时候,一个线程获取到了锁,并且执行代码块,那么其他的线程需要等待正在使用的线程释放掉这个锁,那么释放锁的方法只有两种,一种是代码执行完毕自动释放,一种是发生异常以后jvm会让线程去释放锁。那么如果这个正在执行的线程遇到什么问题,比如等待IO或者调用sleep方法等等被阻塞了,无法释放锁,而这时候其他线程只能一直等待,将会特别影响效率。那么有没有一种办法让其他线程不必一直傻乎乎的等在这里吗?
2)当一个文件,同时被多个线程操作时,读操作和写操作会发生冲突,写操作和写操作会发生冲突,而读操作和读操作并不会冲突,但是如果我们用synchronized的话,会导致一个线程在读的时候,其他线程想要读的话只能等待,那么有什么办法能不锁读操作吗?
3)在使用synchronized时,我们无法得知线程是否成功获取到锁,那么有什么办法能知道是否获取到锁吗?
2.两者之间的使用和区别:
synchronized 代码简洁,Lock:获取锁可以被中断,超时获取锁,尝试获取锁,读多写少用读写锁
1.synchronized是Java的关键字,是内置特性,而Lock是一个接口,可以用它来实现同步。
2.synchronized同步的时候,其中一条线程用完会自动释放锁,而Lock需要手动释放,如果不手动释放,可能会造成死锁。
3.使用synchronized如果其中一个线程不释放锁,那么其他需要获取锁的线程会一直等待下去,知道使用完释放或者出现异常,而Lock可以使用可以响应中断的锁或者使用规定等待时间的锁
4.synchronized无法得知是否获取到锁,而Lcok可以做到。
5.用ReadWriteLock可以提高多个线程进行读操作的效率
三、可重入锁
Lcok在Java中是一个接口,一般在面试问题中问到的可能是ReentrantLock与synchronized的区别。ReentrantLock是Lock的一个实现类,字面意思的话就是可重入锁,那么什么是可重入锁呢。
可重入锁是锁的一个相关概念,并不是特指我们的ReentrantLock,而是如果一个锁具备可重入性,那我们就说这是一个可重入锁。ReentrantLock和synchronized都是可重入锁。至于什么是可重入性,这里举个简单的例子,现在在一个类里我们有两个方法(代码如下),一个叫做去北京,一个叫做买票,那我们在去北京的方法里可以直接调用买票方法,假如两个方法全都用synchronized修饰的话,在执行去北京的方法,线程获取了对象的锁,接着执行买票方法,如果synchronized不具备可重入性,那么线程已经有这个对象的锁了,现在又来申请,就会导致线程永远等待无法获取到锁。而synchronized和ReentrantLock都是可重入锁,就不会出现上述的问题。
举个简单的例子:
class Trip public synchronized void goToBeiJing() // 去北京 buyATicket(); public synchronized void buyATicket() // 买票
四、公平锁和非公平锁
公平锁严格按照先来后到的顺去获取锁,而非公平锁允许插队获取锁。
公平锁获取锁的过程上有些不同,在使用公平锁时,某线程想要获取锁,不仅需要判断当前表示状态的变量的值是否为0,还要判断队列里是否还有其他线程,若队列中还有线程则说明当前线程需要排队,进行入列操作,并将自身阻塞;若队列为空,才能尝试去获取锁。而对于非公平锁,当表示状态的变量的值是为0,就可以尝试获取锁,不必理会队列是否为空,这样就实现了插队获取锁的特点。通常来说非公平锁的吞吐率比公平锁要高,我们一般常用非公平锁。
其中ReentrantLock,默认是非公平锁,但是构造函数可以传入值,传入true时候,就是公平锁;
五、读写锁
读写锁:同一时刻允许多个读线程同时访问,但是写线程访问的时候,所有的读和写都被阻塞,最适宜与读多写少的情况。
代码看效果:
public class GoodsInfo private final String name; private double totalMoney;//总销售额 private int storeNumber;//库存数 public GoodsInfo(String name, int totalMoney, int storeNumber) this.name = name; this.totalMoney = totalMoney; this.storeNumber = storeNumber; public double getTotalMoney() return totalMoney; public int getStoreNumber() return storeNumber; public void changeNumber(int sellNumber) this.totalMoney += sellNumber*25; this.storeNumber -= sellNumber; public class UseSyn implements GoodsService private GoodsInfo goodsInfo; public UseSyn(GoodsInfo goodsInfo) this.goodsInfo = goodsInfo; @Override public synchronized GoodsInfo getNum() try Thread.sleep(5); catch (InterruptedException e) e.printStackTrace(); return this.goodsInfo; @Override public synchronized void setNum(int number) try Thread.sleep(5); catch (InterruptedException e) e.printStackTrace(); goodsInfo.changeNumber(number); public class UseRwLock implements GoodsService private GoodsInfo goodsInfo; private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock getLock = lock.readLock();//读锁 private final Lock setLock = lock.writeLock();//写锁 public UseRwLock(GoodsInfo goodsInfo) this.goodsInfo = goodsInfo; @Override public GoodsInfo getNum() getLock.lock(); try try Thread.sleep(5); catch (InterruptedException e) e.printStackTrace(); return this.goodsInfo; finally getLock.unlock(); @Override public void setNum(int number) setLock.lock(); try try Thread.sleep(5); catch (InterruptedException e) e.printStackTrace(); goodsInfo.changeNumber(number); finally setLock.unlock(); public interface GoodsService public GoodsInfo getNum();//获得商品的信息 public void setNum(int number);//设置商品的数量 public class BusiApp static final int readWriteRatio = 10;//读写线程的比例 static final int minthreadCount = 3;//最少线程数 //static CountDownLatch latch= new CountDownLatch(1); //读操作 private static class GetThread implements Runnable private GoodsService goodsService; public GetThread(GoodsService goodsService) this.goodsService = goodsService; @Override public void run() // try // latch.await();//让读写线程同时运行 // catch (InterruptedException e) // long start = System.currentTimeMillis(); for(int i=0;i<100;i++)//操作100次 goodsService.getNum(); System.out.println(Thread.currentThread().getName()+"读取商品数据耗时:" +(System.currentTimeMillis()-start)+"ms"); //写操做 private static class SetThread implements Runnable private GoodsService goodsService; public SetThread(GoodsService goodsService) this.goodsService = goodsService; @Override public void run() // try // latch.await();//让读写线程同时运行 // catch (InterruptedException e) // long start = System.currentTimeMillis(); Random r = new Random(); for(int i=0;i<10;i++)//操作10次 SleepTools.ms(50); goodsService.setNum(r.nextInt(10)); System.out.println(Thread.currentThread().getName() +"写商品数据耗时:"+(System.currentTimeMillis()-start)+"ms---------"); public static void main(String[] args) throws InterruptedException GoodsInfo goodsInfo = new GoodsInfo("Cup",100000,10000); GoodsService goodsService = /*new UseRwLock(goodsInfo);*/new UseSyn(goodsInfo); for(int i = 0;i<minthreadCount;i++) Thread setT = new Thread(new SetThread(goodsService)); for(int j=0;j<readWriteRatio;j++) Thread getT = new Thread(new GetThread(goodsService)); getT.start(); SleepTools.ms(100); setT.start(); //latch.countDown();
总结信息:
ReentrantLock和Syn关键字,都是排他锁,即只能有一个线程可以访问
以上是关于显式锁和AQS的主要内容,如果未能解决你的问题,请参考以下文章
从 synchronized www2015338com到 CAS 和 19908836661AQS
从 synchronized 到 CAS 和 AQS - 彻底弄懂 Java 各种并发锁