【并发】Lock与ReentrantLock

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();
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容