zoukankan      html  css  js  c++  java
  • 显式锁和AQS

    首先我们需要知道的是:锁可以分为公平锁和不公平锁,重入锁和非重入锁;

    一、Lock接口

    Lock是java 1.5中引入的线程同步工具,它主要用于多线程下共享资源的控制。本质上Lock仅仅是一个接口(位于源码包中的javautilconcurrentlocks中),

    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

     六、LockCondition实现等待通知

    • Condition类有很好的灵活性,可以实现多路通知功能,一个Lock对象中可以创建多个Condition对象实例,线程对象可以注册在指定的Condition中,进而有选择的进行线程通知,在调度线程上更加灵活
    • wait与notify/notifyAll进行等待通知时,被通知的线程是随机的,但是Condition与Lock结合的通知是有选择性的通知
    • synchronized就相当于整个Lock对象中只有一个单一的Condition对象,所有线程对象都注册在一个Condition对象身上,线程开始notifyAll时,需要通知所有的WAITING线程,没有选择性,会出现很大的效率问题

    看代码事例:

    package com.xiangxue.ch4.condition;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 
     *@author Mark老师   享学课堂 https://enjoy.ke.qq.com 
     *
     *类说明:
     */
    public class ExpressCond {
        public final static String CITY = "ShangHai";
        private int km;/*快递运输里程数*/
        private String site;/*快递到达地点*/
        private Lock lock = new ReentrantLock();
        private Condition keCond = lock.newCondition();
        private Condition siteCond = lock.newCondition();
    
        public ExpressCond() {
        }
    
        public ExpressCond(int km, String site) {
            this.km = km;
            this.site = site;
        }
    
        /* 变化公里数,然后通知处于wait状态并需要处理公里数的线程进行业务处理*/
        public void changeKm(){
            lock.lock();
            try {
                this.km = 101;
                keCond.signalAll();
            }finally {
                lock.unlock();
            }
        }
    
        /* 变化地点,然后通知处于wait状态并需要处理地点的线程进行业务处理*/
        public  void changeSite(){
            lock.lock();
            try {
                this.site = "BeiJing";
                siteCond.signal();
            }finally {
                lock.unlock();
            }        
        }
    
        /*当快递的里程数大于100时更新数据库*/
        public void waitKm(){
            lock.lock();
            try {
                while(this.km<=100) {
                    try {
                        keCond.await();
                        System.out.println("check km thread["+Thread.currentThread().getId()
                                +"] is be notifed.");
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }            
            }finally {
                lock.unlock();
            }
    
            System.out.println("the Km is "+this.km+",I will change db");
        }
    
        /*当快递到达目的地时通知用户*/
        public void waitSite(){
            lock.lock();
            try {
                while(CITY.equals(this.site)) {
                    try {
                        siteCond.await();
                        System.out.println("check site thread["+Thread.currentThread().getId()
                                +"] is be notifed.");
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }finally {
                lock.unlock();
            } 
            System.out.println("the site is "+this.site+",I will call user");
        }
    }
    
    
    
    package com.xiangxue.ch4.condition;
    
    /**
     *@author Mark老师   享学课堂 https://enjoy.ke.qq.com 
     *
     *类说明:测试Lock和Condition实现等待通知
     */
    public class TestCond {
        private static ExpressCond express = new ExpressCond(0,ExpressCond.CITY);
    
        /*检查里程数变化的线程,不满足条件,线程一直等待*/
        private static class CheckKm extends Thread{
            @Override
            public void run() {
                express.waitKm();
            }
        }
    
        /*检查地点变化的线程,不满足条件,线程一直等待*/
        private static class CheckSite extends Thread{
            @Override
            public void run() {
                express.waitSite();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            for(int i=0;i<3;i++){
                new CheckSite().start();
            }
            for(int i=0;i<3;i++){
                new CheckKm().start();
            }
    
            Thread.sleep(1000);
            express.changeKm();//快递里程变化
        }
    }
    View Code

    总结信息:

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

    七、具体的AQS和Condition还有的是具体实现原理。包括的是数据结构和实现过程,还需要以后的时间在慢慢研究一下。

  • 相关阅读:
    业务场景和业务用例场景的区别(作者:Arthur网友)
    svn 安装
    PHP has encountered an Access Violation at
    邀请大象一书的读者和广大网友写关于分析设计、建模方面的自愿者文章
    手机网页 复制信息方法 免费短信
    delphi Inno Setup 制作安装程序
    Special Folders
    Windows mobile上获取输入光标位置
    加壳程序无法准确读输入表的解决办法
    C++ PostMessage 模拟键盘鼠标
  • 原文地址:https://www.cnblogs.com/lys-lyy/p/11055583.html
Copyright © 2011-2022 走看看