zoukankan      html  css  js  c++  java
  • 三个线程交替顺序打印ABC

    题目描述

    建立三个线程A、B、C,A线程打印10次字母A,B线程打印10次字母B,C线程打印10次字母C,但是要求三个线程同时运行,并且实现交替打印,即按照ABCABCABC的顺序打印。

    5种方法

    1. 使用synchronized, wait和notifyAll
    2. 使用Lock->ReentrantLock 和 state标志
    3. 使用Lock->ReentrantLock 和Condition(await 、signal、signalAll)
    4. 使用Semaphore
    5. 使用AtomicInteger

    1、使用synchronized, wait和notifyAll

    使用同步块和wait、notify的方法控制三个线程的执行次序。具体方法如下:从大的方向上来讲,该问题为三线程间的同步唤醒操作,主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA循环执行三个线程。为了控制线程执行的顺序,那么就必须要确定唤醒、等待的顺序,所以每一个线程必须同时持有两个对象锁,才能进行打印操作。一个对象锁是prev,就是前一个线程所对应的对象锁,其主要作用是保证当前线程一定是在前一个线程操作完成后(即前一个线程释放了其对应的对象锁)才开始执行。还有一个锁就是自身对象锁。主要的思想就是,为了控制执行的顺序,必须要先持有prev锁(也就前一个线程要释放其自身对象锁),然后当前线程再申请自己对象锁,两者兼备时打印。之后首先调用self.notify()唤醒下一个等待线程(注意notify不会立即释放对象锁,只有等到同步块代码执行完毕后才会释放),再调用prev.wait()立即释放prev对象锁,当前线程进入休眠,等待其他线程的notify操作再次唤醒。

    
    package com.beike.offer;
    public class Synchronized_ABC {
        public static class ThreadPrinter implements Runnable {
            private String name;
            private Object prev;
            private Object self;
    
            private ThreadPrinter(String name, Object prev, Object self) {
                this.name = name;
                this.prev = prev;
                this.self = self;
            }
    
            @Override
            public void run() {
                int count = 10;
                while (count > 0) {// 多线程并发,不能用if,必须使用whil循环
                    synchronized (prev) { // 先获取 prev 锁
                        synchronized (self) {// 再获取 self 锁
                            System.out.print(name);// 打印
                            count--;
    
                            self.notifyAll();// 唤醒其他线程竞争self锁,注意此时self锁并未立即释放。
                        }
                        // 此时执行完self的同步块,这时self锁才释放。
                        try {
                            if (count == 0) {// 如果count==0,表示这是最后一次打印操作,通过notifyAll操作释放对象锁。
                                prev.notifyAll();
                            } else {
                                prev.wait(); // 立即释放 prev锁,当前线程休眠,等待唤醒
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    
        public static void main(String[] args) throws Exception {
            Object a = new Object();
            Object b = new Object();
            Object c = new Object();
            ThreadPrinter pa = new ThreadPrinter("A", c, a);
            ThreadPrinter pb = new ThreadPrinter("B", a, b);
            ThreadPrinter pc = new ThreadPrinter("C", b, c);
    
            new Thread(pa).start();
            Thread.sleep(10);// 保证初始ABC的启动顺序
            new Thread(pb).start();
            Thread.sleep(10);
            new Thread(pc).start();
            Thread.sleep(10);
        }
    }

    2、使用Lock 和 state标志

    public class Lock_State_ABC {
        private static Lock lock=new ReentrantLock();
        private static int state=0;//通过state的值来确定是哪个线程打印
    
       static class ThreadA extends Thread{
            @Override
            public void run(){
                for (int i = 0; i <10 ; ) {
                    try{
                        lock.lock();
                        while(state%3==0){// 多线程并发,不能用if,必须用循环测试等待条件,避免虚假唤醒
                            System.out.print("A");
                            state++;
                            i++;
                        }
                    }finally{
                        lock.unlock();
                    }
                }
            }
        }
    
        static class ThreadB extends Thread{
            @Override
            public void run(){
                for (int i = 0; i <10 ; ) {
                    try{
                        lock.lock();
                        while(state%3==1){
                            System.out.print("B");
                            state++;
                            i++;
                        }
                    }finally{
                        lock.unlock();
                    }
                }
            }
        }
    
    
        static class ThreadC extends Thread{
            @Override
            public void run(){
                for (int i = 0; i <10 ; ) {
                    try{
                        lock.lock();
                        while(state%3==2){
                            System.out.print("C");
                            state++;
                            i++;
                        }
                    }finally{
                        lock.unlock();
                    }
                }
            }
        }
    
        public static void main(String[] args) {
            new ThreadA().start();
            new ThreadB().start();
            new ThreadC().start();
        }
    }
    //输出ABCABCABCABCABCABCABCABCABCABC

    3、使用Lock->ReentrantLock 和Condition(await 、signal、signalAll)

    与ReentrantLock搭配的通行方式是Condition,如下:

    private Lock lock = new ReentrantLock();  
    private Condition condition = lock.newCondition(); 
    condition.await();//this.wait();  
    condition.signal();//this.notify();  
    condition.signalAll();//this.notifyAll();

    Condition是被绑定到Lock上的,必须使用lock.newCondition()才能创建一个Condition。从上面的代码可以看出,Synchronized能实现的通信方式,Condition都可以实现,功能类似的代码写在同一行中。这样解题思路就和第一种方法基本一致,只是采用的方法不同。

    public class Lock_Condition_ABC {
        private static Lock lock = new ReentrantLock();
        private static Condition A = lock.newCondition();
        private static Condition B = lock.newCondition();
        private static Condition C = lock.newCondition();
    
        private static int count = 0;
    
        static class ThreadA extends Thread {
            @Override
            public void run() {
                try {
                    lock.lock();
    
                    for (int i = 0; i < 10; i++) {
                        while (count % 3 != 0){//注意这里是不等于0,也就是说没轮到该线程执行,之前一直等待状态
                            A.await(); //该线程A将会释放lock锁,构造成节点加入等待队列并进入等待状态
                        }
                        System.out.print("A");
                        count++;
                        B.signal(); // A执行完唤醒B线程
                    }
    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    
        static class ThreadB extends Thread {
            @Override
            public void run() {
                try {
                    lock.lock();
                    for (int i = 0; i < 10; i++) {
                        while (count % 3 != 1)
                            B.await();// B释放lock锁,当前面A线程执行后会通过B.signal()唤醒该线程
                        System.out.print("B");
                        count++;
                        C.signal();// B执行完唤醒C线程
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    
        static class ThreadC extends Thread {
            @Override
            public void run() {
                try {
                    lock.lock();
                    for (int i = 0; i < 10; i++) {
                        while (count % 3 != 2)
                            C.await();// C释放lock锁
                        System.out.print("C");
                        count++;
                        A.signal();// C执行完唤醒A线程
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            new ThreadA().start();
            new ThreadB().start();
            new ThreadC().start();
        }
    }

    4、使用Semaphore

    4.1 Semaphore介绍

    Semaphore又称信号量,是操作系统中的一个概念,在Java并发编程中,信号量控制的是线程并发的数量。
    ————————————————————————————————————————————————
    Semaphore实现原理简单理解:
    Semaphore是用来保护一个或者多个共享资源的访问,Semaphore信号量内部维护了一个计数器,其值为可以访问的共享资源的个数。一个线程要访问共享资源,先获得信号量,如果信号量的计数器值大于1,意味着有共享资源可以访问,则使其计数器值减去1,再访问共享资源。

    就好比一个厕所管理员,站在门口,只有厕所有空位,就开门允许与空侧数量等量的人进入厕所。多个人进入厕所后,相当于N个人来分配使用N个空位。为避免多个人来同时竞争同一个侧卫,在内部仍然使用锁来控制资源的同步访问。

    如果计数器值为0,线程进入休眠。当某个线程使用完共享资源后,释放信号量,并将信号量内部的计数器加1,之前进入休眠的线程将被唤醒并再次试图获得信号量。
    ————————————————————————————————————————————————
    Semaphore内部主要通过AQS(AbstractQueuedSynchronizer)实现线程的管理。Semaphore有两个构造函数,第一个参数permits表示许可数,它最后传递给了AQS的state值。线程在运行时首先获取许可,如果成功,许可数就减1,线程运行,当线程运行结束就释放许可,许可数就加1。如果许可数为0,则获取失败,线程位于AQS的等待队列中,它会被其它释放许可的线程唤醒。在创建Semaphore对象的时候还可以指定它的公平性。一般常用非公平的信号量,非公平信号量是指在获取许可时先尝试获取许可,而不必关心是否已有需要获取许可的线程位于等待队列中,如果获取失败,才会入列。而公平的信号量在获取许可时首先要查看等待队列中是否已有线程,如果有则入列。

    //非公平的构造函数
    public Semaphore(int permits);//permits=10,表示允许10个线程获取许可证,最大并发数是10;
    ////通过fair参数决定公平性
    public Semaphore(int permits,boolean fair)
    
    Semaphore semaphore = new Semaphore(10,true);  
    semaphore.acquire();  //线程获取许可证
    //do something here  
    semaphore.release();  //线程归还许可证

    4.2 代码

    public class Semaphore_ABC {
        // 以A开始的信号量,初始信号量数量为1
        private static Semaphore A = new Semaphore(1);
        // B、C信号量,A完成后开始,初始信号数量为0
        private static Semaphore B = new Semaphore(0);
        private static Semaphore C = new Semaphore(0);
    
        static class ThreadA extends Thread {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 10; i++) {
                        A.acquire();// A获取信号执行,A信号量减1,当A为0时将无法继续获得该信号量
                        System.out.print("A");
                        B.release();// B释放信号,B信号量加1(初始为0),此时可以获取B信号量
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        static class ThreadB extends Thread {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 10; i++) {
                        B.acquire();
                        System.out.print("B");
                        C.release();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        static class ThreadC extends Thread {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 10; i++) {
                        C.acquire();
                        System.out.println("C");
                        A.release();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            new ThreadA().start();
            new ThreadB().start();
            new ThreadC().start();
        }
    
    }
    

    可以看到信号量的变化情况如下:
    初始(A=1,B=0,C=0)—>第一次执行线程A时(A=1,B=0,C=0)—->第一次执行线程B时(A=0,B=1,C=0)—->第一次执行线程C时(A=0,B=0,C=1)—>第二次执行线程A(A=1,B=0,C=0)如此循环。

    5、使用AtomicInteger

    public class Atomtic_ABC {
    
        private AtomicInteger ai = new AtomicInteger(0);
        private static final int MAX_SYC_VALUE = 3 * 10;
    
        private class RunnableA implements Runnable {
            public void run() {
                while (ai.get() < MAX_SYC_VALUE-1) {
                    if (ai.get() % 3 == 0) {
                        System.out.print("A");
                        ai.getAndIncrement();
                    }
                }
    
            }
        }
    
        private class RunnableB implements Runnable {
            public void run() {
                while (ai.get() < MAX_SYC_VALUE) {
                    if (ai.get() % 3 == 1) {
                        System.out.print("B");
                        ai.getAndIncrement();
                    }
                }
    
            }
        }
    
        private class RunnableC implements Runnable {
            public void run() {
                while (ai.get() < MAX_SYC_VALUE) {
                    if (ai.get() % 3 == 2) {
                        System.out.println("C");
                        ai.getAndIncrement();
                    }
                }
    
            }
        }
    
    
        public static void main(String[] args) {
            Atomtic_ABC atomic_ABC = new Atomtic_ABC();
            ExecutorService service = Executors.newFixedThreadPool(3);
    
            service.execute(atomic_ABC.new RunnableA());
            service.execute(atomic_ABC.new RunnableB());
            service.execute(atomic_ABC.new RunnableC());
    
            service.shutdown();
        }
    }
    
    
     
     
  • 相关阅读:
    JavaScript模态对话框类
    事件模块的演变(1)
    html5中可通过document.head获取head元素
    How to search for just a specific file type in Visual Studio code?
    What do 'lazy' and 'greedy' mean in the context of regular expressions?
    正则非获取匹配 Lookahead and Lookbehind ZeroLength Assertions
    regex length 正则长度问题
    Inversion of Control vs Dependency Injection
    How to return View with QueryString in ASP.NET MVC 2?
    今天才发现Google Reader
  • 原文地址:https://www.cnblogs.com/snake23/p/13832708.html
Copyright © 2011-2022 走看看