详述Java中的线程2——线程常用方法

目录

 

一、引入

二、线程常用方法:

(1)join方法:

(2)interrupt方法:

(3)currentThread方法:

(4)isAlive方法:

(5)setDaemon方法

(6)void setPriority(int newPriority)方法


一、引入

上一篇博客详述Java中的线程1——线程与进程我详述了Java中线程与进程,本片我就讲一下在Java多线程中常用的方法:

二、线程常用方法:

(1)join方法:

执行该方法的线程进入阻塞状态,直到调用该方法的线程结束后再由阻塞转为就绪状态

定义往这儿一搁,相比很多人看了也是一头雾水,我先把它通俗地解释一遍,再用代码演示,首先我们知道,线程的运行实际上是对CPU使用权的争夺,这就样就有了点儿排队的意思,join说白了就是插队,就是说是在调用它,谁就是在插队,在上篇博客中我提到,多线程中,每个大的方法实际上都是一个线程,那么那么调用该方法的线程写在哪个线程(代码块中)就是在插谁的队。也就是被插队的的进程要被阻塞,调用该方法的线程优先得到CPU使用的争夺权。(注意虽然优先得到CPU使用的争夺权,但不代表调用该方法的线程就一定能运行)。。

package Mars;

import java.util.Date;

public class Test {
	public static void main(String[] args) {
		TimeThread timeThread = new TimeThread();
		CountThread countThread = new CountThread(timeThread);
		
		countThread.start();//开启线程
		timeThread.start();//开启线程
		
	}
}

class CountThread extends Thread {// 计数线程

	TimeThread timeThread;

	CountThread(TimeThread timeThread) {
		this.timeThread = timeThread;
	}

	@Override
	public void run() {
		for (int i = 0; i < 15; i++) {
			System.out.println("计数:"+i);
			if (i == 5) {
				try {
					timeThread.join();//时间线程调用join()方法“插队”,计数线程被阻塞
					sleep(1000);// sleep()是Thread类中的静态方法,所以子类继承后可以直接调用,会使得线程的运行进入暂时地等待的状态,用参数设置等待时间,这里设置为1000毫秒
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

class TimeThread extends Thread {// 时间线程

	@Override
	public void run() {
		for (int i = 0; i < 15; i++) {
			System.out.println("时间: " + new Date());
			try {
				Thread.sleep(1000);// 该类不是Thread类的子类,所以需要用Thread类名调用
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
		}
	}

}

5~14行:程序运行,主线程开启,接着时间线程和计数线程开启。

16~38行:为了能让时间线程类对象能在计数线程馁调用join方法,我们要为计数线程类添加有参构造方法,以便计数线程创建线程对象是能将时间线程对象加入到计数线程中调用join方法。

28~30行:当计数线程记到5时,让时间线程插队。

40~55行:因为该程序有三个线程,主线程开启时间线程后便终止,而计数线程在插队后被阻塞,所以从调用join方法开始,时间线程会一直执行到其终止,计数线程才有机会继续运行。

运行结果:

详述Java中的线程2——线程常用方法_第1张图片

有结果可知,显然在线程刚开启时,计数线程就先抢占到了 CPU的使用权,之后就被时间线程“插队”,这样的结果比较直观,好理解。但两个线程运行所需时间都比较长时,完全会出现在时间线程“插队”前,两线程来回抢占CPU使用权的场景,可惜实例的输出结果太少,无法还原这一“戏剧性”的场景。

注意:

线程对象在调用join方法前必须先调用start方法,否则该线程永远不会进入执行状态。

package Mars;

import java.util.Date;

public class Test {
	public static void main(String[] args) {
		TimeThread timeThread = new TimeThread();
		CountThread countThread = new CountThread(timeThread);
		
		countThread.start();
//		timeThread.start();
		
	}
}

class CountThread extends Thread {// 计数线程1

	TimeThread timeThread;

	CountThread(TimeThread timeThread) {
		this.timeThread = timeThread;
	}

	@Override
	public void run() {
		for (int i = 0; i < 15; i++) {
			System.out.println("计数:"+i);
			if (i == 5) {
				try {
					timeThread.join();
					sleep(1000);// sleep()是Thread类中的静态方法,所以子类继承后可以直接调用,会使得线程的运行进入暂时地等待的状态,用参数设置等待时间,这里设置为1000毫秒
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

class TimeThread extends Thread {// 时间线程

	@Override
	public void run() {
		for (int i = 0; i < 15; i++) {
			System.out.println("时间: " + new Date());
			try {
				Thread.sleep(1000);// 该类不是Thread类的子类,所以需要用Thread类名调用
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
		}
	}

}

把第11行开启时间线程的语句注释掉,在看输出结果:

详述Java中的线程2——线程常用方法_第2张图片

可见,线程未开启的情况下,无法“插队”。

(2)interrupt方法:

结束线程在调用Object类的wait方法或该类的join方法、sleep方法过程中的阻塞状态,并在调用waitjoinsleep方法处产生InterruptedException异常。

package Mars;

import java.util.Date;

public class Test {
	public static void main(String[] args) {
		CountThread countThread = new CountThread();
		countThread.start();
		System.out.println("开启线程时时间"+new Date());
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		countThread.interrupt();
		
	}
}

class CountThread extends Thread {// 计数线程

	@Override
	public void run() {
		try {
			sleep(10000000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("执行输出语句时时间"+new Date());
	}
}

本例我们只需要一个计数线程,在第24行我们不妨夸张一点,根据我们的定义,计数线程开启后,需要10000秒后第29行语句才会输出结果,那我们先在这句输出语句这里让它输出时间,以便我们计时。为了计时,那么我们势必也要在第9行开启计数线程时输出一个时间,在开启计数线程5秒后第11行调用interrupt()方法,观察开启计数线程后究竟过了多久才会输出结果:

详述Java中的线程2——线程常用方法_第3张图片

结果证明 :从开启线程到输出结果只过了5秒,证明了interrupt()方法对阻塞的打断作用。

(3)currentThread方法:

返回当前正在执行的线程对象。这个方法的理解点在于判断该对象创建时究竟位于哪一个线程

下面直接用代码来分析:

代码示例一:

package com.thread;

public class Test {

	public static void main(String[] args) {
		Thread thread = new TimeThread();//创建时间线程类对象,用来验证
		System.out.println(thread);//将这个对象输出,观察与在时间线程中调用currentThread()方法返回的对象是否为同一对象
		thread.start();
	}
}

class TimeThread extends Thread{

	@Override
	public void run() {
		Thread thread = Thread.currentThread();//这里的对象位于时间线程,所以返回对象为时间线程对象
		System.out.println(thread);
	}
}

详述Java中的线程2——线程常用方法_第4张图片

有结果知,确实返回正在执行的线程类对象。

代码示例二: 

package com.thread;

public class Test {

	public static void main(String[] args) {
		TimeThread timeThread = new TimeThread();//创建时间线程类对象
		System.out.println("########"+timeThread);//输出时间线程类对现象
		timeThread.start();//开启时间线程
		
		timeThread.run();
	}
}

class TimeThread extends Thread{

	@Override
	public void run() {
		Thread thread = Thread.currentThread();
		System.out.println("@@@@@@@@"+thread);//输出currentThread()方法返回的线程类对象
	}
}

运行结果:

详述Java中的线程2——线程常用方法_第5张图片

我们一步一步来分析 :

首先因为第7行输出的就是我们定义的时间线程类对象,这是毋庸置疑的,对应输出结果的第一行;

第8行,时间线程开启,此时currentThread()就位于时间线程中所以返回结果为时间线程类对象,对应输出结果的第3行;

第10行,虽然此时时间线程类对象调用时间线程类中的run方法,但是该方法现在是在之线程中执行的,所以run方法中返回的线程类对象实际上是主线程对象对应输出结果的第2行

这里我们会发现,输出结果并没有按代码执行顺序来输出,这是因为线程在抢占CPU的过程中执行顺序发生了变化。

 

(4)isAlive方法:

判定该线程是否处于就绪、运行或阻塞状态,如果是则返回true,否则返回false

通俗来讲,这个方法就是用来判断调用该方法的线程对象是否还“活着”。但该方法往往和currentThread方法连用,所以,认清对象依旧是最需要注意的。

直接附上代码解读:

package com.thread;

public class Test {

	public static void main(String[] args) {
		Thread thread = Thread.currentThread();
		new PrintThread(thread).start();
		System.out.println("main线程状态:"+thread.isAlive());
	}
}

class PrintThread extends Thread{

	private Thread thread;
	
	public PrintThread(Thread thread){
		this.thread = thread;
	}
	
	@Override
	public void run() {
		try {
			sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.err.println("main线程状态:"+thread.isAlive());
	}
}

大体来看,示例代码除了主线程之外,只有一个输出线程;

整个代码示例中有两处输出,且只有两处输出语句处调用了isAlive方法,所以我们只需判断这两处调用该方法的对象;

首先看第8行的输出语句,这里调用isAlive方法的是第6行创建的主线程Thread类对象(currentThread方法写在主线程内,返回主线程类对象),但别忘了,第7行开启了输出线程,当进行到第8行时,主线程一定未终止,否则该输出语句就不会执行,所以该语句只要输出,即为true;

再看输出线程类,我们会发现第23行输出线程出现了一秒的阻塞,有因该程序只开启了两个线程,所以主线程一定先终止,又因为主线程终止后输出线程还没终止,且调用输出线程中isAlive方法的是主线程类对象,所以输出线程结束时,主线程早已终止,输出结果一定为false,这里第27行输出为false的语句用err标记:

详述Java中的线程2——线程常用方法_第6张图片

这样就验证了我们的分析。

(5)setDaemon方法

用于将一个尚未调用线程start方法的线程设置为守护线程。守护线程主要用于为其他线程的运行提供服务(Java中的垃圾回收机制就是守护线程),这种线程属于创建它的线程。

之所以叫守护线程是因为守护线程在Java的多线程中会为其他线程断后,因为守护线程随着最后一个非守护线程的终止而终止。

package com.thread;

public class Test {

	public static void main(String[] args) {
		CounterThread counterThread = new CounterThread();
		counterThread.setDaemon(true);
		counterThread.start();
		try {
			Thread.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

class CounterThread extends Thread {
	
	public void run() {
		int i=1;
		while(true){
			System.out.println("计数器:"+i);
			i++;
		}
	}
}

首先,整个程序中只有两个线程:主线程和计数线程;

第6行和第8行的创建和开启线程的语句不再赘述;

在开启计数线程前,先在第7行将计数线程设置为守护线程;

第9行~13行,开启计数线程后,让主线程阻塞1毫秒后终止;

第20~24行,在技术线程中写一个死循环,让其不断输出,如果该线程不被终止,则一直输出;

 

运行程序,观察计数线程是否真的会随着主线程在开启技术线程1毫秒后终止;

运行结果:

详述Java中的线程2——线程常用方法_第7张图片

我们观察到,计数线程确实因为主线程的终止而终止。

(6)void etPriority(int newPriority)方法

void setPriority(int newPriority):设置当前线程的优先级,线程优先级越高,线程获得执行的次数越多,Java线程的优先级用整数表示,取值范围是1~10Thread类有以下三个静态常量:

1.static int MAX_PRIORITY   最高优先级值为10

2.static int NORM_PRIORITY   默认优先级值为5

3.static int MIN_PRIORITY   最低优先级值为1

注意:同一个线程类创建的多个线程,线程优先级越高的线程获得的执行次数极有可能越多;但是不同线程类创建的线程,线程优先级越高,执行次数不一定越多,这个run方法的代码复杂度有关

你可能感兴趣的:(Java课程)