文章目录
一、synchronized的缺陷
1.1 多把锁
多把锁指多个不相关的共享资源,各锁各的,将锁细分可以增强并发度。但是多把锁也有一些问题。先看下正常情况
代码示例
public class ManyLock {
static WorkingRoom workingRoom=new WorkingRoom();
static SleepingRoom sleepingRoom =new SleepingRoom();
public static void main(String[] args) {
new Thread(()->{
synchronized (workingRoom){
workingRoom.work();
}
},"t1").start();
new Thread(()->{
synchronized (sleepingRoom){
sleepingRoom.sleep();
}
},"t2").start();
}
}
class WorkingRoom{
void work(){
System.out.println(Thread.currentThread().getName()+"工作");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class SleepingRoom{
void sleep(){
System.out.println(Thread.currentThread().getName()+"睡觉");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
1.2 死锁
多把锁的时候,如果一个线程要同时获取多把锁就容易发生死锁。看示例代码,t1线程要先睡觉再工作,t2线程要先工作再睡觉就发生了死锁
public class DeadLock {
static WorkingRoom workingRoom=new WorkingRoom();
static SleepingRoom sleepingRoom =new SleepingRoom();
public static void main(String[] args) {
new Thread(()->{
synchronized (sleepingRoom){
sleepingRoom.sleep();
synchronized (workingRoom){
workingRoom.work();
}
}
},"t1").start();
new Thread(()->{
synchronized (workingRoom){
workingRoom.work();
synchronized (sleepingRoom){
sleepingRoom.sleep();
}
}
},"t2").start();
}
}
执行的结果,程序不能正常退出
使用VisualVM查看线程Dump,发现死锁
1.2.1 哲学家就餐问题
哲学家必须同时拿到左右手的筷子才能就餐,这就可能发生死锁。看下代码
public class PhilosopherEat {
public static void main(String[] args) {
Chopsticks c1 = new Chopsticks();
Chopsticks c2 = new Chopsticks();
Chopsticks c3 = new Chopsticks();
Chopsticks c4 = new Chopsticks();
Chopsticks c5 = new Chopsticks();
new Philosopher("柏拉图",c1,c2).start();
new Philosopher("亚里士多德",c2,c3).start();
new Philosopher("阿基米德",c3,c4).start();
new Philosopher("哥白尼",c4,c5).start();
new Philosopher("爱因斯坦",c5,c1).start();
}
}
class Chopsticks {
}
class Philosopher extends Thread {
private String name;
private Chopsticks left;
private Chopsticks right;
public Philosopher(String name, Chopsticks left, Chopsticks right) {
this.name = name;
this.left = left;
this.right = right;
}
@Override
public void run() {
while (true) {
synchronized (left) {
synchronized (right) {
System.out.println(System.currentTimeMillis() + name + "就餐");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
运行后发生死锁
解决方法在后面学习ReentrantLock时讲
1.3 活锁
两个线程相互改变对方的结束条件,导致都不能结束,出现活锁。代码示例
public class LiveLock {
static volatile int count = 10;
public static void main(String[] args) {
new Thread(() -> {
while (count < 20) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
System.out.println(Thread.currentThread().getName()+count);
}
},"t1").start();
new Thread(() -> {
while (count > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+count);
}
},"t2").start();
}
}
二、ReentrantLock
相对于synchronized具备如下特点
- 可被线程的interrupt中断,放弃竞争锁
- 可以设置超时时间
- 可以设置公平锁
- 支持多个共享资源
与synchronized一样都支持重入
基本语法
reentrantlock.lock();
try{
//业务逻辑
}finally{
reentrantlock.unlock();
}
2.1 可中断
可中断意思是,在获取lock失败之后,线程进入lock的阻塞队列,再调用了线程的interrupt方法之后,线程将被中断,不再阻塞并抛出异常InterruptedException。
示例代码:
public class ReentrantLock1 {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
//interrupt打断加锁
lock.lockInterruptibly();
} catch (InterruptedException e) {
System.out.println("放弃竞争锁");
e.printStackTrace();
return;
}
try {
System.out.println("获取到锁");
} finally {
lock.unlock();
}
}, "t1");
//主线程先拿到锁
lock.lock();
t1.start();
Thread.sleep(1000);
System.out.println("打断t1线程");
t1.interrupt();
}
}
2.2 锁超时
使用ReentrantLock的tryLock(n,TimeUnit)方法,可以设定等待锁的时间,等待期间也可以被interrupt方法打断。
示例代码
public class ReentrantLock2 {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
if (!lock.tryLock(2, TimeUnit.SECONDS)) {
System.out.println("时间到,未获取到锁");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("被打断,未获取到锁");
return;
}
System.out.println(Thread.currentThread().getName()+"获取到锁");
}, "t1");
System.out.println("主线程获取锁");
lock.lock();
t1.start();
Thread.sleep(1000);
// System.out.println("打断t1");
// t1.interrupt();
System.out.println("主线程释放锁");
lock.unlock();
}
}
2.2.1 锁超时解决哲学家就餐问题
锁超时解决哲学家就餐问题的关键就是使用tryLock方法,拿不到锁就退出等待。
代码示例
public class ReentrantLock3 {
public static void main(String[] args) {
Chopsticks c1 = new Chopsticks();
Chopsticks c2 = new Chopsticks();
Chopsticks c3 = new Chopsticks();
Chopsticks c4 = new Chopsticks();
Chopsticks c5 = new Chopsticks();
new Philosopher("柏拉图", c1, c2).start();
new Philosopher("亚里士多德", c2, c3).start();
new Philosopher("阿基米德", c3, c4).start();
new Philosopher("哥白尼", c4, c5).start();
new Philosopher("爱因斯坦", c5, c1).start();
}
}
class Chopsticks extends ReentrantLock {
Chopsticks() {
super(true);
}
}
class Philosopher extends Thread {
private String name;
private Chopsticks left;
private Chopsticks right;
Philosopher(String name, Chopsticks left, Chopsticks right) {
this.name = name;
this.left = left;
this.right = right;
}
@Override
public void run() {
while (true) {
//尝试左手筷子
if (left.tryLock()) {
try {
//尝试右手筷子
if(right.tryLock()){
try {
System.out.println(System.currentTimeMillis() + name + "就餐");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
right.unlock();
}
}
}finally {
left.unlock();
}
}
}
}
}
2.3 await/signal
await/signal方法与wait/notifyl类似,他俩是ReentrantLock用于线程通信,控制执行顺序的方法。使用流程
- await调用前需要获取锁
- await执行后会释放锁,进入conditionObject等待
- await被唤醒(signal)、打断(interrupt)、超时,重新竞争锁
- 竞争成功,获得锁之后从await方法继续执行
代码示例:
public class ReentrantLock4 {
static ReentrantLock lock = new ReentrantLock();
static boolean wakeUp = false;
static boolean beFull = false;
public static void main(String[] args) throws InterruptedException {
Condition wakeUpRoom = lock.newCondition();
Condition beFullRoom = lock.newCondition();
new Thread(() -> {
lock.lock();
try {
//条件不成立,进入等待
while (!wakeUp) {
wakeUpRoom.await();
System.out.println("没睡醒,继续等待");
}
//结束等待
System.out.println("睡醒了,开始工作");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}, "t1").start();
new Thread(() -> {
lock.lock();
try {
while (!beFull) {
beFullRoom.await();
System.out.println("没吃饱,继续等待");
}
//结束等待
System.out.println("吃饱了,开始工作");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}, "t2").start();
Thread.sleep(50);
lock.lock();
beFull = true;
beFullRoom.signal();
lock.unlock();
lock.lock();
wakeUpRoom.signal();
wakeUp = true;
lock.unlock();
}
}
三、案例
3.1 顺序执行
按照2–>1输出
3.1.1 wait、notify实现
public class sequenceExecute {
static Object object = new Object();
static boolean ist2exe=false;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (object){
while(!ist2exe){
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("1");
}
},"t1");
Thread t2 = new Thread(() -> {
synchronized (object){
System.out.println("2");
ist2exe=true;
object.notifyAll();
}
},"t2");
t1.start();
t2.start();
}
}
3.1.2 park、unpark实现
public class sequenceExecute1 {
static boolean ist2exe=false;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while(!ist2exe){
LockSupport.park();
}
System.out.println("1");
},"t1");
Thread t2 = new Thread(() -> {
System.out.println("2");
ist2exe=true;
LockSupport.unpark(t1);
},"t2");
t1.start();
t2.start();
}
}
3.2 交替执行
三个线程t1/t2/t3,t1输出五次a,t2输出5次b,t3输出5次c;要求最终输出顺序为abcabcabcabcabc。
3.2.1 wait、notify实现
public class AlternatelyExecute {
public static void main(String[] args) {
ExecuteStatus executeStatus = new ExecuteStatus();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
synchronized (executeStatus) {
while (executeStatus.getFlag() != 1) {
try {
executeStatus.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("a");
executeStatus.setFlag(2);
executeStatus.notifyAll();
}
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
synchronized (executeStatus) {
while (executeStatus.getFlag() != 2) {
try {
executeStatus.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("b");
executeStatus.setFlag(3);
executeStatus.notifyAll();
}
}
}, "t2");
Thread t3 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
synchronized (executeStatus) {
while (executeStatus.getFlag() != 3) {
try {
executeStatus.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("c");
executeStatus.setFlag(1);
executeStatus.notifyAll();
}
}
}, "t3");
t3.start();
t2.start();
t1.start();
}
}
class ExecuteStatus {
//1--t1,2--t2,3--t3
private int flag = 1;
int getFlag() {
return flag;
}
void setFlag(int flag) {
this.flag = flag;
}
}
3.2.2 await、signal实现
public class AlternatelyExecute1 {
public static void main(String[] args) {
AwaitSignal awaitSignal = new AwaitSignal(5);
Condition a=awaitSignal.newCondition();
Condition b=awaitSignal.newCondition();
Condition c=awaitSignal.newCondition();
new Thread(()->{
awaitSignal.print("a",a,b);
},"t1").start();
new Thread(()->{
awaitSignal.print("b",b,c);
},"t2").start();
new Thread(()->{
awaitSignal.print("c",c,a);
},"t2").start();
awaitSignal.lock();
try{
System.out.println("开始...");
a.signal();
}finally {
awaitSignal.unlock();
}
}
}
class AwaitSignal extends ReentrantLock {
private int cycleCount;
AwaitSignal(int cycleCount){
this.cycleCount=cycleCount;
}
void print(String str, Condition current,Condition next){
for (int i = 0; i < cycleCount; i++) {
lock();
try {
try {
//当前线程进入等待
current.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//被唤醒输出字符串
System.out.print(str);
//唤醒下一线程,当前线程进入循环
next.signal();
}finally {
unlock();
}
}
}
}
3.2.2 park、unpark实现
public class AlternatelyExecute2 {
static Thread t1, t2, t3;
public static void main(String[] args) {
ParkUnpark parkUnpark = new ParkUnpark(5);
t1 = new Thread(() -> parkUnpark.print("a", t2));
t2 = new Thread(() -> parkUnpark.print("b", t3));
t3 = new Thread(() -> parkUnpark.print("c", t1));
t1.start();
t2.start();
t3.start();
LockSupport.unpark(t1);
}
}
class ParkUnpark {
private int cycleCount;
ParkUnpark(int cycleCount) {
this.cycleCount = cycleCount;
}
void print(String str, Thread next) {
for (int i = 0; i < cycleCount; i++) {
LockSupport.park();
System.out.print(str);
LockSupport.unpark(next);
}
}
}