多线程的锁相关内容

重入锁

1.重入锁基本操作:

public class ReentranLockTest implements Runnable{
    private static ReentrantLock lock = new ReentrantLock();
    
    public void run() {
            // TODO Auto-generated method stub
            lock.lock();
            System.out.println("i get the lock");
            System.out.println("other threads can not reach this area");
            lock.unlock();
            }
}

如果一个线程在lock.lock()到lock.unlock()之间,其他线程如果也在访问这段,将会阻塞在lock.lock()上,即在等待获取锁。

重入锁叫重是因为获取锁的那个线程可以设置多重锁,如果需要,可以:

    @Override
    public void run() {
        // TODO Auto-generated method stub
        
        lock.lock();
        lock.lock();
        System.out.println("i get the lock");
        lock.unlock();
        lock.unlock();

需要记得的是,锁定了几次就要解锁几次,否则就其他线程就永远无法获得锁了。

2.可中断的重入锁

首先重入锁的使用场景是,如果线程A已经占据着锁了,另外一个线程B在申请锁的过程中必然阻塞,如果这个时候线程B设置了中断变量,如果是用Synchronsized或者1中的lock.lock()来阻塞等待的话,是无法相应中断的。所以引入了 lock.lockInterruptibly();

示例代码:

public class ReentranLockTest implements Runnable{
    private static ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        // TODO Auto-generated method stub//        
        try {
            //由于锁被主线程持有,所以此时阻塞在这里。
            lock.lockInterruptibly();
            System.out.println("get the lock");
            lock.unlock();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            System.out.println("give up the lock");
            e.printStackTrace();
        }   
        
    }
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //主线程先获取锁
        lock.lock();
        Thread t = new Thread(new ReentranLockTest());
        t.start();
        //将线程中断标志位置为中断
        t.interrupt();
        lock.unlock();
    }
}

控制台打印如下:

give up the lock
java.lang.InterruptedException
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1220)
    at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
    at cn.senninha.concurrent.annotations.ReentranLockTest.run(ReentranLockTest.java:15)
    at java.lang.Thread.run(Thread.java:745)

线程在等待获取锁的情况下依然响应了中断,放弃对锁的申请。

3.tryLock()

    public class ReentranLockTest implements Runnable {
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();

    @Override
    public void run() {
        // TODO Auto-generated method stub//
        try {
            //等待1s,如果1s内不能获得锁,返回false。
            if (lock.tryLock(1, TimeUnit.SECONDS)) {
                System.out.println("get the lock");
                lock.unlock();
            } else {
                System.out.println("cannot get the lock after 1s");
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        lock.lock();
        Thread t = new Thread(new ReentranLockTest(), "b");
        t.start();

    }
}

运行结果:

cannot get the lock after 1s

4.重入锁配合Conditon使用

ReentrantLock与Condition的使用与 Synchronized与wait的使用搭配有相似之处。

首先Condition由ReentrantLock.newCondition()来实例化,实际上实例化是这样:

  final ConditionObject newCondition() {
            return new ConditionObject();
        }

public class ReentranLockTest implements Runnable{
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();

    @Override
    public void run() {
        // TODO Auto-generated method stub//        
        try {
            lock.lockInterruptibly();
            System.out.println("wait");
            System.out.println(System.currentTimeMillis());
            //等待。需要唤醒才能停止等待。
            condition.await();
            System.out.println("after wait");
            System.out.println(System.currentTimeMillis());
            lock.unlock();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            System.out.println("give up the lock");
            e.printStackTrace();
        }   
        
    }
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //首先启动线程,线程马上进入等待状态,并释放锁
        Thread t = new Thread(new ReentranLockTest());
        t.start();
        //然后主线程重新申请锁
        lock.lock();
        try {
        //等待1s后唤醒其他等待的线程。
            condition.await(1, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        condition.signal();
        //记得释放当前的锁
        lock.unlock();
    }
}

运行结果:

wait
1491476750599
after wait
1491476751599

如预期,线程b先获取锁,然后在调用condition.await()后释放锁,然后主线程获取了锁,并且等待了1s后唤醒在condition上等待的线程b,然后主线程释放锁,等待的b线程重新申请获取锁后继续运行。

需要注意的点:

  • 调用conditon的相关方法需要在获取锁的前提下,否则会抛出

java.lang.IllegalMonitorStateException

  • 在等待过程中会释放锁,这个和Object.wait()是一样的。

5.信号量(Semaphore)

信号量可以限制临界区可以有多少个线程进入:

public class SemaphoreDemo implements Runnable{
    //第一个参数是信号量个数,第二个是是否是公平的信号量。
    private static Semaphore semaphore = new Semaphore(5,true);

    @Override
    public void run() {
        // TODO Auto-generated method stub
        try {
            //申请信号量
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + System.currentTimeMillis());
            //休眠1s
            Thread.sleep(1000);
            //释放信号量
            semaphore.release();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args){
        SemaphoreDemo demo = new SemaphoreDemo();
        //申请10个线程
        ExecutorService exec = Executors.newFixedThreadPool(10);
        for(int i = 0 ; i < 10 ; i++){
            exec.submit(demo);
        }
        try {
            /休眠3s后停止线程池
            Thread.sleep(3000);
            exec.shutdown();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

由于只允许5个线程同时进入临界区,所以十个线程会分两批进入,那么打印出的时间戳应该是差1000ms的,结果如下:

pool-1-thread-11491479241712
pool-1-thread-31491479241715
pool-1-thread-21491479241715
pool-1-thread-51491479241716
pool-1-thread-41491479241716

pool-1-thread-61491479242713
pool-1-thread-71491479242715
pool-1-thread-81491479242715
pool-1-thread-91491479242717
pool-1-thread-101491479242717

可以看到先打印出来的前5个和后5个差了1000ms

4.读写锁(ReadWriteLock)

读写锁就是可以多个读进程进入,在读多写少的应用场景,可以大大提高效率。

当前操作 新增加操作 新增操作是否阻塞
阻塞
非阻塞
阻塞
阻塞

来个先写然后读的操作示例:


public class ReadWriteLockDemo {
    //实例化读写锁
    private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private static ReadLock rLock = lock.readLock();
    private static WriteLock wLock = lock.writeLock();

    private static void read(Lock rLock) {
        //在申请读取锁不等待1s
        rLock.lock();
        System.out.println(Thread.currentThread() + "read get the lock");
        try {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread() + "read release the lock");
            rLock.unlock();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private static void wirte(Lock wLock) {
        //在申请写锁后等待1s
        wLock.lock();
        System.out.println(Thread.currentThread() + "write get the lock");
        try {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread() + "write release the lock");
            wLock.unlock();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        long start = System.currentTimeMillis();
        
        //读线程
        Thread t0 = new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                read(rLock);
            }

        }, "t0");

        //写线程
        Thread t1 = new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                wirte(wLock);
            }

        }, "t1");

        try {
            //为了确保读操作先进行
            t0.start();
            //,主线程休眠100ms
            Thread.sleep(100);
            //写线程开始
            t1.start();
            //主线程等待t1线程完成
            t1.join();
            //获取消耗时间
            System.out.println("cost time" + (System.currentTimeMillis() - start));
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

消耗时间2s,因为读操作的时候,会阻塞写操作。

Thread[t0,5,main]read get the lock
Thread[t0,5,main]read release the lock
Thread[t1,5,main]write get the lock
Thread[t1,5,main]write release the lock
cost time2003

多线程读取,将代码做一点小修改

Thread t0 = new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                read(rLock);
            }

        }, "t0");

        Thread t1 = new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                wirte(wLock);
            }

        }, "t1");

结果1103ms,因为我们在线程之间等待了100ms,所以1103ms减去100ms就是1s了,说明多线程的读是不阻塞的。

Thread[t1,5,main]read get the lock
Thread[t0,5,main]read release the lock
Thread[t1,5,main]read release the lock
cost time1103

5.CountDownLatch(倒计时器)

倒计时器可以设置进入临界区的线程数量到了某个数量后就进停止等待,往下执行

首先是临界区设置,每进入一个线程计数一次:

CountDownLatch.countDown();

然后一个线程等待,只有指定数量的线程进入后才会停止等待

CountDownLatch.await();

具体代码:

public class ConutDownLatchTest implements Runnable{
    //指定五个线程计数量
    private static CountDownLatch cdl = new CountDownLatch(5);
    
    @Override
    public void run() {
        // TODO Auto-generated method stub
        try {
            Thread.currentThread().sleep(1000);
            //计数
            cdl.countDown();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("count");
    }

    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ExecutorService service = Executors.newFixedThreadPool(5);
        ConutDownLatchTest test = new ConutDownLatchTest();
        for(int i = 0 ; i < 5 ; i++){
            service.submit(test);
        }
        try {
            //等待5个计数的线程进入
            cdl.await();
            System.out.println("five thread finish");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

其实就是五次调用countDown()计数后即可使之停止等待。。。循环里改成五次调用countDown()函数,然后就会发现不会等待。。直接就继续运行了。。

6.CyclicBarrier(循环栅栏)

循环栅栏,是要等到所有的线程都准备完毕了才会继续执行

public class CyclicBarrierTest implements Runnable{
    private static CyclicBarrier barrier = new CyclicBarrier(5);

    @Override
    public void run() {
        // TODO Auto-generated method stub
        try {
            //开始等待五个线程进入啦
            barrier.await();
            //如果是等待五个线程才继续执行,那么这里五个线程打印的时间应该是很接近的,后面看运行结果
            System.out.println("run~" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ExecutorService service = Executors.newFixedThreadPool(5);
        for(int i = 0 ; i < 5 ; i++){
            try {
                //提交线程进入,为了显示是五个线程准备好才进入的,休眠1000ms
                service.execute(new CyclicBarrierTest());
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    

}

运行结果:

run 1491582362091
run 1491582362092
run 1491582362092
run 1491582362092
run 1491582362092
果然是等待五个线程后才一起执行。

7.LockSupport 线程阻塞工具

与wait()相比,LockSuport不用获取某个对象的锁,并且不会抛出中断异常InterruptedExcetion
示例代码如下,

public class LockSupportTest implements Runnable{
    
    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println(Thread.currentThread().getName() + "before park");
        //挂起当前线程
        LockSupport.park();
        System.out.println(Thread.currentThread().getName() + "after park");
    }
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        LockSupportTest lt0 = new LockSupportTest();
        LockSupportTest lt1 = new LockSupportTest();
        Thread t1 = new Thread(lt0,"t1");
        Thread t2 = new Thread(lt1,"t2");
        t1.start();
        t2.start();
        //使线程2停止挂起
        LockSupport.unpark(t2);
    }




}

运行结果:

t1before park
t2before park
t2after park

而且LockSuppor.unpark()方法发生在LockSupprot.park()之前也不会影响
比如,稍微修改一下run方法:

@Override
    public void run() {
        // TODO Auto-generated method stub
        //挂起之前就unpark()
        LockSupport.unpark(Thread.currentThread());
        System.out.println(Thread.currentThread().getName() + "before park");
        LockSupport.park();
        System.out.println(Thread.currentThread().getName() + "after park");
    }

结果:

t1before park
t1after park
t2before park
t2after park

结果当然是可以正常结束,有点像信号量的意思。。但是这个这个unpark()是不像信号量一样可以累加的。有且只有一个。

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

推荐阅读更多精彩内容

  • 文章来源:http://www.54tianzhisheng.cn/2017/06/04/Java-Thread/...
    beneke阅读 1,473评论 0 1
  • 1.解决信号量丢失和假唤醒 public class MyWaitNotify3{ MonitorObject m...
    Q罗阅读 873评论 0 1
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,444评论 1 15
  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,952评论 1 18
  • 我开着车 车载着你 你带着青春和活力 我们驱车来到伏尔加河畔的大草原 释放心情 放飞自己
    一叶茶阅读 65评论 0 1