线程(一):交替打印
文章目录
1.线程同步的两个重要方法
方法名 | 方法说明 |
---|---|
notify() | 唤醒其它一个线程,但不释放锁 |
notifyAll() | 唤醒其它所有线程,但不释放锁 |
wait() | 休眠当前线程,释放锁 |
唤醒方法(notify()/notifyAll()
)只是让其它”休眠“的线程被唤醒,让线程有争夺锁的机会,本身并没有释放锁,只有执行到wait()
时,才会释放线程,同时进入”休眠“(释放线程并进入休眠的原因是,保证释放锁以后不参与下一次争夺锁,让其它线程来抢,达到线程同步的目的)
2.线程通信
2.1 线程通信模板三原则
- 同步代码块外的循环控制次数;同步代码块内的循环控制行为(休眠/唤醒)
- 同步代码块内,循环内写休眠条件,循环下写线程任务与唤醒条件
- 每次唤醒都需要判断 次数 ,因为可能在当前线程休眠时已经满足打印次数了,唤醒以后无需再去争夺锁,而是结束线程
2.2 线程通信的两个案例
【例2.2.1】两个线程交替打印1~20
一个线程仅打印奇数,另一个线程仅打印偶数,两线程交替打印
public class PrintThread {
// 线程同步的锁
public static final Object LOCK = new Object();
private static int count = 1;
public static int border = 20;
public static void main(String[] args) throws InterruptedException {
// 打印奇数的线程
new Thread(() -> {
// 外层循环写次数
while (count <= border) {
synchronized (LOCK) {
// 内层循环写控制条件,不符合的就休眠,该线程打印奇数,因此当count为偶数时休眠
while (count % 2 == 0) {
try {
LOCK.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 唤醒以后做校验,不通过跳出内层循环
if (count > border) break;
}
// 跳出内层循环后跳出外层循环
if (count > border) break;
// 符合条件,则正常输出
System.out.println(Thread.currentThread().getName() + " 正在打印 " + count++);
LOCK.notifyAll();
}
}
}, "奇数线程").start();
// 打印偶数的线程
new Thread(() -> {
// 外层循环写次数
while (count <= border) {
synchronized (LOCK) {
// 内层循环写控制条件,不符合的就休眠,该线程打印偶数,因此当count为奇数时休眠
while (count % 2 == 1) {
try {
LOCK.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 唤醒以后做校验,不符合条件则跳出循环
if (count > border) break;
}
// 跳出内层循环后跳出外层循环
if (count > border) break;
// 符合条件,正常输出
System.out.println(Thread.currentThread().getName() + " 正在打印 " + count++);
LOCK.notifyAll();
}
}
}, "偶数线程").start();
}
}
【例2.2.2】三个线程轮流打印1~20
三个线程a/b/c,轮流打印从1~20,打印顺序如下:a打印1,b打印2,c打印3,a打印4,b打印5,···,如此循环,直到打印到20
public class PrintThread {
// 线程同步的锁
public static final Object LOCK = new Object();
private static int count = 1;
// 线程标记,初始情况下,让a线程第一个打印
public static String flag = "a";
public static int border = 20;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
// 外层循环写次数
while (count <= border) {
synchronized (LOCK) {
// 内层循环写控制条件,不符合的就休眠
while (!Thread.currentThread().getName().equals(flag)) {
try {
LOCK.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 唤醒以后做校验,不符合条件则跳出循环
if (count > border) break;
}
// 跳出内层循环后跳出外层循环
if (count > border) break;
// 符合条件,则正常输出
System.out.println(Thread.currentThread().getName() + " 正在打印 " + count++);
flag = "b";
LOCK.notifyAll();
}
}
System.out.println(Thread.currentThread().getName() + " 线程任务运行完成 ");
}, "a").start();
new Thread(() -> {
while (count <= border) {
synchronized (LOCK) {
while (!Thread.currentThread().getName().equals(flag)) {
try {
LOCK.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (count > border) break;
}
if (count > border) break;
System.out.println(Thread.currentThread().getName() + " 正在打印 " + count++);
flag = "c";
LOCK.notifyAll();
}
}
System.out.println(Thread.currentThread().getName() + " 线程任务运行完成 ");
}, "b").start();
new Thread(() -> {
while (count <= border) {
synchronized (LOCK) {
while (!Thread.currentThread().getName().equals(flag)) {
try {
LOCK.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (count > border) break;
}
if (count > border) break;
System.out.println(Thread.currentThread().getName() + " 正在打印 " + count++);
flag = "a";
LOCK.notifyAll();
}
}
System.out.println(Thread.currentThread().getName() + " 线程任务运行完成 ");
}, "c").start();
}
}
3. 可重入锁ReentrantLock实现三线程轮流打印
上面的wait()/notify()/notifyAll()
方法虽然可以实现线程的同步,但是无法做到“点对点”通信。当执行"notify()
"方法时,会随机唤醒等待池中的一个线程,不能指定唤醒某一个特定的线程,所以才需要引入一个“flag”用于标记。Condition
接口解决了这个问题,它可以“点对点”指定,唤醒指定的线程
方法名 | 方法说明 |
---|---|
signal() | 唤醒指定线程,但不释放锁 |
await() | 休眠当前线程,释放锁 |
public class PrintThread {
// 线程同步的锁
private static int count = 1;
// 线程标记,初始情况下,让a线程第一个打印
public static String flag = "a";
public static int border = 20;
public static void main(String[] args) throws InterruptedException {
// 创建可重入锁
ReentrantLock lock = new ReentrantLock();
// 分别创建线程a、b、c的condition
Condition threadA = lock.newCondition();
Condition threadB = lock.newCondition();
Condition threadC = lock.newCondition();
new Thread(() -> {
// 外层循环控制次数
while (count <= border) {
lock.lock();
try {
// 内层循环控制行为,不符合条件则进入等待
while (!Thread.currentThread().getName().equals(flag)) {
try {
threadA.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (count > border) break;
}
if (count > border) break;
// 符合条件,执行代码
System.out.println(Thread.currentThread().getName() + " 正在打印 " + count++);
flag = "b";
// 好处是不会唤醒所有线程,而是只唤醒特定的那个线程
threadB.signalAll();
} finally {
lock.unlock();
}
}
System.out.println(Thread.currentThread().getName() + " 线程任务运行完成 ");
}, "a").start();
new Thread(() -> {
// 外层循环控制次数
while (count <= border) {
lock.lock();
try {
// 内层循环控制行为,不符合条件则进入等待
while (!Thread.currentThread().getName().equals(flag)) {
try {
threadB.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (count > border) break;
}
if (count > border) break;
// 符合条件,执行代码
System.out.println(Thread.currentThread().getName() + " 正在打印 " + count++);
flag = "c";
// 好处是不会唤醒所有线程,而是只唤醒特定的那个线程
threadC.signal();
} finally {
lock.unlock();
}
}
System.out.println(Thread.currentThread().getName() + " 线程任务运行完成 ");
}, "b").start();
new Thread(() -> {
// 外层循环控制次数
while (count <= border) {
lock.lock();
try {
// 内层循环控制行为,不符合条件则进入等待
while (!Thread.currentThread().getName().equals(flag)) {
try {
threadC.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (count > border) break;
}
if (count > border) break;
// 符合条件,执行代码
System.out.println(Thread.currentThread().getName() + " 正在打印 " + count++);
flag = "a";
// 好处是不会唤醒所有线程,而是只唤醒特定的那个线程
threadA.signalAll();
} finally {
lock.unlock();
}
}
System.out.println(Thread.currentThread().getName() + " 线程任务运行完成 ");
}, "c").start();
}
}