重入锁:ReentrantLock
相比synchronized, 重入锁有着显示的操作过程;
ReentrantLock lock = new ReentrantLock();
lock.lock();
lock.lock();
try{
//do something
}finally{
lock.unlock();
lock.unlock();
}
一个线程可连续多次获得同一把锁,释放的时候需要释放相同的次数。如果过释放锁的次数过多,会得到异常 IllegalMonitorStateException 。
中断响应
lock.lockInterruptibly();
- 在等待锁的时候,可对中断响应,得到异常 InterruptedException。
锁申请等待限时
boolean success = lock.tryLock(5,TimeUnit.SECONDES);
- 第一个参数为等待时长,第二个参数计时单位
- 成功获得锁返回true,等待时间内未获得锁返回false。
- 也可以不带参数,如果锁未被其他线程占用,立即返回true,如果锁被其他线程占用,当前进程不会等待,立即放回false。
公平锁
ReentrantLock lock = new ReentrantLock(true);
公平锁需要一个有序队列,因此实现成本高,性能相对也非常低下。
条件:Condition
Condition和wait()、notify()方法的功能大致相同。但是 后者与 synchronized 关键字合作使用,而Conditon与重入锁相关联。
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//...
@Override
public void run(){
try{
lock.lock();
condition.await();
} catch( InterruptedException e){
// 响应中断
} finally{
lock.unlock();
}
}
//...
public static void main( String[] args)throws InterruptedException {
lock.lock();
condition.signal();
lock.unlock();
}
通过Lock 接口 可以生成一个与当前重入锁绑定的Condition实例。
await();
awaitUninterruptibly();
signal();
signalAll();
awaitUninterruptibly() 不会在等待中响应中断。
其他的与Object.wait()、Object.notify()、Object.notifyAll()类似。await()会释放占用的锁。
线程调用的时候,需要持有对应的重入锁。
信号量:Semaphore
public Semaphore( int permits);//指定permits 个数的线程,可同时访问。
public Semaphore( int permits, boolean fair);// 第二个参数指定是否公平
无论是内部锁synchronized 还是重入锁 ReentrantLock, 一次只允许一个线程访问一个资源。而信号量允许多个线程同时访问某个资源。
public void acquire();
public void acquireUninterruptibly();
public boolean tryAcquire();
public boolean tryAcquire( long timeout; TimeUnit unit);
public void release();
实例:P84
读写锁:ReadWriteLock
ReadWriteLock 是JDK5中提供的读写分离锁。读写分离可有效地帮助减少锁竞争,以提升性能。
只有读-读之间不会阻塞,读-写、写-读、写-写 都会阻塞。
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
Lock readLock = readWriteLock.readLock();
Lock wrieteLock = readWriteLock.writeLock();
readlock.lock();
readlock.unlock();
倒计时锁:CountDownLatch
调用countDownLatch.await()的线程等待,其他任务的完成(调用countDown())。
public class Test implements Runnable{
static Test demo = new Test();
static CountDownLatch count = new CountDownLatch(10);// 参数为计数个数
@Override
public void run() {
try{
Thread.sleep(1000);// 模拟任务
count.countDown();// 通知CountDownLatch一个线程以及完成
} catch (InterruptedException e) {
// sleep 中断
}
}
public static void main(String[] args){
ExecutorService exec = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
exec.submit(demo);
}
try {
count.await();// 等待所有N个线程调用了countDown()
} catch (InterruptedException e) {
// CountDownLatch.await() 中断
}
}
}
循环栅栏:CyclicBarrier
public class Test implements Runnable{
static Test demo = new Test();
static CyclicBarrier barrier = new CyclicBarrier(3, new Runnable() {
@Override
public void run() {
System.out.println("本次循环完毕!");
}
});
@Override
public void run() {
try {
System.out.println("正在等待的线程数:" + barrier.getNumberWaiting());
barrier.await();
System.out.println("第一个循环完成!");
barrier.await();
System.out.println("第二个循环完成!");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
for (int i = 0; i < 3; i++) {
new Thread(demo).start();
Thread.sleep(1000);
}
}
}
- 当调用N次 barrier.await()后,计数器完成一次计数,并执行第二参数Runnable(最后一个调用await()的线程会直接在本线程中调用run()方法,然后调用signalAll()唤醒前面等待的线程 )。
- CyclicBarriar 可能抛出两个异常:InterruptedException 和 其特有的BrokenBarrierException,表示当前的CyclicBarrier已经损坏了,在执行就没有意义了。比如其中一个调用barrier.await()的线程被中断了,那么其他调用barrier.await()的线程再继续等待就徒劳无功了。
线程阻塞工具:LockSupport
让调用LockSupport.park() 的线程阻塞,线程状态为WAITING,它不需要先获得某个对象的锁,也不会抛出InterruptedException。
unpark()唤醒线程,不会因unpark()在park() 之前调用而永久挂起。
park();
parkNanos();
parkUntil();
park(Object);
unpark();
unpark函数为线程提供“许可(permit)”,线程调用park函数则等待“许可”。这个有点像信号量,但是这个“许可”是不能叠加的,“许可”是一次性的
此处个人理解为:线程的许可初始是不可用的,当先调用park()时,当前线程等待许可可用,调用unpark()使许可可用后,等待的线程继续执行;当先调用unpark()时,将不可用的许可变为可用,后续调用park()时,检测到许可可用,则立即直接返回,继续执行。