实现方式:
-
继承Thread类并且重写run()方法(必须)
调用方式:new一个该已经继承Thread类的类后调用start()方法
构造器的几种参数:
-
String name
Thread的名称,默认为Thread-N(N是唯一的数字)
-
Runnable target
runnable是thread要执行的指令列表。默认情况下,这是thread本身的run()这个method的消息。Thread class本身实现了Runnable接口。
-
Thread group
默认情况下,thread会分配给予调用constructor的thread一样的thread group
-
Stack size
每个thread都有一个method运行时保存临时变量的stack,stack大小由平台而定,不建议在可移植程序使用此变量
Thread中,调用start后的执行流程为:start->start0->run->target.run
观察其init函数,会发现其会获取当前运行线程为父线程,并使用父线程相关属性继承——daemon、priority、inheritableThreadLocal
-
-
实现Runnable接口并且重写run()方法(必须)
调用方式:先new一个实现该接口的类a,再new一个Thread类并且将类a作为参数传入,调用start()方法执行产生线程执行
注意:Runnable不是线程,而是线程运行的代码宿主
-
通过 Callable 和 Future 创建线程
步骤:
- 创建 Callable 接口的实现类,并实现 call() 方法,该 call()方法将作为线程执行体,并且有返回值。
- 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable对象(作为FutureTask的参数),FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
- 使用FutureTask对象作为 Thread 对象的 target 创建并启动新线程
- 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
线程的状态:
-
创建(new)状态: 准备好了一个多线程的对象
线程创建之后,不会立即进入就绪状态(new后的状态为创建状态)。因为线程的运行需要一些条件(比如内存资源,程序计数器、Java栈、本地方法栈等,这些都是线程私有的,需要为其分配一定的内存空间),只有线程运行需要的所有条件满足了,才进入就绪状态。
就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度
运行(running)状态: 执行run()方法
-
阻塞(blocked)状态: 暂时停止执行
多个原因导致,用户主动让线程等待,或者被同步块给阻塞等事件
-
终止(dead)状态: 线程销毁,退出run方法
阻塞分为三种:
-
等待
运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法或者中断才能被唤醒
-
超时等待
不同于等待,到达一定时间将返回,包含wait(...)、sleep(...)、join(...)。
-
阻塞
运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。
sleep和wait的区别:
- sleep是Thread类的静态方法,wait是Object类中定义的方法.
- Thread.sleep不会导致锁行为的改变,如果当前线程是拥有锁的, 那么Thread.sleep不会释放锁;调用wait方法会导致线程放弃对象的锁,进入对象的等待池(wait pool)
- 调用sleep方法,当前线程会暂停执行一段时间,时间到了回到就绪状态。调用wait后,需要别的线程执行notify/notifyAll才能够重新获得CPU(只有调用对象的notify/notifyAll才能唤醒等待池中的线程进入等锁池(lock pool))
- wait方法必须在synchronized块中
上下文切换:
实际上就是存储和恢复CPU状态的过程,线程切换需要记录程序计数器、CPU寄存器状态等数据,它使得线程执行能够从中断点恢复执行。
部分方法:
currentThread():
返回代码段正在被哪个线程调用的信息。
sleep(...):
两个重载版本:
sleep(long millis) //参数为毫秒
sleep(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒
调用sleep方法,如果此线程持有一对象的锁,其他线程也无法访问这个对象;sleep方法是让当前正在运行的线程休眠,也就是currentThread方法返回的对象
yield():
让当前线程交出CPU权限,跟sleep类似,不会释放对象锁,但是yield不能控制交出CPU的时间。另外,yield方法只能让拥有>=优先级的线程有获取CPU执行时间的机会。
调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间。
sleep()和yield()的区别
- sleep切换其他线程不会考虑优先级,yield只会给相同优先级或更高优先级的线程以运行的机会。
- sleep后转入阻塞状态,yield后转入就绪状态
- sleep()抛出InterruptedException异常,yield无
对象方法:
-
start()
启动新的线程
-
run()
不需要用户调用,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。继承Thread类必须重写run方法
-
getId()
取得线程的唯一标识
public static void main(String[] args) { Thread t= Thread.currentThread(); System.out.println(t.getName()+" "+t.getId()); }
-
isAlive()
判断当前线程是否处于活动状态(run或者ready)
-
join()
很多情况下,主线程创建并启动线程,若子线程需要进行耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。
public class Thread4 extends Thread{ public Thread4(String name) { super(name); } public void run() { for (int i = 0; i < 5; i++) { System.out.println(getName() + " " + i); } } public static void main(String[] args) throws InterruptedException { // 启动子进程 new Thread4("new thread").start(); for (int i = 0; i < 10; i++) { if (i == 5) { Thread4 th = new Thread4("joined thread"); th.start(); th.join(); } System.out.println(Thread.currentThread().getName() + " " + i); } } }
结果:
main 0 main 1 main 2 main 3 main 4 new thread 0 new thread 1 new thread 2 new thread 3 new thread 4 joined thread 0 joined thread 1 joined thread 2 joined thread 3 joined thread 4 main 5 main 6 main 7 main 8 main 9
可见,main主进程等待joined thread线程先执行完了才结束。
-
getName和setName
得到或者设置线程名称。
-
getPriority和setPriority
获取和设置线程优先级。Java中,线程的优先级分为1~10这10个等级。线程优先级是决定线程需要多或者少分配一些CPU资源的线程属性,针对频繁阻塞(休眠或者I/O操作)的线程应该有较高优先级,偏重计算的(需要较多CPU时间或运算)的线程则设置较低优先级
然而,操作系统也可以不理会Java线程对优先级的设定。
源码比较简单:
public final void setPriority(int newPriority) { ThreadGroup g; checkAccess(); if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { throw new IllegalArgumentException(); } if((g = getThreadGroup()) != null) { if (newPriority > g.getMaxPriority()) { newPriority = g.getMaxPriority(); } setPriority0(priority = newPriority); } }
线程优先级特性:
- 继承性
A线程启动B线程,则B线程的优先级与A是一样的。
- 规则性
高优先级的线程总是大部分先执行完,但不代表高优先级线程全部先执行完。
- 随机性
优先级较高的线程不一定每一次都先执行完。
一般地,优先级高的会运行稍快。
-
setDaemon和isDaemon
设置线程是否成为守护线程和判断线程是否是守护线程,需要在线程启动之前设置。
守护进程与用户进程的区别:守护线程依赖于创建它的线程,而用户线程则不依赖。好比在main中创建了一个守护进程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。GC就是守护进程。
停止线程:
停止线程是在多线程开发时很重要的技术点。可以使用Thread.stop()方法,但最好不用它,因为该方法是不安全的,已被弃用。有3种方法停止线程:
run方法完成后线程终止
-
stop
stop和suspend(停止)及resume(继续)一样,都是作废过期的方法,使用他们可能产生不可预料的结果。suspend和resume方法会导致对公共同步对象的独占和由于暂停导致对数据的不同步。
该方法将会抛出java.lang.ThreadDeath异常,不需要显示捕捉;使用此方法将会导致线程的清理工作得不到完成,而且会对对象进行解锁,可能导致数据不一致问题。
interrupt方法中断线程,但这个不会终止一个正在运行的线程,只是在当前线程打了一个停止的标记。需要加入一个判断才可以完成线程的停止。
-
判断线程是否停止:
Thread提供了两种方法:
- this.interrupted():静态方法,测试当前线程是否已经中断,但是,调用此函数将会清除线程的中断状态。
- this.isInterrupted():测试线程是否已经中断,调用此函数不会清除中断状态
-
真正的线程停止:
比较常用的方法
-
加入判断this.interrupted获取标记,如果当前线程被标记需要停止,抛出异常,在后面捕获异常即可;有一些特殊的情况:
- 在sleep之后interrupt(sleep会自动要求抛出InterruptedException),在catch代码块用isInterrupted进行判断的时候会发现停止状态值会被自动清除
- 先interrupt,再sleep,也会进入异常
-
使用return停止线程
不过使用return停止线程可能造成代码污染(多个return),建议还是使用异常,因为catch能对异常信息进行处理,方便控制程序
-
wait和notify概览
参考:
http://www.importnew.com/21136.html