美文网首页Java Concurrency
【并发】Lock与ReentrantLock

【并发】Lock与ReentrantLock

作者: 秀叶寒冬 | 来源:发表于2020-03-24 17:05 被阅读0次

    1 Lock基本使用

    Lock能实现代码同步,它比synchronized更具灵活性,什么时候锁住,什么时候释放锁等都是看得见的,使用时必须使用try{}finally{},意思是万一发生异常或者错误都可以释放锁。

    try{
    }finally{
        //释放锁
    }
    
    • 使用示例
    public class SaleTicket implements Runnable {
        private int ticket = 10 * 100000;
        private Lock mLock = new ReentrantLock();
    
        @Override
        public void run() {
            try {
                while (ticket > 0) {
                    mLock.lock();
                    if(ticket>0){
                        if (ticket % 100000 == 0) {
                            System.out.println("名称:" + Thread.currentThread().getName() + "窗口卖出第" + (ticket / 100000) + "张票");
                        }
                        ticket--;
                    }
                }
            } finally {
                mLock.unlock();
            }
        }
    }
    
            Runnable saleTicket = new SaleTicket();
            Thread thread1 = new Thread(saleTicket);
            Thread thread2 = new Thread(saleTicket);
            Thread thread3 = new Thread(saleTicket);
            Thread thread4 = new Thread(saleTicket);
    
            thread1.start();
            thread2.start();
            thread3.start();
            thread4.start();
    

    结果:


    image.png

    2 ReentrantLock

    ReentrantLock有两种锁机制:忽略中断锁和响应中断锁。如:如果A、B两个线程去竞争锁,A获得锁,B等待,但A线程要做的事情太多,一直不返回锁,B线程就想先中断自己不再等待这个锁,转而去处理其他事情。这时候ReentrantLock提供了2种机制,第一种,B线程中断自己(或者别的线程中断它),但是ReentrantLock不去响应,继续让B线程等待,这就是忽略中断锁(lock());第二种,B线程中断自己(或者别的线程中断它),ReentrantLock处理了这个中断,并且不再等待这个锁的到来,完全放弃,这就是响应中断锁(lockInterruptibly())。

    • 响应中断锁示例:
    public class BufferInterruptibly {
        private ReentrantLock mLock = new ReentrantLock();
        public void write(){
            mLock.lock();
            try {
                long startTime = System.currentTimeMillis();
                System.out.println("开始往这个buff写入数据…");
                for (; ; ) {
                    if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE) {
                        break;
                    }
                }
                System.out.println("终于写完了");
            }finally {
                mLock.unlock();
            }
        }
        public void read() throws InterruptedException{
            mLock.lockInterruptibly();
            try{
                System.out.println("从这个buff读数据");
            }finally {
                mLock.unlock();
            }
        }
    }
    
    final BufferInterruptibly buff = new BufferInterruptibly();
            Thread write = new Thread(new Runnable() {
                @Override
                public void run() {
                    buff.write();
                }
            });
            final Thread read = new Thread(new Runnable() {
                @Override
                public void run() {
                    try{
                        Thread.sleep(500);
                        buff.read();
                    }catch (Exception e){
                        System.out.println("我不读了");
                    }
                    System.out.println("读结束");
                }
            });
    
            write.start();
            read.start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    long start = System.currentTimeMillis();
                    for (;;){
                        if (System.currentTimeMillis()-start>500){
                            System.out.println("不等了,尝试中断");
                            read.interrupt();
                            break;
                        }
                    }
                }
            }).start();
    

    结果:


    image.png

    ReentrantLock叫做重入锁,意思是外层方法获取到锁后,内层方法会自动获取到锁。synchronized也是可重入锁。我们先看一下synchronized锁的可重入性

    class SychronizedReentrant implements Runnable{
        private Object object = new Object();
        /**
         * 方法一调用方法二
         */
        public void method1(){
            synchronized (object){
                System.out.println(Thread.currentThread().getName()+ " method1 ");
                method2();
            }
        }
        
        /**
            方法二,打印获取obj锁
            如果是同一线程,锁不可以重入,method2需要等待method1释放锁
             */
        public void method2(){
            
            synchronized (object){
                System.out.println(Thread.currentThread().getName()+ " method2 ");
            }
        }
    
        @Override
        public void run() {
            method1();
        }
    }
    

    结果

    Thread-0 method1
    Thread-0 method2

    method1有一个同步块,同步块中调用了method2,而method2中也有个同步块,一般来说,synchronized块里面的内容执行完才会释放锁,其它synchronized块才能竞争到锁,而向上述调用步骤,明显是外部方法的synchronized还没有释放锁,内部方法的synchronized就可以得到锁,这就是重入锁。

    例子(摘抄网上):

    package tags;
    
    import java.util.Calendar;
    
    public class TestLock {
        private ReentrantLock lock = null;
        
        public int data = 100;     // 用于线程同步访问的共享数据
    
        public TestLock() {
            lock = new ReentrantLock(); // 创建一个自由竞争的可重入锁
        }
        public ReentrantLock getLock() {
            return lock;
        }
        
        public void testReentry() {
            lock.lock();
            Calendar now = Calendar.getInstance();
            System.out.println(now.getTime() + " " + Thread.currentThread()    + " get lock.");
        }
    
        public static void main(String[] args) {
            TestLock tester = new TestLock();
    
            //1、测试可重入
            tester.testReentry();
            tester.testReentry(); // 能执行到这里而不阻塞,表示锁可重入
            tester.testReentry(); // 再次重入
    
            // 释放重入测试的锁,要按重入的数量解锁,否则其他线程无法获取该锁。
            tester.getLock().unlock();
            tester.getLock().unlock();
            tester.getLock().unlock();
    
            //2、测试互斥
            // 启动3个线程测试在锁保护下的共享数据data的访问
            new Thread(new workerThread(tester)).start();
            new Thread(new workerThread(tester)).start();
            new Thread(new workerThread(tester)).start();
        }
    
    
        // 线程调用的方法
        public void testRun() throws Exception {
            lock.lock();
    
            Calendar now = Calendar.getInstance();
            try {
                // 获取锁后显示 当前时间 当前调用线程 共享数据的值(并使共享数据 + 1)
                System.out.println(now.getTime() + " " + Thread.currentThread()+ " accesses the data " + data++);
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
    
    // 工作线程,调用TestServer.testRun
    class workerThread implements Runnable {
    
        private TestLock tester = null;
    
        public workerThread(TestLock testLock) {
            this.tester = testLock;
        }
    
        public void run() {
            try {
                tester.testRun();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    相关文章

      网友评论

        本文标题:【并发】Lock与ReentrantLock

        本文链接:https://www.haomeiwen.com/subject/tbwiyhtx.html