前言:
🌟🌟本期讲解多线程的知识哟~~~,希望能帮到屏幕前的你。
🌈上期博客在这里:【后端开发】JavaEE初阶—线程的理解和编程实现-CSDN博客
🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客
目录
📚️1.引言
📚️2.Thread常见的构造方法
2.1创建对象名字
1.第一种创建线程对象,并命名
2.使用Runnable对象,并命名
3.检查线程名字
2.2线程组
📚️3.Theard类常见属性
3.1属性ID与名称
3.2是否后台线程
3.3是否存活
📚️4.启动线程
4.1多次启动线程
4.2实现多次启动线程
4.3start()与run()使用
📚️5.终止一个线程
5.1设置标志位isQuit
5.2标志位的替代
📚️6.等待线程
📚️7.获取线程引用
7.1使用Thread继承
7.2不使用Thread继承
📚️8.总结
📚️1.引言
Hello!!! 小伙伴们咱们又见面啦,继小编上期讲解了多线程编程的相关重要知识,以及如何实现线程的创建之后,本期将继续讲解关于多线程的相关方法和属性,开始发车了加油加油~~~🥳🥳🥳
且听小编讲解,包你学会!!!
📚️2.Thread常见的构造方法
主要方法如下:
这里的创建线程对象,以及使用Runnable对象创建线程小编是在上期是讲解过的,想要了解的小伙伴可以去小编主页看看;那么接下来就来几个陌生的方法吧
2.1创建对象名字
1.第一种创建线程对象,并命名
代码如下:
Thread t=new Thread(()->{
System.out.println("这是thread线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"我的线程");
注意:小编这里使用了lambda表达式进行演示,其中在方法体内进行了补充,在方法体后,用双引号表示的就是为这个线程所编写的名字;
2.使用Runnable对象,并命名
代码如下:
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
}
},"这是我的线程");
注意:此时小编没有对run方法进行重写,但是仍然对线程进行了命名;
3.检查线程名字
这里就要用到JDK中的jconsole了,这个在JDK中存在;
然后点击进入jconsole,并进行我们创建的线程的连接,图片展示:
很明显这里小编创建的项目是threadDemo6,并点击连接,找到线程,就可以发现我们创建线程的名字了,图片展示如下:
这就是我们创建线程的名字啦~~~
注意:在执行上述操作时,一定要保证从开始到结束中,idea上的线程代码要跑起来,否则在连接处就无法找到我们创建的线程;
当然这里也可以使用getName()方法,小编会在后面演示~~~
2.2线程组
这里的线程组是java中的概念,和系统内核中的线程组是不一样的;
线程组的作用:
1. 组织线程:可以将多个线程归为一个线程组,方便对相关线程进行整体操作和管理。
2. 控制权限:线程组可以控制其包含的线程的访问权限,例如设置是否允许某个线程组中的线程修改系统资源等。
3. 异常处理:当一个线程组中的某个线程抛出未捕获的异常时,线程组可以对这个异常进行统一的处理。
这里的线程组,咱们了解即可~~~
📚️3.Theard类常见属性
常见属性如下:
这里的getState()方法就是描述线程的状态,进程存在就绪状态与阻塞状态,那么线程也存在对应的状态,以及这里的getPriority()方法描述的是线程的调度优先级,但是效果不明显,至于收否被中断,后面小编会进行讲解;接下来即小编重点实例讲解的了;
3.1属性ID与名称
如何获取线程的ID与名称;代码如下:
Thread t=new Thread(()->{
System.out.println("这是thread线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"我的线程");
t.start();
System.out.println(t.getId()+" "+t.getName());
注意:在通过Theard实例调用线程的ID和名字时,对应的是JVM自动分配的身份标识,对于名字一般是Thread-0,Thread-1......但是自主给线程起名后,对应的名字也会变为所起名字;
3.2是否后台线程
按照之前的理解,当main函数执行结束后,整个函数就应该执行结束了,但是在多线程中并不是如此,因为JVM内置线程为后台线程,而后台线程不会阻止线程结束;
我们创建的代码线程默认为前台线程,前台线程会阻止线程结束,所以即使main函数执行完了,那么线程仍然不会结束此时;
此时我们就要将前台线程改为后台线程,代码如下:
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("hello thread");
}
}
}, "这是我的线程");
// 在 start 之前, 设置线程为 后台线程 (不能在 start 之后设置)
t.setDaemon(true);
t.start();
}
此时主线程走完了就不会有任何输出:
注意:前台线程会阻止进程结束,而后台线程不会阻止进程结束,并且在改写前台线程为后台线程时,必须在创建线程之前!!!
3.3是否存活
这里的是否存活表示的是内核中的PCB线程是否存在;
Thread实例,虽然来说表示的是一个线程,但是这里和内核中的PCB线程的生命周期是不一样的
代码实例如下:
Thread t=new Thread(()->{
System.out.println("这是thread线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"我的线程");
System.out.println("线程创建之前"+t.isAlive());//false
t.start();
System.out.println("线程启动之后"+t.isAlive());//true
Thread.sleep(3000);//保证t线程结束
System.out.println("t线程结束之后"+t.isAlive());//false
这里的输出结果就如同上述注解;
注意:内核中的PCB线程是在t调用start方法后创建线程才会存在,当线程中run()方法执行完后,内核PCB被释放了,此时isAlive表示为没有存活;
这里的主线程休眠是为了保证另一线程执行完毕;
📚️4.启动线程
对于启动线程来说就使用start方法就行了,但是这里要进行扩展
4.1多次启动线程
代码如下:
Thread t=new Thread(()->{
System.out.println("这是thread线程");
},"我的线程");
t.start();
t.start();
这里小编进行了线程的两次启动,那么结果就是:
很明显这里抛出异常了即:非法的线程状态异常~~~
注意:线程只能启动一次,一个线程实例对象,多次调用start方法就会抛出非法线程状态异常
4.2实现多次启动线程
由于上述讲到,一个线程实例化对象不能多次启动线程,那么对于多次启动线程,就要实现多个线程,多个启动;
代码实例如下:
public class ThreadDemo10 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println("hello1");
});
Thread t2 = new Thread(() -> {
System.out.println("hello2");
});
t1.start();
t2.start();
}
此时我们创建了两个线程,这里就可以实现多次启动线程啦~~~
4.3start()与run()使用
对于这两种方法,两者其实是互不相干的;
start()方法:是通过系统调用API实现了一个线程的创建,其中的JVM在创建好线程后自动调用run方法,此时就有多个线程;
run()方法:就是之前在JavaSE部分中,类中方法的调用一致,但是这里调用后,没有创建新的现场,仍然为单线程;
代码实例:
class MyThread4 extends Thread {
@Override
public void run() {
while (true) {
System.out.println("hello thread");
}
}
}
public class ThreadDemo11 {
public static void main(String[] args) {
Thread t = new MyThread4();
t.start();
// t.run();
while (true) {
System.out.println("hello main");
}
}
注意:在如上述代码中,当调用start方法后,会创建两个线程,那么会不断输出(hello thread)和(hello main)这两个输出交替出现,但是调用run方法,就会陷入死循环打印(hello thread)不会向下执行主线程了;
📚️5.终止一个线程
这里终止一个线程就是:让线程run方法执行完毕;
5.1设置标志位isQuit
代码如下:
public static boolean isQuit=true;
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
while (isQuit){
System.out.println("这是一个thread线程,在工作");
try {
Thread.sleep(2000);
}catch (InterruptedException e){
throw new RuntimeException();
}
}
System.out.println("线程结束");
});
t.start();
Thread.sleep(4000);
System.out.println("让t线程结束");
isQuit=false;
此时的输出结果就是:
此时我们想让线程结束,那么就要将循环内的判断条件进行改变,即将isQuit的值变为false,并且为了观察,进行必要的输出打印;
那么我们将代码中的isQuit改为主线程的局部变量,就是不可行的!!!
因为lambda表达式/匿名内部类存在变量捕获的问题,此时这里的isQuit已经被final修饰了,并且匿名内部类是可以访问外部类的成员变量的,所以不可以改为main函数的局部变量;
5.2标志位的替代
这里就用Thread.currentThread().isInterrupted()来代替isQuit,但是这里表示为false,interrupt表示修改值;
代码如下:
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
while (!Thread.currentThread().isInterrupted()){
System.out.println("线程在工作");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("工作结束");
});
t.start();
Thread.sleep(4000);
System.out.println("结束线程工作");
t.interrupt();//类似改变标志的布尔值
}
此时我进行结果打印时,发现抛出异常了:
当我们输出结束线程工作时,这里没有结束继续工作,并抛出异常:休眠终止异常~~~
注意:
在执行 sleep时调用interrupt会导致sleep提前唤醒;会导致抛出上述异常,或者将Thread实例中的isInterrupted()标志位,改为false,这就会导致程序继续执行;
解决方法 :
此时我们就可以加上break,让线程立即结束,代码如下:
while (!Thread.currentThread().isInterrupted()){
System.out.println("线程在工作");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
// break;
}
}
当然或者直接省去sleep休眠状态,直接一直工作知道终止线程;
经过以上解释,程序的终止是一种软性操作,需要线程的配合才能实现!!!
📚️6.等待线程
由于线程是调度执行的,底层调度是不确定的,但是可以通过一些API来影响线程的执行顺序,此时join就提供了这样的操作;
代码如下:
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
int n = 5;
for (int i = 0; i < n; i++) {
System.out.println("我是一个线程, 正在工作中...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程执行结束");
});
t.start();
// 这个操作就是线程等待.
t.join();
System.out.println("这是主线程, 期望这个日志在 t 结束后打印");
}
那么此时小编就使用了join来等待线程的执行完毕,再输出主线程,输出结果:
那么此时达到了我们的要求;但是我们运用sleep方法也可以实现,为啥不用它呢???
解答:在使用sleep方法时要知道线程结束的具体时间,因为休眠时间是要根据线程执行完毕才能够实现的,而使用join就不必知道这一点;
注意:
在使用join方法时要进行异常的抛出;
在那个线程使用join方法,谁就处于等待状态,而调用的线程实例就是被等待的一方;
使用sleep方法可以实现上述需求,但是需要知道线程的执行时间,所以不推荐;
join中也可以添加等待时间,如果不添加就是“死等”状态,一般在计算机中不推荐死等
这里也可以使用线程等待来实现一系列运算;
public static long sum=0;
public static void main(String[] args)throws InterruptedException {
Thread t1=new Thread(()->{
for (long i = 0; i <50 ; i++) {
sum+=i;
}
});
Thread t2=new Thread(()->{
for (long i = 50; i <=100 ; i++) {
sum+=i;
}
});
t1.start();
t2.start();
long begin=System.currentTimeMillis();
t1.join();
t2.join();
long end=System.currentTimeMillis();
System.out.println("sum="+sum);
System.out.println("等待时间"+(end-begin)+"ms");
}
这里小编就使用多线程,和等待机制来实现算数相加,此时在两个线程开启后,在让主线程进行等待,这里使用时间打印可以来表示单个线程和多个线程来执行这个任务的时间相差,小编这里就不再过多演示;
📚️7.获取线程引用
7.1使用Thread继承
代码如下:
public static void main(String[] args) throws InterruptedException {
Thread t=new mythread7();
t.start();
}
}
class mythread7 extends Thread{
@Override
public void run(){
System.out.println(this.getId()+" "+this.getName());
}
}
注意:在Thread继承中可以使用this拿到线程实例;
7.2不使用Thread继承
代码如下:
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
Thread t1=Thread.currentThread();
System.out.println(t1.getId());
});
t.start();
}
注意:如果是Runnable或者是lambda表达式,this就无法指向Thread对象,这个时候就要用到Thread.currentThread()
至于为啥不直接使用(t)来得到 属性:
在 Lambda 表达式中,变量必须是最终变量或有效最终变量,而 t 是一个非最终变量,因为他还没有完全初始化
📚️8.总结
💬💬小编本期讲解了关于线程的某些重要属性和方法,例如线程的启动,终止,等待以及常见的属性的获取方法和构造方法,并附上代码供小伙伴们参考~~~
代码在这里哟:GGBondlctrl/Thread (gitee.com)😊 😊 😊
🌅🌅🌅~~~~最后希望与诸君共勉,共同进步!!!
💪💪💪以上就是本期内容了, 感兴趣的话,就关注小编吧。
😊😊 期待你的关注~~~