场景:两个线程交替打印字母和数字,效果如下:12ab34cd56ef......
synchronized
使用 synchronized 同步锁和 Object#wait() 和 Object#notifyAll(),在各个线程传入不同的 type 做区分,线程类型和当前打印类型不一致时则 wait。
java
package com.example.demo;
public class Main {
private static volatile int flag = 0; // 0 表示数字线程,1 表示字母线程
private static int i = 1; // 数字计数器
private static char c = 'a'; // 字母计数器
public static void main(String[] args) {
new Thread(new Print(0), "数字").start();
new Thread(new Print(1), "字母").start();
}
static class Print implements Runnable {
int type;
public Print(int type) {
this.type = type;
}
@Override
public void run() {
synchronized (Main.class) {
while (true) {
if (flag != type) {
try {
Main.class.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
if (flag == 0) {
System.out.println(Thread.currentThread().getName() + ": " + i++);
System.out.println(Thread.currentThread().getName() + ": " + i++);
flag = 1;
} else if (flag == 1) {
System.out.println(Thread.currentThread().getName() + ": " + c++);
System.out.println(Thread.currentThread().getName() + ": " + c++);
flag = 0;
}
Main.class.notifyAll(); // 每次操作后唤醒所有线程
if (i == 29 || c == 'z' + 1) {
break;
}
}
}
}
}
}
Lock
2.1 Lock 初级用法
注意:当线程调用 lock.lock()
时,如果锁已被其他线程持有,当前线程会进入阻塞状态(BLOCKED),不会消耗CPU资源。
java
package com.example.demo;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
private static Lock lock = new ReentrantLock();
private static volatile int flag = 0;
private static int i = 1; // 数字计数器
private static char c = 'a'; // 字母计数器
public static void main(String[] args) {
new Thread(new Print(0), "数字").start();
new Thread(new Print(1), "字母").start();
}
static class Print implements Runnable {
int type;
public Print(int type) {
this.type = type;
}
@Override
public void run() {
while (true) {
lock.lock();
try {
if (flag == type) {
if (flag == 0) {
System.out.println(Thread.currentThread().getName() + ": " + i++);
System.out.println(Thread.currentThread().getName() + ": " + i++);
flag = 1;
} else if (flag == 1) {
System.out.println(Thread.currentThread().getName() + ": " + c++);
System.out.println(Thread.currentThread().getName() + ": " + c++);
flag = 0;
}
if (i == 29 || c == 'z' + 1) {
break;
}
}
} finally {
lock.unlock();
}
}
}
}
}
问题:
- 不必要的锁竞争 :线程每次循环都会尝试获取锁,即使当前
flag
不符合自己的类型; - 公平性问题 :
ReentrantLock
默认是非公平锁,可能导致线程饥饿(线程长期抢不到锁);
2.2 Lock 进阶用法
通过 Condition 类实现了类似方法一中线程等待的效果,避免了锁竞争和公平性问题。
java
package com.example.demo;
import java.util.concurrent.locks.*;
public class Main {
private static final Lock lock = new ReentrantLock();
private static final Condition condition = lock.newCondition();
private static int flag = 0;
private static int i = 1;
private static char c = 'a';
public static void main(String[] args) {
new Thread(() -> print(0, "数字"), "数字").start();
new Thread(() -> print(1, "字母"), "字母").start();
}
private static void print(int type, String name) {
while (true) {
lock.lock();
try {
// 条件不满足时主动等待
while (flag != type) {
condition.await(); // 释放锁并等待,避免竞争
}
if (type == 0) {
System.out.println(name + ": " + i++);
System.out.println(name + ": " + i++);
flag = 1;
} else {
System.out.println(name + ": " + c++);
System.out.println(name + ": " + c++);
flag = 0;
}
if (i >= 29 || c > 'z') break;
// 唤醒其他线程
condition.signalAll();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
}
}
此外还可以借助 Condition 类实现精准唤醒
java
import java.util.concurrent.locks.*;
public class Main {
private static final Lock lock = new ReentrantLock();
private static final Condition c1 = lock.newCondition();
private static final Condition c2 = lock.newCondition();
private static int flag = 0;
private static int i = 1;
private static char c = 'a';
public static void main(String[] args) {
new Thread(() -> print(0, "数字", c1, c2), "数字").start();
new Thread(() -> print(1, "字母", c2, c1), "字母").start();
}
private static void print(int type, String name, Condition cur, Condition next) {
while (true) {
lock.lock();
try {
// 条件不满足时主动等待
while (flag != type) {
cur.await(); // 释放锁并等待,避免竞争
}
if (type == 0) {
System.out.println(name + ": " + i++);
System.out.println(name + ": " + i++);
flag = 1;
} else {
System.out.println(name + ": " + c++);
System.out.println(name + ": " + c++);
flag = 0;
}
if (i >= 29 || c > 'z') break;
// 精准唤醒下一个线程
next.signal();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
}
}
Semaphore
通过信号量控制
java
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.*;
public class Main {
private static final Lock lock = new ReentrantLock();
private static Semaphore s1 = new Semaphore(1);
private static Semaphore s2 = new Semaphore(0);
private static int flag = 0;
private static int i = 1;
private static char c = 'a';
public static void main(String[] args) {
new Thread(() -> print(0, "数字", s1, s2), "数字").start();
new Thread(() -> print(1, "字母", s2, s1), "字母").start();
}
private static void print(int type, String name, Semaphore cur, Semaphore next) {
try {
while (true) {
// 如果当前线程对应的信号量是0,则阻塞
cur.acquire();
if (type == 0) {
System.out.println(name + ": " + i++);
System.out.println(name + ": " + i++);
flag = 1;
} else {
System.out.println(name + ": " + c++);
System.out.println(name + ": " + c++);
flag = 0;
}
// 释放下一个线程的信号量
next.release();
if (i >= 29 || c > 'z') {
break;
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}