重入锁(Java.util.concurrent.locks.ReentrantLock):用来替代synchronized、notify()、notifyAll()。
ReentrantLock得几个重要方法整理:
- lock():获得锁,如果锁被占用,则等待。
- lockInterruptibly():获得锁,但优先响应中断;
- tryLock():尝试获得锁,如果成功,返回true;如果返回false,不等待,直接返回;
- unlock():释放锁。
//如果锁没有被其他线程占有,该线程立即得到该锁
//如果该线程已经占有了该锁,那锁的计数+1
public void lock() { sync.lock();}
//如果该锁被当前线程占有,那么计数器-1,如果计数器归零,锁释放。
//如果锁不归当前线程所有,那么抛出异常:IllegalMonitorStateException
public void unlock() { sync.release(1);}
中断响应
对于synchronized来说,如果一个线程正在等待锁,那么只要两种情况:
- 获得该锁继续执行;
- 保持等待;
当使用了重入锁,就另外多了一种可能性:
- 线程可以被中断。
即在等待锁的过程中,程序可以根据需要取消对锁的请求。
/**
* 获取锁,除非线程被打断;
* 如果锁不被其他线程持有,则获取锁,并立即返回,计数器=1;
* 如果当前线程已经持有此锁,那么计数器+1;
* 如果锁被另外一个线程持有,那么当前线程将处于线程调度的目的被禁用,
* 并处于休眠状态,直到发生以下两个情况:
* 1.锁由当前线程获取;
* 2.其他线程打断当前线程。
*
* 如果当前线程:进入此方法,如果中断状态已经设置,或者
* 获取锁被打断,
* 则抛出interruptedException
*/
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
公平锁
大多数情况下,锁的申请都是非公平的。
公平锁:按照时间的先后顺序,保证先到者先得,不会产生饥饿现象。
synchronized关键字进行锁控制,锁就是非公平得。而重入锁可以对公平性进行设置
//当fair是true时,锁是公平得
//默认情况下锁是非公平得,因为公平锁需要维护一个有序队列,性能相对低
public ReentrantLock(boolean fair)
public class FairLock implements Runnable {
//true表示此对象得锁是公平得
public static ReentrantLock fairLock = new ReentrantLock(true);
public void run() {
while (true){
try {
fairLock.lock();
System.out.println(Thread.currentThread().getName() + "--->获得锁");
}finally {
fairLock.unlock();
}
}
}
public static void main(String[] args) {
FairLock fl = new FairLock();
Thread t1 = new Thread(fl, "Thread_t1");
Thread t2 = new Thread(fl, "Thread_t2");
t1.start();
t2.start();
}
}
上述代码由于是公平锁,所以应该是t1和t2轮流得到锁。
Condition条件
condition和wait()和notify()方法作用大致相同,但是配合重入锁使用
通过Lock接口的Condition newCondition()方法可以生成一个与重入锁绑定的实例。
//使当前线程等待,同时释放当前锁,当其他线程使用signal()或者signalAll()时,会重新获得该锁并继续执行
void await() throws InterruptedException;
//与await()方法基本相同,但是不会在等待过程中响应中断
void awaitUninterruptibly();
//signal()方法唤醒一个在等待中得线程。
void signal();
void signalAll();
public class ReenterLockCondition implements Runnable{
public static ReentrantLock lock = new ReentrantLock();
public static Condition condition = lock.newCondition();
public void run() {
try {
//1.对象得到锁
lock.lock();
//2.要求线程在Condition对象上等待
condition.await();
System.out.println("Thread is going on");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReenterLockCondition t = new ReenterLockCondition();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
Thread.sleep(2000);
//3.通知t1继续执行
lock.lock();
//4.唤醒线程
condition.signal();
lock.unlock();
}
}
信号量(Semaphore):允许多个线程同时访问
无论是synchronized还是重入锁,一次只允许一个线程访问一个资源。
信号量却可以指定多个线程,同时访问某一个资源。
public Semaphore(int permits)
//第二个参数可以指定是否公平
public Semaphore(int permits, boolean fair)
//尝试获取一个准入得许可,无法获得则等待
public void acquire();
public void acquireUninterruptibly();
//尝试获得一个许可,成功返回true,失败返回false。
public boolean tryAcquire();
public boolean tryAcquire(long timeout, TimeUnit unit);
//线程访问资源结束后,释放许可
public void release();
下面是一个实例:
public class SemapDemo implements Runnable{
final Semaphore semp = new Semaphore(5);
public void run() {
try {
semp.acquire();
//为临时区管理代码,意味着同时可以由5个线程进入这段代码
//模拟耗时操作
Thread.sleep(2000);
System.out.println(Thread.currentThread().getId()+":done!");
semp.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//同时开启20个线程
ExecutorService exec = Executors.newFixedThreadPool(20);
final SemapDemo semapDemo = new SemapDemo();
for (int i = 0; i < 20; i++) {
//最终会发现线程是5个为一组进入临时区
exec.submit(semapDemo);
}
}
}
读写锁:ReadWriteLock
读写分离锁可以有效帮助减少锁竞争;
读写锁允许多个线程同时读,真正并行,写写操作和读写操作之间还是要相互等待和持有锁。
倒计时锁:CountDownLatch
倒计时锁是个非常实用得多线程控制工具类。
通常用来控制线程等待,可以让某个线程等待直到倒计时结束,再开始执行