线程概念:
1)线程定义:线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际执行单元。每个线程都可以按照自己的顺序执行各自的代码,多个线程之间同时执行着多份代码。
2)为什么要有线程?:
单核cpu算力遇到瓶颈,需要多核cpu来提高算力。而并发编程可以解决这一问题。
其次,虽然多进程也可以实现并发编程,但是线程比进程更轻量,更具有优势:
1.创建线程比创建进程更快
2.销毁线程比销毁进程更快
3.调度线程比调度进程更快
所以大家通常会使用多线程
可以举一个形象的例子来形容多进程和多线程:
有一个房间,一张桌子,一个人和桌子上的一百只鸡,这个人的任务是将这一百只鸡吃完。
为了提高效率,引入多进程: 有两个房间,房间里分别有一个桌子,一个人和50只鸡,相比之前,效率会大幅度提升。
而多线程则是:一个房间,一张桌子,一百只鸡,两个人共同来完成这个任务,节省下了房间和桌子的开销,效率仍然能够大幅提高。
3.试想,如果引入更多的线程效率是否会进一步提高?
答案是否定的:
当线程数目太多,线程调度的开销也会进一步扩大,进而拖慢程序的性能。
4.在任务进行的过程中,如果有两个人同时看中了同一只鸡,就有可能产生“冲突”,导致线程不安全,有可能会使代码产生bug。当这两个人中的一个没能抢到鸡时,非常生气,就把桌子给掀了,我吃不了,大家都别吃了。
此时这个线程便会抛出异常(如果及时捕获处理掉,也不一定会导致进程终止),可能会带走整个进程,所有其他的线程都无法再继续工作。
3).进程和线程的区别:
1.进程包含线程,每个进程至少有一个线程存在,即主线程
2.进程与进程之间不共享内存空间,同一个进程的线程之间共享同一个内存空间。
3.进程是系统分配资源的最小单位,线程是系统调度的最小单位
4.一个进程挂了不会影响其他进程,但一个线程挂了,可能会把同进程内的其他线程一同带走(整个进程崩溃)
4)创建线程的四种写法:
4.11.继承Thread,重写run
- 首先定义一个类(这里我的类名为MyThread),这个类需要继承Thread
- 然后需要重写run方法,run方法内部就是我们要执行的线程代码
- 最后启动线程
class MyThread extends Thread{ @Override public void run(){ while(true){ System.out.println("hello Thread"); try { Thread.sleep(1000); }catch (InterruptedException e){ throw new RuntimeException(e); } } } } public class Demo1 { public static void main(String[] args) throws InterruptedException { //创建Thread类的子类,在子类中重写Run方法 Thread t = new MyThread(); t.start(); while(true){ System.out.println("hello main"); Thread.sleep(1000); } } }在Thread父类中,本身有一个run方法,而我们可以重写这个方法,编写属于自己的逻辑
该代码中共有两个线程:
1.由newThread创建并调用t.start()启动,进入while循环后每秒打印“hello Thread”.
2.主线程(main):进入while循环每秒打印“hello main”
4.111sleep简单介绍:
sleep为休眠,意味着让当前线程放弃cpu,休息1000毫秒后再开始执行该任务。
注:
调用sleep会让当前线程处于阻塞状态(TIMED_WAITING),在此期间,其他线程会调用该线程的Interrupt()方法来中断它的休眠,被中断后,sleep便会抛出InterruptedException异常,从而让线程有机会响应中断请求。
而Java要求强制要求处理这个异常,通常在run方法中用try-catch或在方法签名中throws异常。
运行结果:
我们可以看到,执行过程中,有的时候main方法在前,有的时候Thread在前,
这是因为多个线程,调度顺序是随机的(操作系统内核控制),无法预测执行顺序,称为抢占式调度。
将t.start改为t.run后,由于主线程中并没有创建新的线程,而run又只是一个普通方法调用,主线程会直接执行run方法,进入while死循环打印hellothread。
4.112: 可以借助第三方工具来查看线程的具体执行情况:
上方的图标代表线程数量,左下角的代表当前进程中的线程。
4.12. 实现Runnable方法,重写run
- 定义一个类实现Runnable接口
- 重写run方法
- 构建Thread对象,将创建的Runnable对象作为参数传入
- 启动线程(t.start)
class MyRunnable implements Runnable { @Override public void run() { while (true) { System.out.println("hello Thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } public class Demo2 { public static void main(String[] args) throws InterruptedException { Runnable runnable = new MyRunnable(); Thread t = new Thread(runnable); t.start(); while(true){ System.out.println("hello main"); Thread.sleep(1000); } } }这两种方式推荐哪一个?
第二种,因为Runnable更加解耦合,方便后续代码的修改
耦合:两个代码的关联关系越大,耦合越大,推荐低耦合(后续代码如果出错,不会影响其他代码)
高内聚:将与某种逻辑关联的代码放到一块。(有条理),反之同理。
写代码的时候推荐低耦合高内聚。
4.13:使用Thread的匿名内部类:
1.创建一个Thread类的子类(匿名)
2.{}里编写子类的定义代码
3.创建这个匿名内部类的实例,将实例的引用赋值给t
public class Demo3 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread() { public void run() { while (true) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }; t.start(); while(true){ System.out.println("hello main"); Thread.sleep(1000); } } }4.14 使用Runnable的匿名内部类
public class Demo4 { public static void main(String[] args)throws InterruptedException { Runnable runnable = new Runnable() { @Override public void run() { while(true){ System.out.println("hello thread"); try{ Thread.sleep(1000); }catch (InterruptedException e){ throw new RuntimeException(e); } } } }; Thread t = new Thread(runnable); t.start(); while(true){ System.out.println("hello main"); Thread.sleep(1000); } } }4.15:引入lambda表达式(推荐)
public class Demo5 { public static void main(String[] args)throws InterruptedException { Thread t = new Thread(()->{ while(true){ System.out.println("hello thread"); try{ Thread.sleep(1000); }catch(InterruptedException e){ throw new RuntimeException(e); } } }); t.start(); while(true){ System.out.println("hello main"); Thread.sleep(1000); } } }lambd表达式相当于是匿名内部类的替换写法,这种方法可以快速方便的就创建出一个线程
//lambda表达式本质上,是一个匿名函数(没有名字的函数,用一次就完了),主要来实现“回调函数”的效果
4.2 Thread类的其他属性和方法
前台线程与后台线程的区别:
前台线程:main线程及用户创建的线程,
后台线程:垃圾回收等辅助作用的线程(也叫做守护线程)
注:jvm会等待所有前台线程结束后,才会结束运行(不会等待后台进程)
前台线程好比一个酒桌中的话事人,而后台线程好比酒桌里的透明人,什么时候结束酒席由酒桌中的多个话事人决定。
4.21: 以下代码来形象的说明前台线程:
public class Demo6 { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { while (true) { System.out.println("hello 1"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t1.start(); Thread t2 = new Thread(() -> { while (true) { System.out.println("hello 2"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t2.start(); Thread t3 = new Thread(() -> { while (true) { System.out.println("hello 3"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t3.start(); for (int i = 0; i < 3; i++) { System.out.println("hello main"); Thread.sleep(1000); } } }虽然main线程结束了,但自己创建的3个前台线程还存在,所以进程还会存在,继续执行三个线程
4.22 IsAlive:判断线程是否存活:
public class Demo8 { public static void main(String[] args) throws InterruptedException{ Thread t1 = new Thread(()->{ while(true){ System.out.println("hello thread"); try{ Thread.sleep(1000); }catch (InterruptedException e){ throw new RuntimeException(e); } } }); System.out.println(t1.isAlive()); //false 因为线程还没有创建 t1.start(); System.out.println(t1.isAlive()); while(true){ System.out.println("hello main"); Thread.sleep(1000); } } }在t1线程还未创建之前,线程不存在,false,创建之后,为true(线程存在)。
4.23: setDaemon: 设置为后台进程。
public class Demo7 { public static void main(String[] args) throws InterruptedException{ Thread t = new Thread(()->{ while(true){ System.out.println("hello thread"); try{ Thread.sleep(1000); }catch (InterruptedException e){ throw new RuntimeException(e); } } }); t.setDaemon(true); t.start(); for (int i = 0; i <3 ; i++) { System.out.println("hello main"); Thread.sleep(1000); } System.out.println("线程结束"); } }将前台线程t设置为后台线程后,当main线程(前台线程)三次执行完后,后台进程也会随之关闭
4.24:中断线程interrupt:
常见的有两种方法来实现:
1.通过共享的标记来沟通、
2.调用interrupt方法来沟通
1.使用自定义的变量来做标志位:
定义一个当作线程中断标志的变量,通过其他线程对这个变量的修改,实现线程中断:
public class Demo10 { private static boolean isFinished = false; public static void main(String[] args) throws InterruptedException{ Test test = new Test(); Thread t = new Thread(()->{ while(!isFinished) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println("thread 结束"); }); t.start(); Thread.sleep(3000); isFinished = true; } }通过设置一个成员变量isFinished,在线程执行3s后更改成员变量isfinished的值,从而中断t线程。
lambda表达式会自动捕获方法内,之前出现的变量
lambda表达式内使用的标志,必须是final或者常量
2.使⽤ Thread.interrupted()或者 Thread.currentThread().isInterrupted() 代替⾃定义标志位
public class Demo11 { public static void main(String[] args) throws InterruptedException{ Thread t = new Thread(()->{ System.out.println(Thread.currentThread().getName()); while(!Thread.currentThread().isInterrupted()){ System.out.println("hello thread"); try{ Thread.sleep(1000); }catch (InterruptedException e){ //throw new RuntimeException(e); break; } } System.out.println("t线程结束"); }); t.start(); Thread.sleep(3000); System.out.println("main线程尝试终止t线程" ); t.interrupt(); } }Thread.currentThread(),静态方法,哪个线程调用,就获取到哪个线程的引用
isInterrupted,是为了判断线程是否被中断,thread变量里的Boolean值,中断为true,反之为false。
而interrupt方法会发送中断请求,修改Boolean变量里的值,还能唤醒像sleep这样的阻塞方法。
以上就是博主对线程知识的分享,在之后的博客中会陆续分享有关线程的其他知识,如果有不懂的或者有其他见解的欢迎在下方评论或者私信博主,也希望多多支持博主之后和博客!!🥰🥰