认识线程(Thread)
在进程中,要创建一个进程和销毁一个进程所消耗的硬件和软件资源是巨大的,因此为了优化上述过程,我们引入了“线程”。
线程是系统调度的基本单位。
1)线程和进程的关系
可以认为进程包含线程,一个进程里最少有一个线程,每个线程都可以单独执行一段逻辑,并且可以单独在CPU上调度,因此线程可以叫做“轻量级进程”。
在一个进程中,多个线程共享进程资源,而在后面在这一进程中创建的线程就直接使用进程创建的资源,可以省下申请资源的开销。
2)线程安全问题
当两个线程同时对同一个变量进行修改时,可能会引发线程之间的冲突,这时就会引发线程安全问题(后面详解)。
使用代码实现线程
线程是操作系统中的概念,操作系统中实现了线程这样的机制,因此操作系统对用户层提供了一些API供用户调用,JAVA中就是用Thread这个类进行封装。
Thread类:Thread是JAVA中标准库里的一个类,操作系统本身提供了一些函数进行操作线程,JVM就把这些函数封装成JAVA版本的Thread,这里操作Tread就是操作线程。
五种创建线程的方法:
1)定义一个静态内部类继承自Thread,实现Thread中的Run方法,创建一个这个静态内部类的实例。
public class dome2 {
static class MyThread extends Thread{
@Override
public void run() {
System.out.println("hah");
}
}
public static void main(String[] args) {
Thread dome = new MyThread();
dome.start();
}
}
Run方法就是进行执行一段逻辑的入口,run不需要代码显性调用。
注意点1:Start方法就是真正在操作系统中创建一个线程,run方法就是这个线程要执行逻辑的入口,这里如果只是调用run方法就会只执行main这个主线程而不会创建一个新的线程,以一段代码为例:
上述代码调用了start方法,创建了一个新的线程,所以创建的线程和主线程会并发执行,两个打印操作会同时进行。
如果将start去掉,直接调用run,就会只执行主线程,将run中的逻辑执行完才会执行下面,但是run中是死循环,因此只会打印出hah。
注意点2:主线程和新线程之间的执行顺序是由操作系统随机执行的,我们可以通过第三方工具JDK中的jconsole进行观察线程的执行情况。
2)创建一个子类,实现Runnable接口,重写Run方法,搭配Thread进行start
3)创建一个匿名内部类,重写run方法,start方法
4)创建一个runnable的匿名内部类,重写run方法,调用start
5)最简单的写法,在Thread中使用lambda表达式
线程中的几个常见属性
JAVA中对线程的状态进行了进一步扩充(后面细说)
1)后台线程:线程没执行完,进程可以结束(线程不可以结束进程)。
前台线程:线程没执行完,进程不可以结束(线程可以组织进程结束)。
main线程和我们自己创建的线程是前台线程,其他的都是后台线程。
我们可以通过setDaemon设置前台线程为后台线程(必须在线程start前设置),后台线程结束程序就结束了。
(这个程序当主线程运行三次之后结束进程,我们创建的后台线程就会随进程结束而结束)
2)是否存活
当线程没结束时就是true,结束或还没开始执行就是false
启动一个线程
启动一个线程要调用start,调用start才是真正调用系统中创建线程的api,而线程启动后会自动调用run中的内容。
1.调用start是非常快的,不会有任何的阻塞等待。
这里调用完start后就立刻调用下面得sout,打印出main和thread,但是要注意这里先打印出的是main,这是因为调用start后要创建线程。并不是一直是这样的,可能会有例外,这是因为系统调用线程是随机调度的。
2.一个线程只能start一次
一个线程start后就是就绪/阻塞状态,而对于阻塞/就绪状态的线程,就不能start了。
对于一个Thread而言,对应着操作系统中的一个线程。
中断(打断)一个线程
1.通过变量
通过修改线程内的变量进行打断
2.使用isInterrupted和interrupt中断
这里的currentThread是获取到调用此方法对象的引用,isInterrupted是默认false,下面的interrupt是将条件改为true。
注意事项:
如果在线程里加上sleep,再在main中调用interrupt,就会提前唤醒线程,触发异常,同时将isInterrupted重置为false。
总结:1).没使用sleep等阻塞操作时,interrupt会将isInterrupted从false改为true。
2).使用sleep的阻塞操作时,调用interrupt会抛出interruptException异常,将isInterrupted重置为false,同时提前唤醒sleep,但此时我们可以在catch中进行进一步操作,手动决定线程是否结束。
线程的等待
操作系统调用线程是随机调度(抢占式执行),线程等待就是约定好线程的结束顺序。
1.使用join进行等待
这里就是main等待线程t结束,再执行mian中的逻辑,这里的join是死等。
main等待线程t也是可以的,但是要获取的main的引用。
join不一定执行阻塞,当执行join时线程结束,join就不会阻塞等待。
join还有一个重载版本,设置等待的最大时间,最多等待xxx毫秒。