(三)JDK并发包——锁

synchronized可以用于控制一个线程是否可以访问临界区资源,Object.wait()Object.notify()方法可以实现线程等待和通知。这些工具都很简单可靠,但是想要实现更复杂和高级的功能,就要用到Java中的锁。

1.重入锁(ReentrantLock)

  • lock.lock()简单的上锁
    重入锁完全可以替代synchronized关键字,在Java早期版本中重入锁的性能远远优于synchronized,而从JDK1.6开始,synchronized进行了大量的优化,两者性能不相上下。
public class Main {
    public static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args){
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                lock.lock();
                lock.lock();
                try {
                    System.out.println(LocalDateTime.now());
                } finally {
                    lock.unlock();
                    lock.unlock();
                }
            }).start();
        }
    }
}

重入锁如它的名字一样,一个线程可以连续两次获得同一把锁,(否则线程会在第二次请求锁时和自己产生死锁),同样,多次获得锁之后也要多次释放锁,否则其他线程将无法获取锁。

  • lock.lockInterruptibly()响应中断
public class Main {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args){
            MyThread t1 = new MyThread();
            MyThread t2 = new MyThread();
            t1.start();
            t2.start();
            t2.interrupt();
    }
    
    static class MyThread extends Thread {
        @Override
        public void run() {
            while (true) {
                try {
                    lock.lockInterruptibly();
                    System.out.println(LocalDateTime.now());
                } catch (InterruptedException e) {
                    System.out.println("break");
                    break;
                } finally {
                    lock.unlock();
                }
            }
        }
    }
}

使用lock.lockInterruptibly()可以使线程在等待锁时响应中断,此时线程会抛出一个InterruptedException异常并放弃锁的竞争。

  • lock.tryLock()超时结束
public boolean tryLock()
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException 

无参数的lock.tryLock()方法会在调用后尝试获得锁,如果成功立刻返回true,失败立刻返回false。而有参数的方法可以持续请求一段时间后自动退出请求并返回false,同时有参数的lock.tryLock()同样可以响应中断。

  • new ReentrantLock(true)公平锁
    使用synchronized关键字进行锁控制,产生的锁就是非公平锁,即在分配锁时不会管线程请求锁的时间先后,所有线程都有可能分配到锁。而公平锁则按照请求锁的时间先后分配锁,这保证了不会出现饥饿现象,但公平锁需要维护一个有序的请求队列,因此开销更大,性能较低。
public ReentrantLock(boolean fair)
  • 重入锁的实现
    第一,原子状态。原子状态使用CAS操作来存储当前锁的状态,判断锁是否已经被其他线程持有。
    第二,等待队列。所有没有请求到锁的线程,会进入等待队列中进行等待。待有线程释放锁之后,系统就能从等待队列中唤醒一个线程,继续工作。
    第三,阻塞原语(park)和(unpark),用来挂起和恢复线程。没有得到锁的线程将被挂起。

2.Condition接口

Condition的作用类似于Object.wait()Object.notify(),不过Condition是用于和ReentrantLock合作。它有以下方法。

// await()方法会使当前线程等待,同时释放当前锁,当其他线程使用
// signal()或signalAll()方法时,线程会重新获得锁并继续执行。或者
// 当线程被中断时,也能跳出等待。功能上类似于Object.wait()方法。
void await() throws InterruptedException;
// 与await()方法类似,但是并不响应中断
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
// 用于唤醒一个在等待中的线程,类似于Object.notify()
void signal();
void signalAll();

类似于Object.wait()Object.notify(),执行前必须用synchronized获得Object对象的锁。Condition对象由锁的newCondition()方法生成,使用await()signal()方法前线程必须获得锁对象,而使用后要释放锁对象。

public Condition newCondition() { return sync.newCondition(); }

3.信号量(Semaphore)

信号量是对锁的拓展,可以允许多个线程同时访问临界区资源。

public Semaphore(int permits)
public Semaphore(int permits, boolean fair)

构造信号量时,必须要指定信号量的准入数,还可以指定是否公平分配信号量。

// 尝试获取信号量,获取时可以响应中断
public void acquire() throws InterruptedException 
// 尝试获取信号量,获取时不响应中断
public void acquireUninterruptibly()
// 尝试获取信号量,立刻返回结果,成功为true,失败为false
public boolean tryAcquire()
// 尝试获取信号量一段时间,可以响应中断
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException 
// 释放占有的信号量
public void release()

信号量的方法与重入锁基本类似,区别只有同时进入临界区的线程数量。

4. 读写锁(ReadWriteLock)

如果使用synchronized关键字或者重入锁,则所有读与读之间、读与写之间、写与写之间都是串行操作,而读写锁允许多个线程同时读,写写和读写之间依然相互排斥。如果系统中的读远大于写,则读写锁可以很好的提升系统性能。

ReadWriteLock lock = new ReentrantReadWriteLock();
Lock readLock = lock.readLock();
Lock writeLock = lock.writeLock();

使用时,从ReadWriteLock上分别获得读锁和写锁,读写操作时分别请求对应的锁。

5.倒计时器(CountDownLatch)

倒计时器可以让某个线程等待直到计数器归零。


public class Main {
    private static CountDownLatch count = new CountDownLatch(10);

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    Thread.sleep(new Random().nextInt());
                    count.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }

        try {
            count.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // todo
    }
}

countDownLatch.await()方法同样可以响应中断。

6.循环栅栏(CyclicBarrier)

循环栅栏类似于倒计时器但功能更多一些,首先循环栅栏可以重复触发,另外可以接受一个Runnable对象,作为一次计数完成后系统会触发的动作。

public CyclicBarrier(int parties)
public CyclicBarrier(int parties, Runnable barrierAction) 
public int await() throws InterruptedException, BrokenBarrierException 

在使用时,每有一个线程执行CyclicBarrier.await(),程序计数加一,到达指定数后就会触发barrierActionCyclicBarrier.await()会返回线程到达的名次,最后一个到达的将会返回0。CyclicBarrier.await()可以响应中断,而当线程已经不可能满足程序计数时(比如某个线程被中断),则其余线程会抛出BrokenBarrierException异常。

7.线程阻塞工具类(LockSupport)

LockSupport可以在线程中的任意位置让线程阻塞,与Thread.suspend()方法相比,弥补了由于resume()方法发生导致线程无法继续执行的情况;与Object.wait()方法相比,不需要先获得某个对象的锁,也不会抛出InterruptedException异常。

public static void park(Object blocker) 
public static void unpark(Thread thread) 

即使unpark()方法发生在park()方法之前,它也能使下一次的park()方法立即返回,同时,处于park()方法挂起状态的线程不会像Thread.suspend()方法一样显示Runnable状态,而是明确的Waiting状态,并且还会标注是由park()方法引起的。

8.限流(RateLimiter)

限流算法的思路:

  • 最简单的限流算法就是给出一个单位时间,然后使用一个计数器count统计单位时间内收到的请求数量,当请求数量超过门限时,余下的请求丢弃或等待。
  • 漏桶算法:利用一个缓冲区,无论请求的速率如何,都先进入缓冲区等待,然后以固定的流速离开缓冲区。
  • 令牌桶算法:系统以一定的速率生成令牌并存入令牌桶中,桶中只能存放一定时限内的令牌。当有请求到来时,拿走桶中的一个令牌,如果桶中没有令牌,则等待或丢弃请求。

RateLimiter就是是Google旗下一个库Guava中的一个工具,采用了令牌桶算法来控制流量。可以防止过量的请求创建的线程压垮服务器。

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

推荐阅读更多精彩内容