實現兩個線程交替運行(一)


兩個線程交替運行

有個需求,打印1-100;線程A打印奇數,線程B打印偶數,效果如下: 

線程A:==>1
線程B:==>2
線程A:==>3
線程B:==>4  .................................

線程A:==>97
線程B:==>98
線程A:==>99
線程B:==>100

1,使用CyclicBarrier

  此方案需要兩個線程執行次數相等;此方法的靈感來源於cyclicBarrier的Horse賽跑,詳見日志

  注意:1,此實例中.shutDown();和shutDownNow();都能夠正確運行,原因是 

      1.1,此實例中是CyclicBarrier中先執行的函數控制線程的結束;

    1.2,此實例中線程結束有具體的條件,並不是 !Thread.interrupted();

2,本例子中將.newFixedThreadPool(1); 替換為 .newCachedThreadPool();,則可以不調用.shutDown();或shutDownNow();

.newCachedThreadPool(); corePoolSize == 0,不存在核心線程數,執行任務完成后大概60s無新任務,線程池就會被關閉。

【不是立刻關閉,需要等大概60s。】

此操作是由於下方下划線思考后嘗試。

3,注意此實例中的 B線程 並不是一個循環。

public class ThreadCommunicate_1 {

    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(1,new B_Th());
        ExecutorService service = Executors.newFixedThreadPool(1);
        service.execute(new A_Th(barrier, service));
    }
}

/**
 * 打印奇數
 */
class A_Th implements Runnable{

    private final AtomicInteger num = new AtomicInteger(1);

    private CyclicBarrier barrier;

    private ExecutorService service;

    public A_Th(CyclicBarrier barrier, ExecutorService service) {
        this.barrier = barrier;
        this.service = service;
    }

    @Override
    public void run() {
//        for(int i = 0; i < 100; i++){
//            System.out.println(Thread.currentThread().getName()+"==>"+i);
//        }
        while (num.intValue() < 100){
            System.out.println("線程A:==>" + num.getAndAdd(2));
            try {
                barrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
        //service.shutdownNow();
        service.shutdown();
    }
}

/**
 * 打印偶數
 */
class B_Th implements Runnable{
    
    private final AtomicInteger num = new AtomicInteger(2);

    @Override
    public void run() {
        //while (num.intValue() < 100){
            System.out.println("線程B:==>" + num.getAndAdd(2));

        //}
    }
}

 

2,使用wait();notify();

方式一:

  由 生產者-消費者模式 轉換而來;

  思路:既然要求兩個線程交替執行,那么直接將隊列長度設為1。

  不使用線程池的目的是,整個程序可以在達到對應的條件后自動結束,不需要手動停止。

  思考:或許有運行完就自動關閉的線程池?;newCachedThreadPool滿足需求。

/**
 * @program: 
 * @description:
 *
 * 1,線程交替打印 一個線程打印偶數,一個線程打印奇數
 *
 * 使用wait()/notify();
 * 類似於生產者與消費者,將隊列長度設置為1
 */
public class ThreadCommunicate_2 {

    public static void main(String[] args) {
        Object lock = new Object();
        List<String> list = new ArrayList<>();

//        Runnable a_R = new A_Th_2(list, lock);
//        Runnable b_R = new B_Th_2(list, lock);
//
//        ExecutorService service = Executors.newFixedThreadPool(2);
//        service.execute(b_R);
//        service.execute(a_R);
        Thread a_Th =  new A_Th_2(list, lock);
        Thread b_Th =  new B_Th_2(list, lock);
        b_Th.start();
        a_Th.start();
    }
}

/**
 * 打印 奇數線程
 */
class A_Th_2 extends Thread {

    private static final int MAX_SIZE = 1;

    private List<String> arrayList;

    private Object lock;

    private int num = 1;

    public A_Th_2(List<String> arrayList,Object lock) {
        this.arrayList = arrayList;
        this.lock = lock;
    }

    @Override
    public void run() {
        while (num < 100){
            synchronized (lock){
                while (arrayList.size() == MAX_SIZE){
                    try {
                        //System.out.println("線程A:==> wait...前");
                        lock.wait();
                        //System.out.println("線程A:==> wait...后");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("線程A:==>" + num);
                arrayList.add("xxxx");
                num = num + 2;
                try {
                    //休眠1s
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock.notifyAll();
            }
        }
    }
}

/**
 * 打印 偶數線程
 */
class B_Th_2 extends Thread {

    private List<String> arrayList;

    private Object lock;

    private int num = 2;

    public B_Th_2(List<String> arrayList, Object lock) {
        this.arrayList = arrayList;
        this.lock = lock;
    }

    @Override
    public void run() {
        while (num <= 100 ){
            synchronized (lock){
                while (arrayList.isEmpty()){
                    try {
                        //System.out.println("線程B:==> wait...前");
                        lock.wait();
                        //System.out.println("線程B:==> wait...后");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("線程B:==>" + num);
                arrayList.remove(0);
                num = num + 2;
                try {
                    //休眠1s
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
                lock.notifyAll();
            }
        }
    }
}

 

優化策略:syn可以存放在循環之外,個人覺得此寫法比較優秀。

方式二

思路:簡化代碼,不使用list參數。

此代碼可能先打印A,也可能先打印B,重點關注交替運行。

public class ThreadCommunicate_2 {

    public static void main(String[] args) {
        Object lock = new Object();
        List<String> list = new ArrayList<>();

        Thread a_Th = new A_Th_2_1("A",lock);
        Thread b_Th = new A_Th_2_1("B",lock);
        a_Th.start();
        b_Th.start();

    }
}
/**
 * 打印 0-100
 */
class A_Th_2_1 extends Thread {

    private String name;
    private Object lock;

    private int num = 1;

    public A_Th_2_1(String name, Object lock) {
        this.name = name;
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock){
            while (num <= 100 ){
                System.out.println("線程"+name+":==>" + num);
                num = num + 1;
                try {
                    //休眠任意s
                    TimeUnit.SECONDS.sleep(new Random().nextInt(3)+1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock.notifyAll();
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //循環結束后使程序結束
            lock.notifyAll();
        }
    }
}

 

運行結果:

線程A:==>1
線程B:==>1
線程A:==>2
線程B:==>2
線程A:==>3
線程B:==>3
線程A:==>4
線程B:==>4
線程A:==>5
線程B:==>5...  ...  ...直到100

 

3,使用Lock,await();signal();

  此處代碼垃圾,純屬脫褲子放屁,只是提供一個思路。

  用boolean值控制兩個線程的交替執行可以實現,但是沒必要使用此種方式實現。

  1 /**
  2  * @program:
  3  * @description:
  4  *
  5  * lock;singal
  6  */
  7 public class ThreadCommunicate_3 {
  8 
  9     public static void main(String[] args) {
 10         ReentrantLock lock = new ReentrantLock();
 11         Condition condition = lock.newCondition();
 12         //Boolean flag = Boolean.FALSE;
 13         A_Th_3 a_Th = new A_Th_3(lock, condition, false);
 14         B_Th_3 b_Th = new B_Th_3(lock, condition, false);
 15        
 16         a_Th.setB_th_3(b_Th);
 17         b_Th.setA_th_3(a_Th);
 18 
 19         a_Th.start();
 20         b_Th.start();
 21 
 22     }
 23 }
 24 
 25 /**
 26  * 打印奇數線程
 27  */
 28 class A_Th_3 extends Thread {
 29 
 30     private final ReentrantLock lock;
 31 
 32     private final Condition condition;
 33 
 34     private Boolean flag;
 35 
 36     private B_Th_3 b_th_3;
 37 
 38     public A_Th_3(ReentrantLock lock,Condition condition,Boolean flag) {
 39         this.lock = lock;
 40         this.condition = condition;
 41         this.flag = flag;
 42     }
 43 
 44     public Boolean getFlag() {
 45         return flag;
 46     }
 47 
 48     public void setFlag(Boolean flag) {
 49         this.flag = flag;
 50     }
 51 
 52     public B_Th_3 getB_th_3() {
 53         return b_th_3;
 54     }
 55 
 56     public void setB_th_3(B_Th_3 b_th_3) {
 57         this.b_th_3 = b_th_3;
 58     }
 59 
 60     @Override
 61     public void run() {
 62         for (int i = 1; i < 100; ){
 63              lock.lock();
 64              //true就等着
 65               while (flag){
 66                   try {
 67                       condition.await();
 68                   } catch (InterruptedException e) {
 69                       e.printStackTrace();
 70                   }
 71               }
 72             System.out.println("線程A:==>" + i);
 73             i = i + 2;
 74             //讓本線程下個循環wait()
 75             flag = true;
 76             //設置B線程 標識
 77             b_th_3.setFlag(true);
 78 
 79             try {
 80                 TimeUnit.SECONDS.sleep(1);
 81             } catch (InterruptedException e) {
 82                 e.printStackTrace();
 83             }
 84 
 85             condition.signalAll();
 86             lock.unlock();
 87         }
 88     }
 89 }
 90 
 91 /**
 92  * 打印偶數線程
 93  */
 94 class B_Th_3 extends Thread{
 95 
 96     private final ReentrantLock lock;
 97 
 98     private final Condition condition;
 99 
100     private Boolean flag ;
101 
102     private A_Th_3 a_th_3;
103 
104     public B_Th_3(ReentrantLock lock,Condition condition, Boolean flag) {
105         this.lock = lock;
106         this.condition = condition;
107         this.flag = flag;
108     }
109 
110     public Boolean getFlag() {
111         return flag;
112     }
113 
114     public void setFlag(Boolean flag) {
115         this.flag = flag;
116     }
117 
118     public A_Th_3 getA_th_3() {
119         return a_th_3;
120     }
121 
122     public void setA_th_3(A_Th_3 a_th_3) {
123         this.a_th_3 = a_th_3;
124     }
125 
126     @Override
127     public void run() {
128         for (int i = 2; i < 101; ){
129             lock.lock();
130             //false就等着
131             //System.out.println(flag);
132             while (!flag){
133                 try {
134                     condition.await();
135                 } catch (InterruptedException e) {
136                     e.printStackTrace();
137                 }
138             }
139             System.out.println("線程B:==>" + i);
140             i = i + 2;
141             //設置本線程下個循環wait
142             flag = false;
143             //設置 A線程標識
144             a_th_3.setFlag(false);
145 
146             try {
147                 TimeUnit.SECONDS.sleep(1);
148             } catch (InterruptedException e) {
149                 e.printStackTrace();
150             }
151 
152             condition.signalAll();
153             lock.unlock();
154         }
155     }
156 }

 

小小思考: 

為什么要使用condition.await();?似乎某些情況不使用也沒問題。

但是此示例是一定要使用的,否則程序就會出現假死的情形。一般打到1-3就會停止打印,但其實程序一直運行。

其原因是:

在lock,與unlock之間,存在一個while(){} ,

在某些情況下(其實這個情況很容易發生,例如:當A 線程連續兩次競爭到鎖時),A線程會一直在循環中,

此時需要codition.await();來釋放鎖 ,讓另外的線程競爭鎖。

 

使用鎖相關,一般來說,共享變量就不再需要使用volatile修飾。

 

當然,Lock相較與sync來說,一個明顯的優點的就是可以精確喚醒相應線程,所以其實另外某個方法更為貼切。

 

public class ThreadCommunicate_3 {

    public static void main(String[] args) {

        ReentrantLock lock = new ReentrantLock();
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();

        CountDownLatch latch = new CountDownLatch(1);

        new A_Th_3_1(lock, condition1, condition2, latch).start();
        new B_Th_3_1(lock, condition1, condition2, latch).start();

    }
}

/**
 * 打印奇數線程
 */
class A_Th_3_1 extends Thread {

    private ReentrantLock lock;

    private Condition condition1;

    private Condition condition2;

    private CountDownLatch latch;

    public A_Th_3_1(ReentrantLock lock, Condition condition1, Condition condition2, CountDownLatch latch) {
        this.lock = lock;
        this.condition1 = condition1;
        this.condition2 = condition2;
        this.latch = latch;
    }

    @Override
    public void run() {
        System.out.println("---------------A_out");
        for (int i = 1; i < 100; ){
            //System.out.println("---------------A_for");
            try {
                lock.lock();
                System.out.println("線程A:==>" + i);
                if ( i == 1){
                    //放開柵欄
                    latch.countDown();
                }
                i = i + 2;
                //先喚醒,再等待
                condition2.signalAll();
                condition1.await();
                TimeUnit.SECONDS.sleep(1);
             } catch (InterruptedException e) {
                    e.printStackTrace();
             } finally {
                lock.unlock();
            }
        }
        //此處是為了解決當循環結束時,程序不結束的問題。
        lock.lock();
        condition2.signalAll();
        lock.unlock();
    }
}

/**
 * 打印偶數線程
 */
class B_Th_3_1 extends Thread{

    private final ReentrantLock lock;

    private final Condition condition1;

    private final Condition condition2;

    private CountDownLatch latch;

    public B_Th_3_1(ReentrantLock lock, Condition condition1, Condition condition2, CountDownLatch latch) {
        this.lock = lock;
        this.condition1 = condition1;
        this.condition2 = condition2;
        this.latch = latch;
    }

    @Override
    public void run() {
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("---------------B_out");
        for (int i = 2; i < 101; ){
            //System.out.println("---------------B_for");
            try {
                lock.lock();
                System.out.println("線程B:==>" + i);
                i = i + 2;
                TimeUnit.SECONDS.sleep(1);
                condition1.signalAll();
                condition2.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
        lock.lock();
        condition1.signalAll();
        lock.unlock();
    }
}

 

該代碼 在循環結束之后還需喚起另外的線程,否則程序會一直運行不結束。

使用了CountDownLatch來控制A B線程運行的順序,否則A B線程雖然是交替運行,但是順序隨機。

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM