【线程二(创建线程及一些方法使用)】
线程二
一.创建线程
1.继承Thread类,重写run方法
class MyThread extends Thread{
public void run(){
for (int i = 0; i < 3; i++) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
sleep:休眠,让当前的线程暂时放弃CPU,休息一会,时间过了再执行。
public static void main(String[] args) throws InterruptedException{
MyThread t = new MyThread();
t.start();
for (int i = 0; i < 3; i++) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
上述,代码是有两个线程,一个是t线程,main线程是入口。由于,每个线程之间是并发执行的,是无法预支结果的执行顺序的。
2.实现runable 接口,重写run方法
class MyRunnable implements Runnable{
public void run(){
for (int i = 0; i < 3; i++) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public static void main(String[] args) throws InterruptedException{
Runnable myRunnable = new MyRunnable();
Thread t = new Thread(myRunnable);
t.start();
for (int i = 0; i < 3; i++) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
上述代码的写法,可以降低耦合度,即模块之间,依赖尽量小,影响也会小。
3.使用匿名内部类
Thread t = new Thread(){
public void run(){
for (int i = 0; i < 3; i++) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
这种情况,一般如果某个代码是“一次性”的,就可以使用匿名内部类。
Thread t = new Thread(){}
这种方式进行创建,1.创建子类,我们是不知道子类是什么名字的。
2.创建了这个匿名内部类的实例,并且把实例的引用赋值给了t.
4.使用Runable,使用匿名内部类
Runnable runnable = new Runnable(){
public void run(){
for (int i = 0; i < 3; i++) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
Thread t = new Thread(runnable);
t.start();
5.lambda表达式
Thread t = new Thread(() ->{
public void run(){
for (int i = 0; i < 3; i++) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
lambda表达式 本质上是一个“匿名函数”,主要用途“回调函数”。“回调函数”:写一个函数,自己不去调用,让别人去调用。
二.Tread类及常见的方法
Thread类是JVM管理线程的一个类,即每个线程都有唯一1个Thread对象与之对应。
下边这些呢,是一些常用的构造方法。

Thread(Runnable target ,String name)使用:
Runnable myRunnable = new MyRunnable();
Thread t = new Thread(myRunnable,"t1");
在我们没有创建名字的时候,默认是Thread-0,Thread-1 这种写法。
Thread的几个常见属性

1. getId()
用来表示线程身份的效果,类似于Pid
2. getName()
用来获取当前线程的名字。
3. isDaemon()
本身的含义:是否是守护线程
守护线程==后台线程
什么是后台线程? 相当于是没有什么存在感
它们的存在不会影响进程的结束,即使他们还继续存在,如果进程要结束了,它们也结束了。前台线程:这几个线程的存在,会影响到进程的继续存在,这样的线程,称为“前台线程”。
代码实例如下:
Thread t = new Thread(() ->{
while(true){
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
for (int i =0;i<3;i++){
System.out.println("hello main");
Thread.sleep(1000);
}
}
执行结果:
上述代码,
1.线程的执行顺序是不确定的,无法保证执行顺序。
2.我们自己创建的线程 t线程是属于前台线程,main也属于前台线程。t线程就会控制线程执行的时间,由于 t 线程一直是while(true),所以将会一直执行,直到强制结束。
3.Thread.sleep()会让当前线程暂停执行指定时间,期间不会占用 CPU 资源。
我们想要让上述代码,在main线程结束的时候,
就结束整个线程。我们的做法是:
在t.start()之前设置 setDaemon
t.setDaemon(true);
t.start();
添加了这一行代码之后,我们就将t线程变成了后台线程,无法阻止线程的结束。当主线程for循环结束之后,JVM会查看是否还有其他用户线程还再继续,由于t线程设置为了后台线程,JVM不会等待t线程结束,而是直接终止。
4.isAlive()
Java中创建的Thread对象,和系统中的线程是一一对应的。
但是,Thread对象的生命周期,和系统中的线程生命周期,是不同的。
(可能会存在,Thread对象还存活,但是系统中的线程已经销毁了)
Thread t = new Thread(() ->{
for (int i =0;i<3;i++){
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
//没有启动线程之前,观察线程的存活状态
System.out.println(t.isAlive());
t.start();
for (int i =0;i;i++) {
System.out.println(t.isAlive());
Thread.sleep(1000);
}
}

在上述代码中,首先,在没有启动线程的时候,存活状态是false,在启动之后,main线程执行到最后一次的时候,可能t线程还没有完全的结束,所以返回的还是true。
5.isInterrupted()
终止线程,让线程直接结束,即让线程的入口方法执行完毕,线程就随之结束了。
方式一:设置变量的方式
private static boolean flag = false;
public static void main(String[] args) throws InterruptedException{
Thread t= new Thread(() ->{
while (!flag) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t线程结束");
});
t.start();
Thread.sleep(3000);
flag = true;
}
在上述代码中,
private static boolean flag = false; 设置为了静态成员变量
是通过flag变量来进行线程终止的,lamdba表达式是函数式接口,即匿名内部类,在内部类中访问外部类的静态成员变量,是可以直接进行访问的,所以,当线程休眠3秒之后,flag变为true,是可以让线程终止的。
private static boolean flag = false;
当flag设置为局部变量的时候,情况如下

当设置为局部变量的时候,我们想要在lamdba表达式里边访问flag变量,这时,就会触发“变量捕获”。
lamdba是回到函数,执行时机是很久之后,(操作系统真正创建出线程之后,才会执行的),很有可能后续线程创建好了,当前main这里的方法已经执行完毕了,对应的flag也就销毁了。
那么,为了解决上述的问题:把被捕获的变量拷贝1份,拷贝到lamdba里面。就算外部的变量销毁了,也不会影响里边的执行了。
拷贝呢就意味着 这个变量不适合进行修改。
在拷贝了一份之后,可能 lamdba表达式里边的变量和外部定义的变量就不是一回事了。所以,这样,一边改变,一边不变的,会很不好。
所以,这里就压根不能进行修改。

所以,flag这里的提示是:应该是final 或 实际上是final.
使用interrupt()去主动终止线程
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
System.out.println("t 结束");
});
t.start();
Thread.sleep(3000);
System.out.println("main 线程尝试终止 t 线程");
t.interrupt();
}
在上述代码中,
Thread.currentThread() 获取当前正在执行的线程对象
Thread.currentThread().isInterrupted() 判断当前线程是否被终止。
t.interrupt() 主动去终止,除了设置标志位之外,还可以唤醒像sleep这样的阻塞方法。
t.interrupt();
调用这个interrupt 这个方法会将isInterrupted 方法内部的标志位设为true. 但是,上述代码呢,是因为提前把sleep唤醒,在sleep唤醒之后,就会把isInterrupted在此设置回false.
如果,还是要进行循环的判定,则会发现代码继续执行下去。
所以,可以针对异常处理来使循环结束。
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
break : 就会立即终止线程
什么也不做处理 : 不终止
如果是抛出异常,在结果中就会显示异常
更多推荐



所有评论(0)