JavaEE之多线程
线程概念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.startclass 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.创建这个匿名内部类的实例将实例的引用赋值给tpublic 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这样的阻塞方法。以上就是博主对线程知识的分享在之后的博客中会陆续分享有关线程的其他知识如果有不懂的或者有其他见解的欢迎在下方评论或者私信博主也希望多多支持博主之后和博客