序言
很想把一个问题弄清楚,特别是以前一直模模糊糊的并发编程,今天在华为OJ上碰到一道题,“顺序打印ABC的两种方法开始写起”,就以这道题开篇,希望日后有时间把并发编程的基本问题弄清楚。
问题
启动三个线程,一个线程打印A,一个打印B,一个打印C,按顺序打印ABC....。如输入3,输出就是“ABCABCABC”
程序
线程的调度是由系统操作的,要想多个线程按照要求顺序打印,就必须做好线程间的同步。
思路:四个线程循环打印,但是一个线程打印一个字母释放锁后无法确定获得锁的是哪一个线程,这就需要用一个标志判断是否轮到自己打印,是的话就打印,然后让下一个线程线程打印,否则等待。
主要有两种方法:
1、使用synchronized关键字,以及对象的wait、notify和notifyAll方法
2、使用Lock和Condition
这里先给出第二种的程序,打印ABCD。
1 import java.util.Scanner; 2 import java.util.concurrent.locks.Condition; 3 import java.util.concurrent.locks.Lock; 4 import java.util.concurrent.locks.ReentrantLock; 5 6 public class Main { 7 public static void main(String[] args) throws Exception 8 { 9 Scanner scanner=new Scanner(System.in); 10 int n=scanner.nextInt(); 11 new Thread(new Printer('A', n)).start(); 12 new Thread(new Printer('B', n)).start(); 13 new Thread(new Printer('C', n)).start(); 14 new Thread(new Printer('D', n)).start(); 15 scanner.close(); 16 } 17 18 static class Printer implements Runnable 19 { 20 //所有的线程操作都需要这把锁,所以必须是静态的,否则就不是同一对象了,锁的作用就没了。 21 //可以把这个锁理解成synchronize的里面的对象锁 22 public static Lock lock=new ReentrantLock(); (1)锁 23 //Condition依赖于锁,并且可以获得多个Condition,能够精准地控制线程 24 public static Condition conditionA=lock.newCondition(); (2)精准唤醒或者线程等待 25 public static Condition conditionB=lock.newCondition(); 26 public static Condition conditionC=lock.newCondition(); 27 public static Condition conditionD=lock.newCondition(); 28 public static char last='D'; (3)判断本线程是否该await 29 private char c; 30 private int n; 31 public Printer(char c,int n) 32 { 33 this.c=c; 34 this.n=n; 35 } 36 @Override 37 public void run() { 38 // TODO Auto-generated method stub 39 switch (c) { 40 case 'A': 41 printA(); 42 break; 43 case 'B': 44 printB(); 45 break; 46 case 'C': 47 printC(); 48 break; 49 case 'D': 50 printD(); 51 break; 52 default: 53 break; 54 } 55 } 56 57 private void printA() 58 { 59 for(int i=0;i<n;i++) 60 { 61 lock.lock(); 62 try { 63 if(last!='D') 64 { 65 conditionA.await(); 66 } 67 System.out.print(c);//假如当前是D,那么下一个肯定就是A即自身,可以打印了 68 last=c; 69 conditionB.signal(); 70 } catch (InterruptedException e) { 71 // TODO Auto-generated catch block 72 e.printStackTrace(); 73 } 74 finally 75 { 76 lock.unlock(); 77 } 78 } 79 } 80 81 private void printB() 82 { 83 for(int i=0;i<n;i++) 84 { 85 lock.lock(); 86 try { 87 if(last!='A') 88 { 89 conditionB.await(); 90 } 91 System.out.print(c); 92 last=c; 93 conditionC.signal(); 94 } catch (InterruptedException e) { 95 // TODO Auto-generated catch block 96 e.printStackTrace(); 97 } 98 finally 99 { 100 lock.unlock(); 101 } 102 } 103 } 104 105 private void printC() 106 { 107 for(int i=0;i<n;i++) 108 { 109 lock.lock(); 110 try { 111 if(last!='B') 112 { 113 conditionC.await(); 114 } 115 System.out.print(c); 116 last=c; 117 conditionD.signal(); 118 } catch (InterruptedException e) { 119 // TODO Auto-generated catch block 120 e.printStackTrace(); 121 } 122 finally 123 { 124 lock.unlock(); 125 } 126 } 127 } 128 129 private void printD() 130 { 131 for(int i=0;i<n;i++) 132 { 133 lock.lock(); 134 try { 135 if(last!='C') 136 { 137 conditionD.await(); 138 } 139 System.out.print(c); 140 last=c; 141 conditionA.signal(); 142 } catch (InterruptedException e) { 143 // TODO Auto-generated catch block 144 e.printStackTrace(); 145 } 146 finally 147 { 148 lock.unlock(); 149 } 150 } 151 } 152 153 } 154 }
需要注意对Condition的理解,Condition不能从名字上判断跟哪个线程相关,比如ConditionA就与AThread相关,这种理解是错的,condition只与lock相关,conditionB.await()不能让BThread阻塞,而只是让当前线程阻塞并放弃锁,进入睡眠等待被唤醒,conditionA.await()的目的是阻塞放弃锁并可以通过conditionA.signal()唤醒(并不是立即唤醒,而是在unlock后)。整个程序的执行过程大概如下:
假设ABCD四个线程同时执行,线程B发现上一个字母不是B则阻塞释放锁,CD同理,只有A能够运行打印A,然后唤醒B,以此类推,就不断地打印ABCDABCD。
在网上看到另外一种,利用线程调度的随机性到了自己的就打印,没有到就释放锁,重新竞争,这样效率较低,不能控制,但是还是能够实现。
1 import java.util.concurrent.locks.Lock; 2 import java.util.concurrent.locks.ReentrantLock; 3 4 public class Main { 5 private static int state = 0; 6 7 public static void main(String[] args) { 8 final Lock l = new ReentrantLock(); 9 10 Thread B = new Thread(new Runnable(){ 11 @Override 12 public void run() { 13 while (state<=30) { 14 l.lock(); 15 if(state%3==1){ 16 System.out.print("B"); 17 state ++; 18 } 19 else { 20 //System.out.println("NOT"); 21 } 22 l.unlock(); 23 } 24 } 25 }); 26 Thread A = new Thread(new Runnable(){ 27 @Override 28 public void run() { 29 while (state<=30) { 30 l.lock(); 31 if(state%3==0){ 32 System.out.print("A"); 33 state ++; 34 } 35 else { 36 //System.out.println("NOT"); 37 } 38 l.unlock(); 39 } 40 } 41 }); 42 Thread C = new Thread(new Runnable(){ 43 @Override 44 public void run() { 45 while (state<=30) { 46 l.lock(); 47 if(state%3==2){ 48 System.out.print("C"); 49 state ++; 50 } 51 else { 52 //System.out.println("NOT"); 53 } 54 l.unlock(); 55 } 56 } 57 }); 58 B.start(); 59 C.start(); 60 A.start(); 61 } 62 63 }
使用synchronized比较经典的方法
1 package com.mythread.test; 2 3 import java.util.concurrent.atomic.AtomicInteger; 4 5 public class TestAsynTreadXunlei { 6 public static void main(String argv[]) { 7 8 AtomicInteger synObj = new AtomicInteger(0); 9 10 TestPrint a = new TestPrint(synObj, "A", 0); 11 TestPrint b = new TestPrint(synObj, "B", 1); 12 TestPrint c = new TestPrint(synObj, "C", 2); 13 14 a.start(); 15 b.start(); 16 c.start(); 17 } 18 } 19 20 class TestPrint extends Thread { 21 22 private AtomicInteger synObj; 23 private String name; 24 private int flag; 25 26 private int count = 0; 27 28 public TestPrint(AtomicInteger synObj, String name, int flag) { 29 this.synObj = synObj; 30 this.name = name; 31 this.flag = flag; 32 } 33 34 @Override 35 public void run() { 36 while (true) { 37 synchronized (synObj) { 38 if (synObj.get() % 3 == flag) { 39 synObj.set(synObj.get() + 1); 40 System.out.println(name); 41 count++; 42 synObj.notifyAll(); 43 if (count == 10) { 44 break; 45 } 46 } else { 47 try { 48 synObj.wait(); 49 } catch (InterruptedException e) { 50 // TODO Auto-generated catch block 51 e.printStackTrace(); 52 } 53 } 54 } 55 } 56 } 57 }