如何使用多线程有序打印n遍ABC?

使用锁的机制完成多线程问题

Posted by MasterJen on November 13, 2018

Hey Thread

Sow nothing, reap nothing. –春不播,秋不收.

最近闲暇无事的时候,逛了逛论坛,发现有的同学对于多线程有着一定的盲区,并且提出了几个多线程的问题,我就在私下里写了两个例子简单说明下多线程的应用,就是多线程有序打印n遍abc的问题.

实现方法一 : 通过java中的AtomicInteger原子性操作完成,线程无非就是原子性的问题

/**
 * 使用多线程 循环打印  20 遍 ABC
 */
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        AtomicInteger runFlag = new AtomicInteger(1);
//        当前的是1
        System.out.println(runFlag.get());
//        期待的是1  所以是true  然后更新为  update 4
        boolean b1 = runFlag.compareAndSet(1, 4);
        System.out.println(b1);
//        此时变为了 4
        System.out.println(runFlag.get());
        Thread a = new Thread(new PrintRunable("A", 1, 2));
        Thread b = new Thread(new PrintRunable("B", 2, 3));
        Thread c = new Thread(new PrintRunable("C", 3, 1));
        a.start();
        b.start();
        c.start();
//        主线程让子线程.
        a.join();
        b.join();
        c.join();
    }
    static class PrintRunable implements Runnable{
//        需要打印的字符串
        private String str;
//        什么时候执行
        private static AtomicInteger runFlag = new AtomicInteger(1);
//        当前的顺序
        private int mySequence;
//        下一个执行
        private int next;
//        构造方法  构造参数是 打印的值,当前的序号,下一个是谁
        private PrintRunable(String str,int mySequence,int next){
            this.mySequence = mySequence;
            this.next = next;
            this.str = str;
        }
//        打印的次数
        private static int count = 20;
        @Override
        public void run() {
            while (true) {
                if(count<0){
                    return;
                }
//                判断 当前的 runFlag 是否和 标志是否一样  如果一样,那么就打印str
                if (runFlag.get() == mySequence) {
                    show();
                }
            }
        }
        public void show(){
//            打印
            System.out.println(str);
//            判断 下一个是否比当前的书序小  如果是的话,需要重新开始打印,又是新的开始
            if(next<mySequence)
                System.out.println("------>"+count--);
//              执行下一次。。将next  给mySequence  进行换值
            runFlag.compareAndSet(mySequence,next);
        }
    }
}

方法二: 通过Lock锁,保证原子性操作.

public class ThreadTestB {
    static int num = 1;
    int count = 1;
    //    创建  锁
    private Lock lock = new ReentrantLock();
    //    穿件 Condition 队列
    Condition locka = lock.newCondition();
    Condition lockb = lock.newCondition();
    Condition lockc = lock.newCondition();
//    打印 A
    public void showA() {
//        上锁
        lock.lock();
        try {
//            如果 不是  1  那么 等待
            while (num!=1){
                locka.await();
            }
//            num 是 1
            System.out.println("A");
//            轮到 b 输出
            num=2;
//            解开 b 的锁
            lockb.signal();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void showB() {
        lock.lock();
        try {
            while (num!=2){
                lockb.await();
            }
            System.out.println("B");
            num=3;
            lockc.signal();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void showC() {
        lock.lock();
        try {
            while (num!=3){
                lockc.await();
            }
            System.out.println("C");
            num=1;
            locka.signal();
            System.out.println("---》" + count);
            count++;
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
      public static void main(String[] args) {
          ThreadTestB printABC = new ThreadTestB();
          new Thread(new Runnable() {

              @Override
              public void run() {
                  for (int i = 0; i < 20; i++) {
                      printABC.showA();
                  }
              }
          }).start();
          new Thread(new Runnable() {

              @Override
              public void run() {
                  for (int i = 0; i < 20; i++) {
                      printABC.showB();
                  }
              }
          }).start();
          new Thread(new Runnable() {
              @Override
              public void run() {
                  for (int i = 0; i < 20; i++) {
                      printABC.showC();
                  }
              }
          }).start();
    }
}

以上两种方法就简单的实现了多线程的应用. 至于 为什么第一种方法中的AutomicInteger 是原子性的,我翻了翻源码,发现了关键字volatile,由此保证了线程之间的可见性.

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    //保证了  原子性,可见性
    private volatile int value;

    /**
     * Creates a new AtomicInteger with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
 }