0、概念 - Java线程具有五种基本状态
- 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new Thread();
- 就绪状态(Runnable):当调用线程对象的start()方法(如:t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
- 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口。
- 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
- 1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
- 2.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
- 3.其他阻塞 – 通过调用线程的sleep() 或join() 或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
- 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
1、基础 - 常用方法
- currentThread( ) :获取当前线程。注意Thread.currentThread()与this的区别。
- isAlive( ) :如果此线程已经开始但尚未正常终止或中止,则为 true,否则为 false。
- sleep( ) :使正在执行的线程休眠,不释放持有的锁。
- getId( ) :取得线程的唯一标识。
- interrupt( ) : 中断线程(并不是直接中断,仅仅是在当前线程打了一个停止的标记,要配合interrupted( )或isInterrupted( )使用。interrupted( )测试当前线程是否是停止状态,执行后具有将状态标志清除为false的功能,isInterrupted( )测试线程Thread对象是否是停止状态)。
- stop( ) :暴力停止线程,以弃用(使用stop( )释放锁将会给数据造成不一致性的结果)。
- yield( ) : 放弃当前的CPU资源,由运行状态变为就绪状态,而不是阻塞状态。不能由用户指定暂停多长时间,并且yield( )方法只能让同优先级的线程有执行的机会。
- setPriority( ) : 设置线程的优先级。getPriority( )获得线程的优先级。优先级分为1~10这十个等级,默认为5,若大于10或小于1,JDK抛出IllegalArgumentException( )异常。线程的优先级具有继承性,若A线程启动B线程,则B线程的优先级与A一样。
2、入门 - 并发访问(synchronized)
- 修饰一个代码块
- synchronized(this) :获得this对象锁
public void XXX(){
synchronized (this){};
}
- synchronized(非this对象) :获得非this对象锁
public void XXX(){
synchronized (非this对象){};
}
- 修饰普通方法 :获得this对象锁
synchronized public void XXX(){
}
- 修饰静态方法: 获得类锁
synchronized public static void XXX(){
}
- 修饰类: 获得类锁
public void XXX(){
synchronized (XXX.class){};
}
当多个线程同时访问同一个对象加X锁的方法或代码块时,只有一个线程能执行此对象中加了X锁的内容,而加了其他锁或者没加锁的方法或代码块不受约束。
- 例如:某个代码块加了synchronized(this)
- 当一个线程获得this锁进入此代码块后,其他线程不能访问synchronized修饰的普通方法、synchronized(this)修饰的其他代码块,因为他们都是this锁;
- 但是其他线程可以访问synchronized(非this对象) 修饰的代码块、没加锁的代码块或方法、synchronized修饰的静态方法、synchronized修饰类,因为他们都没有加this锁。
当多个线程同时访问同一个对象加类锁的方法或代码块时,只有一个线程能执行此对象中加了类锁的内容,而加了其他锁或者没加锁的方法或代码块不受约束。!!!同时:类锁对该类的所有对象实例都起作用,当一个线程获得该类的某个对象实例的类锁后,其他线程就算访问的是该类的不同对象实例,也会阻塞等待类锁。
3、进阶 - 线程间通信
- wait( )方法
- 执行wait:当线程执行wait()时,会把当前的锁释放,然后让出CPU,进入等待池。
- 结束wait:
- notify( )方法:会从等待池随机选择一个呈wait状态的线程,对其发出通知notify,并使其等待获取该对象的对象锁。需要注意的是:执行notify( )方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放该对象锁,呈wait状态的线程才能获取该对象锁。用一句话总结:wait()使线程停止运行,notify()使线程继续运行。
- notifyAll( )方法:使等待池中的所有线程运行。
- wait( time )方法:若超过等待时间time后,线程自动唤醒。
- 管道
- JDK提供了4个类使线程间可以进行通信:
//字节流
PipedInputStream PipedOutputStream
//字符流
PipedReader PipedWriter
- join( )方法
- join( )的作用是等待线程对象销毁。例如:主线程创建并启动子线程,子线程要进行大量的耗时运算,主线程往往早于子线程结束。若想让主线程等待子线程执行完后再结束,就要用到join( )方法。 join( time )方法是使用wait( time )来实现的。
- 类ThreadLocal
- ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题:
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。