接上面内容继续说
9.启动一个线程(start())
前面我们看到当我们开始一个线程任务的时候都会有一个start(),它的主要作用就是用来开启线程的。但我们也发现在我们创建线程的时候会有一个重写run()方法的操作。那么这两个有什么区别呢?具体看下面的这个例子。
所以总结:start()和run()区别:
(1)作用功能不同:
(a)run方法它只是描述了这个线程要执行什么任务,并不具备执行它的功能。
(b)start方法的话才是真正的去开启了这个任务,去执行它。
就比如说我要去食堂吃一碗螺蛳粉,那我吃螺蛳粉就相当于是我要做的任务(吃粉——run()),那这个粉谁来煮呢?就是我们的食堂人员来做,她们听到了我这个要吃粉的这个请求后,就要开始开火烧水煮粉这一系列操作,此时呢她们呢就是开启为我做螺狮粉的这个操作(做螺狮粉——start())。
(2)运行结果不同
(a)run方法是一个类中的普通方法,主动调用和调用普通方法是一样的,会顺序执行一次。
(b)start方法调用后,内部会调用Java本地方法(封装了对系统底层的调用)真正的启动线程,并且执行run方法中的代码,当run方法执行完毕后线程就会进入销毁阶段。
调用start方法,才真正的在操作系统的底层创建一个线程。
10.总结Thread类的基本用法
1.线程创建
线程的创建的5种方式:
(1)继承Thread类,重写run方法
(2)实现Runnable接口,重写run方法
(3)继承Thread类,使用匿名内部类
(4)实现Runnable接口,使用匿名内部类
(5)lambda表达式(推荐写法)
不太清楚的可以看一下上面部分内容哦。
2.线程中断
中断,从字面意思可以看出也就是打断的意思,比如说一个老师他正在给几个同学讲题,这个时候你就慌慌张张的跑过去大叫一声老师找你有事呢。此时所有人的目光都看向了你,老师他们此时所讲的内容也会暂停下来,此时他们也就好比是被中断了。
所以中断的意思呢也就是让一个线程停下来。那我们又该如何停止一个线程呢?
(1)给线程中设定一个结束标志位
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true) {
System.out.println("老师在给同学们讲题");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
System.out.println("小名跑过去找老师了");
}
此时我们的代码在开启线程时候就会一直循环。我们想要当我们主线程等待老师讲题这个线程执行3秒后停止,所以我们设置了isQuit这个标志位,当它为false的时候就停止。
package thread1;
public class Test02 {
public static boolean isQuit = false;
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (!isQuit) {
System.out.println("老师在给同学们讲题");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("小名你有什么事?也就是代表此时讲题被打断了");
System.out.println(Thread.currentThread().getName()+ "线程终止了");
},"老师讲题");
thread.start();
// 主线程中修改isQuit
try {
System.out.println("小名跑过去找老师了");
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
isQuit = true;
}
}
其中这里还有一个问题解释,就是这里的isQuit变量为啥不是局部变量?也就是说为啥不放在局部变量这个位置,为什么它是成员变量?
这里原因是因为我们的lambda表达式它可以访问到外面的局部变量,java要求变量捕获,捕获的变量必须是final或者实际的final。而我们的变量是没有被final修饰的,所以会报错。
(2)上面这种方式是我们使用自定义方式去定义标志位,所以我们使用Thread对象的interrupted()方法通知线程结束。
public class Test04 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("老师正在给同学们讲题");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("小名你有什么事?也就是代表此时讲题被打断了");
System.out.println(Thread.currentThread().getName()+ "线程终止了");
},"老师讲题");
thread.start();
try {
System.out.println("小名跑去找老师");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 把thread内部的标志位设置true
thread.interrupt();
}
}
运行后我们发现当3秒时间到了,抛出异常后又继续执行,此时的线程并没有真正结束。而是在3秒后通过e.printStackTrace();打印出当前出现异常位置的调用栈。
那为啥会出现上面的问题呢?原因是sleep的影响。通过上面代码可以知道interrupt方法的作用它是设置标志位为true,同时呢如果当前线程正在阻塞中,(比如说在执行sleep)那么此时就会把阻塞状态唤醒,通过抛出异常的方式让sleep结束。而当我们唤醒sleep的时候,sleep它就会自动的把inInterrupter标志位给清空,这样就会继续循环执行。
如果线程因为调用wait/join/sleep等方法而阻塞挂起,则会以InterruptedException异常的形式通知,清除中断标志。
那如果我们要结束循环的话就需要在catch中使用break;
我们也可以在第一异常中在抛出一个异常,当线程在休眠期间被中断时候,抛出异常,此时第一个异常会被忽略继续执行第二个,睡眠3秒后就会break;
补充:
Thread.Interrupted() 与 Thread.currentThread().isInterrupted()区别:
Thread.interrupted()用来判断当前线程的中断标志被设置,清除中断标志。
Thread.curentThread().isInterrupted()用来判断指定线程的中断标志被设置,不清除中断标志。
public class Test05 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.interrupted());
}
});
thread.start();
// 把thread内部的标志位设置true
thread.interrupt();
}
}
可以看到除了第一个其余都是false,因为标志位已经被清空了,当调用到thread.interupt()会设置为true,但是它会被清空,后面的也就是false了。
public class Test05 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
// System.out.println(Thread.interrupted());
System.out.println(Thread.currentThread().isInterrupted());
}
});
thread.start();
// 把thread内部的标志位设置true
thread.interrupt();
}
}
可以看到此时全部都为true,是因为当我们调用thread.interrupted()时候,我们此时的标志位就设置成了true,但是此时我们是Thread.currentInterrupted().isInterrupted()不会清除标志位,所以都是true了。
3.线程等待 join()
线程等待的话也就是我们有些时候需要等待某些线程执行完我们再去接着执行。
public class Test06 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("我先执行");
});
thread.start();
System.out.println("我才是先执行");
}
}
对于几行代码我们可以从之前的内容中知道,多线程的执行是无序的,主线程和其它线程是随机出现的,我们不知道这两个他可能会以什么顺序先执行,虽然这个代码大部分是先显示“我才是先执行”,那是因为线程thread的创建也是需要耗费一定的资源的,所以大部分都会看到的是它,但是不能说明它们两个是有序的,因为线程之间是并发执行的,操作系统对于线程的调度是无序的。无法判定它两的顺序。但是呢有时候我们一些需求是要有顺序的,所以就引入了线程等待的这个概念。
线程等待中我们使用的是join()方法,当我们在主线程(main)中调用thread.join表示让主线程阻塞等待thread线程执行完它在执行。
public class Test06 {
public static void main(String[] args) throws InterruptedException{
Thread thread = new Thread(() -> {
System.out.println("我先执行");
});
thread.start();
thread.join();
System.out.println("我才是先执行");
}
}
再举一个例子,老师今天要抽人背书,首先是小明先背书,接着小花,小红,小海,假设现在就这四个人,那就是4个线程,代码如下:
package thread1;
public class Test07 {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "锄禾日当午");
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
},"小名");
Thread thread2 = new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "汗滴禾下土");
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
},"小花");
Thread thread3 = new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "谁知盘中餐");
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
},"小红");
Thread thread4 = new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "粒粒皆辛苦");
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
},"小海");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
try {
System.out.println("老师说:你们真不错!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
可以看到当前的按理来讲应该是在同学们背完之后老师在夸赞,所以我们需要添加join()线程等待。
在线程等待这里需要注意的是我们线程调用了join()需要抛出异常不然是报错的。
同时这里的join()也是可以设置等待时间的thread.join(时间);
4.线程休眠
线程休眠的话也就是我们上面代码中看的sleep(),里面的参数是毫秒,将当前线程休眠多少时间。而且在使用slee()时候需要使用try-catch抛异常。不然也是汇会报错误的
5.获取线程实例
在上面的代码中我们使用到了Thread.currentThread()这个是获取当前线程的引用。类比this表示指向当前对象。比如下面的代码我们通过它获取当前线程的名字。
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
Thread.sleep(3000);
System.out.println("获取当前线程的引用");
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程1");
thread.start();
System.out.println("主线程");
}
好啦这节的笔记就大概是这样啦,文中有理解不对的记得给俺说说哦!