javaEE之线程初步认识
一、为什么要用多线程
因为多线程他能让程序同时进行工作,如:一个程序中,一个线程用来接受请求,另一个线程用于计算。如果是单线程的话,就只能一个结束后再进行下一个。
但此时肯定会右朋友问,用进程也可以实现这功能啊,为什么要用线程。你说得对,但是,相对于多线程而言,其实进程的创建销毁的开销是很大。这里就要讲一下线程和进程的关系了
众所周知,电脑内部有一个叫CPU的元件,它能能接受线程和进程的执行指令,就好比CPU是工人,而线程进程是任务,任务分配给工人去做。其中,任务中肯定也有大任务包含小任务,如:计划出国旅游(大任务指出国旅游,小任务指做攻略,订酒店等细节)。而这时,我们就能理解成进程是大任务,线程是小任务
同时,每个出国旅游的计划(进程),都有独立的预算和行李(体现出进程和进程之间资源是不共享的)。其中一个出国旅游计划泡汤了,不会影响到其他的旅游计划(体现了进程与进程的独立性),并且启动一个旅游计划成本较高(体现了创建进程的开销大)。而小任务中的每一样都有牵连,如预算的多少影响酒店的选择等(体现了线程与线程间共享线程资源)。计划中的细节可以随意添加(体现线程创建开销小),小任务间可以方便交流(体现线程通信成本低),并且酒店等细节会影响大任务(体现线程会引起进程崩溃)
因此,综上描述,我们能总结到以下几点
1.线程的开销比进程的开销要小
2.进程包含线程,一个进程有多个线程
3.每个线程都是一个执行流,都可以放在CPU上调度执行,线程是操作系统调度执行的基本单位
4.进程间资源是不能共享,线程间资源是共享的
5.某个线程的出错,会影响整个进程
二、创建多线程
1.创建子类,继承Thread,重写run
2.使用Runnable接口
3.继承Thread,使用匿名内部类
4.实现Runnable,使用匿名内部类
或者是
5.用lambda表达式,一般使用这种
6.问题
看到这里,有些小伙伴可能会疑惑,为什么Thread和Runnable。使用时,为什么不用import包啊?其实,Thread和Runnable这两个类是在java.lang包中,java.lang包这个是特殊的包,能够被默认import
那为什么用同一个Thread对象,重复start会报错的?那是因为,Java中约定了一个Thread对象,和一个操作系统线程是“一一对应的”,也就是说一个Thread对象只能start一次
三、查看线程运行情况
查看线程运行情况,我们可以打开我们 jdk 的文件夹,点进 bin 文件夹,找到 jconsole.exe
点进去后,点本地进程,再选择刚刚运行线程的类名,点击连接
进去后,点击线程按钮,就进到线程的页面了
这就是之这个类所有线程了
四、属性及其方法
1.常见的属性
前台线程会阻止整一个线程结束,后台线程不会阻止整个线程结束。就好像在饭桌上,领导是前台线程,员工是后台线程。当领导吃饱了,他能够直接让饭局结束,但如果员工吃饱了,并不行,他需要等待领导的指示来做事。当然,能用代码体现,如代码:
而主线程包括代码中创建的线程,默认都是前台线程,如代码:
2.常见的方法
1.修改线程名字
其中,我们想给线程起个名字时,我们能这样做,再lambada里面加引号,引号里写线程的名称
2.使用interrupt中断线程
中断一个线程。对于Java来讲,一个线程终止,就是这个线程入口方法执行完毕。也就是说java不提供强制终止(所有让线程终止的方法,都要围绕入口方法执行)。就如当A线程对B线程执行终止,不能强制终止,这样是不安全的,因为不确定B线程正在做什么。强制终止会导致程序崩坏。
如下面代码就是能进行终止,当输入0的时候,线程t就终止
不过值得注意的是,running这个变量需要是final或者是事实final(指没有final修饰,但这个变量并没有被修改)。因为在Java中,lambada变量捕获时要求时final或者时事实final。为什么呢?其实,Java中捕获变量是将变量的值拷贝一份。一旦拷贝不一致,代码就会乱。那为什么写成成员变量就可以呢?因为当写成成员变量时,触发的就不是lambada捕获变量,而是内部类访问外部类。众所周知,lambada其实本质上是基于函数式接口的匿名内部类。所以就没有限制,但访问局部变量就是有限制的,下面是错误示范:
OK,细节弄明白后,这时有会有一个问题出现,当sleep里面的时间变成100s的时候,我们就发现,当我们输入完零之后,他并非立即停止,而是一直保持sleep状态,直到100s过后,不符合我们的预期。所以,这时我们要将while里面的条件进行修改,用isInterupted来进行判断,与线程方法interrupt配合使用。并且此处的cur和t其实是同一个线程对象
可是,值得注意的是,虽然两个是同一个对象,但不能省略创建当前线程对象这一步。原因就是Java在执行的时候都是先对构造方法的参数从左到右依次求值,然后再创建对象,最后赋值给左边变量,所以一定先是对Thread()括号中参数进行求值。再执行Thread这个构造方法,再把Thread的构造方法的结果,赋值给t,定义t。通俗的讲就是从后往前编译,先编译蓝色圈内的代码,再编译黄色圈的代码
因此,在第一阶段的时候这个t还没有被创建出来,不能这样写。那如果是这样呢
语法上是可以的,但是要保证t这个对象是不可变的,接着,再将if内的代码进行修改,直接使用Thread中的interrupt方法来进行终止。由于interrupt这个方法不仅能设置标志位,还能唤醒sleep等导致阻塞的方法,会使sleep抛出异常。所以是整个程序按预期进行
不过,这个强制唤醒的方法,它会将标志位进行了重置,如果使用的是打印异常的处理的话,会导致多输出一次hello Thread,而throw不会,这是因为throw搞完之后,他会中断线程,而打印异常不会,但如果我们在打印异常的后面加个break,那就能阻止这个情况发生
3.jion方法的使用
这个是用来让一个线程先执行,让另一个线程后执行的方法。因为当线程被start时,他要进行三步,调用api、系统创建线程以及系统线程调度。以下面这个代码为例:
导致这样的结果的原因可能是系统创建线程需要一定时间,在还没创建完,主线程就已经到输出这一步了,导致result并没有进行计算就被打印了。所以当我们使用join时,就能解决这个问题
这里join是表示main线程等待 t线程 执行完后,再执行。这里的join的时间是不确定的,取决于 t 何时退出。所以,为了避免长期阻塞,我们能在括号内添加时间。当超过这个指定时间,join只是从阻塞状态变成就绪状态,至于下一步如何做,还要等待系统调度
4.获取线程名字
我们能在线程里使用getName方法来获取线程名字,但如果在Runnable这个对象里,就不能了,因为它不支持这个方法,我们只能通过创建当前线程对象来进行使用此方法
五、六种状态
Java中给线程引入六种状态
1.new
创建了对象,但没start
public class Demo7 {public static void main(String[] args) {Thread t = new Thread(() -> {});System.out.println(t.getState());t.start();}
}
2.terminated
操作系统内部线程已经销毁了,但Thread对象还在。也就是线程入口方法执行完毕
public class Demo7 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {});t.start();Thread.sleep(10);System.out.println(t.getState());}
}
3.runnable
可工作的。又可以分成正在工作的和即将开始工作的
public class Demo7 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {Thread cur = Thread.currentThread();System.out.println("hehe");System.out.println(cur.getState());});t.start();}
}
4.waiting
死等状态
public class Demo7 {public static void main(String[] args) throws InterruptedException {Thread manThread = Thread.currentThread();Thread t = new Thread(() -> {while(true){System.out.println("主线程: " + manThread.getState());try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();t.join();}
}
5.timed_waiting
带有超时间等待
public class Demo7 {public static void main(String[] args) throws InterruptedException {Thread manThread = Thread.currentThread();Thread t = new Thread(() -> {while(true){System.out.println("主线程: " + manThread.getState());try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();t.join(100000);}
}
6.blocked
特指由于锁引起的阻塞
public class Demo7 {public static void main(String[] args) throws InterruptedException {Object locker = new Object();Thread manThread = Thread.currentThread();Thread t = new Thread(() -> {Thread cur = Thread.currentThread();synchronized (locker) {try {Thread.sleep(1000_000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread t1 = new Thread(() -> {Thread cur1 = Thread.currentThread();synchronized (locker){}});t.start();Thread.sleep(100);t1.start();Thread.sleep(500);System.out.println(t1.getState());}
}
好啦,这期内容就先到这啦,欢迎大家留言指正!!!