对JAVA多线程并发编程的理解
Java多线程编程关注的焦点主要是对单一资源的并发访问,本文从Java如何实现支持并发访问的角度,浅析对并发编程的理解,也算是对前段时间所学的一个总结。
线程状态转换
Java语言定义了5中线程状态,在任何一个时间点,一个线程只能有且只有其中一种状态,这5中状态分别是:
?
新建(New):创建后尚未启动的线程处于这种状态
?
运行(Runable):Runable包括了操作系统线程状态中的Running和Ready,也就是处于此状态的线程可能正在执行,也有可能正在等待CPU为它分配执行时间
?
无限期等待(Waiting):处于这种状态的线程不会被分配CPU执行时间,他们要等待被其他线程显式的唤醒。以下方法会让线程陷入无限期的等待状态:
u
没有设置Timeout参数的Object.wait()方法。
u
没有设置Timeout参数的Thread.join()方法。
u
LockSupport.park()方法。
?
限期等待(TimedWaiting):处于这种状态的线程也不会被分配CPU执行时间,不过无须等待被其他线程显式地唤醒。在一定时间这后它们会由系统自动唤醒。以下方法会让线程进行限期等待状态:
Thread.sleep()方法。
u
设置了Timeout参数的Object.wait()方法。
u
设置了Timeout参数的Thread.join()方法。
u
LockSupport.parkNanos()方法
u
LockSupport.parkUntil()方法
?
阻塞(Blocked):线程被阻塞了,“阻塞状态”与“等待状态”的区别是:“阻塞状态“在等待着获取到一个排它锁,这个事件将在另外一个线程放弃这个锁的时候发生:而”等待状态“则是在等待一段时间,或都唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。
?
结束(Terminated):已终止线程的线程状态,线程已经结束执行。
各种状态转换如下图:
资源监视器
在进行多线程并发操作时,关键的任务是如何保证在并发的多个线程中对竞争资源的安全访问。所谓安全访问是指一个线程正在在竞争性资源操作(会修改资源的状态)时,其它线程不能对该竞争性资源进行操作(如果所有线程都是对该资源进行读操作,则不会产生安全性问题)。
Java为解决此类问题,提出了监视器的概念。形象的讲,一个资源的监视器,好比是在要访问的资源上包了一层安全防护壳,如下图:
Java虚拟机保证,在同一时间点上,有且只会有一个线程获得该资源的监视器。换句话说,也是获得了该资源的锁。获得锁的线程可以进行后续操作,没有获得锁的线程则只能进行等待或阻塞。
一个已经获得该资源的监视器的线程,后续还可以多次获得该监视器,每获得一次,锁计数会加1,每退出一次资源监视器,锁计数也相应的减1。等到线程完全退出了该资源监视器,锁计数也清零了。此时,其他线程就可以获得该资源的监视器了。
Java监视器支持两种线程,互斥和协作。Java虚拟机通过对象锁来实现互斥,允许多个线程在一个共享数据上独立而互不干扰地工作。协作是通过Object类的wait方法和notify方法来实现,允许多个线程为了同一个目标而共同工作。
互斥可以在多线程中互斥地执行一段被称作监视区域的代码,在任务时候,特定监视器上只有一个线程执行监视区域。通常,互斥只在多个线程需要共享数据或其他资源时显得重要,如果两个线程并不使用任务公有数据或资源,它们通常会互不干扰,也就不需要进行互斥执行。
互斥帮助线程在访问共享数据时不被其他线程干扰,而协作帮助线程与其他线程共同工作。
当一个线程需要一些特别状态的数据,而由另一个线程负责改变这些数据的状态时,协作就显得非常重要。生产者和消费者就是这样一个例子。一个”读线程“会从数据缓冲区中读取数据,而另一个”写线程”会向数据缓冲区中写入数据。读线程缓冲区处理”非空“状态时才能读数据。如果读线程发现数据缓冲区是空的,它就必须等待。
Java提供”等待并唤醒“的方式来支持多线程的协作行为。对应到Java
API上就是Object对象的wait()/notify()/notifyAll()方法。
wait()方法使得当前线程必须要等待,等到另外一个线程调用notify()或者notifyAll()方法。
当前的线程必须拥有当前对象的monitor,也即lock,就是锁。
线程调用wait()方法,释放它对锁的拥有权,然后等待另外的线程来通知它(通知的方式是notify()或者notifyAll()方法),这样它才能重新获得锁的拥有权和恢复执行。
要确保调用wait()方法的时候拥有锁,即,wait()方法的调用必须放在synchronized方法或synchronized块中。
一个小比较:
当线程调用了wait()方法时,它会释放掉对象的锁。
另一个会导致线程暂停的方法:Thread.sleep(),它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁的。
notify()方法会唤醒一个等待当前对象的锁的线程。
如果多个线程在等待,它们中的一个将会选择被唤醒。这种选择是随意的,和具体实现有关。(线程等待一个对象的锁是由于调用了wait方法中的一个)。
被唤醒的线程是不能被执行的,需要等到当前线程放弃这个对象的锁。
被唤醒的线程将和其他线程以通常的方式进行竞争,来获得对象的锁。也就是说,被唤醒的线程并没有什么优先权,也没有什么劣势,对象的下一个线程还是需要通过一般性的竞争。
notify()方法应该是被拥有对象的锁的线程所调用。
(Thismethod should only be called by a thread that is the owner of this object‘smonitor.)
换句话说,和wait()方法一样,notify方法调用必须放在synchronized方法或synchronized块中。
前面说wait、nofity、notifyAll方法被调用时,应确保在调用的对象上已经获得了该对你的监视器。即该线程操作该对象的锁。
一个线程变为一个对象的锁的拥有者是通过下列三种方法:
1.执行这个对象的synchronized实例方法。
2.执行这个对象的synchronized语句块。这个语句块锁的是这个对象。
3.对于Class类的对象,执行那个类的synchronized、static方法。