3.1 线程的命名和取得
线程本身属于不可见的运行状态,即每次操作的时间都是无法预料的,所以如果要想在程序中操作线程,唯一依靠的就是线程名称,而要想取得和设置线程的名称可以使用如表所示的方法。
由于多线程的状态不确定,线程的名字就成为唯一的分辨标记,所以在定义线程名称的时候一定要在线程启动前设置名字,而且尽量不要重名,尽量不要为已经启动的线程修改名字。
由于线程的状态不确定,所以每次可以操作的都是正在执行run()方法的线程实例,依靠Thread类的以下方法实现。
取得当前线程对象: public static Thread currentThread()
范例:观察线程的命名操作
package cn.kuiba.util;
class MyThread implements Runnable{
@Override
public void run(){
System.out.println(Thread.currentThread().getName()); //当前线程名称
}
}
public class Main {
public static void main(String[] args){
MyThread mt=new MyThread();
new Thread(mt,"线程A").start(); //设置了线程的名字
new Thread(mt).start(); //未设置线程名字
new Thread(mt,"线程B").start(); //设置了线程的名字
}
}
程序执行结果:
线程A(线程手动命名)
线程B(线程手动命名)
Thread-0(线程自动命名)
通过本程序可知,如果已经为线程设置名字的话,那么会使用用户定义的名字;而如果没有设置线程名称,系统会自动为其分配一个名称,名称的形式以“Thread-xxx”的方式出现。
提示:关于线程名称的自动命名。
在第5章讲解static关键字的时候曾经提到过一个统计实例化对象个数与成员属性自动命名的操作,实际上Thread对象的自动命名形式与之类似,下面截取了Thread类的部分源代码。
public class Thread{
private static int threadInitNumber; //记录线程初始化个数
public Thread(Runnable target){
init(null,target,"Thread-"+nextThreadNum(),0); //线程自动命名
}
public Thread(Runnable target,String name){
init(null,target,name,0); //线程手动命名
}
private static synchronized int nextThreadNum(){
return threadInitNumber++; //线程对象个数增长
}
}
通过源代码可以发现,每当实例化Thread类对象时都会调用init()方法,并且在没有为线程设置名称时自动为其命名。
所有的线程都是在程序启动后在主方法中进行启动的,所以主方法本身也属于一个线程,而这样的线程就称为主线程,下面通过一段代码来观察主线程的存在。
package cn.kuiba.util;
class MyThread implements Runnable{
@Override
public void run(){
System.out.println(Thread.currentThread().getName()); //获取线程名称
}
}
public class Main {
public static void main(String[] args){
MyThread mt =new MyThread(); //线程类对象
new Thread(mt,"线程对象").start(); //设置了线程的名字
mt.run(); //对象直接调用run()方法
}
}
程序执行结果:
main(主程序,mt.run()执行结果)
线程对象(子线程)
本程序在主方法中直接利用线程实例化对象调用了run()方法,这样所获取到的对象就是main线程对象。
提示:所有的线程都是在进程的基础上划分的,当用户使用Java命令执行一个类的时候就表示启动了一个JVM的进程,而主方法是在这个进程上的一个线程而已,而当一个类执行完毕后,此进程会自动消失。
通过以上分析就可以发现实际开发中所有的子线程都是通过主线程来创建的,这样的处理机制主要是可以利用多线程来实现一些资源耗费较多的复杂业务,如图。
通过如图可以发现,当程序中存在多线程机制后,就可以将一些耗费时间和资源较多的操作交由子线程处理,这样就不会因为执行速度而影响主线程的执行。
范例:子线程处理复杂逻辑
package cn.kuiba.util;
public class Main {
public static void main(String[] args) throws Exception{
System.out.println("1、执行操作任务一。"); //主线程执行
new Thread(()->{ //子线程负责统计
int temp=0;
for (int x=0;x<Integer.MAX_VALUE;x++){
temp +=x; //【模拟】执行耗时操作
}
}).start();
System.out.println("2、执行操作任务二。"); //主线程执行
System.out.println("3、执行操作任务三。"); //主线程执行
}
}
本程序启动了一个子线程进行耗时的业务处理,在子线程的执行过程中,主线程中的其他代码将不会受到该耗时任务的影响。
3.2 线程休眠
当一个线程启动后会按照既定的结构快速执行完毕,如果需要暂缓线程的执行速度,就可以利用Thread类中提供的休眠方法完成,该方法定义如表。
在进行休眠的时候有可能会产生中断异常InterruptionException,中断异常属于Exception的子类,程序中必须强制性进行该异常的捕获与处理。
范例:线程休眠
package cn.kuiba.util;
public class Main {
public static void main(String[] args) throws Exception{
Runnable runnable=()->{ //Runnable接口实例
for (int x=0;x<10;x++){
System.out.println(Thread.currentThread().getName()+"、x="+x);
try {
Thread.sleep(1000); //暂缓1秒(1000毫秒)执行
}catch (InterruptedException e){ //强制性异常处理
e.printStackTrace();
}
}
};
for (int num=0;num<5;num++){
new Thread(runnable,"线程对象-"+num).start(); //启动线程
}
}
}
本程序设计了5个线程对象,并且每一个线程对象执行时都需要暂停1秒。但是,多线程的启动与执行都是由操作系统随机分配的,虽然看起来这5个线程的休眠是同时进行的,但是也有先后顺序,只不过速度较快,不宜观察。
3.3 线程中断
在Thread类提供的线程操作方法中很多都会抛出InterruptionException中断异常,所以线程在执行过程中也可以被另外一个线程中断执行,线程中断的操作方法如表。
范例:线程中断操作
package cn.kuiba.util;
public class Main {
public static void main(String[] args) throws Exception{
Thread thread=new Thread(()->{
System.out.println("【BEFORE】准备睡觉10秒钟的时间,不要打扰我!!");
try {
Thread.sleep(10000); //预计准备休眠10秒
System.out.println("【FINISH】睡醒了,开始学习和工作!");
}catch (InterruptedException e){
System.out.println("【EXCEPTION】睡觉被打扰了,坏脾气爆发!");
}
});
thread.start(); //线程启动
Thread.sleep(10000); //保证子线程先运行1秒
if (!thread.isInterrupted()){ //该线程中断?
System.out.println("【INTERRUPT】敲锣打鼓地路过你睡觉的地方!");
thread.interrupt(); //中断执行
}
}
}
程序执行结果:
【BEFORE】准备睡觉10秒钟的时间,不要打扰我!!
【INTERRUPT】敲锣打鼓地路过你睡觉的地方!
【EXCEPTION】睡觉被打扰了,坏脾气爆发!
本程序实现了线程执行的中断操作,可以发现线程的中断是被动完成的,每当被中断执行后就会产生InterruptionException异常。
3.4 线程强制执行
在多线程并发执行中每一个线程对象都会交替执行,如果此时某个线程对象需要优先执行完成,则可以设置为强制执行,待其执行完毕后其他线程再继续执行,Thread类定义的线程强制执行方法如下。
线程强制执行:public final void join() throws InterruptionException
范例:线程强制执行
package cn.kuiba.util;
public class Main {
public static void main(String[] args) throws Exception{
Thread mainThread= Thread.currentThread(); //获得主线程
Thread thread=new Thread(()->{
for (int x=0;x<100;x++){
if (x==3){ //设置强制执行条件
try {
mainThread.join(); //强制执行线程任务
}catch (InterruptedException e){
e.printStackTrace();
}
}
try {
Thread.sleep(100); //延缓执行
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"执行、x="+x);
}
},"玩耍的线程");
thread.start();
for (int x=0;x<100;x++){ //主线程
Thread.sleep(100);
System.out.println("【霸道的main线程】number="+x);
}
}
}
本程序启动了两个线程:main线程和子线程,在不满足强制执行条件时,两个线程会交替执行,而当满足了强制执行条件(x==3)时会在主线程执行完毕后再继续执行子线程中的代码。
3.5 线程礼让
线程礼让是指当满足某些条件时,可以将当前的调度让给其他线程执行,自己再等待下次调度再执行,方法定义如下。
线程礼让:public static void yield()
范例:线程礼让
package cn.kuiba.util;
public class Main {
public static void main(String[] args) throws Exception{
Thread thread=new Thread(()->{
for (int x=0;x<100;x++){
if (x%3==0){
Thread.yield(); //线程礼让
System.out.println("【YIELD】线程礼让,"+Thread.currentThread().getName());
}
try {
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"执行、x="+x);
}
},"玩耍的线程");
thread.start();
for (int x=0;x<100;x++){
Thread.sleep(100);
System.out.println("【霸道的main线程】number="+x);
}
}
}
程序执行结果(截取部分随机结果):
【YIELD】线程礼让,玩耍的线程
玩耍的线程执行、x=0
【霸道的main线程】number=0
玩耍的线程执行、x=1
【霸道的main线程】number=1
玩耍的线程执行、x=2
【霸道的main线程】number=2
【YIELD】线程礼让,玩耍的线程
【霸道的main线程】number=3
玩耍的线程执行、x=3
玩耍的线程执行、x=4
【霸道的main线程】number=4
玩耍的线程执行、x=5
【霸道的main线程】number=5
【YIELD】线程礼让,玩耍的线程
【霸道的main线程】number=6
在多线程正常执行中,原本的线程应该交替执行,但是由于礼让的关系,会出现某一个线程暂时让出调度资源的情况,让其他线程优先调度。
3.6 线程优先级
在Java的线程操作中,所有的线程在运行前都会保持在就绪状态,那么此时会根据线程的优先级进行资源调度,即哪个线程的优先级高,哪个线程就有可能会先被执行,如图。
如果想要进行线程优先级的设置,在Thread类中有以下支持的方法及常量,如表。
范例:观察线程优先级
package cn.kuiba.util;
public class Main {
public static void main(String[] args) throws Exception{
Runnable run =()->{ //线程类对象
for (int x=0;x<10;x++){
try {
Thread.sleep(1000); //暂缓执行
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"执行。");
}
};
Thread threadA=new Thread(run,"线程对象A"); //线程对象
Thread threadB=new Thread(run,"线程对象B");
Thread threadC=new Thread(run,"线程对象C");
threadA.setPriority(Thread.MIN_PRIORITY); //修改线程优先级
threadB.setPriority(Thread.MIN_PRIORITY);
threadC.setPriority(Thread.MAX_PRIORITY);
threadA.start(); //线程启动
threadB.start();
threadC.start();
}
}
本程序为线程设置了不同的优先级,理论上讲优先级越高的线程越有可能先抢占资源。(主方法的优先级对应的是中等优先级。)