多线程---线程的状态及常用方法

1. 线程的状态

        在Java程序中,一个线程对象通过调用start()方法启动线程,并且在线程获取CPU时,自动执行run()方法。run()方法执行完毕,代表线程的生命周期结束。

        在整个线程的生命周期中,线程的状态有以下六种:

New:新建状态,新创建的线程,此时尚未调用start()方法;

Runnable:运行状态,运行中的线程,已经调用了start()方法,线程正在或者即将调用run()方法;

Blocked:阻塞状态,运行中的程序在等待竞争锁时,被阻塞,暂不执行;

Waiting:等待状态,运行中的线程,因为join()等方法,进入等待;

Timed Waiting:计时等待状态,运行中的线程,因为执行sleep(等待时间)或join(等待时间)等方法,进入计时等待;

Terminated:终止状态,线程已经终止,因为run()方法执行完毕

 线程终止的原因有:
○线程正常终止:run()方法执行到return语句返回;
○线程意外终止:run()方法因为未捕获的异常导致线程终止;
○对某个线程的Thread实例调用stop()方法强制终止(不推荐使用);

2.线程的常用方法

2.1 线程的插队:join( )方法

作用:

        t.join()方法会使当前线程( 主线程 或者调用t.join()的线程 )进入等待池,并等待 线程t 执行完毕后才会被唤醒。此时,并不影响同一时刻处在运行状态的其他线程。

示例:
        myThread.join()被主线程调用,则主线程进入WAITING或者TIMED_WAITING(调用myThread.join(long millis))等待状态,主线程Main必须等子线程myThread执行完毕后才能继续执行。当子线程myThread执行完毕后,进入TERMINATED终止状态,会自动调用notifyAll()方法,唤醒主线程,主线程重新进入RUNNABLE运行状态,继续执行;

public class Test03_join {
	private static void printWithThread(String content) {
		System.out.println("[" + Thread.currentThread().getName() + "线程]" +content);
	}
	
	public static void main(String[] args) {
		printWithThread("开始执行main方法");
		Thread myThread = new Thread(new Runnable() {

			@Override
			public void run() {
				printWithThread("我在自定义的线程的run方法里");
				printWithThread("我马上要休息1秒钟,并让出CPU给别的线程使用.");
				
				try {
					Thread.sleep(1000);
					printWithThread("已经休息了1秒,又重新获得了CPU");
					printWithThread("我休息好了,马上就退出了");
				}catch (InterruptedException e) {
					e.printStackTrace();
			}
			
		}
		
	});
		try {
			myThread.start();
			printWithThread("我在main方法里面,我要等下面这个线程执行完了才能继续往下执行.");
			myThread. join();
			printWithThread("我在main方法里面,马上就要退出了.");
		}catch (InterruptedException e) {
			e.printStackTrace();

		}
	}
}

//运行结果:

[main线程]开始执行main方法
[main线程]我在main方法里面,我要等下面这个线程执行完了才能继续往下执行.
[Thread-0线程]我在自定义的线程的run方法里
[Thread-0线程]我马上要休息1秒钟,并让出CPU给别的线程使用.
[Thread-0线程]已经休息了1秒,又重新获得了CPU
[Thread-0线程]我休息好了,马上就退出了
[main线程]我在main方法里面,马上就要退出了.

join( )方法的实现原理

○join()方法的底层是利用wait()方法实现;
○join()方法是一个synchronized同步方法,当主线程调用 线程t.join( )方法时,主线程先获得了 线程t对象 的锁,随后进入join()方法,调用 线程t对象 的wait()方法,使主线程进入了 线程t对象 的等待池;
○等到 线程t 执行完毕之后,线程在TERMINATED终止状态的时候会自动调用自身的notifyAll()方法,来唤醒所有处于等待状态的线程:这个机制在隐藏在native本地方法中,由一个C++实现的方法ensure_join()函数实现。在该函数的尾部,执行了lock.notify_all(thread);,相当于调用了notifyAll()方法。

        综上所述:join()方法实际上是通过调用wait()方法, 来实现同步的效果的。
        例如:A线程中调用了B线程的join()方法,则相当于A线程调用了B线程的wait()方法,在调用了B线程的wait()方法后,A线程就会进入WAITING或者TIMED_WAITING等待状态,因为它相当于放弃了CPU的使用权。
        注意:join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕:即join(0)=join();

        join()方法的源代码:

join( )方法和wait( )方法的区别

①定义的类不同

○sleep()方法是Thread类的静态方法

○wait()方法是Object类的方法

②锁的释放不同

sleep()方法不会释放任何锁,如果一个线程正在执行同步代码块或方法并且调用sleep()方法,它将继续持有该对象的锁,直到睡眠时间结束

wait()方法会释放当前线程持有的锁,允许其他线程访问同步代码块或方法。当线程被唤醒并重新竞争到锁后才能继续执行。

③调用位置不同

sleep()方法可以在任何地方调用,不需要在同步块或方法内;

wait()方法必须在同步块或方法中调用,因为它是 Object类的方法,通常在持有对象锁的情况下调用;

④唤醒机制不同

sleep()方法在指定的时间过后会自动恢复线程的执行;

wait() 方法需要被其他线程调用 notify()或 notifyA11()方法来唤醒,除非使用 wait(long timeout) 指定

超时时间,否则线程会一直等待直到被显式唤醒;

⑤异常抛出不同

sleep() 方法可能会抛出 InterruptedException(如果线程在睡眠期间被中断);

wait() 方法除了会抛出InterruptedException 以外,如果没有在同步代码块中调用,还会抛出 IllegalMonitorStateException

⑥作用目的

sleep()主要用于暂停线程的执行,可以用于定时任务、延迟执行等情况;

wait()主要用于线程间的通信和同步,如生产者消费者模型中等待资源可用或通知其他线程;

⑦sleep()和 wait()的主要区别在于锁的处理、调用环境和唤醒机制。用于线程之间的同步和通信。

sleep() 是一个简单的延时机制,而 wait()则

join( )方法和sleep( )方法的区别

两个方法都可以实现类似"线程等待"的效果,但是仍然有区别;
○join()是通过在内部使用synchronized + wait()方法来实现的,所以join()方法调用结束后,会释放锁
sleep()休眠没有结束前,不会释放锁

2.2 线程的中断:interrupt( )方法

        如果线程需要执行一个长时间任务,就可能需要能中断线程。中断线程就是其他线程给该线程发一个信号,该线程收到信号后结束执行run()方法,使得自身线程能立刻结束运行。
例如:假设从网络下载一个100M的文件,如果网速很慢,用户等得不耐烦,就可能在下载过程中点“取消”,这时,程序就需要中断下载线程的执行。
中断一个线程非常简单,只需要在其他线程中对目标线程调用interrupt()方法,目标线程需要反复检测自身状态是否是interrupted状态,如果是,就立刻结束运行。

interrupt( )方法的作用

        interrupt()方法的作用是设置该线程的中断状态为true,线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于中断状态。线程会不时地检测这个中断状态值,以判断线程是否应该被中断(中断状态值是否为true)。

interrupt( )方法的原理

        interrupt()方法只是改变中断状态,不会像stop()中断一个正在运行的线程。支持线程中断的方法(Thread.sleep() 、join()、wait()等方法)就是在监视线程的中断状态,一旦发现线程的中断状态值被置为“true”,就会抛出线程中断的异常InterruptedException,给WAITING或者TIMED_WAITING等待状态的线程发出一个中断信号,线程检查中断标识,就会以退出WAITING或者TIMED_WAITING等待状态。

注意事项:

●线程被Object.wait(), Thread.join()和Thread.sleep()三种方法阻塞或等待,此时调用该线程的interrupt()方法,那么该线程将抛出一个 InterruptedException中断异常,从而提前终结被阻塞状态。
如果线程没有被阻塞或等待,调用 interrupt()将不起作用,直到执行到wait(),sleep(),join()等方法进入阻塞或等待时,才会抛出 InterruptedException异常

public class Test02_interrupt {
	public static void main(String[] args) {
		//创建子线程
		Thread t1=new Thread() {
			public void run() {
                System.out.println("子线程开始执行,进入RUNNABLE状态");
                try {
					//子线程休眠6秒,进入TIMED_WAIT状态
					Thread.sleep(1000*6);
				} catch(InterruptedException e) {
					e.printStackTrace();
					System.out.println("子线程中断,进入TERMUNATED状态");
					return;
				}
                System.out.println("子线程执行结束,进入TERMUNATED状态");
            }
        };
        //启动子线程
		t1.start();
        //main主线程休眠3
		try {
			//子线程休眠6秒,进入TIMED_WAIT状态
			Thread.sleep(1000*3);
		} catch(InterruptedException e) {
			e.printStackTrace();
		}
        //main主线程修改子线程中断状态为true
		t1.interrupt();
    }
}

//运行结果:

 //当我们没有使用wait(),sleep(),join()等会自动检测中断状态的方法时,中断就会失效,这时我们就要使用isInterrupted()方法来手动检测当前线程是否有中断发生:

public class Main {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("main主线程:开始执行");

        // 创建2个子线程
        Thread t1 =new Thread("线程1"){
        @Override
        public void run() {
            System.out.println(getName()+":开始执行");
            while(!isInterrupted()) {
                System.out.println(UUID.randomUUID());
            }
            System.out.println(getName()+":结束执行");

        }

    };

        // 启动子线程
        t1.start();

        //主线程休眠10毫秒
        Thread.sleep(10);

        // 10毫秒后,中断子线程1
        t1.interrupt();

        //子线程1执行结束后,继续执行主线程
        t1.join();
        System.out.println("main主线程:结束执行");

    }
}

        分析以下示例:

public class Main {
	public static void main(String[] args) throws InterruptedException {
		System.out.println("主线程:开始执行");
		// main主线程创建子线程MyThread
		MyThread t=new MyThread();
		t.start();
		//主线程休眠1000毫秒
		Thread.sleep(1000);
		
		// 结束休眠后
		t.interrupt();//中断t线程
		t.join();// 等待t线程结束
		
		System.out.println("主线程:结束执行");		
	}
}
class MyThread extends Thread{
	public void run() {
		System.out.println("MyThread线程:开始执行");
		
		// MyThread线程创建子线程HelloThread
		HelloThread hello = new HelloThread();
		hello.start();//启动HelloThread线程
		
		try {
			hello.join();//等待hello线程结束
			
		}catch (InterruptedException e) {

		System.out.println("MyThread线程:结束执行,interrupted!");
		
	}
		// MyThead线程结束后,中断子线程HelloThread
		hello.interrupt();	
}
class HelloThread extends Thread {
	public void run() {
		System.out.println("Hello线程:开始执行");
		int n =0;
		
		//检查当前线程是否已经中断
		while(!isInterrupted()){
			n++;
			System.out.println(n +" hello!");
		}
	}
}
	
}

//执行流程

Step1:执行主线程,输出:"主线程:开始执行"

Step2:子线程t通过t.start()转变为可运行态

Step3:主线程休眠1000毫秒,这时子线程t开始运行

Step4:执行子线程,输出:MyThread线程:开始执行

Step5:创建子线程t的子线程hello,启动HelloThread线程

Step6:子线程t调用hello线程的join()方法,等待hello线程结束子线程t才能继续执行

Step7:hello线程开始运行,输出:Hello线程:开始执行

Step8:程序进入死循环,只有当hello线程产生中断,才会跳出循环,停止输出n +" hello!"

Step9:直到主线程休眠后,主线程继续执行t.interrupt()方法,中断t线程

Step10:t线程中断,输出:MyThread线程:结束执行,interrupted!

Step11:子线程继续执行hello.interrupt(),hello线程跳出死循环,hello线程结束

Step12:t线程结束,继续执行主线程,输出:主线程:结束执行

Step13:结束

2.3 线程的让出:yield( )方法

yield( )方法的作用

○线程通过调用yield()方法告诉JVM的线程调度,当前线程愿意让出CPU给其他线程使用
○至于系统是否采纳,取决于JVM的线程调度模型:分时调度模型和抢占式调度模型
分时调度模型:所有的线程轮流获得 cpu的使用权,并且平均分配每个线程占用的 CPU 时间片
抢占式调度模型:优先让可运行池中优先级高的线程占用 CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。(JVM虚拟机采用的是抢占式调度模型 )

public class Main {
	public static void main(String[] args) {
		//创建子线程1:打印字母A-Z
		Thread t1=new Thread() {
			public void run() {
				for(char c='A';c<='Z';c++) {
					System.out.println(c);
				}
			}
		};
		
		//创建子线程1:打印65-99
				Thread t2=new Thread() {
					public void run() {
                        //数字线程让出cpu
						Thread.yield();
						for(int n=65;n<=99;n++) {
							System.out.println(n);
						}
					}
				};
		t1.start();
		t2.start();
		
	}

}

        但是,数字线程让出当前cup不代表字母线程就一定会优先执行,只是字母线程优先执行的概率比较大,可以多运行几次,观察结果。

守护线程(Daemon Thread)

用户线程与守护线程的区别

用户线程:我们平常创建的普通线程;
守护线程:用来服务于用户线程的线程,在JVM中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出;而守护线程执行结束后,虚拟机不会自动退出。

设置守护线程

        在调用start()方法前,调用setDaemon(true)把该线程标记为守护线程:

public class Main {
	public static void main(String[] args) {
		long startTime=System.currentTimeMillis();
		new Thread(){
			public void run() {
				//子线程休眠3秒钟
				try {
					Thread.sleep(4*1000);
				}catch(InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("用户线程,运行耗时"+(System.currentTimeMillis()- startTime));
			}
		}.start();// 启动线程
		
		//创建守护线程
		Thread daemonThread=new Thread() {
			public void run() {
				//守护线程休眠10秒钟
				try {
					Thread.sleep(10*1000);
				}catch(InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("守护线程,运行耗时"+(System.currentTimeMillis()- startTime));
			}
		};
		daemonThread.setDaemon(true);//设置子线程为守护线程
		daemonThread.start();
		
		//主线程休眠1秒,确保在守护线程之前结束休眠
		try {
			Thread.sleep(1*1000);
		}catch(InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("主线程,运行耗时"+(System.currentTimeMillis()- startTime));
	}
}

//结果:

主线程,运行耗时1010
用户线程,运行耗时4016

        主线程和子线程已经结束了,但是守护线程还在定时等待,那么直接结束本次运行,并不用等待守护线程结束。只有当非守护线程没有结束前,守护线程已经结束了才会输出(将守护线性等待的时间改成小于主线程或者子线程等待时间)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值