![](https://i-blog.csdnimg.cn/blog_migrate/7b6907d32d8fecf62643210443a8ac25.gif)
架构师入门笔记六 初识线程安全锁
这章主要介绍Synchronized,ThreadLock,Lock,Concurrent.util等方法。
1 Lock 锁
在java多线程中,可以使用synchronized关键字实现线程间的同步互斥工作,而Lock对象,能更好地完成同步互斥的工作(灵活性高。在jdk1.8之前,性能比synchronized关键字好)。今天主要学习它的重入锁和读写锁。
1.1 ReentrantLock重入锁
重入锁,在需要进行同步的代码部分加上锁定,当不能忘记在最后一步要释放锁定,不然会造成锁永远无法释放,其他线程永远进不来的结果
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyReentrantLock {
private Lock lock = new ReentrantLock(); // 重入锁
private void lockMethod() {
try {
lock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + "进入lockMethod..");
consoleTime("lockMethod" , 5);
System.out.println("当前线程:" + Thread.currentThread().getName() + "退出lockMethod..");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
private void lockMethod2() {
try {
lock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + "进入lockMethod..");
consoleTime("lockMethod2" , 3);
System.out.println("当前线程:" + Thread.currentThread().getName() + "退出lockMethod..");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final MyReentrantLock myLock = new MyReentrantLock();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
myLock.lockMethod(); // 加锁后,该方法执行结束,释放锁后,才会执行下一个方法
myLock.lockMethod2();
}
});
thread.start();
}
private void consoleTime(String methodName, int num) {
for (int i = num; i > 0; i--) {
try {
System.out.println(methodName + " : " + i + "s");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
从打印的结果可以看出,只有当LockMethod方法执行完了(锁释放了),才开始执行LockMethod2方法。
1.2 锁的等待与通知
Synchronized的等待与通知是通过wait() 和 notify() 方法结合使用实现的。而Lock对象这是则是通过Condition类来实现的。
单个Condition:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyReentrantLock {
private Lock lock = new ReentrantLock(); // 重入锁
private Condition condition = lock.newCondition();
private void lockMethod() {
try {
lock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + " 进入等待状态..");
consoleTime("lockMethod" , 3);
System.out.println("当前线程:" + Thread.currentThread().getName() + " 释放锁..");
condition.await(); // Object wait
System.out.println("当前线程:" + Thread.currentThread().getName() +" 继续执行...");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
private void lockMethod2() {
try {
lock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + " 进入..");
consoleTime("lockMethod2" , 3);
System.out.println("当前线程:" + Thread.currentThread().getName() + " 发出唤醒..");
condition.signal(); //Object notify
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final MyReentrantLock myLock = new MyReentrantLock();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
myLock.lockMethod(); // 加锁后,该方法执行结束,释放锁后,才会执行下一个方法
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
myLock.lockMethod2();
}
});
thread.start();
thread2.start();
}
private void consoleTime(String methodName, int num) {
for (int i = num; i > 0; i--) {
try {
System.out.println(methodName + " : " + i + "s");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
从打印的结果可以看出,t1线程执行到await()方法后释放锁,t1线程开始等待,t2线程开始执行,执行到signal()方法后发出唤醒。
多个Condition(Lock锁比Synchronized灵活的原因):
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class UseManyCondition {
private ReentrantLock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
public void m1(){
try {
lock.lock();
System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m1等待..");
c1.await();
System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m1继续..");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void m2(){
try {
lock.lock();
System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m2等待..");
c1.await();
System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m2继续..");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void m3(){
try {
lock.lock();
System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m3等待..");
c2.await();
System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m3继续..");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void m4(){
try {
lock.lock();
System.out.println("当前线程:" +Thread.currentThread().getName() + "唤醒..");
c1.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void m5(){
try {
lock.lock();
System.out.println("当前线程:" +Thread.currentThread().getName() + "唤醒..");
c2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final UseManyCondition umc = new UseManyCondition();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
umc.m1();
}
},"t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
umc.m2();
}
},"t2");
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
umc.m3();
}
},"t3");
Thread t4 = new Thread(new Runnable() {
@Override
public void run() {
umc.m4();
}
},"t4");
Thread t5 = new Thread(new Runnable() {
@Override
public void run() {
umc.m5();
}
},"t5");
t1.start(); // c1
t2.start(); // c1
t3.start(); // c2
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t4.start(); // c1
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t5.start(); // c2
}
}
代码摘录网络,t1 ,t2线程 处于c1的等待中,t3线程处于c2的等待中。t4线程负责唤醒全部c1的等待线程。t5线程负责唤醒t3线程。可以从打印的结果中清晰看出
当前线程:t1进入方法m1等待..
当前线程:t2进入方法m2等待..
当前线程:t3进入方法m3等待..
当前线程:t4唤醒..
当前线程:t1方法m1继续..
当前线程:t2方法m2继续..
当前线程:t5唤醒..
当前线程:t3方法m3继续..
1.3 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 MyReentrantReadWriteLock {
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); // 读写锁
private ReadLock readLock = readWriteLock.readLock(); // 读锁
private WriteLock writeLock = readWriteLock.writeLock(); // 写锁
private void read() {
try {
readLock.lock();
System.out.println("当前读线程:" + Thread.currentThread().getName() + "进入...");
Thread.sleep(3000);
System.out.println("当前读线程:" + Thread.currentThread().getName() + "退出...");
} catch (Exception e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
}
public void write(){
try {
writeLock.lock();
System.out.println("当前写线程:" + Thread.currentThread().getName() + "进入...");
Thread.sleep(3000);
System.out.println("当前写线程:" + Thread.currentThread().getName() + "退出...");
} catch (Exception e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
public static void main(String[] args) {
final MyReentrantReadWriteLock myLock = new MyReentrantReadWriteLock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
myLock.read();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
myLock.read();
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
myLock.write();
}
});
Thread t4 = new Thread(new Runnable() {
@Override
public void run() {
myLock.write();
}
});
// t1 和 t2 是读方法,t3 和 t4 是写方法
System.out.println("读 * 读操作");
t1.start();
t2.start();
// System.out.println("读 * 写操作");
// t1.start();
// t3.start();
// System.out.println("写 * 写操作");
// t3.start();
// t4.start();
}
通过打印的情况,可以看出,只有读*读的时候,两个线程是同时执行,而其他情况都是有先后顺序的。
2 ThreadLocal
ThreadLocal为变量在每个线程中都创建了一个跟特定线程有关的变量的副本,这样就可以使每个线程在运行中只可以使用与自己线程有关的特定的副本变量,而不会影响其它线程的副本变量,保证了线程间变量的隔离性。
ThreadLocal 和 Synchronized的区别在于,ThreadLocal是将数据在多线程中隔离开,而Synchronized是将数据在多线程中实现共享。
学习博客:
跟我学Java多线程——ThreadLocal
3 Concurrent.utils 常用方法
3.1 CountDownLacth
CountDownLacth:用于监听初始化操作,等初始化执行完毕后,通知主线程继续工作。
import java.util.concurrent.CountDownLatch;
public class UseCountDownLatch {
public static void main(String[] args) {
// CountDownLatch 中的值,可以尝试用1,2,3执行结果,会发现如果填入的值是1,t2线程执行完毕后,t1就继续执行了
// 如果填入的值是3,t2和t3都执行完毕了,t1线程还未执行。
final CountDownLatch countDown = new CountDownLatch(2);
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("进入线程t1" + "等待其他线程处理完成...");
countDown.await();
System.out.println("t1线程继续执行...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("t2线程进行初始化操作...");
Thread.sleep(3000);
System.out.println("t2线程初始化完毕,通知t1线程继续...");
countDown.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("t3线程进行初始化操作...");
Thread.sleep(4000);
System.out.println("t3线程初始化完毕,通知t1线程继续...");
countDown.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
t3.start();
}
}
现在只是初识 CountDownLacth 方法(入门),在之后学习zookeeper的时候会详细简介。
3.2 CyclicBarrier
CyclicBarrier:当每个线程都准备好了,每个线程再执行。import java.io.IOException;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class UseCyclicBarrier {
static class Runner implements Runnable {
private CyclicBarrier barrier;
private String name;
public Runner(CyclicBarrier barrier, String name) {
this.barrier = barrier;
this.name = name;
}
@Override
public void run() {
try {
Thread.sleep(1000 * (new Random()).nextInt(5));
System.out.println(name + " 准备OK.");
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(name + " 开始执行!!");
}
}
public static void main(String[] args) throws IOException, InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(3); // 3
ExecutorService executor = Executors.newFixedThreadPool(3); // 如果线程此不足3个,只要有一个线程没有准备好,其他线程都不会执行
executor.submit(new Thread(new Runner(barrier, "t1")));
executor.submit(new Thread(new Runner(barrier, "t2")));
executor.submit(new Thread(new Runner(barrier, "t3")));
executor.shutdown();
}
}
3.3 Future
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
public class UseFuture implements Callable<String>{
private String para;
public UseFuture(String para){
this.para = para;
}
@Override
public String call() throws Exception {
//模拟执行耗时 .这里是真实的业务逻辑,其执行可能很慢
Thread.sleep(5000);
String result = this.para + "处理完成";
return result;
}
//主控制函数
public static void main(String[] args) throws Exception {
//构造FutureTask,并且传入需要真正进行业务逻辑处理的类,该类一定是实现了Callable接口的类
FutureTask<String> futureTask = new FutureTask<String>(new UseFuture("query"));
FutureTask<String> futureTask2 = new FutureTask<String>(new UseFuture("update"));
//创建一个固定线程的线程池且线程数为1,
ExecutorService executor = Executors.newFixedThreadPool(2);
//这里提交任务future,则开启线程执行RealData的call()方法执行
//submit和execute的区别: 第一点是submit可以传入实现Callable接口的实例对象, 第二点是submit方法有返回值
Future future1 = executor.submit(futureTask); //单独启动一个线程去执行的,异步
Future future2 = executor.submit(futureTask2);
System.out.println("Future 异步请求完毕");
//这里可以做额外的数据操作,也就是主程序执行其他业务逻辑
System.out.println("处理实际的业务逻辑...");
Thread.sleep(5000);
//调用获取数据方法,如果call()方法没有执行完成,则依然会进行等待
System.out.println("数据:" + futureTask.get()); // get() 方法获取实际结果
System.out.println("数据:" + futureTask2.get());
executor.shutdown();
}
}
以上便是线程锁,并附带了其他相关的知识,方便自己查阅,同时也希望能帮助到读者。下一章讲解有关并发框架Disruptor知识。更多干货尽在
ITDragon博客