Java之线程的五大状态及其常用方法(上)

1. 线程的五大状态及其转换

线程的五大状态分别为:创建状态(New)、就绪状态(Runnable)、运行状态(Running)、阻塞状态(Blocked)、死亡状态(Dead)。

下面画出线程五大状态之间的关系图:


(1)新建状态:即单纯地创建一个线程,创建线程有三种方式,在我的博客:线程的创建,可以自行查看!

(2)就绪状态:在创建了线程之后,调用Thread类的start()方法来启动一个线程,即表示线程进入就绪状态!

(3)运行状态:当线程获得CPU时间,线程才从就绪状态进入到运行状态!

(4)阻塞状态:线程进入运行状态后,可能由于多种原因让线程进入阻塞状态,如:调用sleep()方法让线程睡眠,调用wait()方法让线程等待,调用join()方法、suspend()方法(它现已被弃用!)以及阻塞式IO方法。

(5)死亡状态:run()方法的正常退出就让线程进入到死亡状态,还有当一个异常未被捕获而终止了run()方法的执行也将进入到死亡状态!

2. 设置或获取多线程的线程名称的方法

由于在一个进程中可能有多个线程,而多线程的运行状态又是不确定的,即不知道在多线程中当前执行的线程是哪个线程,所以在多线程操作中需要有一个明确的标识符标识出当前线程对象的信息,这个信息往往通过线程的名称来描述。在Thread类中提供了一些设置或获取线程名称的方法:

(1)创建线程时设置线程名称:

public Thread(Runnable target,String name)

(2)设置线程名称的普通方法:

public final synchronized void setName(String name)

(3)取得线程名称的普通方法:

public final String getName()

下面将在代码中使用以上方法,作为演示:

class MyThread implements Runnable{
	@Override
	public void run() {
		for(int i=0;i<5;i++)
		{
			//currentThread()方法用于取得当前正在JVM中运行的线程
			//使用getName()方法,用于获取线程的名称
			System.out.println("当前线程:"+Thread.currentThread().getName()+"-----i="+i);
		}
	}
}
public class Test1 {
	public static void main(String[] args){
		//创建线程对象thread1且没有设置线程名称
		MyThread myThread1=new MyThread();
		Thread thread1=new Thread(myThread1);
		thread1.start();
		//创建线程对象thread2且使用setName设置线程名称
		MyThread myThread2=new MyThread();
		Thread thread2=new Thread(myThread2);
		thread2.setName("线程2");
		thread2.start();
		//创建线程对象thread3并在创建线程时设置线程名称
		MyThread myThread3=new MyThread();
		Thread thread3=new Thread(myThread3,"线程3");
		thread3.start();
	}
}

某次运行结果如下所示:

当前线程:Thread-0-----i=0
当前线程:Thread-0-----i=1
当前线程:Thread-0-----i=2
当前线程:线程3-----i=0
当前线程:线程2-----i=0
当前线程:线程2-----i=1
当前线程:线程2-----i=2
当前线程:线程2-----i=3
当前线程:线程3-----i=1
当前线程:Thread-0-----i=3
当前线程:Thread-0-----i=4
当前线程:线程3-----i=2
当前线程:线程3-----i=3
当前线程:线程3-----i=4
当前线程:线程2-----i=4

通过上述代码及其运行结果可知:

(1)若没有手动设置线程名称时,会自动分配一个线程的名称,如线程对象thread1自动分配线程名称为Thread-0。

(2)多线程的运行状态是不确定的,不知道下一个要执行的是哪个线程,这是因为CPU以不确定方式或以随机的时间调用线程中的run()方法。

(3)需要注意的是,由于设置线程名称是为了区分当前正在执行的线程是哪一个线程,所以在设置线程名称时应避免重复!

3. 线程休眠------sleep()方法

线程休眠:指的是让线程暂缓执行,等到预计时间之后再恢复执行。

(1)线程休眠会交出CPU,让CPU去执行其他的任务。

(2)调用sleep()方法让线程进入休眠状态后,sleep()方法并不会释放锁,即当前线程持有某个对象锁时,即使调用sleep()方法其他线程也无法访问这个对象。

(3)调用sleep()方法让线程从运行状态转换为阻塞状态;sleep()方法调用结束后,线程从阻塞状态转换为可执行状态。

sleep()方法:

public static native void sleep(long millis) throws InterruptedException;

从上面方法参数中可以看出sleep()方法的休眠时间是以毫秒作为单位。

关于sleep()方法的操作如下:

class MyThread implements Runnable{
	@Override
	public void run() {
		for(int i=0;i<5;i++)
		{
			//使用Thread类的sleep()方法,让线程处于休眠状态
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("当前线程:"+Thread.currentThread().getName()+"-----i="+i);
		}
	}
}
public class Test1 {
	public static void main(String[] args){
		MyThread myThread=new MyThread();
		//利用myThread对象分别创建三个线程
		Thread thread1=new Thread(myThread);
		thread1.start();
		Thread thread2=new Thread(myThread);
		thread2.start();
		Thread thread3=new Thread(myThread);
		thread3.start();
	}
}

某次运行结果如下所示:

当前线程:Thread-2-----i=0
当前线程:Thread-1-----i=0
当前线程:Thread-0-----i=0
当前线程:Thread-2-----i=1
当前线程:Thread-0-----i=1
当前线程:Thread-1-----i=1
当前线程:Thread-2-----i=2
当前线程:Thread-1-----i=2
当前线程:Thread-0-----i=2
当前线程:Thread-2-----i=3
当前线程:Thread-1-----i=3
当前线程:Thread-0-----i=3
当前线程:Thread-2-----i=4
当前线程:Thread-0-----i=4
当前线程:Thread-1-----i=4

注:

(1)通过运行代码进行观察,发现运行结果会等待一段时间,这就是sleep()方法让原本处于运行状态的线程进入了休眠,从而进程的状态从运行状态转换为阻塞状态。

(2)以上代码创建的三个线程肉眼观察,发现它们好像是同时进入休眠状态,但其实并不是同时休眠的。

4. 线程让步------yield()方法

线程让步:暂停当前正在执行的线程对象,并执行其他线程。

(1)调用yield()方法让当前线程交出CPU权限,让CPU去执行其他线程。

(2)yield()方法和sleep()方法类似,不会释放锁,但yield()方法不能控制具体交出CPU的时间。

(3)yield()方法只能让拥有相同优先级的线程获取CPU执行的机会。

(4)使用yield()方法不会让线程进入阻塞状态,而是让线程从运行状态转换为就绪状态,只需要等待重新获取CPU执行的机会。

yield()方法:

public static native void yield();

关于yield()方法的操作如下:

class MyThread implements Runnable{
	@Override
	public void run() {
		for(int i=0;i<5;i++)
		{
			//使用Thread类的yield()方法
			Thread.yield();
			System.out.println("当前线程:"+Thread.currentThread().getName()+"-----i="+i);
		}
	}
}
public class Test1 {
	public static void main(String[] args){
		MyThread myThread=new MyThread();
		//利用myThread对象分别创建三个线程
		Thread thread1=new Thread(myThread);
		thread1.start();
		Thread thread2=new Thread(myThread);
		thread2.start();
		Thread thread3=new Thread(myThread);
		thread3.start();
	}
}

某次运行结果如下所示:

当前线程:Thread-0-----i=0
当前线程:Thread-0-----i=1
当前线程:Thread-0-----i=2
当前线程:Thread-0-----i=3
当前线程:Thread-0-----i=4
当前线程:Thread-1-----i=0
当前线程:Thread-2-----i=0
当前线程:Thread-2-----i=1
当前线程:Thread-2-----i=2
当前线程:Thread-2-----i=3
当前线程:Thread-2-----i=4
当前线程:Thread-1-----i=1
当前线程:Thread-1-----i=2
当前线程:Thread-1-----i=3
当前线程:Thread-1-----i=4

5. 等待线程终止------join()方法

等待线程终止:指的是如果在主线程中调用该方法时就会让主线程休眠,让调用join()方法的线程先执行完毕后再开始执行主线程。

join()方法:

 public final void join() throws InterruptedException {
        join(0);
    }

注:上面的join()方法是不带参数的,但join()方法还可以带参数,下去自行了解!

关于join()方法的操作如下:

class MyThread implements Runnable{
	@Override
	public void run() {
		for(int i=0;i<2;i++)
		{
			//使用Thread类的sleep()方法
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("当前线程:"+Thread.currentThread().getName()+"-----i="+i);
		}
	}
}
public class Test1 {
	public static void main(String[] args) throws InterruptedException{
		MyThread myThread=new MyThread();
		Thread thread1=new Thread(myThread,"自己创建的线程");
		thread1.start();
		System.out.println("主线程:"+Thread.currentThread().getName());
		//线程对象thread1调用join()方法
		thread1.join();
		System.out.println("代码结束");
	}
}

运行结果如下所示:

主线程:main
当前线程:自己创建的线程-----i=0
当前线程:自己创建的线程-----i=1
代码结束

若不调用join()方法的话,运行结果如下所示:

主线程:main
代码结束
当前线程:自己创建的线程-----i=0
当前线程:自己创建的线程-----i=1

故通过两个运行结果可以更加深刻地感受到调用join()方法后的作用!调用join()方法和不调用join()方法的区别!

6. 线程停止

多线程中停止线程有三种方式:

(1)设置标记位,让线程正常停止。

class MyThread implements Runnable{
	//设置标记位
	private boolean flag=true;
	public void setFlag(boolean flag) {
		this.flag = flag;
	}
	@Override
	public void run() {
		int i=0;
		while(flag)
		{
			System.out.println("第"+(i++)+"次执行-----"+"线程名称:"+Thread.currentThread().getName());
		}
	}
}
public class Test1 {
	public static void main(String[] args) throws InterruptedException{
		MyThread myThread=new MyThread();
		Thread thread1=new Thread(myThread,"自己创建的线程");
		thread1.start();
		//让主线程sleep一毫秒
		Thread.sleep(1);
		//修改标记位的值,让自己创建的线程停止
		myThread.setFlag(false);
		System.out.println("代码结束");
	}
}

运行结果如下所示:

第0次执行-----线程名称:自己创建的线程
第1次执行-----线程名称:自己创建的线程
第2次执行-----线程名称:自己创建的线程
第3次执行-----线程名称:自己创建的线程
第4次执行-----线程名称:自己创建的线程
第5次执行-----线程名称:自己创建的线程
第6次执行-----线程名称:自己创建的线程
第7次执行-----线程名称:自己创建的线程
第8次执行-----线程名称:自己创建的线程
第9次执行-----线程名称:自己创建的线程
第10次执行-----线程名称:自己创建的线程
第11次执行-----线程名称:自己创建的线程
第12次执行-----线程名称:自己创建的线程
第13次执行-----线程名称:自己创建的线程
第14次执行-----线程名称:自己创建的线程
第15次执行-----线程名称:自己创建的线程
第16次执行-----线程名称:自己创建的线程
第17次执行-----线程名称:自己创建的线程
第18次执行-----线程名称:自己创建的线程
第19次执行-----线程名称:自己创建的线程
第20次执行-----线程名称:自己创建的线程
第21次执行-----线程名称:自己创建的线程
第22次执行-----线程名称:自己创建的线程
第23次执行-----线程名称:自己创建的线程
第24次执行-----线程名称:自己创建的线程
第25次执行-----线程名称:自己创建的线程
第26次执行-----线程名称:自己创建的线程
代码结束

(2)使用stop()方法强制使线程退出,但是使用该方法不安全,已经被废弃了!

class MyThread implements Runnable{
	@Override
	public void run() {
		for(int i=0;i<100;i++)
		{
			System.out.println("线程名称:"+Thread.currentThread().getName()+"------i="+i);
		}
	}
}
public class Test1 {
	public static void main(String[] args) throws InterruptedException{
		MyThread myThread=new MyThread();
		Thread thread1=new Thread(myThread,"自己创建的线程");
		thread1.start();
		//让主线程sleep一毫秒
		Thread.sleep(1);
		//调用已被弃用的stop()方法去强制让线程退出
		thread1.stop();
		System.out.println("代码结束");
	}
}

某次运行结果如下所示:

线程名称:自己创建的线程------i=0
线程名称:自己创建的线程------i=1
线程名称:自己创建的线程------i=2
线程名称:自己创建的线程------i=3
线程名称:自己创建的线程------i=4
线程名称:自己创建的线程------i=5
线程名称:自己创建的线程------i=6
线程名称:自己创建的线程------i=7
线程名称:自己创建的线程------i=8
线程名称:自己创建的线程------i=9
线程名称:自己创建的线程------i=10
线程名称:自己创建的线程------i=11
线程名称:自己创建的线程------i=12
线程名称:自己创建的线程------i=13
线程名称:自己创建的线程------i=14
线程名称:自己创建的线程------i=15
线程名称:自己创建的线程------i=16
线程名称:自己创建的线程------i=17
线程名称:自己创建的线程------i=18
线程名称:自己创建的线程------i=19
线程名称:自己创建的线程------i=20
线程名称:自己创建的线程------i=21
线程名称:自己创建的线程------i=22
线程名称:自己创建的线程------i=23
线程名称:自己创建的线程------i=24
线程名称:自己创建的线程------i=25
线程名称:自己创建的线程------i=26
线程名称:自己创建的线程------i=27
线程名称:自己创建的线程------i=28
线程名称:自己创建的线程------i=29
线程名称:自己创建的线程------i=30
线程名称:自己创建的线程------i=31
线程名称:自己创建的线程------i=32
线程名称:自己创建的线程------i=33
线程名称:自己创建的线程------i=34
线程名称:自己创建的线程------i=35
线程名称:自己创建的线程------i=36
线程名称:自己创建的线程------i=37
线程名称:自己创建的线程------i=38
线程名称:自己创建的线程------i=39
线程名称:自己创建的线程------i=40
线程名称:自己创建的线程------i=41
线程名称:自己创建的线程------i=42
线程名称:自己创建的线程------i=43
线程名称:自己创建的线程------i=44
线程名称:自己创建的线程------i=45
线程名称:自己创建的线程------i=46
线程名称:自己创建的线程------i=47
线程名称:自己创建的线程------i=48
线程名称:自己创建的线程------i=49
线程名称:自己创建的线程------i=50
线程名称:自己创建的线程------i=51线程名称:自己创建的线程------i=51代码结束

从上述代码和运行结果可以看出,原本线程对象thread1的run()方法中应该执行100次语句“System.out.println("线程名称:"+Thread.currentThread().getName()+"------i="+i);”,但现在没有执行够100次,所以说stop()方法起到了让线程终止的作用。再从运行结果上可以看出,i=51被执行了两次且没有换行,这就体现了调用stop()方法的不安全性!

下面正式地解释stop()方法为什么不安全?

因为stop()方法会解除由线程获得的所有锁,当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程会立即停止,假如一个线程正在执行同步方法:

public synchronized void fun(){
	x=3;
	y=4;
}

由于方法是同步的,多线程访问时总能保证x,y被同时赋值,而如果一个线程正在执行到x=3;时,被调用的stop()方法使得线程即使在同步方法中也要停止,这就造成了数据的不完整性。故,stop()方法不安全,已经被废弃,不建议使用!

(3)使用Thread类的interrupt()方法中断线程。

class MyThread implements Runnable{
	@Override
	public void run() {
		int i=0;
		while(true)
		{
			//使用sleep()方法,使得线程由运行状态转换为阻塞状态
			try {
				Thread.sleep(1000);
				//调用isInterrupted()方法,用于判断当前线程是否被中断
				boolean bool=Thread.currentThread().isInterrupted();
				if(bool) {
					System.out.println("非阻塞状态下执行该操作,当前线程被中断!");
					break;
				}
				System.out.println("第"+(i++)+"次执行"+" 线程名称:"+Thread.currentThread().getName());
			} catch (InterruptedException e) {
				System.out.println("退出了!");
				//这里退出了阻塞状态,且中断标志bool被系统自动清除设置为false,所以此处的bool为false
				boolean bool=Thread.currentThread().isInterrupted();
				System.out.println(bool);
				//退出run()方法,中断进程
				return;
			}
		}
	}
}
public class Test1 {
	public static void main(String[] args) throws InterruptedException{
		MyThread myThread=new MyThread();
		Thread thread1=new Thread(myThread,"自己创建的线程");
		thread1.start();
		//让主线程sleep三秒
		Thread.sleep(3000);
		//调用interrupt()方法
		thread1.interrupt();
		 System.out.println("代码结束");
	}
}

运行结果如下所示 :

第0次执行 线程名称:自己创建的线程
第1次执行 线程名称:自己创建的线程
第2次执行 线程名称:自己创建的线程
代码结束
退出了!
false

(1)interrupt()方法只是改变中断状态而已,它不会中断一个正在运行的线程。具体来说就是,调用interrupt()方法只会给线程设置一个为true的中断标志,而设置之后,则根据线程当前状态进行不同的后续操作。

(2)如果线程的当前状态出于非阻塞状态,那么仅仅将线程的中断标志设置为true而已;

(3)如果线程的当前状态出于阻塞状态,那么将在中断标志设置为true后,还会出现wait()、sleep()、join()方法之一引起的阻塞,那么会将线程的中断标志位重新设置为false,并抛出一个InterruptedException异常。

(4)如果在中断时,线程正处于非阻塞状态,则将中断标志修改为true,而在此基础上,一旦进入阻塞状态,则按照阻塞状态的情况来进行处理。例如,一个线程在运行状态时,其中断标志设置为true之后,一旦线程调用了wait()、sleep()、join()方法中的一种,立马抛出一个InterruptedException异常,且中断标志被程序自动清除,重新设置为false。

总结:调用Thread类的interrupted()方法,其本质只是设置该线程的中断标志,将中断标志设置为true,并根据线程状态决定是否抛出异常。因此,通过interrupted()方法真正实现线程的中断原理是 :开发人员根据中断标志的具体值来决定如何退出线程。

        以上是我所介绍的关于线程的五大状态和常用方法,关于状态转换中的synchronized关键字以及wait()方法、notify()方法、notifyAll()方法在下一篇博客中介绍!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章