总览:什么是锁:锁就是一种资源,用来进行共享资源的保护操作(也可以认为锁是一条封锁线,一个对象获得了可以通过,否则就只能在隔离带后面等待,除非轮到自己获取到权限)。
一.synchronized功能拓展:重入锁 ReentrantLock
1.性能和synchronized 相差不大代码,代码:
package com.fortune.util;
import java.util.concurrent.locks.ReentrantLock;
//重入锁代替synchronized 两者消耗差不多两千万次循环
public class ThreadUtils implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static int i=0;
@Override
public void run() {
for (int j=0;j<10000000;j++){
lock.lock();
try {
i++;
}finally {
lock.unlock();
}
/*synchronized (this){
i++;
}*/
}
}
public static void main(String[] args) throws InterruptedException {
Long st = System.currentTimeMillis();
ThreadUtils tt = new ThreadUtils();
Thread t1 = new Thread(tt);
Thread t2 = new Thread(tt);
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
System.out.println("消耗时间:"+(System.currentTimeMillis()-st));
}
总结:ReentrantLock需要手动加锁以及释放锁(显示操作),因此灵活度高,但是要在退出临界区要释放锁,否则其它线程也进入不了。之所以叫 重入锁,是因为可以反复进入,但是仅限于一个线程,如上线程可以改为:
lock.lock();
lock.lock();
try {
i++;
}finally {
lock.unlock();
lock.unlock();
}
注意:锁定了几次就要释放几次,获取以及释放锁的消耗时间相比于第一次会大很多。
2.ReentrantLock 可以中断响应:对于synchronized,如果一个线程在等待锁,要么获得锁继续执行,要么保持等待,不能中途退出。而使用重入锁则可以被中断。也就是在等待锁的过程中,程序可以根据需要取消对锁的请求。
3.限时锁:在一定时间内尝试获取锁失败之后,即返回(不进行任何操作),方法Reentrant().tryLock(),如果不带参数,当有线程获取到锁后就直接返回 true,否则立即返回false,不会进行等待,不会产生死锁。代码:
public class ThreadUtils implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public void run() {
try {
//在5s内尝试获取锁
if (lock.tryLock(5, TimeUnit.SECONDS)){
//这个thread应该是当前执行的线程
System.out.println("我获取到锁了");
Thread.sleep(6000);
lock.unlock();
}else {
System.out.println("get lock failed");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
ThreadUtils threadUtils = new ThreadUtils();
Thread t1 = new Thread(threadUtils);
Thread t2 = new Thread(threadUtils);
t1.start();t2.start();
}
}
4.公平锁:大多数情况下琐是不公平的。当线程a请求了锁1,线程b也请求了锁1,当锁1可用时,到底是先分配a还是b。系统只会从等待队列中随机选取一个,不能保证其公平性。而公平锁就是为了保证先到先得,按照申请的时间顺序进行锁的分配。不会产生饥饿。总会获取到资源。而synchronized则是非公平锁,利用Public Reentrant(boolean fair);进行设置。
根据系统调度:一个线程会倾向于再次获取已经持有的锁,也就是说一个线程第一次获取了锁之后,下一次获取锁的概率就相对来说大一点。这种方式很高效,但是是不公平的,而公平锁需要一个有序队列进行记录,非常消耗系统资源。
5.重入锁的好搭档:Condition条件
condition对象通过调用方法实现和wait()以及notify()方法作用相同。但是 wait()和notify()都是和synchronized 关键字合作使用。 而Condition是与重入锁相关联的。通过Lock接口的Condition.newCondition()方法生成一个与当前重入锁绑定的condition实例。利用Condition对象,可以让线程进行等待或者得到通知等。
方法:
await() 方法会使当前线程等待,同时释放锁,当其他线程中使用signal()或者signalAll()方法时,线程会重新获得锁,并继续执行,或者线程被中断,也能跳出等待和Object.wait()方法相似
awaitUninterruptibly()方法与 await()方法基本相同,但是不会在等待过程中响应中断。
signal()方法用于唤醒一个在等待中的线程。相对signalAll()会唤醒所有在等待的线程。和Object.notify()方法类似。
代码:
public class ThreadUtils implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static Condition condition = lock.newCondition();
public void run() {
lock.lock();
try {
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 {
ThreadUtils threadUtils = new ThreadUtils();
Thread t1 = new Thread(threadUtils);
t1.start();
Thread.sleep(7000);
//通知线程t1继续执行(加锁是为了防止共享资源冲突,因为线程唤醒),因为最开始的锁已经释放过了,此时共享资源未获取锁。
lock.lock();
condition.signal();
//唤醒一个线程,此时需要将共享资源的锁进行释放,否则唤醒了也获取不到锁,一直保持等待状态。
lock.unlock();
}
}
和Object().wait()和notify()方法一样,当线程使用Condition.await()时,要求线程持有相关的重入锁,(没获取锁进行此操作会报错)在Condition.await()调用后,这个线程会释放这把锁。同理在Condition.signal()方法调用时,也要求线程先获得相关的锁。在signal()方法调用后,系统会从当前Condition对象的等待队列中,唤醒一个线程。一旦线程被唤醒,会重新尝试获得与之绑定的重入锁,成功获取继续执行,因此在signal()方法调用之后,一般需要释放相关的锁。谦让给被唤醒的线程,否则唤醒了线程但也无法获得锁,因此无法真正继续执行。