Java的线程分为5种状态:创建、就绪、运行、阻塞和死亡。
创建:
在java种创建线程的方式有两种,一种是通过继承Thread类并且重写run方法,run方法中执行的代码便是线程执行的代码。另一种是通过实现Runnable接口,并将该接口实例传入一个Thread实例。通过对Thread的引用调用start()方法,即可让线程进入就绪状态。如果直接调用run方法,并不会生成线程,而是在当前线程中把run()当做一个普通方法执行。
public class Thread1 extends Thread{
/*
* 实现线程的方法一:通过继承Thread并覆盖run()方法来实现多线程。
*/
@Override
public void run(){
System.out.println(Thread.currentThread().getName()+"线程开始!");
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
try{
sleep((int)Math.random()*10);
}catch(InterruptedException e){
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"线程结束!");
}
}
public class Thread2 implements Runnable{
/*
* 实现线程的方法二:通过实现Runnable接口来实现多线程
* 实现Runnable接口比继承Thread类所具有的优势:
* 1):适合多个相同的程序代码的线程去处理同一个资源
* 2):可以避免java中的单继承的限制
* 3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
*/
@Override
public void run(){
System.out.println(Thread.currentThread().getName()+"线程开始!");
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
try{
Thread.sleep((int)Math.random()*10);
}catch(InterruptedException e){
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"线程结束!");
}
}
就绪:
处于就绪状态的线程随时可以被JVM的线程调度器调度,进入运行状态。对于处于就绪状态的线程,我们并不能对他们被调度的顺序进行任何估计,也就是说,线程的执行顺序是不可预测的。处于运行状态的线程,通过调用yield()方法,可以返回到就绪状态,然而它有可能瞬间被再次调度。yield()方法把运行机会让给了同等优先级的其他线程。
public class ThreadYield extends Thread{
@Override
public void run(){
for (int i = 1; i <= 50; i++) {
System.out.println("" +Thread.currentThread().getName() + "-----" + i);
// 当i==25时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)
if (i==25) {
this.yield();
}
}
}
}
public class ThreadYieldTest {
/*
* Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。
* 该方法让当前线程回到可运行状态,以允许其他具有相同优先级的线程获得运行机会。
* 但是实际中无法保证yield()达到让步目的,因为当前线程有可能被线程调度程序再次选中。
*/
public static void main(String[] args){
ThreadYield thread1=new ThreadYield();
ThreadYield thread2=new ThreadYield();
thread1.start();
thread2.start();
}
}
运行:
处于运行状态的线程随时有可能被线程调度器换下,进入到就绪状态。想要规定线程的顺序,需要调用join方法,对某个线程 的调用join方法,则主线程会阻塞到该线程执行完后再继续执行。或者使用一种叫做锁的机制(下文会提及)。当一个线程完成它run()里面的所有工作时,线程会自动死亡。调用sleep(),线程会进入休眠,并且在一段时间内不会被再度调用。睡眠时间过后,线程才再次进入就绪队列中。
public class ThreadJoinTest {
/*
* join是Thread类的一个方法,作用是等待该线程终止。例如对子线程A调用join()方法,
* 主线程将等待子线程A终止后才能继续后面的代码。
*/
public static void main(String[] args){
System.out.println("主线程开始!");
Thread1 thread1=new Thread1();
Thread1 thread2=new Thread1();
thread1.start();
thread2.start();
try{
thread1.join();
}catch(InterruptedException e){
e.printStackTrace();
}
try{
thread2.join();
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("主线程结束!");
}
}
死亡:
线程因为代码执行完毕而正常结束自身线程,或者因为某些异常而结束线程。
public class ThreadInterrupt extends Thread{
/*
* wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。
* 如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正
* 在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中
* 直接return即可安全地结束线程。
* 需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。
* 对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛
* 出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就
* 会立刻抛出InterruptedException 。
*/
@Override
public void run(){
try{
for(int i=0;i<50;i++){
System.out.println("i="+i);
Thread.sleep(100);
}
}catch(InterruptedException e){
System.out.println("线程被终结!!!!");
//return;
}
}
}
锁机制
在介绍阻塞之前,先了解一下java的锁机制。java的锁机制通过synchronized来实现。所有的Java对象都有一个与synchronzied关联的监视器对象(monitor),允许线程在该监视器对象上进行加锁和解锁操作。
a、静态方法:Java类对应的Class类的对象所关联的监视器对象。
b、实例方法:当前对象实例所关联的监视器对象。
c、代码块:代码块声明中的对象所关联的监视器对象。
当锁被释放,对共享变量的修改会写入主存。
public class ThreadSynchronizedTest {
private static int value = 0;
//获得该类对应的Class类的对象所关联的监视器的锁
public synchronized static int getNext(){
return value++;
}
//获得当前实例所关联的监视器的锁
public synchronized int getNext2(){
return value++;
}
//获得当前实例所关联的监视器的锁
public int getNext3(){
synchronized(this){
return value++;
}
}
public static void main(String[] args){
for(int i=0;i<3;i++){
new Thread(new Runnable(){
@Override
public void run(){
ThreadSynchronizedTest x=new ThreadSynchronizedTest();
System.out.println("value="+x.getNext());
System.out.println("value="+x.getNext2());
System.out.println("value="+x.getNext3());
}
}).start();
}
}
}
阻塞:
阻塞跟Obj.wait(),Obj.notify()方法有关。当调用wait方法时,线程释放对象锁,进入阻塞状态,直到其他线程唤醒它。
Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用
Obj.notify()作用:对对象锁的唤醒操作。notify()调用后,并不是马上就释放对象锁的,而
是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线
程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。
Obj.wait()作用:线程在获取对象锁后,主动释放对象锁,同时本线程休眠,直到有其它线程调用
对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。
下面我们通过一道题目来加深理解。
问题:建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。
代码如下:
public class ThreadPrintABCTest {
/*
* 建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。
*
*/
public static void main(String[] args) throws Exception {
Object a = new Object();
Object b = new Object();
Object c = new Object();
ThreadPrintABC pa = new ThreadPrintABC("A", c, a);
ThreadPrintABC pb = new ThreadPrintABC("B", a, b);
ThreadPrintABC pc = new ThreadPrintABC("C", b, c);
new Thread(pa).start();
Thread.sleep(100); //确保按顺序A、B、C执行
new Thread(pb).start();
Thread.sleep(100);
new Thread(pc).start();
Thread.sleep(100);
}
}
public class ThreadPrintABC implements Runnable{
private String data;
private Object pre;
private Object self;
public ThreadPrintABC(String data,Object pre,Object self){
this.data=data;
this.pre=pre;
this.self=self;
}
@Override
public void run(){
int count=10;
while(count>0){
synchronized(pre){
synchronized(self){
if(data=="C"){
System.out.println(data);
}else{
System.out.print(data);
}
count--;
self.notify();
}
try{
pre.wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
}
死锁:
两个或两个以上的线程在执行过程当中,由于竞争资源或者彼此之间通信而造成的一种阻塞现象。比如,当线程A调用wait()方法等待线程B的唤醒,而线程B同时也调用wait方法等待线程A的唤醒,这时两个线程将陷入僵持状态,永远处在阻塞状态, 成为死锁进程,即两个线程永远也不会被执行。
sleep方法与wait方法的区别及细节:
sleep()睡眠时,保持对象锁,仍然占有该锁;而wait()睡眠时,释放对象锁。
sleep()使当前线程进入停滞状态(阻塞当前线程),让出CPU的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;
sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。
在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。
wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问;
wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。
wiat()必须放在synchronizedblock中,否则扔出”java.lang.IllegalMonitorStateException“异常。