在java多线程中,我们知道可以使用synchronized关键字来实现线程间的同步互斥工作,那么其实还有一个更优秀的机制去完成这个“同步互斥”工作,他就是Lock对象,我们主要学习两种锁,重入锁和读写锁。他们具有比synchronized更为强大的功能,并且有嗅探锁定、多路分支等功能。
1、ReentrantLock重入锁
重入锁,在需要进行同步的代码部分加上锁定,但不要忘记最后一定要释放锁定,不然会造成锁永远无法释放,其他线程永远进不来的结果。
使用:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockTest {
private Lock lock = new ReentrantLock();
private void method1() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "线程" + "进入method1方法");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "线程" + "退出method1方法");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
private void method2() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "线程" + "进入method2方法");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "线程" + "退出method2方法");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final ReentrantLockTest rt = new ReentrantLockTest();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
rt.method1();
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
rt.method2();
}
}, "t2");
t1.start();
t2.start();
}
}
执行结果:
2、锁与等待/通知
还记得我们在使用synchronized的时候,如果需要多线程间进行协作工作则需要0bject的wait()和notify()、notifyAI()方 法进行配合工作。
那么同样,我们在使用Lock的时候,可以使用一个新的等待/通知的类,它就是Condition。这个Condition一定是针对具体某一把锁的。也就是在只有锁的基础之上才会产生Condition。
使用:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionTest {
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private void method1() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "线程进入method1");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "线程进入wait,释放锁");
condition.await();
System.out.println(Thread.currentThread().getName() + "线程继续执行method1");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
private void method2() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "线程进入method2");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "线程唤醒其他线程,不是释放锁,两秒后结束执行,释放锁");
condition.signal();//唤醒其他线程
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
final ConditionTest ct = new ConditionTest();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
ct.method1();
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
ct.method2();
}
}, "t2");
t1.start();
Thread.sleep(10);
t2.start();
}
}
执行结果:
3、多condition
我们可以通过一个Lock对象产生多个Condition进行多线程间的交互,非常的灵活。可以使得部分需要唤醒的线程唤醒,其他线程则继续等待通知。
例子:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class MutilCondition {
private ReentrantLock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private void m1() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "线程进入m1方法并进行等待(c1)");
c1.await();
System.out.println(Thread.currentThread().getName() + "线程继续执行m1方法");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
private void m2() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "线程进入m2方法并进行等待(c1)");
c1.await();
System.out.println(Thread.currentThread().getName() + "线程继续执行m2方法");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
private void m3() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "线程进入m3方法并进行等待(c2)");
c2.await();
System.out.println(Thread.currentThread().getName() + "线程继续执行m3方法");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
private void m4() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "线程进入m4方法并唤醒所有c1等待");
c1.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
private void m5() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "线程进入m5方法并唤醒c2等待");
c2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
final MutilCondition mtc = new MutilCondition();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
mtc.m1();
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
mtc.m2();
}
}, "t2");
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
mtc.m3();
}
}, "t3");
Thread t4 = new Thread(new Runnable() {
@Override
public void run() {
mtc.m4();
}
}, "t4");
Thread t5 = new Thread(new Runnable() {
@Override
public void run() {
mtc.m5();
}
}, "t5");
t1.start();
Thread.sleep(10);
t2.start();
Thread.sleep(10);
t3.start();
Thread.sleep(10);
t4.start();
Thread.sleep(10);
t5.start();
}
}
执行结果:
4、Lock/condition其他方法和用法
公平锁和非公平锁:
Lock lock = new ReentrantLock(boolean isFair);
lock用法:
tryLock():尝试获得锁,获得结果用true/false返回。
tryLock():在给定的时间内尝试获得锁,获得结果用true/false返回。
isFair():是否是公平锁。
isLocked():是否锁定。
getHoldCount():查询当前线程保持此锁的个数,也就是调用lock()次数。
lockInterruptibly():优先响应中断的锁。
getQueueLength():返回正在等待获取此锁定的线程数。
getWaitQueueLength():返回等待与锁定相关的给定条件Condition的线程数。hasQueuedThread(Thread thread):查询指定的线程是否正在等待此锁。
hasQueuedThreads():查询是否有线程正在等待此锁。
hasWaiters():查询是否有线程正在等待与此锁定有关的condition条件。
公平锁:先执行的先获得锁,浪费性能,因为要维护顺序。
非公平锁:由CPU调度
5、ReentrantReadWriteLock(读写锁)
读写锁ReentrantReadWriteLock,其核心就是实现读写分离的锁。在高并发访问下,尤其是读多写少的情况下,性能要远高于重入锁。
之前学synchronized、ReentrantLock时, 我们知道,同一时间内,只能有一个线程进行访问被锁定的代码,那么读写锁则不同,其本质是分成两个锁,即读锁、写锁。在读锁下,多个线程可以并发的进行访问,但是在写锁的时候,只能一个一个的顺序访问。
口诀:读读共享,写写互斥,读写互斥。
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
public class ReentrantReadWriteLockTest {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private ReadLock readLock = lock.readLock();
private WriteLock writeLock = lock.writeLock();
public void read() {
try {
readLock.lock();
System.out.println(Thread.currentThread().getName() + "线程进入read...");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "线程退出read...");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
readLock.unlock();
}
}
public void write() {
try {
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "线程进入write...");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "线程退出write...");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
writeLock.unlock();
}
}
public static void main(String[] args) {
final ReentrantReadWriteLockTest rrt = new ReentrantReadWriteLockTest();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
rrt.read();
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
rrt.read();
}
}, "t2");
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
rrt.write();
}
}, "t3");
//t1.start();
//t2.start();
t1.start();
t3.start();
}
}
6、锁优化总结
1避免死锁
2减小锁的持有时间
3减小锁的粒度
4锁的分离
5尽量使用无锁的操作,如原子操作(Atomic 系列类) , volatile 关键字