几个多线程概念的介绍
线程状态转换

- 新建 (new): 新创建一个线程对象
- 可运行 (runnable):线程对象创建后,其他线程(比如 main 线程)调用了该对象的 start() 方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取 cpu 的使用权 。
- 运行 (running):可运行状态(runnable) 的线程获得了 cpu 时间片(timeslice) ,执行程序代码。
- 阻塞 (block):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice,暂时停止运行。直到线程进入可运行(runnable) 状态,才有机会再次获得 cpu timeslice 转到运行 (running) 状态。阻塞的情况分三种:
- 等待阻塞:运行 (running) 的线程执行 o.wait()方法,JVM 会把该线程放入等待队列 (waitting queue) 中。
- 同步阻塞:运行 (running) 的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池 (lock pool) 中。
- 其他阻塞:运行 (running) 的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep() 状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入可运行 (runnable) 状态。
- 死亡 (dead):线程 run()、main() 方法执行结束,或者因异常退出了 run() 方法,则该线程结束生命周期。死亡的线程不可再次复生。
新建线程
Thread thread = new Thread();
thread.start();
这样就开启了一个线程。 有一点需要注意的是
Thread thread = new Thread();
thread.run();
直接调用 run 方法是无法开启一个新线程的。 start 方法其实是在一个新的操作系统线程上面去调用 run 方法。换句话说,直接调用 run 方法而不是调用 start 方法的话,它并不会开启新的线程,而是在调用 run 的当前的线程当中执行你的操作。
Thread thread = new Thread("t1"){
@Override
public void run(){
System.out.println(Thread.currentThread().getName());
}
};
thread.start();
如果调用 start,则输出是 t1
Thread thread = new Thread("t1"){
@Override
public void run(){
System.out.println(Thread.currentThread().getName());
}
};
thread.run();
如果是 run, 则输出 main。(直接调用 run 其实就是一个普通的函数调用而已,并没有达到多线程的作用) run 方法的实现有两种方式
第一种方式,直接覆盖 run 方法,就如刚刚代码中所示,最方便的用一个匿名类就可以实现。
Thread thread = new Thread("t1")
{
@Override
public void run()
{
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName());
}
};
第二种方式
# CreateThread3() 实现了 Runnable 接口。
Thread t1=new Thread(new CreateThread3());
终止线程
Thread.stop() 不推荐使用。它会释放所有 monitor
在源码中已经明确说明 stop 方法被 Deprecated,在 Javadoc 中也说明了原因。 原因在于 stop 方法太过”暴力”了,无论线程执行到哪里,它将会立即停止掉线程。
当写线程得到锁以后开始写入数据,写完 id = 1,在准备将 name = 1 时被 stop, 释放锁。读线程获得锁进行读操作,读到的 id 为 1,而 name 还是 0,导致了数据不一致。 最重要的是这种错误不会抛出异常,将很难被发现。
线程中断
public void Thread.interrupt() // 中断线程
public boolean Thread.isInterrupted() // 判断是否被中断
public static boolean Thread.interrupted() // 判断是否被中断,并清除当前中断状态
Java 的中断是一种协作机制。也就是说调用线程对象的 interrupt 方法并不一定就中断了正在运行的线程,它只是要求线程自己在合适的时机中断自己。每个线程都有一个 boolean 的中断状态(不一定就是对象的属性,事实上,该状态也确实不是 Thread 的字段),interrupt 方法仅仅只是将该状态置为 true。对于非阻塞中的线程, 只是改变了中断状态, 即 Thread.isInterrupted() 将返回 true,并不会使程序停止;
优雅的终止线程
public void run(){
while(true){
if(Thread.currentThread().isInterrupted()){
System.out.println("Interruted!");
break;
}
Thread.yield();
}
}
对于可取消的阻塞状态中的线程, 比如等待在这些函数上的线程, Thread.sleep(), Object.wait(), Thread.join(), 这个线程收到中断信号后, 会抛出 InterruptedException, 同时会把中断状态置回为 false.
对于取消阻塞状态中的线程:
public void run(){
while(true){
if(Thread.currentThread().isInterrupted()){
System.out.println("Interruted!");
break;
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println("Interruted When Sleep");
// 设置中断状态,抛出异常后会清除中断标记位
Thread.currentThread().interrupt();
}
Thread.yield();
}
}
线程挂起
挂起(suspend)和继续执行(resume)线程
- suspend() 不会释放锁
- 如果加锁发生在 resume() 之前 ,则死锁发生
这两个方法都是 Deprecated 方法,不推荐使用。 原因在于,suspend 不释放锁,因此没有线程可以访问被它锁住的临界区资源,直到被其他线程 resume。因为无法控制线程运行的先后顺序,如果其他线程的 resume 方法先被运行,那则后运行的 suspend,将一直占有这把锁,造成死锁发生。
使用代码模拟:
public class Test{
static Object u = new Object();
static TestSuspendThread t1 = new TestSuspendThread("t1");
static TestSuspendThread t2 = new TestSuspendThread("t2");
public static class TestSuspendThread extends Thread{
public TestSuspendThread(String name){
setName(name);
}
@Override
public void run(){
synchronized (u){
System.out.println("in " + getName());
Thread.currentThread().suspend();
}
}
}
public static void main(String[] args) throws InterruptedException{
t1.start();
Thread.sleep(100);
t2.start();
t1.resume();
t2.resume();
t1.join();
t2.join();
}
}
让 t1,t2 同时争夺一把锁,争夺到的线程 suspend,然后再 resume,按理来说,应该某个线程争夺后被 resume 释放了锁,然后另一个线程争夺掉锁,再被 resume。
in t1
in t2
说明两个线程都争夺到了锁,但是控制台的红灯还是亮着的,说明 t1,t2 一定有线程没有执行完。
join 和 yeild
yeild 是个 native 静态方法,这个方法是想把自己占有的 cpu 时间释放掉,然后和其他线程一起竞争 (注意 yeild 的线程还是有可能争夺到 cpu,注意与 sleep 区别)。在 javadoc 中也说明了,yeild 是个基本不会用到的方法,一般在 debug 和 test 中使用。
join 方法的意思是等待其他线程结束,就如 suspend 那节的代码,想让主线程等待 t1,t2 结束以后再结束。没有结束的话,主线程就一直阻塞在那里。
public class Test{
public volatile static int i = 0;
public static class AddThread extends Thread{
@Override
public void run(){
for (i = 0; i < 10000000; i++)
;
}
}
public static void main(String[] args) throws InterruptedException{
AddThread at = new AddThread();
at.start();
at.join();
System.out.println(i);
}
}
如果把上述代码的 at.join 去掉,则主线程会直接运行结束,i 的值会很小。如果有 join, 打印出的 i 的值一定是 10000000。
join 的本质:
while(isAlive()) {
wait(0);
}
join() 方法也可以传递一个时间,意为有限期地等待,超过了这个时间就自动唤醒。 这样就有一个问题,谁来 notify 这个线程呢,在 thread 类中没有地方调用了 notify? 在 javadoc 中,找到了相关解释。当一个线程运行完成终止后,将会调用 notifyAll 方法去唤醒等待在当前线程实例上的所有线程, 这个操作是 jvm 自己完成的。 所以 javadoc 中还给了我们一个建议,不要使用 wait 和 notify/notifyall 在线程实例上。因为 jvm 会自己调用,有可能与你调用期望的结果不同。
守护线程
- 在后台默默地完成一些系统性的服务,比如垃圾回收线程、JIT 线程就可以理解为守护线程。
- 当一个 Java 应用内,所有非守护进程都结束时,Java 虚拟机就会自然退出。
开启守护进程:
Thread t=new DaemonT();
t.setDaemon(true);
t.start()
线程优先级
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
线程优先级只是表示获取锁的概率大小
基本的线程同步操作
synchronized 有三种加锁方式:
- 指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
- 直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
- 直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。
作用于实例方法,则不要 new 两个不同的实例 作用于静态方法,只要类一样就可以了,因为加的锁是类.class,可以 new 两个不同实例。
wait 和 notify 的用法: 用什么锁住,就用什么调用 wait 和 notify
