线程
线程和进程
进程:系统运行程序的最小单位;一个进程最少有一个线程
线程:程序执行任务的最小单位;线程与线程之间并行
一个进程至少有一个线程,在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-run MyThread-run 或 MyThread-run MyRunnable-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); } } } 由于输出结果的顺序完全不确定,这里同学们一定要自行测试