Java多线程
什么是多线程
提到线程,不得不先说一下什么是进程。
进程就是正在运行的程序。当一个程序进入内存运行时,即变成一个进程。每个进程都有独立的代码和数据空间(进程上下文)。一个进程包含1--n个线程。一个线程不能独立的存在,它必须是进程的一部分。
线程是进程的组成部分,一个进程可以有多个线程,但一个线程必须只有一个父进程。线程可以拥有自己的栈,自己的程序计数器和自己的局部变量,但不拥有父进程资源,处理资源一般是父进程把资源(对象指针)传给线程,它与父进程的其他线程共享该进程所拥有的全部资源。
多线程就是同一进程可以同时并发处理多个线程,它们独立运行,互不妨碍。多线程能满足程序员编写高效率的程序来达到充分利用CPU的目的。
一个线程的生命周期
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。下图显示了一个线程完整的生命周期。
下面是对其中名词的解释:
新建状态(New):新创建了一个线程对象。
就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(1)等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(2)同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(3)其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
线程睡眠(sleep()):Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性。注意,sleep是不会释放持有的锁。
线程加入(join()):执行threadObj.join()方法,父线程(主线程)会等到threadObj运行终止后再执行。在当前线程(主线程)中调用另一个线程(子线程)的join()方法,则当前线程转入阻塞状态,直到另一个线程运行结束,当前线程再由阻塞转为就绪状态。也就是主线程中,子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。
线程让步(yield()):Thread.yield()方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。yield()让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
线程等待/线程唤醒:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的notify()方法或notifyAll()唤醒方法。要注意的是:
(1)obj.wait(),与obj.notify()必须要与synchronized(obj)一起使用,也就是wait与notify是针对已经获取了obj锁进行操作,从语法角度来说就是obj.wait(),obj.notify()必须在synchronized(obj){...}语句块内。
(2)当线程执行wait()时,会把当前的锁释放,然后让出CPU,进入等待状态。
(3)当执行notify()/notifyAll()方法时,会唤醒一个处于等待该对象锁的线程,然后继续往下执行,直到执行完退出对象锁锁住的区域(synchronized修饰的代码块)后再释放锁。