湘潭做网站价格优选磐石网络,建设企业网站方案,做网站需要多少兆专线,如何通过国外社交网站做外销#x1f970;#x1f970;#x1f970;来都来了#xff0c;不妨点个关注叭#xff01; #x1f449;博客主页#xff1a;欢迎各位大佬!#x1f448; 文章目录 1. Java中多线程编程1.1 操作系统线程与Java线程1.2 简单使用多线程1.2.1 初步创建新线程代码1.2.2 理解每个… 来都来了不妨点个关注叭 博客主页欢迎各位大佬! 文章目录 1. Java中多线程编程1.1 操作系统线程与Java线程1.2 简单使用多线程1.2.1 初步创建新线程代码1.2.2 理解每个线程是一个独立的执行流 2. 初始Thread类2.1 Thread类的常见构造方法2.2 Thread类的常见属性方法 3. Thread类的基本用法3.1 创建线程3.1.1 使用继承Thread重写run的方式3.1.2 使用实现Runnable重写run的方式3.1.3 继承Thread使用匿名内部类的方式3.1.4 实现Runnable类使用匿名内部类3.1.5 lambda表达式(最推荐使用最简单最直观写法 3.2 线程启动 —— start()3.3 线程休眠 —— sleep()3.4 获取当前线程引用 ——currentThread()3.4 线程中断 —— interrupt()3.4.1 给线程设置一个标志位(1) 设置一个标志位isQuit(2) 注意isQuit的位置(3) isQuit不能为局部变量的原因 3.4.2 调用interrupt()方法来通知(1) interrupt方法的作用(2) sleep为什么要清空标志位 3.5 线程等待 —— join()3.5.1 join无参数3.5.2 join有参数 1. Java中多线程编程
1.1 操作系统线程与Java线程
【操作系统线程】线程是操作系统中的概念操作系统内核实现线程这样的机制并对用户层提供一些 API 供用户使用(例如 Linux的pthread库) 【Java线程】Java 标准库中提供了一个类Thread能够表示一个线程可以认为是对操作系统提供的 API 进行了进一步的抽象和封装即关于线程的操作是依赖操作系统提供的API
1.2 简单使用多线程
1.2.1 初步创建新线程代码
class MyThread extends Thread {Overridepublic void run() {System.out.println(hello world~);}
}public class ThreadDemo {public static void main(String[] args) {MyThread t new MyThread();t.start();}
}打印结果如下~ 简单了解这个例子的执行过程在上述代码中涉及到两个线程分别是 1main方法所对应的线程也可以成为主线程(一个进程里至少得有一个线程) 2通过t.start()创建的新线程 即主线程中调用t.start()创建一个新的线程这个新线程调用t.run()当run()执行结束后这个新线程也会随之销毁同时main线程也执行结束了(因为t.start()是main线程中最后一个语句)
1.2.2 理解每个线程是一个独立的执行流
class MyThread extends Thread {Overridepublic void run() {while(true) {System.out.println(hello t!);}}
}
public class ThreadDemo1 {public static void main(String[] args) {Thread t new MyThread();t.start();while(true) {System.out.println(hello main!);}}
}打印的结果如下两个语句交替打印main线程和新线程是同时在进行的打印几个hello t!再打印几个hello main!..尽管两个线程同时进行(通过快速调度能够交替运行)两个线程在同一控制台打印同一控制条必须顺序输出所以打印会有先后(如果是单线程只会打印其中一个而看不到另一个) 【注意】 交替打印一直执行因为代码没有创建其它线程两个死循环在同一线程快速调度执行所以会一直循环下去~ 接下来我们进一步了解Thread类~一起来看看吧!
2. 初始Thread类
2.1 Thread类的常见构造方法 (1Thread() 创建线程对象
Thread t1 new Thread();(2) Thread(Runnable target) 使用Runnable对象创建线程对象
Thread t2 new Thread(new MyRunnable());(3) Thread(String name) 创建线程对象并命名
Thread t3 new Thread(我的线程);(4) Thread(Runnable target, String name) 使用Runnable对象创建线程对象并命名
Thread t4 new Thread(new MyRunnable(),我的线程);可以通过jconsole工具查看出java进程里面的线程详情线程名字在这里显示 (5)Thread(ThreadGroup group, Runnable target 线程可以被用来分组管理分好的组即为线程组(仅作了解)
2.2 Thread类的常见属性方法 1ID -- getID() ID 是线程的唯一标识每个线程的ID都是独一无二的
public class ThreadDemo5 {public static void main(String[] args) {Thread t new Thread( () - {System.out.println(hello t);});t.start();//获取ID属性System.out.println(t.getId());System.out.println(hello main!);}
}打印结果如下 2名称 -- getName() 名称是各种调试工具用到的
public class ThreadDemo5 {public static void main(String[] args) {Thread t new Thread( () - {System.out.println(hello t);});t.start();//获取线程的名字System.out.println(t.getName());System.out.println(hello main!);}
}打印结果如下 3状态 -- getState() 每个线程都有自己的状态优先级上下文记账信息等(在进程与线程这期内容讲到进程的这些属性都是线程的只不过之前谈到的进程是属于只有一个线程的进程)状态表示线程当前所处的一个情况下期内容会进一步说明~敬请期待
public class ThreadDemo5 {public static void main(String[] args) {Thread t new Thread( () - {System.out.println(hello t);});t.start();//获取线程的状态System.out.println(t.getState());System.out.println(hello main!);}
}
打印结果如下 4优先级 -- getPriority() 优先级对于系统来说只是给出建议理论上优先级越高更容易被调度到~
public class ThreadDemo5 {public static void main(String[] args) {Thread t new Thread( () - {System.out.println(hello t);});t.start();//获取ID属性System.out.println(t.getPriority());System.out.println(hello main!);}
}打印结果如下 5是否为后台线程 -- isDaemon() 如果是true表示为后台线程false表示是前台线程 【后台线程】后台线程不阻止java进程结束哪怕后台线程还没执行完java进程该结束就结束 【前台线程】创建的线程默认是前台线程可以通过setDaemon()设置成后台线程JVM会在一个进程的所有非后台线程结束后才会结束运行
public class ThreadDemo5 {public static void main(String[] args) {Thread t new Thread( () - {System.out.println(hello t);});t.start();//判断线程t是否为后台线程System.out.println(t.isDaemon());System.out.println(hello main!);}
}打印结果如下 6是否存活 -- isAlive() 判断当前的线程是否处于活动状态描述的是操作系统里的那个线程是否存活线程处于正在运行或准备开始运行的状态就认为线程是存活的状态 1该线程还没调用start方法并未创建此时调用该线程isAlive是false 2线程的入口方法执行完毕此时系统中对应的线程就没了此时调用该线程isAlive就是false 下面通过一个例子深入理解isAlive()方法判断线程是否存活的标准
public class MyThread extends Thread {Overridepublic void run() {System.out.println(run this.isAlive());}
}public class ThreadDemo1{public static void main(String[] args) throws InterruptedException {Thread t new MyThread();System.out.println(开始 t.isAlive());t.start();System.out.println(结束t.isAlive());}
}打印结果如下 【注意】 此处结束返回值不确定
System.out.println(结束t.isAlive()); //该值是不确定的在上述代码结束输出true是因为t线程还未执行完毕即输出true如果将代码为如下形式
public class ThreadDemo1{public static void main(String[] args) throws InterruptedException {Thread t new MyThread();System.out.println(开始 t.isAlive());t.start();Thread.sleep(1000); //延迟1sSystem.out.println(结束t.isAlive());}
}此时结束的返回值为false因为t对象已经在1秒内执行完毕 7是否被中断 -- isInterrupted() 判断该线程是否被中断被中断返回true未被中断返回false(在本期内容结尾处介绍interrupted方法此处不作过多解释)
public class ThreadDemo5 {public static void main(String[] args) {Thread t new Thread( () - {System.out.println(hello t);});//线程是否中断t.start();System.out.println(t.isInterrupted());System.out.println(hello main!);}
}打印结果如下
3. Thread类的基本用法
3.1 创建线程
创建线程的五种方法如下
3.1.1 使用继承Thread重写run的方式
class MyThread extends Thread {Overridepublic void run() {while(true) {System.out.println(hello t!);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class ThreadDemo1 {public static void main(String[] args) {Thread t new MyThread();t.start();while(true) {System.out.println(hello main!);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}3.1.2 使用实现Runnable重写run的方式
class MyRunnable implements Runnable {Overridepublic void run() {while(true){System.out.println(hello t!);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class ThreadDemo2 {public static void main(String[] args) {MyRunnable myRunnable new MyRunnable();Thread t new Thread(myRunnable);t.start();while(true){System.out.println(hello main!);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}3.1.3 继承Thread使用匿名内部类的方式
public class ThreadDemo3 {public static void main(String[] args) {Thread t new Thread() {Overridepublic void run() {while(true){System.out.println(hello t!);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};t.start();while(true){System.out.println(hello main!);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}3.1.4 实现Runnable类使用匿名内部类
public class ThreadDemo4 {public static void main(String[] args) {Thread t new Thread(new Runnable() {Overridepublic void run() {while(true) {System.out.println(hello t!);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}});t.start();while(true) {System.out.println(hello main!);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
3.1.5 lambda表达式(最推荐使用最简单最直观写法
public class ThreadDemo5 {public static void main(String[] args) {Thread t new Thread( () - {while(true) {System.out.println(hello t!);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();while(true) {System.out.println(hello main!);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}【解释说明】 1第一种写法使用Thread的run方法描述线程入口 2第二种写法使用Runnable interface来描述线程入口使用Runnable 描述一个具体的任务 3第一种写法和第二种写法没有本质区别 4匿名内部类匿名则是没有名字内部类是定义在类里面的类放到哪里就是针对哪个类创建的匿名内部类
3.2 线程启动 —— start()
调用start()方法才是启动线程了线程才真正独立去执行了 【run方法】 run()叫做入口方法是特殊的方法重写了父类的方法这个run方法就会被java自动执行到即能被自动调用到而随便写一个方法只是一个普通方法 没有特殊含义需要手动去调用 【start方法】 start()调用操作系统的API创建新的线程新的线程调用run方法即通过调用Thread类的start方法来启动一个线程 【区别】 1当一个程序调用start方法将会创建一个新的线程去自动执行run方法中的代码但如果未使用start方法而是直接调用run方法则是直接在当前线程中执行run方法代码而不会创建新线程 2当一个线程启动后不能再调用start方法否则会报异常IllegalThreadStateException异常 【总结】 run()是一个入口方法start()会去创建一个新线程去自动行run()的代码只有通过调用线程类的start方法才能真正达到多线程的目的
3.3 线程休眠 —— sleep() public static void sleep(long millis) throws InterruptedException 【含义】sleep方法是Thread的静态方法参数单位是ms(回顾一下静态方法即类方法直接可以通过类调用无需创建对象)因为线程的调度是不可控的这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的 【作用】使当前线程休眠进入阻塞状态即暂停执行如果线程在睡眠状态被中断将会抛出InterruptedException中断异常中断/打断即sleep睡眠过程中还没到点就被提前唤醒了 【例子】以上面例子加上sleep方法如下
class MyThread extends Thread {Overridepublic void run() {while(true) {System.out.println(hello t!);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class ThreadDemo1 {public static void main(String[] args) {Thread t new MyThread();t.start();while(true) {System.out.println(hello main!);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}打印结果如下 通过结果可以看到交替打印不是像上一个例子那么快因为加了sleep将会暂停执行1000ms同时可以看到此处的交替不是严格的交替每1000毫秒(即1秒)过后是先打印main还是先打印t是不确定的原因是多线程在CPU上调度执行的顺序是不确定的即是随机的尽管线程有优先级但对于系统只是“建议”并不一定采取 【注意】在哪个线程里面调用sleep()方法就休眠哪个线程
3.4 获取当前线程引用 ——currentThread() public static Thread currentThread(); public class ThreadDemo5 {public static void main(String[] args) {Thread t Thread.currentThread();System.out.println(t.getName());}
}打印结果如下即获取了当前线程的实例打印该线程的名字由于并未新创建线程当前线程为main线程即获取到main线程实例
3.4 线程中断 —— interrupt()
当小丁同学去上学进入学习状态他就会按照学校规定按时上课放学了才能回家但在生活中可能会出现家里有一个急事的情况必须让小丁同学回家我可以直接给老师打电话或者直接去学校找小丁这就涉及该如何通知小丁回家停止当前学习的问题~接下来会介绍两种常见的方式 中断这里就是字面意思让一个线程停止下来线程终止本质上来说让一个线程终止的办法就一种即让该线程的入口run方法执行完毕(让入口run方法执行完毕可以有直接return抛出异常等等) 目前常见的有以下两种方式
1.给线程设定一个结束标志位通过共享的标记来进行沟通
2.调用interrupt()方法来通知3.4.1 给线程设置一个标志位
(1) 设置一个标志位isQuit
基于上述思路可以手动给线程设置一个标志位isQuit 首先创建线程t该线程的代码是死循环导致t的入口方法永远无法结束线程也不会结束一直在执行如下
public class ThreadDemo6 {public static void main(String[] args) {Thread t new Thread(()- {while(true) {System.out.println(hello t~);}});t.start();}
}但是可以通过控制循环条件设置变量isQuit像手动开关一样控制线程的结束当!isQuit为true时执行循环内的逻辑否则跳出循环执行完毕结束线程
public class ThreadDemo6 {public static boolean isQuit false;public static void main(String[] args) {Thread t new Thread(()- {while(!isQuit) {System.out.println(hello t~);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(t线程终止);});t.start();//在主线程中修改isQuittry {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}isQuit true;}
}打印结果如下 该代码执行过程为打印3次hello t~“!isQuit 被修改为falset 线程中的循环终止打印” t线程终止!这个语句后 t 线程结束此时main线程代码也执行完毕main线程结束因此整个进程也随之结束
(2) 注意isQuit的位置
为什么这里将isQuit设置为成员变量编写在main方法外而不写在main方法里作为局部变量呢? 下面进行演示将isQuit设置为局部变量发现代码报错 -------------------------------------------------------- 在此特别说明 --------------------------------------------------------
(3) isQuit不能为局部变量的原因
错误理解 有些同学可能认为出现该错误是因为isQuit这个使用变量超出它的作用域但事实并非如此线程t是可以正常拿到main线程中的变量同一进程的线程和线程之间共用内存地址空间比如同一进程创建线程1、2、3线程1创建出来的变量在线程2、3中同样也能访问到内存地址共用即变量也共用 正确原因 变量捕获lambda表达式是可以访问它外面的局部变量但这里涉及到变量捕获这一语法规则Java语法要求变量捕获捕获到的变量必须是final或实际finalfinal指的是被final修饰的变量实际final指的是虽然一个变量没有用final关键字修饰但是代码中并没有尝试过修改它即该变量没有做出过修改在上述代码中在最后一行作出修改isQuit为true的操作违背变量捕获的语法要求因此变量捕获失败程序编译报错 解决方式 即按照最开始的代码将isQuit设置为成员变量在main中访问成员变量不受变量捕获规则的限制即不会存在上述问题
3.4.2 调用interrupt()方法来通知
要知道我们自己手动创建变量控制循环是比较麻烦的~Thread类内置了一个标志位isInterrupted()更方便实现上述效果isInterrupted()可以理解是t对象自带的一个结束标志位通过 t.interrupt() 方法将t内部的标志位给设置为 true代码如下
public class ThreadDemo7 {public static void main(String[] args) {Thread t new Thread(()-{//currentThread 是获取到当前线程实例//此处currentThread得到对象就是t//isInterrupted 就是t对象自带的一个标志位while(!Thread.currentThread().isInterrupted()) {System.out.println(hello t!);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();break;}}});t.start();try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}//把t内部标志位给设置为truet.interrupt();}
}打印结果如下 但事实上运行结果并不是我们想要的线程中断效果通过上述打印结果可以看到3秒时间到调用 t.interrupt() 方法的时候线程t并没有真正终止而是打印异常信息之后又继续执行该异常信息由while循环中catch捕获并打印 为什么会出现这样的情况呢这就需要我们了解interrupt方法的两个作用它会将sleep提前唤醒这正是上述代码异常的原因
(1) interrupt方法的作用 (1) 设置标志位为true (2) 如果该线程正在阻塞中比如正在执行的sleep、wait、join等此时就会把该阻塞状态唤醒通过抛出异常的方式让其立即结束 【解释说明】 在上述情况中如果sleep被提前唤醒的时候sleep会自动把isInterrupted标志位给清空true变为false这就导致下次再判断循环条件循环条件还成立循环仍然可以继续执行而interrupt()执行时如果t线程正在sleepinterrupt()在将标志位设置为true后会直接将sleep强行唤醒。sleep的时间已经占据了整个循环体的绝大部分非常非常多部分因此当interrupt()执行时几乎一定会遇到正在sleep的情况sleep第一次执行清空标志位并抛出异常这次设置的中断就翻篇sleep第二次执行没有这个中断的标志位了而如果设置interrupt的时候恰好sleep唤醒这时候是非常巧的概率极其低此时执行到下一轮的循环条件就直接结束了
打个比方这就好比你今天打算7点起来但是你的妈妈五点来你的房间喊醒你还把你房间的灯打开了你一看手机发现才凌晨五点又把灯关了继续睡觉了~
主线程只调用一次interrupt()即主线程并非是循环反复设置t内部的标志位而是只执行一次因此抛出1次异常后就不会再次抛出异常 如果需要结束循环需要怎么做呢 【解决方案】在catch{}中加一个break
(2) sleep为什么要清空标志位
【目的】让线程自身能够对于线程何时结束有一个更明确的控制 当前interrupt方法的效果不是让线程立即结束而是告诉它你该结束了它是否真的要结束立即结束还是过一会结束都是由代码灵活控制的 interrupt方法只是通知而不是命令 比如我在学习我的妈妈让我去超市买点菜 1直接无视她的要求继续学习(不去) 2立即放下学习赶紧起身去买菜(立马去) 3我说等我学完这一节课我再去(等会去) 为什么Java这里不强制设置为命令结束操作即一调用interrupt不是通知而是命令就立即结束了 【原因】 这种设定非常不友好线程t何时结束一定是线程t自己最清楚交给t自身来决定比较好
3.5 线程等待 —— join()
线程之间是并发执行的即操作系统对于线程的调度是无序的无法判定两个线程谁先执行结束谁后执行结束
public class ThreadDemo1{public static void main(String[] args) throws InterruptedException {Thread t new Thread(()- {System.out.println(hello t);});t.start();System.out.println(hello main);}
}
本次运行该代码打印结果如下 先输出hello main还是hello t这是无法确定的这个代码实际执行的时候大部分情况下都是先出hello main比如上述打印结果因为线程创建也有开销但是不排除特定情况下主线程hello main没有立即执行到的~ 要知道程序猿不喜欢不确定的~有时候就需要明确规定线程的结束顺序可以使用线程等待来实现 等待线程就是控制两个线程结束的顺序接下来join方法闪亮登场
3.5.1 join无参数
public class ThreadDemo1{public static void main(String[] args) throws InterruptedException {Thread t new Thread(()- {System.out.println(hello t);});t.start();t.join();System.out.println(hello main);}
}
打印结果如下 在t.join执行的时候如果t线程还没有结束main线程就会阻塞等待 如果是t1线程中调用t2.join就是让t1线程等待t2线程结束t1线程阻塞其它线程正常调度 在哪个线程调用该线程的join方法就是让哪个线程等待该线程结束 【t.join作用】 1main线程调用t.join的时候如果t还在运行此时main线程阻塞直到t线程结束即t的run方法执行完毕main才从阻塞中解除才继续执行 2main线程调用t.join的时候如果t已经结束了此时join不会阻塞就会立即执行下去 总之t.join都能保证t线程是先结束的那个明确控制线程结束的执行顺序
3.5.2 join有参数
但是如果t线程里面有死循环那main线程就要一直等待下去吗 这里就介绍join的另一个版本带参数的join可以填一个参数作为超时时间即等待的最大时间~ join的无参版本效果是死等必须等到该线程结束 join的有参版本效果是指定最大时间如果等待时间到了上限还没等到就不等了在生活中也是如此~所以这是很常见的设定
public class ThreadDemo1{public static void main(String[] args) throws InterruptedException {Thread t new Thread(()- {while (true) {System.out.println(hello t);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();t.join(3000);System.out.println(hello main);}
}打印结果如下 因为t线程中代码是死循环将会一直打印hello t使用带参数的join方法则超过3秒后这里举例子实际最大等待时间可能更长main线程执行打印hello main只打印一次接着将一直循环打印hello t~这就是带参数的join方法 本期内容就到这里结束啦~全是干货疯狂进行知识输入嘿嘿一起加油努力吧 本期内容回顾 ✨✨✨本期内容就到这里结束啦继续努力吧~