智渔课堂官方免费教程三十九 :Java基础教程之线程

时间:2021-08-22 00:01:48

线程

线程和进程

进程:系统运行程序的最小单位;一个进程最少有一个线程
线程:程序执行任务的最小单位;线程与线程之间并行
一个进程至少有一个线程,在java中这个线程称为主线程,由系统创建,运行main方法。这样只有一个线程的程序也被称为单线程程序。
主线程从程序入口main方法开始执行代码,执行任意方法中的代码都是按照自上而下的顺序执行的,如果只有一个主线程,又想实现在线听音乐这个功能的话,就很难实现。因为主线程必须先去下载音乐;下载完成后,在执行播放音乐;这显然不能满足当今人们对在线听音乐的需求。所以现在的程序都是多线程的,就在线听音乐这个功能而言,可以在main方法中创建一个线程负责下载音乐,在创建一个线程负责播放音乐。这样在听音乐的同时,用于还可以进行其他操作。


创建线程

创建线程的两种方式
1、实现Runnable接口:还可以继承其他类
2、继承Thread类:使用简单

方式一:<span style="font-size: 14px;">实现Runnable接口</span>package thread;
/**
* 创建MyRunnable类,并实现Runnable接口
* @author 学霸联盟 - 赵灿
*/
public class MyRunnable implements Runnable {
// 必须重写Runnable接口中的方法,不重写会出现语法错误
// Runnable接口中只声明了一个方法run()
@Override
public void run() {
// 线程执行的代码
System.out.println("MyRunnable-run");
}
}
方式二:package thread; /** * 创建MyThread类,并继承Thread接口  * @author 学霸联盟 - 赵灿 */public class MyThread extends Thread {	/*	 * Thread类也实现了Runnable接口,并重写了接口中的run方法	 * 所以此处如果没有重写run方法,不会出现语法错误,但创建的这个线程也就没有意义了	 */	@Override	public void run() {		// 线程执行的代码		System.out.println("MyThread-run");	}}
package thread;/** * 创建测试类Test,测试以上两种方式创建的线程  * @author 学霸联盟 - 赵灿 */public class Test {	public static void main(String[] args) {		/*********** 方式一 ************/		// 创建一个MyRunnable类型的对象mr		MyRunnable mr = new MyRunnable();		// 创建一个线程对象t;并将mr作为参数传入Thread对象		Thread t = new Thread(mr);		/*		 * 注意:启动线程调用的是Thread类中的start方法,而不是调用run方法;		 * 如果直接调用run方法,将只表示run方法的调用,不会启动线程;		 * 调用start方法启动线程t,线程启动后,会自动调用对象mr中的run方法		 */		t.start();		/*********** 方式二 ************/		// 创建一个MyThread类型的对象mt		MyThread mt = new MyThread();		// 因为start方法在Thread类中声明,MyThread类继承了Thread类		// 所以此处使用对象mt调用的start方法是从父类Thread中继承的方法		mt.start();	}}输出结果:MyRunnable-runMyThread-run或MyThread-runMyRunnable-run
由于多个线程是并行执行的,即同时运行。
就以上代码而言,会先执行t.start();再执行mt.start();但是由于这里的代码非常简单,在非常非常短的时间内,两个现场就启动完成了;而线程启动后并不是立即执行的,要等待调度程序选择哪个线程执行,所以此处启动的两个线程谁先执行并不能确定。所以输出顺序也不确定。

线程的生命周期

新建(初始态):执行new操作后。此时在已经在内存中创建出了线程对象,但线程还没有启动运行
可运行:调用start()方法后。此时线程已经启动,但还没有运行(线程的执行是通过CPU执行完成的),另一种说法是:还没有获得CPU的使用权;正在和其他可运行状态的线程一起等待系统的调度程序选取,选中哪个线程,哪个现在使用CPU执行程序,执行一定的时间(CPU的时间片)后,退出对CPU的占用,转入可运行状态,和其他线程一起等待系统调度程序的选取。
运行:已获得CPU的使用权,正在运行
阻塞:执行sleep(int)、yield()、join()、wait()方法后,阻塞状态的线程不会被调度程序选中。只有从阻塞状态恢复至可运行态以后,才有机会获得CPU的使用权
死亡:运行结束或执行stop()、destroy()方法后;这两个方法均已过时,不推荐使用;此时线程已经彻底停止运行,释放了线程所占用的资源。


线程的调度

sleep方法

static void sleep(int)静态方法,建议使用类名Thread调用;
作用:线程休眠;哪个线程执行Thread.sleep();这条语句,哪个线程就会休眠(阻塞);int类型的参数,代表线程休眠的时间(毫秒),时间结束自动恢复至可运行态,和其他线程一起等待调度程序的选取
一句话:谁执行,谁休眠。
实例:package thread.sleep;
/**
* 创建SleepDemo类
* 用于测试sleep方法
* @author 学霸联盟 - 赵灿
*/
public class SleepDemo{
public static void main(String[] args){
System.out.println("主线程正在执行");
System.out.println("主线程休眠开始");
// 执行Thread.sleep();可能产生异常,使用try-catch语句捕获异常
try {
// 这条语句会被主线程执行,执行后主线程休眠1000毫秒(1秒)
Thread.sleep(1000);
} catch (InterruptedException e) {
// 如果try中的代码产生异常,程序执行这条输出语句
System.out.println("线程休眠时出现异常");
}
System.out.println("主线程休眠结束");
System.out.println("主线程恢复执行");
}
}
输出结果:
主线程正在执行
主线程休眠开始【这里会休眠(停止)1秒,1秒后继续输出以下内容】
主线程休眠结束
主线程恢复执行

yield方法

static void yield()静态方法,建议使用类名Thread调用。
作用:线程让步;哪个线程执行Thread.yield();语句,哪个线程就会把已经获得的CPU使用权让出来,并进入可运行状态,和其他线程一起等待调度程序的选取;
一句话:谁执行,谁让步。
实例:package thread.yield;
/**
* 创建YieldDemo类
* 用于测试线程的让步执行
* @author 学霸联盟 - 赵灿
*/
public class YieldDemo {
public static void main(String[] args) {
//声明ThreadA和ThreadB的对象
ThreadA ta = new ThreadA();
ThreadB tb = new ThreadB();
//启动线程ta
ta.start();
//启动线程tb
tb.start();
}
}


/**
* 创建ThreadA类,并继承Thread类
* @author 学霸联盟 - 赵灿
*/
class ThreadA extends Thread{
//重写父类中的run方法
@Override
public void run() {
//循环30次
for (int i = 1; i <= 30; i ++) {
/*
* 每循环一次ThreadA类的对象(本例是ta)就会让出一次CPU的使用权,
* 给其他线程执行(这个例子中是主线程和ThreadB的对象tb)
*/
Thread.yield();
//输出一个字符串,为了在结果中可以看到线程ta的执行频率
System.out.println("ThreadA-" + i);
}
}
}


/**
* 创建ThreadB类,并继承Thread类
* @author 学霸联盟 - 赵灿
*/
class ThreadB extends Thread{
//重写父类中的run方法
@Override
public void run() {
//循环30次
for (int i = 1; i <= 30; i ++) {
//输出一个字符串,为了在结果中可以看到线程tb的执行频率
System.out.println("ThreadB--" + i);
}
}
}
输出结果:输出顺序是不确定的;多数情况下,线程tb会先执行完,线程ta后执行完,而且线程tb的执行频率要比线程ta高,但不是绝对的

join方法

void  join([int] [,int]);非静态方法,只能使用对象调用。
作用:线程插队;假设现在有线程A和B,线程A中执行了B.join();语句,线程B会插队到线程A前执行,线程A会被阻塞
一句话:谁调用,谁插队;谁执行,谁阻塞。
其中可以传入一个int类型的参数,代表插队的毫秒数,如果传入两个int类型的参数,代表插队的毫秒数(左)和纳秒数(右);插队时间结束后,插队线程和被阻塞线程都回到可运行状态,等待调度程序的选取
实例:package thread.join;
/**
* 创建JoinDemo类
* 用于测试线程的让步执行
* @author 学霸联盟 - 赵灿
*/
public class JoinDemo {
public static void main(String[] args) {
//创建ThreadA的对象ta
ThreadA ta = new ThreadA();
//启动线程ta
ta.start();
}
}


/**
* 创建ThreadA类,并继承Thread类
* @author 学霸联盟 - 赵灿
*/
class ThreadA extends Thread{
//重写父类中的run方法
@Override
public void run() {
//创建ThreadB的对象tb;线程ta运行后便会先执行此句
ThreadB tb = new ThreadB();
//启动线程tb;线程tb启动后,此时线程ta和tb共同等待调度程序的选取
tb.start();
//循环30次
for (int i = 1; i <= 30; i ++) {
/*
* 当i等于10时,执行tb.join();
* 使线程tb插队执行到线程ta前面执行
* 线程ta被阻塞
*/
if ( i == 10 ) {
try {
/*
* 只需插队一次即可
* 在i小于10时,线程ta和tb共同等待调度程序的选取执行
* i==10时,执行tb.join();
* 执行后,线程ta被阻塞,只执行线程tb;
* 直至线程tb执行完,ta才会恢复至可运行态
* 其中ta是代码的执行者,tb是代码的调用者
*/
tb.join();
} catch (InterruptedException e) {
//输出栈内存中的异常信息
e.printStackTrace();
}
}
//输出一个字符串,为了在结果中可以看到线程ta的执行频率
System.out.println("ThreadA-" + i);
}
}
}


/**
* 创建ThreadB类,并继承Thread类
* @author 学霸联盟 - 赵灿
*/
class ThreadB extends Thread{
//重写父类中的run方法
@Override
public void run() {
//循环30次
for (int i = 1; i <= 30; i ++) {
//输出一个字符串,为了在结果中可以看到线程tb的执行频率
System.out.println("ThreadB--" + i);
}
}
}
由于输出结果的顺序完全不确定,这里同学们一定要自行测试