多线程开发是程序开发中经常用到的技术,下文将对对java的多线程开发进行介绍。
一.开发多线程的方式:
1.继承Thread类,覆盖(override) run() 方法;
2.实现Runable接口,实现 run() 方法,这种方式可用于需要继承其它类而不能直接继承Thread类的情况;
3.实现Callable接口,实现其call()方法。Callable与Runable相似,但它有返回值,且它是一个泛型接口。Callable接口不是本文重点。
二.线程的状态
线程可以有4种状态:新生(New)、可运行(Runnable)、被阻塞(Blocked)、死亡(Dead)
1.新生线程
当你用new操作符创建一个线程对象时,线程还没有开始运行,此时线程处于新生状态。
2.可运行线程
线程对象构造完成后,就可以调用start()方法来启动线程,使其处于可运行状态。注意,一定要调用start()方法,而不要直接调用run()方法,那样只是由当前线程执行一遍run方法的内容而不会启动一个新线程。线程启动后,该线程即为可运行线程,一个可运行线程可能实际上正在运行,也可能没有,这取决于操作系统为该线程提供的运行时间。对于支持”time-slicing“的系统平台(如windows),会视当时环境状况分一小段cpu时间给轮到的Thread,一旦时间用完,该线程会立即参加排队,等待下一次取得执行权。线程对象可调用yield()方法,放弃此次获得的执行权,直接去排队等待下一次取得执行权的机会,此时线程依旧处于Runable状态。
3.被阻塞线程
线程被阻塞可能是由于下面五方面的原因:(《Thinking in Java》)
a.调用sleep(毫秒数),使线程进入睡眠状态。在规定时间内,这个线程是不会运行的。
b.用suspend()暂停了线程的执行。除非收到resume()消息,否则不会返回“可运行”状态。(suspend()方法从jdk1.2开始已被弃用。)
c.用wait()暂停了线程的执行。除非线程收到notify()或notifyAll()消息,否则不会变成“可运行”状态。
d.线程正在等候一些IO操作完成。
e.线程试图得到一个锁,而该锁正被其它线程持有。
4.死线程
有两种原因会导致线程死亡:
a.run方法执行完毕正常退出;
b.因一个未捕获的异常终止了run方法而使线程猝死。
三.线程的同步
在实际开发中,经常会有两个或多个线程需要对相同的对象进行共享访问,且会调用改变数据的方法,这就可能会产生腐蚀的对象,造成数据不一致,这种情况通常称为竞争条件(race condition)。为了避免多个线程对共享数据的腐蚀,必须对存取进行同步。旧版java使用synchronized关键字,而jdk5.0之后引入了ReentrantLock类。Lock方式引入了一些新的功能。它引入了条件对象,相对于synchronized关键字方式只能关联一个锁定对象,Lock对象可以有多个相关联的条件对象,这样可以处理更复杂的情况。synchronized关键字方式不能设定超时,Lock对象可以通过调用tryLock()方法设置超时参数。Lock还提供了ReentrantReadWriteLock类,可以抽取读锁和写锁,读锁允许读取操作共享访问,可对所有访问者加读锁;写锁会排斥所有其它的读操作和写操作,应只对写操作加写锁。synchronized关键字方式的代码简洁,在开发过程中,应尽量优先使用synchronized关键字方式。
四.其它概念
1.守护线程(Daemon Thread)
当用户线程(user thread)都已结束,只剩下守护线程在执行时,jvm就会终止这个程序的执行。一个守护线程的唯一作用就是为其它线程提供服务。
2.中断线程
在jdk1.0中,还存在一个stop方法,来终止线程,不过这个方法已被弃用了。尽管现在已经没有强制终止线程的方法,但可以用interrupt方法来申请终止一个线程。该方法将对线程的中断状态进行置位。这是一个布尔型标记,存在于每个线程中,可在线程中调用Thread.currentThread方法取得当前线程,然后调用isInterrupted方法检查。
while(!Thread.currentThread.isInterrupted){ ... }
3.join()方法
join()方法用来将某个线程加入到现在执行中的线程,而现在执行中的线程可能要等待加入的线程结束,或等待其执行一段时间之后才允许在继续执行。
五.补充
按照更细的划分,Java中线程的状态分为6种。
1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
3. 阻塞(BLOCKED):表示线程阻塞于锁。
4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
5. 超时等待(TIMED_WAITING):和WAITING总体上是一致的,区别是它可以在指定在等待一定时间之后,会重新恢复到之前的状态。
6. 终止(TERMINATED):表示该线程已经执行完毕。
此外,sleep和wait的区别为:
wait会释放synchronized锁,也会释放CPU;
sleep释放CPU,但不释放锁。