网站权重等级,推广app赚佣金,wordpress简历页,育贤网站建设本节⽬标 认识多线程 掌握多线程程序的编写 掌握多线程的状态 一. 认识线程#xff08;Thread#xff09;
1概念
1) 线程是什么 ⼀个线程就是⼀个 执⾏流. 每个线程之间都可以按照顺序执⾏⾃⼰的代码. 多个线程之间 同时 执⾏着多份代码. 还… 本节⽬标 认识多线程 掌握多线程程序的编写 掌握多线程的状态 一. 认识线程Thread
1概念
1) 线程是什么 ⼀个线程就是⼀个 执⾏流. 每个线程之间都可以按照顺序执⾏⾃⼰的代码. 多个线程之间 同时 执⾏着多份代码. 还是回到我们之前的银⾏的例⼦中。之前我们主要描述的是个⼈业务即⼀个⼈完全处理⾃⼰的业务。我们进⼀步设想如下场景 ⼀家公司要去银⾏办理业务既要进⾏财务转账⼜要进⾏福利发放还得进⾏缴社保。 如果只有张三⼀个会计就会忙不过来耗费的时间特别⻓。为了让业务更快的办理好张三⼜找来两位同事李四、王五⼀起来帮助他三个⼈分别负责⼀个事情分别申请⼀个号码进⾏排队⾃此就有了三个执⾏流共同完成任务但本质上他们都是为了办理⼀家公司的业务。 此时我们就把这种情况称为多线程将⼀个⼤任务分解成不同⼩任务交给不同执⾏流就分别排队 执⾏。其中李四、王五都是张三叫来的所以张三⼀般被称为主线程Main Thread。 2) 为啥要有线程 ⾸先, 并发编程 成为 刚需. 单核 CPU 的发展遇到了瓶颈. 要想提⾼算⼒, 就需要多核 CPU. ⽽并发编程能更充分利⽤多核 CPU资源. 有些任务场景需要 等待 IO, 为了让等待 IO 的时间能够去做⼀些其他的⼯作, 也需要⽤到并发编程. 其次, 虽然多进程也能实现 并发编程, 但是线程⽐进程更轻量.线程就是轻量级进程 创建线程⽐创建进程更快. 销毁线程⽐销毁进程更快. 调度线程⽐调度进程更快. 最后, 线程虽然⽐进程轻量, 但是⼈们还不满⾜, 于是⼜有了 线程池(ThreadPool) 和 协程(Coroutine) 3) 进程和线程的区别
进程是包含线程的. 每个进程⾄少有⼀个线程存在即主线程。 进程和进程之间不共享内存空间. 同⼀个进程的线程之间共享同⼀个内存空间. ⽐如之前的多进程例⼦中每个客⼾来银⾏办理各⾃的业务但他们之间的票据肯定是不想让别⼈知道的否则钱不就被其他⼈取⾛了么。⽽上⾯我们的公司业务中张三、李四、王五虽然是不同的执⾏流但因为办理的都是⼀家公司的业务所以票据是共享着的。这个就是多线程和多进程的最⼤区别。 进程是系统分配资源的最⼩单位线程是系统调度的最⼩单位。 ⼀个进程挂了⼀般不会影响到其他进程. 但是⼀个线程挂了, 可能把同进程内的其他线程⼀起带⾛(整个进程崩溃). 4) Java 的线程 和 操作系统线程 的关系 线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对⽤⼾层提供了⼀些 API 供用户使⽤(例如 Linux 的 pthread 库). api:application programming interface应用程序编程接口 操作系统提供的原生api是c写的不同操作系统的线程api不相同 Java 标准库中 Thread 类 可以视为是对操作系统提供的 API 进⾏了进⼀步的抽象和封装.Thread类就在Java默认导入的java.lang包里面 2 第⼀个多线程程序 感受多线程程序和普通程序的区别: 每个线程都是⼀个独⽴的执⾏流 多个线程之间是 并发 执⾏的. 使⽤ jconsole 命令观察线程 3 创建线程
⽅法1 继承 Thread 类 1继承 Thread 来创建⼀个线程类. class MyThread extends Thread{Override//run相当于线程的入口函数public void run() {System.out.println(hello world);}
} 2创建 MyThread 类的实例 Thread tnew MyThread(); 3调⽤ start ⽅法启动线程 //真正在系统中创建出一个线程t.start(); 4休眠 try {Thread.sleep(1000);//sleep是静态方法表示休眠休息一会再执行用ms为单位} catch (InterruptedException e) {throw new RuntimeException(e);} 5run 在执行线程时不需要显示运行run方法JVM自动调用 Tip: 在自己写的MyThread类里面不允许用throws只能try-catch因为其父类Thread里面没有实现该功能但main函数中可以 实际开发中异常的处理方式 1.记录异常信息作为日志.后续程序员根据日志调查问题 程序仍然正常往后执行逻辑,不会因为这个异常就终止(对于服务器非常关键的) 2.进行重试有的异常是概率性的网络通讯 3.特别严重的问题必须立即马上处理的问题 通过短信/邮件/微信/电话 通知程序员 报警机制 服务器和客户端指的是两个程序 服务器server被动接受请求返回响应的一方 客户端client主动发起请求的一方 客户端给服务器发送的数据叫做“request请求服务器给客户端返回的叫做“response”响应通常一个服务器可以给多个客户端提供服务服务器基本7*24待命 根据输出可以知道多个线程的调度是随机的“抢占式执行” Q:可以控制输出顺序吗 A输出顺序是操作系统内核的调度器控制的没法在应用应用程序中编写代码控制 (调度器没有提供 api 的) 唯一能做的就是给线程设置优先级(但是优先级对于操作系统来说也是仅供参考,不会严格的定量的遵守) 如果直接使用run方法而没有start那么MyTread实质上没有创建出进程只有main进程遇到run中的死循环之后无法退出。 package Thread;
class MyThread extends Thread{Override//run相当于线程的入口函数public void run() {while(true){System.out.println(hello run);try {Thread.sleep(1000);//sleep是静态方法表示休眠休息一会再执行用ms为单位} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
public class demo1 {public static void main(String[] args) throws InterruptedException {Thread tnew MyThread();//真正在系统中创建出一个线程//t.start();t.run();while(true){System.out.println(hello main);Thread.sleep(1000);}}
}⽅法2 实现 Runnable 接⼝ 1. 实现 Runnable 接⼝ class MyRunnable implements Runnable{Overridepublic void run() {while(true){System.out.println(hello thread);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}} 2. 创建 Thread 类实例, 调⽤ Thread 的构造⽅法时将 Runnable 对象作为 target 参数. Runnable runnablenew MyRunnable();
Thread tnew Thread(runnable); 3. 调⽤ start ⽅法 t.start(); 总代码 package Thread;
class MyRunnable implements Runnable{Overridepublic 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 runnablenew MyRunnable();Thread tnew Thread(runnable);t.start();while (true){System.out.println(hello main);Thread.sleep(1000);}}
}Runnable最终还是要通过 Thread,真正创建线程
线程里要干啥, 通过 Runnable 来表示(而不是通过直接重写 Thread的run 来表示了 Q如何判断是用哪种 A根据线程要执行的任务的定义,是放到 Thread 里面,还是放到外面(Runnable 中) Q使用Runnable有什么好处吗 A解耦合。要执行的任务本身,和 线程这个概念,能够解耦合,从而后续如果变更代码.(比如不通过线程执行这个任务,通过其他方式.….) 采用 Runnable 这样的方案,代码的修改就会更简单. 对⽐上⾯两种⽅法:
继承 Thread 类, 直接使⽤ this 就表⽰当前线程对象的引⽤. 实现 Runnable 接⼝, this 表⽰的是 MyRunnable 的引⽤. 需要使⽤Thread.currentThread() ⽅法3 实现Tread的匿名内部类
package Thread;public class demo3 {public static void main(String[] args) throws InterruptedException {Thread threadnew Thread(){while(true){System.out.println(hello run);try {Thread.sleep(1000);//sleep是静态方法表示休眠休息一会再执行用ms为单位} catch (InterruptedException e) {throw new RuntimeException(e);}}};thread.start();while(true){System.out.println(hello main);Thread.sleep(1000);}}
}⽅法4 匿名内部类创建 Runnable ⼦类对象 package Thread;public class demo4 {public static void main(String[] args) throws InterruptedException {Runnable runnable new Runnable() {Overridepublic void run() {while (true) {System.out.println(hello run);try {Thread.sleep(1000);//sleep是静态方法表示休眠休息一会再执行用ms为单位} catch (InterruptedException e) {throw new RuntimeException(e);}}}};Thread threadnew Thread(runnable);thread.start();while(true){System.out.println(hello main);Thread.sleep(1000);}}
}⽅法5 lambda 表达式创建 Runnable ⼦类对象 package Thread;public class demo5 {public static void main(String[] args) throws InterruptedException {Thread threadnew Thread(()-{while (true) {System.out.println(hello run);try {Thread.sleep(1000);//sleep是静态方法表示休眠休息一会再执行用ms为单位} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();while(true){System.out.println(hello main);Thread.sleep(1000);}}
}4 多线程的优势-增加运⾏速度 可以观察多线程在⼀些场合下是可以提⾼程序的整体运⾏效率的。 使⽤ System.nanoTime() 可以记录当前系统的 纳秒 级时间戳. serial 串⾏的完成⼀系列运算. concurrency 使⽤两个线程并⾏的完成同样的运算. 二. Thread 类及常⻅⽅法 Thread 类是 JVM ⽤来管理线程的⼀个类换句话说每个线程都有⼀个唯⼀的 Thread 对象与之关联。 ⽤我们上⾯的例⼦来看每个执⾏流也需要有⼀个对象来描述类似下图所⽰⽽ Thread 类的对象就是⽤来描述⼀个线程执⾏流的JVM 会将这些 Thread 对象组织起来⽤于线程调度线程管理。 1 Thread 的常⻅构造⽅法 Thread t1 new Thread();
Thread t2 new Thread(new MyRunnable());
Thread t3 new Thread(这是我的名字);
Thread t4 new Thread(new MyRunnable(), 这是我的名字); main方法结束了主线程就结束了 以前认知里main方法结束程序就执行完毕是针对单线程程序的 2 Thread 的⼏个常⻅属性 ID 是线程的唯⼀标识不同线程不会重复 名称是各种调试⼯具⽤到 状态表⽰线程当前所处的⼀个情况下⾯我们会进⼀步说明 优先级⾼的线程理论上来说更容易被调度到 关于后台线程需要记住⼀点JVM会在⼀个进程的所有⾮后台线程结束后才会结束运⾏。 是否存活即简单的理解为 run ⽅法是否运⾏结束了 线程的中断问题下⾯我们进⼀步说明 public class ThreadDemo {public static void main(String[] args) {Thread thread new Thread(() - {for (int i 0; i 10; i) {try {System.out.println(Thread.currentThread().getName() : 我还在);Thread.sleep(1 * 1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() : 我即将死去);});System.out.println(Thread.currentThread().getName() : ID: thread.getId());System.out.println(Thread.currentThread().getName() : 名称: thread.getName());System.out.println(Thread.currentThread().getName() : 状态: thread.getState());System.out.println(Thread.currentThread().getName() : 优先级: thread.getPriority());System.out.println(Thread.currentThread().getName() : 后台线程: thread.isDaemon());System.out.println(Thread.currentThread().getName() : 活着: thread.isAlive());System.out.println(Thread.currentThread().getName() : 被中断: thread.isInterrupted());thread.start();while (thread.isAlive()) {}System.out.println(Thread.currentThread().getName() : 状态: thread.getState());}
} 3 启动⼀个线程 - start() 之前我们已经看到了如何通过覆写 run ⽅法创建⼀个线程对象但线程对象被创建出来并不意味着线程就开始运⾏了。 覆写 run ⽅法是提供给线程要做的事情的指令清单 线程对象可以认为是把 李四、王五叫过来了 ⽽调⽤ start() ⽅法就是喊⼀声”⾏动起来“线程才真正独⽴去执⾏了。 调⽤ start ⽅法, 才真的在操作系统的底层创建出⼀个线程 4 中断⼀个线程 李四⼀旦进到⼯作状态他就会按照⾏动指南上的步骤去进⾏⼯作不完成是不会结束的。但有时我们需要增加⼀些机制例如⽼板突然来电话了说转账的对⽅是个骗⼦需要赶紧停⽌转账那张三该如何通知李四停⽌呢这就涉及到我们的停⽌线程的⽅式了。 ⽬前常⻅的有以下两种⽅式 通过共享的标记来进⾏沟通 调⽤ interrupt() ⽅法来通知 ⽰例1: 使⽤⾃定义的变量来作为标志位. //需要给标志位上加 volatile 关键字(这个关键字的功能后⾯介绍).
public class ThreadDemo {private static class MyRunnable implements Runnable {public volatile boolean isQuit false;Overridepublic void run() {while (!isQuit) {System.out.println(Thread.currentThread().getName() : 别管我我忙着转账呢!);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() : 啊险些误了⼤事);}}public static void main(String[] args) throws InterruptedException {MyRunnable target new MyRunnable();Thread thread new Thread(target, 李四);System.out.println(Thread.currentThread().getName() : 让李四开始转账。);thread.start();Thread.sleep(10 * 1000);System.out.println(Thread.currentThread().getName() : ⽼板来电话了得赶紧通知李四对⽅是个骗⼦);target.isQuit true;}
} ⽰例-2: 使⽤ Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替⾃定义标志位. Thread 内部包含了⼀个 boolean 类型的变量作为线程是否被中断的标记. • 使⽤ thread 对象的 interrupted() ⽅法通知线程结束. public class ThreadDemo {private static class MyRunnable implements Runnable {Overridepublic void run() {// 两种⽅法均可以while (!Thread.interrupted()) {
//while (!Thread.currentThread().isInterrupted()) {System.out.println(Thread.currentThread().getName() : 别管我我忙着转账呢!);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();System.out.println(Thread.currentThread().getName() : 有内⻤终⽌交易);// 注意此处的 breakbreak;}}System.out.println(Thread.currentThread().getName() : 啊险些误了⼤事);}}public static void main(String[] args) throws InterruptedException {MyRunnable target new MyRunnable();Thread thread new Thread(target, 李四);System.out.println(Thread.currentThread().getName() : 让李四开始转账。);thread.start();Thread.sleep(10 * 1000);System.out.println(Thread.currentThread().getName() : ⽼板来电话了得赶紧通知李四对⽅是个骗⼦);thread.interrupt();}
} thread 收到通知的⽅式有两种 1. 如果线程因为调⽤ wait/join/sleep 等⽅法⽽阻塞挂起则以 InterruptedException 异常的形式通 知清除中断标志 ◦ 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择忽略这个异常, 也可以跳出循环结束线程. ◦ Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置不清除中断标志 2. 否则只是内部的⼀个中断标志被设置thread 可以通过这种⽅式通知收到的更及时即使线程正在 sleep 也可以⻢上收到。 5 等待⼀个线程 - join() 有时我们需要等待⼀个线程完成它的⼯作后才能进⾏⾃⼰的下⼀步⼯作。例如张三只有等李四转账成功才决定是否存钱这时我们需要⼀个⽅法明确等待线程的结束。 package Thread;public class ThreadDemo1 {public static void main(String[] args) throws InterruptedException {Runnable target () - {for (int i 0; i 10; i) {try {System.out.println(Thread.currentThread().getName() : 我还在⼯作);Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() : 我结束了);};Thread thread1 new Thread(target, 李四);Thread thread2 new Thread(target, 王五);System.out.println(先让李四开始⼯作);thread1.start();thread1.join();System.out.println(李四⼯作结束了让王五开始⼯作);thread2.start();thread2.join();System.out.println(王五⼯作结束了);}
} ⼤家可以试试如果把两个 join 注释掉现象会是怎么样的呢 附录 6 获取当前线程引⽤ 这个⽅法我们已经⾮常熟悉了 public class ThreadDemo {public static void main(String[] args) {Thread thread Thread.currentThread();System.out.println(thread.getName());}
} 7 休眠当前线程 也是我们⽐较熟悉⼀组⽅法有⼀点要记得因为线程的调度是不可控的所以这个⽅法只能保证实际休眠时间是⼤于等于参数设置的休眠时间的。 public class ThreadDemo {public static void main(String[] args) throws InterruptedException {System.out.println(System.currentTimeMillis());Thread.sleep(3 * 1000);System.out.println(System.currentTimeMillis());}
} 三、 线程的状态
1 观察线程的所有状态 线程的状态是⼀个枚举类型 Thread.State
public class ThreadState {public static void main(String[] args) {for (Thread.State state : Thread.State.values()) {System.out.println(state);}}
} NEW: 安排了⼯作, 还未开始⾏动 RUNNABLE: 可⼯作的. ⼜可以分成正在⼯作中和即将开始⼯作. BLOCKED: 这⼏个都表⽰排队等着其他事情 WAITING: 这⼏个都表⽰排队等着其他事情 TIMED_WAITING: 这⼏个都表⽰排队等着其他事情 TERMINATED: ⼯作完成了. 2. 线程状态和状态转移的意义 ⼤家不要被这个状态转移图吓到我们重点是要理解状态的意义以及各个状态的具体意思。 还是我们之前的例⼦ 刚把李四、王五找来还是给他们在安排任务没让他们⾏动起来就是 NEW 状态 当李四、王五开始去窗⼝排队等待服务就进⼊到 RUNNABLE 状态。该状态并不表⽰已经被银⾏⼯作⼈员开始接待排在队伍中也是属于该状态即可被服务的状态是否开始服务则看调度器的调度; 当李四、王五因为⼀些事情需要去忙例如需要填写信息、回家取证件、发呆⼀会等等时进⼊BLOCKED 、 WATING 、TIMED_WAITING 状态⾄于这些状态的细分我们以后再详解 如果李四、王五已经忙完为 TERMINATED 状态。 所以之前我们学过的 isAlive() ⽅法可以认为是处于不是 NEW 和 TERMINATED 的状态都是活着的。 3 观察线程的状态和转移 观察 1: 关注 NEW 、 RUNNABLE 、 TERMINATED 状态的转换 package Thread;public class ThreadStateTransfer {public static void main(String[] args) throws InterruptedException {Thread t new Thread(() - {for (int i 0; i 1000_0000; i) {}}, 李四);System.out.println(t.getName() : t.getState());;t.start();while (t.isAlive()) {System.out.println(t.getName() : t.getState());;}System.out.println(t.getName() : t.getState());;}
} 观察 2: 关注 WAITING 、 BLOCKED 、 TIMED_WAITING 状态的转换 public static void main(String[] args) {final Object object new Object();Thread t1 new Thread(new Runnable() {Overridepublic void run() {synchronized (object) {while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}}, t1);t1.start();Thread t2 new Thread(new Runnable() {Overridepublic void run() {synchronized (object) {System.out.println(hehe);}}}, t2);t2.start();}
} 使⽤ jconsole 可以看到 t1 的状态是 TIMED_WAITING , t2 的状态是 BLOCKED 修改上⾯的代码, 把 t1 中的 sleep 换成 wait public static void main(String[] args) {final Object object new Object();Thread t1 new Thread(new Runnable() {Overridepublic void run() {synchronized (object) {try {// [修改这⾥就可以了!!!!!]// Thread.sleep(1000);object.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}, t1);
...} 使⽤ jconsole 可以看到 t1 的状态是 WAITING 结论: • BLOCKED 表⽰等待获取锁, WAITING 和 TIMED_WAITING 表⽰等待其他线程发来通知. • TIMED_WAITING 线程在等待唤醒但设置了时限; WAITING 线程在⽆限等待唤醒