并发编程基础-线程间的协作

Posted by Pismery Liu on Friday, November 30, 2018

TOC

线程间的协作

线程状态

  • 新建状态(New):新创建了一个线程对象。
  • 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。
  • 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
  • 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。 阻塞的情况分三种:
    • 等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒,
    • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。
    • 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
  • 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期

sleep、yield、join

sleep()方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态,只能在指定时间会重新唤醒线程或者调用interrupt()方法终止线程。该方法既可以让其他同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。但是sleep()方法不会释放“锁标志”,也就是说如果有synchronized同步块,其他线程仍然不能访问共享数据。

yield()方法和sleep()方法类似,也不会释放“锁标志”,区别在于,它没有参数,即yield()方法只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行,另外yield()方法只能使同优先级或者高优先级的线程得到执行机会,这也和sleep()方法不同。

join()方法会使当前线程等待调用join()方法的线程结束后才能继续执行

示例

## yield

public class Demo {

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                log.debug("thread 1 running...");
                Thread.yield();
            }

        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                log.debug("thread 2 running...");
                Thread.yield();
            }
        });

        /*
         相同优先级,基本是交换执行
         相差一个优先级,高优先级的连续执行次数多一点
         相差两个优先级,基本由高优先级的指令先
         */
        thread1.setPriority(Thread.MAX_PRIORITY);
        thread2.setPriority(Thread.MAX_PRIORITY);

        thread1.start();
        thread2.start();
    }
}
## Join

@Slf4j
public class JoinDemo {

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                log.debug("thread1 running times: " + i);
            }
        });

        thread1.start();

        for (int i = 0; i < 10; i++) {
            if ( i == 3)
                thread1.join();
            log.debug("main running times: " + i);
        }

    }
}

## 结果
当main线程执行到i==3时,将一直等待thread1执行完才继续执行。

wait、notify、notifyAll

介绍

在编写并发应用程序时需要十分谨慎小心,需要时刻注意多个线程在任何时刻访问一个或多个共享资源的问题。在java 5 以及后续版本中已经新增了新的api使得编写并发应用程序更加简单,例如:BlockingQueue、Executors 、Atomic类,但是有时候我们不得不使用旧的api,例如维护旧代码时,这样了解wait、notify、notifyAll就十分必要了。

wait:表示当前线程释放锁持有的锁,进入睡眠状态,直到其他线程通过相同的同步对象调用notify或者notifyAllf方法。在进入wait方法前会释放锁,在返回wait方法前会获取锁。

notify:唤醒一个持有相同对象的wait线程。需要注意的是,notify并不是立刻释放锁,只是唤醒wait线程,notify线程直到synchronized代码运行完毕才会释放锁。也就是说若notify线程调用了notify方法后还需要100ms才能执行完synchronized代码,被唤醒的线程需要等待100ms后才有机会去争夺锁。

notifyAll: 与notify相比,就是唤醒所有持有相同对象的wait线程,也不会立即释放锁。

wait(),notify()及notifyAll()只能在synchronized语句中使用,但是如果使用的是ReenTrantLock实现同步,该如何达到这三个方法的效果呢?解决方法是使用ReenTrantLock.newCondition()获取一个Condition类对象,然后Condition的await(),signal()以及signalAll()分别对应上面的三个方法

调用wait\notify\notifyAll方法的一般使用方式

synchronized( lockObject )
{
    while( ! condition ) { 
    //注意是while而不是if,因为无法保证cpu调用线程的顺序,
    //无法保证创建好的条件在被唤醒后没有被其他的线程破坏了。
        lockObject.wait();
    }
     
    //...;
}
synchronized( lockObject )
{   
    //...
    establish_the_condition();
    lockObject.notifyAll();//or lockObject.notify();
    //...;
}

面试问题

  1. 当前没有wait线程,调用notify/notifyAll方法会如何。调用完后才出现wait线程,wait线程会如何?

当没有wait线程时,调用notify/notifyAll方法将直接返回。调用notify/notifyAll方法后的出现的wait线程不会接收到之前的notify,等待下一个notify。

  1. 当线程被唤醒时,能否确保运行条件已经设置好?

不能,也因此wait条件判断使用while而不是if。原因是当线程被唤醒时,可能争夺不到cpu,运行条件被其它线程破坏了。

  1. 当多个wait线程,调用notify,哪一个线程会被唤醒?

java中有多个复合条件,只能增大某个线程被唤醒的概率,无法保证哪一个线程被唤醒。

  1. notifyAll是确实唤醒了所有线程吗?

能唤醒所有线程,但是只是只有一个线程能够获取到锁,其它被唤醒的线程会被阻塞。

  1. 既然只能有一个线程能够运行,为何有notifyAll?
  • 当需要设计一个notifyAll相当于将资源的分配交给被唤醒的线程去分配的程序。
  • 当producer生产速度能够满足不止一个consumer,而又不能确定具体多少个,因此可以用notifyAll唤醒所有consumer,让它们自己争夺。
  1. Can there be a race condition during the period that the wait() method releases OR reacquires the lock?

The wait() method is tightly integrated with the lock mechanism. The object lock is not actually freed until the waiting thread is already in a state in which it can receive notifications. It means only when thread state is changed such that it is able to receive notifications, lock is held. The system prevents any race conditions from occurring in this mechanism.

Similarly, system ensures that lock should be held by object completely before moving the thread out of waiting state.

参考链接