线程同步的一些思考
A、B轮流
这种没有顺序依赖的打印场景,只需要将notify唤醒提前,就不会有死锁问题
public class PrintUtile {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(new PrintA());
Thread threadB = new Thread(new PrintB());
// extractedA(threadA, threadB);
extractedB(threadA, threadB);
}
private static void extractedA(Thread threadA, Thread threadB) throws InterruptedException {
threadA.start();
Thread.sleep(1000);
threadB.start();
}
private static void extractedB(Thread threadA, Thread threadB) throws InterruptedException {
threadB.start();
Thread.sleep(1000);
threadA.start();
}
static class PrintA implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (lock) {
System.out.println("A");
lock.notify();
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
static class PrintB implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (lock) {
System.out.println("B");
lock.notify();
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
先A后B
正确写法:
class PrintUtile {
private static final Object lock = new Object();
private static boolean isPrintA = false;
public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(new PrintA());
Thread threadB = new Thread(new PrintB());
// extractedA(threadA, threadB);
extractedB(threadA, threadB);
}
private static void extractedA(Thread threadA, Thread threadB) throws InterruptedException {
threadA.start();
Thread.sleep(1000);
threadB.start();
}
private static void extractedB(Thread threadA, Thread threadB) throws InterruptedException {
threadB.start();
Thread.sleep(1000);
threadA.start();
}
static class PrintA implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (lock) {
if (!isPrintA) {
System.out.println("A");
isPrintA = true;
lock.notify();
}
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
static class PrintB implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (lock) {
if (!isPrintA) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("B");
isPrintA = false;
lock.notify();
}
}
}
}
}
总结:
说一下这种场景的特点,就是有顺序,那么意味着需要标识。意味着其它n个线程执行只有其中一个满足条件的线程能够往下执行然后通知,这个很重要,因为线程抢夺资源我们通常不会进行控制,也就是意味着线程刚开始的执行顺序我们是无法控制的。那么也就意味着在其他n-1条线程先执行的情况下,其他线程肯定都会阻塞,也就是说会wait()。那么这里就有一个编码干货,就是目标第一个执行的线程必须先通知然后再阻塞,也就是先notify()再wait()。
误区:之前写的时候会出现这么一种错误,造成了死锁。
写的思路是这样的
- 线程A先进行打印,然后wait,在notify
- 线程B在打印,然后notify
这种思路在线程A先拿到CPU资源的情况下没有问题,但是如果是线程B先拿到CPU资源的话就会导致线程B先wait阻塞了,然后线程A执行打印了,但是先执行了wait,导致A、B同时阻塞,没有其它线程进行通知,造成死锁的情况。
思考,就是A肯定是第一个要执行关键代码逻辑的线程,而其它线程可能都会先行阻塞,所以既然A是第一个会执行关键逻辑代码的线程,那么执行完毕后A虽然也需要阻塞waite,但是在阻塞之前要先通知notify及时的唤醒其它线程才行。
static class PrintA implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (lock) {
if (!isPrintA) {
System.out.println("A");
isPrintA = true;
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lock.notify();
}
}
}
}
static class PrintB implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (lock) {
if (!isPrintA) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("B");
isPrintA = false;
lock.notify();
}
}
}
}