借助这题复习一下 Java 并发类和工具。
题目描述 两个不同的线程将会共用一个 FooBar 实例。其中一个线程将会调用 foo () 方法,另一个线程将会调用 bar () 方法。
请设计修改程序,以确保 “foobar” 被输出 n 次。
我们提供一个类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class FooBar { private int n; public FooBar (int n) { this .n = n; } public void foo (Runnable printFoo) throws InterruptedException { for (int i = 0 ; i < n; i++) { printFoo.run (); } } public void bar (Runnable printBar) throws InterruptedException { for (int i = 0 ; i < n; i++) { printBar.run (); } } }
思路 这题考的是线程同步,那么显而易见,信号量、互斥锁、共享内存这些方案都是可行的。
题解 信号量 Semaphore Semaphore 翻译成字面意思为 信号量,Semaphore 可以控同时访问的线程个数,通过 acquire () 获取一个许可,如果没有就等待,而 release () 释放一个许可。
Semaphore 类位于 java.util.concurrent 包下,它提供了 2 个构造器:
1 2 3 4 5 6 public Semaphore (int permits ) { sync = new NonfairSync (permits ); } public Semaphore (int permits , boolean fair) { sync = (fair)? new FairSync (permits ) : new NonfairSync (permits ); }
下面说一下 Semaphore 类中比较重要的几个方法,首先是 acquire ()、release () 方法:
1 2 3 4 public void acquire () throws InterruptedException { } public void acquire (int permits ) throws InterruptedException { } public void release () { } public void release (int permits ) { }
acquire () 用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许可。
release () 用来释放许可。注意,在释放许可之前,必须先获获得许可。
这 4 个方法都会被阻塞,如果想立即得到执行结果,可以使用下面几个方法:
1 2 3 4 5 6 7 public boolean tryAcquire () { }; public boolean tryAcquire (long timeout, TimeUnit unit) throws InterruptedException { }; public boolean tryAcquire (int permits ) { }; public boolean tryAcquire (int permits , long timeout, TimeUnit unit) throws InterruptedException { };
另外还可以通过 availablePermits () 方法得到可用的许可数目。
此题可设置两个信号量分别标示 FooBar 的可用状态。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class FooBar { private int n; public FooBar (int n) { this .n = n; } Semaphore foo = new Semaphore (1 ); Semaphore bar = new Semaphore (0 ); public void foo (Runnable printFoo) throws InterruptedException { for (int i = 0 ; i < n; i++) { foo.acquire (); printFoo.run (); bar.release (); } } public void bar (Runnable printBar) throws InterruptedException { for (int i = 0 ; i < n; i++) { bar.acquire (); printBar.run (); foo.release (); } } }
公平锁 锁提供对共享资源的独占访问:一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。 但是,一些锁可能允许并发访问共享资源,如 ReadWriteLock 的读写锁。在 Lock 接口出现之前,Java 程序是靠 synchronized 关键字实现锁功能的。JDK1.5 之后并发包中新增了 Lock 接口以及相关实现类来实现锁功能。
Lock 接口的实现类: ReentrantLock
, ReentrantReadWriteLock.ReadLock
, ReentrantReadWriteLock.WriteLock
Lock 接口提供的 synchronized 关键字不具备的主要特性:
特性
描述
尝试非阻塞地获取锁
当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁
能被中断地获取锁
获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会被释放
超时获取锁
在指定的截止时间之前获取锁, 超过截止时间后仍旧无法获取则返回
Lock 接口基本的方法:
方法名称
描述
void lock ()
获得锁。如果锁不可用,则当前线程将被禁用以进行线程调度,并处于休眠状态,直到获取锁。
void lockInterruptibly ()
获取锁,如果可用并立即返回。如果锁不可用,那么当前线程将被禁用以进行线程调度,并且处于休眠状态,和 lock () 方法不同的是在锁的获取中可以中断当前线程(相应中断)。
Condition newCondition ()
获取等待通知组件,该组件和当前的锁绑定,当前线程只有获得了锁,才能调用该组件的 wait () 方法,而调用后,当前线程将释放锁。
boolean tryLock ()
只有在调用时才可以获得锁。如果可用,则获取锁定,并立即返回值为 true;如果锁不可用,则此方法将立即返回值为 false 。
boolean tryLock (long time, TimeUnit unit)
超时获取锁,当前线程在一下三种情况下会返回: 1. 当前线程在超时时间内获得了锁;2. 当前线程在超时时间内被中断;3. 超时时间结束,返回 false.
void unlock ()
释放锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class FooBar { private int n; public FooBar (int n) { this .n = n; } Lock lock = new ReentrantLock (true ); volatile boolean permitFoo = true ; public void foo (Runnable printFoo) throws InterruptedException { for (int i = 0 ; i < n; ) { lock.lock (); try { if (permitFoo) { printFoo.run (); i++; permitFoo = false ; } }finally { lock.unlock (); } } } public void bar (Runnable printBar) throws InterruptedException { for (int i = 0 ; i < n; ) { lock.lock (); try { if (!permitFoo) { printBar.run (); i++; permitFoo = true ; } }finally { lock.unlock (); } } } }
无锁 以上的公平锁方案完全可以改造成无锁方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class FooBar { private int n; public FooBar (int n) { this .n = n; } volatile boolean permitFoo = true ; public void foo (Runnable printFoo) throws InterruptedException { for (int i = 0 ; i < n; ) { if (permitFoo) { printFoo.run (); i++; permitFoo = false ; } } } public void bar (Runnable printBar) throws InterruptedException { for (int i = 0 ; i < n; ) { if (!permitFoo) { printBar.run (); i++; permitFoo = true ; } } } }
CyclicBarrier 类 循环场景中常用 CyclicBarrier
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public FooBar (int n) { this .n = n; } CyclicBarrier cb = new CyclicBarrier (2 ); volatile boolean fin = true ; public void foo (Runnable printFoo) throws InterruptedException { for (int i = 0 ; i < n; i++) { while (!fin); printFoo.run (); fin = false ; try { cb.await (); } catch (BrokenBarrierException e) { } } } public void bar (Runnable printBar) throws InterruptedException { for (int i = 0 ; i < n; i++) { try { cb.await (); } catch (BrokenBarrierException e) { } printBar.run (); fin = true ; } } }