实现两个线程交替运行(一)


两个线程交替运行

有个需求,打印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