畅游多线程问题之顺序打印

索引链接:畅游多线程问题

1114. 按序打印

方法1:synchronized
  • 基础知识

1、wait()、notify/notifyAll() 方法是Object的本地final方法,无法被重写。

2、wait()使当前线程阻塞,前提是 必须先获得锁,一般配合synchronized 关键字使用,即,一般在synchronized 同步代码块里使用 wait()、notify/notifyAll() 方法。

3、 由于 wait()、notify/notifyAll() 在synchronized 代码块执行,说明当前线程一定是获取了锁的。

当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。

只有当 notify/notifyAll() 被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。

也就是说,notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况。所以在编程中,尽量在使用了notify/notifyAll() 后立即退出临界区,以唤醒其他线程让其获得锁

4、wait() 需要被try catch包围,以便发生异常中断也可以使wait等待的线程唤醒。

5、notify 和wait 的顺序不能错,如果A线程先执行notify方法,B线程在执行wait方法,那么B线程是无法被唤醒的。

6、notify 和 notifyAll的区别

notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll 方法。比如在生产者-消费者里面的使用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。

7、在多线程中要测试某个条件的变化,使用if 还是while?

要注意,notify唤醒沉睡的线程后,线程会接着上次的执行继续往下执行。所以在进行条件判断时候,可以先把 wait 语句忽略不计来进行考虑;显然,要确保程序一定要执行,并且要保证程序直到满足一定的条件再执行,要使用while进行等待,直到满足条件才继续往下执行

class Foo {

    public Foo() {

    }

    private int signal = 1;
    private Object obj = new Object();

    public void first(Runnable printFirst) throws InterruptedException {
        synchronized (obj) {
            while (signal != 1) {
                obj.wait();
            }
            printFirst.run();
            signal = 2;
            obj.notifyAll();
        }

    }

    public void second(Runnable printSecond) throws InterruptedException {
        synchronized (obj) {
            while (signal != 2) {
                obj.wait();
            }
            printSecond.run();
            signal = 3;
            obj.notifyAll();
        }

    }

    public void third(Runnable printThird) throws InterruptedException {
        synchronized (obj) {
            while (signal != 3) {
                obj.wait();
            }
            printThird.run();
            signal = 1;
            obj.notifyAll();
        }
    }
}
方法2:synchronized
  • 变量的方式控制线程的执行
class Foo {

    boolean first = false;
    boolean second = false;
    Object obj = new Object();

    public Foo() {

    }

    public void first(Runnable printFirst) throws InterruptedException {
        synchronized (obj) {
            first = true;
            printFirst.run();
            obj.notifyAll();//唤醒其他线程
        }
    }

    public void second(Runnable printSecond) throws InterruptedException {
        synchronized (obj) {
            while (!first) {//当线程1执行的时候,线程2在等待,线程1设置了first=true,while条件不成立,线程2开始执行
                obj.wait();
            }
            printSecond.run();
            second = true;
            obj.notifyAll();
        }

    }

    public void third(Runnable printThird) throws InterruptedException {
        synchronized (obj) {
            while (!second) {
                obj.wait();
            }
            printThird.run();
            obj.notifyAll();
        }

    }
}
方法3:Semaphore
  • Semaphore是一个计数信号量。
  • 从概念上将,Semaphore包含一组许可证。
  • 如果有需要的话,每个acquire()方法都会阻塞,直到获取一个可用的许可证。
  • 每个release()方法都会释放持有许可证的线程,并且归还Semaphore一个可用的许可证。
  • 然而,实际上并没有真实的许可证对象供线程使用,Semaphore只是对可用的数量进行管理维护
  • 总结:如果线程要访问一个资源就必须先获得信号量。如果信号量内部计数器大于0,信号量减1,然后允许共享这个资源;否则,如果信号量的计数器等于0,信号量将会把线程置入休眠直至计数器大于0.当信号量使用完时,必须释放
class Foo {

    Semaphore s12 = new Semaphore(0);
    Semaphore s23 = new Semaphore(0);

    public Foo() {

    }

    public void first(Runnable printFirst) throws InterruptedException {
        printFirst.run();
        s12.release();//释放后s12的值会变成1
    }

    public void second(Runnable printSecond) throws InterruptedException {
        s12.acquire();//没有会阻塞  当为1的时候,说明线程2可以拿到s12了
        printSecond.run();
        s23.release();//释放后s23的值会变成1
    }

    public void third(Runnable printThird) throws InterruptedException {
        s23.acquire();//0的时候拿不到,1的时候可以拿到
        printThird.run();
    }
}
方法4:CountDownLatch
  • CountDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。

  • 是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了

//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await() throws InterruptedException { };   
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  
//将count值减1
public void countDown() { };  
class Foo {
    CountDownLatch latch12 = new CountDownLatch(1);
    CountDownLatch latch23 = new CountDownLatch(1);

    public Foo() {

    }

    public void first(Runnable printFirst) throws InterruptedException {
        printFirst.run();
        latch12.countDown();//唤醒线程2
    }

    public void second(Runnable printSecond) throws InterruptedException {
        latch12.await();//latch12的值为0会执行下面的语句,否则会在此次阻塞
        printSecond.run();
        latch23.countDown();//准备唤醒线程3
    }

    public void third(Runnable printThird) throws InterruptedException {
        latch23.await();//latch23的值为0会执行下面的语句,否则会在此次阻塞
        printThird.run();
    }
}
方法5:SynchronousQueue
  • SynchronousQueue 是一个队列来的,但它的特别之处在于它内部没有容器,一个生产线程,当它生产产品(即put的时候),如果当前没有人想要消费产品(即当前没有线程执行take),此生产线程必须阻塞,等待一个消费线程调用take操作,take操作将会唤醒该生产线程,同时消费线程会获取生产线程的产品(即数据传递),这样的一个过程称为一次配对过程(当然也可以先takeput,原理是一样的)
class Foo {
    //阻塞队列         //同步队列,没有容量,进去一个元素,必须等待取出来以后,才能再往里面放一个元素
    BlockingQueue<Integer> block12 = new SynchronousQueue<Integer>();
    BlockingQueue<Integer> block23 = new SynchronousQueue<Integer>();

    public Foo() {

    }

    public void first(Runnable printFirst) throws InterruptedException {
        printFirst.run();
        block12.put(1);
    }

    public void second(Runnable printSecond) throws InterruptedException {
        block12.take();//
        printSecond.run();
        block23.put(1);
    }

    public void third(Runnable printThird) throws InterruptedException {
        block23.take();
        printThird.run();
    }
}
方法6:ReentrantLock+Condition

Condition可以通俗的理解为条件队列。当一个线程在调用了await方法以后,直到线程等待的某个条件为真的时候才会被唤醒。这种方式为线程提供了更加简单的等待/通知模式。Condition必须要配合锁一起使用,因为对共享状态变量的访问发生在多线程环境下。一个Condition的实例必须与一个Lock绑定,因此Condition一般都是作为Lock的内部实现。

  1. await() :造成当前线程在接到信号或被中断之前一直处于等待状态。
  2. await(long time, TimeUnit unit) :造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
  3. awaitNanos(long nanosTimeout) :造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。返回值表示剩余时间,如果在nanosTimesout之前唤醒,那么返回值 = nanosTimeout - 消耗时间,如果返回值 <= 0 ,则可以认定它已经超时了。
  4. awaitUninterruptibly() :造成当前线程在接到信号之前一直处于等待状态。【注意:该方法对中断不敏感】。
  5. awaitUntil(Date deadline) :造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。如果没有到指定时间就被通知,则返回true,否则表示到了指定时间,返回返回false。
  6. signal() :唤醒一个等待线程。该线程从等待方法返回前必须获得与Condition相关的锁。
  7. signal()All :唤醒所有等待线程。能够从等待方法返回的线程必须获得与Condition相关的锁。
class Foo {

    int num;
    Lock lock;
    //精确的通知和唤醒线程
    Condition condition1, condition2, condition3;

    public Foo() {
        num = 1;
        lock = new ReentrantLock();
        condition1 = lock.newCondition();
        condition2 = lock.newCondition();
        condition3 = lock.newCondition();
    }

    public void first(Runnable printFirst) throws InterruptedException {
        lock.lock();
        try {
            while (num != 1) {//不是1的时候,阻塞
                condition1.await();
            }
            // printFirst.run() outputs "first". Do not change or remove this line.
            printFirst.run();
            num = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void second(Runnable printSecond) throws InterruptedException {
        lock.lock();
        try {
            while (num != 2) {//不是2的时候,阻塞
                condition2.await();
            }
            // printSecond.run() outputs "second". Do not change or remove this line.
            printSecond.run();
            num = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void third(Runnable printThird) throws InterruptedException {
        lock.lock();
        try {
            while (num != 3) {//不是3的时候,阻塞
                condition3.await();
            }
            // printThird.run() outputs "third". Do not change or remove this line.
            printThird.run();
            num = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
方法7:LockSupport
  • LockSupport类的核心方法其实就两个:park()unpark(),其中park()方法用来阻塞当前调用线程,unpark()方法用于唤醒指定线程。
    这其实和Object类的wait()和signal()方法有些类似,但是LockSupport的这两种方法从语意上讲比Object类的方法更清晰,而且可以针对指定线程进行阻塞和唤醒。

LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。
初始时,permit为0,当调用unpark()方法时,线程的permit加1,当调用park()方法时,如果permit为0,则调用线程进入阻塞状态。

       class Foo {
            private AtomicInteger counter = new AtomicInteger(0);
            private Map<String, Thread> threads = new HashMap<>();
         
			public Foo() {

        }

        public void first(Runnable printFirst) throws InterruptedException {
            while (counter.get() != 0) {
                threads.put("first", Thread.currentThread());
                LockSupport.park();
            }
            printFirst.run();
            counter.getAndIncrement();
            threads.forEach((k, v) -> LockSupport.unpark(v));
        }

        public void second(Runnable printSecond) throws InterruptedException {
            while (counter.get() != 1) {
                threads.put("second",Thread.currentThread());
                LockSupport.park();
            }
            printSecond.run();
            counter.getAndIncrement();
            threads.forEach((k, v) -> LockSupport.unpark(v));
        }

        public void third(Runnable printThird) throws InterruptedException {
            while (counter.get() != 2) {
                threads.put("third",Thread.currentThread());
                LockSupport.park();
            }
            printThird.run();
            counter.getAndIncrement();
            threads.forEach((k, v) -> LockSupport.unpark(v));
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值