显式锁和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();

    
View Code

 

 

 

总结信息:

 

ReentrantLockSyn关键字,都是排他锁,即只能有一个线程可以访问

 

以上是关于显式锁和AQS的主要内容,如果未能解决你的问题,请参考以下文章

并发编程--显示锁和AQS

从 synchronized www2015338com到 CAS 和 19908836661AQS

java之AQS和显式锁

从 synchronized 到 CAS 和 AQS - 彻底弄懂 Java 各种并发锁

并发-显示锁Lock和独占锁AQS(AbstractQueuedSynchronizer)

xfchgfwwwbb0002com可重入读写锁 ReentrantReadW19908836661