题目描述:
三个线程分别打印A,B,C,要求这三个线程一起运行,打印n次,输出形如“ABCABCABC....”的字符串。
在看之前不妨先敲代码试试,看似很简单的问题可能代码写起来没那么顺利。
使用Semaphore:
public class PrintABC {
private int times;
private Semaphore semaphoreA =new Semaphore(1);
private Semaphore semaphoreB =new Semaphore(0);
private Semaphore semaphoreC =new Semaphore(0);
public PrintABC(int times) {
this.times=times;
}
public static void main(String[] args) {
PrintABC PrintABC =new PrintABC(10);
new Thread(PrintABC::printA).start();
new Thread(PrintABC::printB).start();
new Thread(PrintABC::printC).start();
}
public void printA() {
print("A",semaphoreA,semaphoreB);
}
public void printB() {
print("B",semaphoreB,semaphoreC);
}
public void printC() {
print("C",semaphoreC,semaphoreA);
}
public void print(String name,Semaphore current,Semaphore next) {
for(int i=0;i<times;i++) {
try {
current.acquire();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(name);
next.release();
}
}
}
逻辑描述:
注意哦,semaphoreB和semaphoreC的值都为0,而semaphoreA的值为1。
也就是说semaphoreB.acquire();前没有使用过semaphoreB.release()的话,调用的这个线程是会被阻塞的。那么这个时候,只有我们的线程A(假设调用printA的线程为线程A)能顺利运行了(因为只有线程A的emaphoreA为1)
线程A启动,调用semaphoreA.acquire(),继续运行,打印A,调用semaphoreB.release(),此时线程B能动了……
如果A又获得时间片,进入了下一个for循环了呢?调用了semaphoreA.acquire();此时也会是阻塞状态,因为上一次循环acquire()的还没有被release()掉。
C也是一样的道理去分析,说多了更容易乱,建议大家在print方法上打个断点调试一下就能轻松理解。
使用Lock
public class PrintABC2 {
private int times;
private int state;
private Lock lock = new ReentrantLock();
public PrintABC2(int times) {
this.times = times;
}
public void printA() {
print("A", 0);
}
public void printB() {
print("B", 1);
}
public void printC() {
print("C", 2);
}
public void print(String name, int stateNow) {
for (int i = 0; i < times;) {
lock.lock();
if (stateNow == state % 3) {
state++;
i++;
System.out.println(name);
}
lock.unlock();
}
}
public static void main(String[] args) {
PrintABC2 printABC2 = new PrintABC2(10);
new Thread(printABC2::printA).start();
new Thread(printABC2::printB).start();
new Thread(printABC2::printC).start();
}
}
逻辑描述:
在这个代码中,三个线程会不断的获取lock,判断stateNow == state % 3,而只有A线程进入if内的代码段后,B线程才有机会执行,否则只是获取锁,判断为false,把锁让出来而已,没有做实际的操作。C也是一样
使用Condition
public class LockCoditionTest {
final Business business = new Business();
public static void main(String[] args) {
new LockCoditionTest().init();
}
private void init() {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 10; i++) {
business.sub2("B");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 10; i++) {
business.sub3("C");
}
}
}).start();
for (int i = 0; i <= 10; i++) {
business.main("A");
}
}
class Business {
private int flag = 1;
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
public void sub2(String s) {
lock.lock();
try {
if (flag != 2) {
condition2.await();
}
System.out.println("B线程输出" + s);
flag = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void sub3(String s) {
lock.lock();
try {
if (flag != 3) {
condition3.await();
}
System.out.println("C线程输出" + s);
flag = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void main(String s) {
lock.lock();
try {
if (flag != 1) {
condition1.await();
}
System.out.println("main线程输出" + s);
flag = 2;
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
逻辑描述:
每个线程都调用了Business类里的方法,别以为我的main()没有创建线程哦,他可是在主线程下运行的,这次我们通过了三个condition和一个flag来实现线程间的通信。
首先flag为1,如果此时B或者C线程拿到了,就会进入if判断,把自己给await啦,然后咱们的A线程拿到,输出A,把flag改成2,再把B线程唤醒,这个时候flag已经是 2啦,b线程if判断为false,就不await自个了,输出B,在把flag改成3,把线程C唤醒。这个较于上面的lock来说,用了condition来更细粒度的操作线程,使得没轮到他们工作的时候就进入阻塞状态,解决了上面lock方法中几个线程间不必要的上下文切换(没轮到你就给我乖乖休息)