发表时间:2022-03-25来源:网络
目前的 CPU 有双核,四核,八核,一般情况下,它和线程数是1:1的对应关系,也就是四核 CPU 一般就能并行执行 4 个线程。但 Intel 引入超线程技术后,使核心数与线程数形成1:2的关系,也就是我们常说的 4核8线程
任意时刻,只有一个线程占用 CPU,处于运行状态。而多线程并发执行就是轮流获取 CPU 执行权。
分时调用模型轮流获取 CPU 执行权,均分 CPU 执行时间。
抢占式调度模型优先级高的线程优先获取 CPU 执行权,这也是 JVM 采用的线程调度模型。
进程是程序运行资源分配的最小单位。
这些资源就包括 CPU,内存空间,磁盘 IO 等。同一进程中的多个线程共享该进程的所有资源,而不同进程是彼此独立的。举个栗子,在手机开启一个 APP 实际上就是开启了一个进程了,而每一个 APP 程序会有很多线程在跑,例如刷新 UI 的线程等,所以说进程是包含线程的。
线程是 CPU 调度的最小单位,必须依赖于进程而存在。
线程是比进程更小的,能独立运行的基本单位,每一个线程都有一个程序计数器,虚拟机栈等,它可以与同一进程下的其它线程共享该进程的资源。
指应用能够同时执行不同的任务。例如多辆汽车可以同时在同一条公路上的不同车道上并行通行。
并发指应用能够交替执行不同的任务,因为一般的计算机只有一个 CPU 也就是只有一颗心,如果一个 CPU 要运行多个进程,那就需要使用到并发技术了,例如时间片轮转进程调度算。比如单 CPU 核心下执行多线程任务时并非同时执行多个任务,而是以一个非常短的时间不断地切换执行不同的任务,这个时间是我们无法察觉的出来的。
两者的区别:并行是同时执行,并发是交替执行。
线程是 CPU 调度的最小的单位,我们的程序是跑在 CPU 的一个核中的某一个线程中的,如果在程序中只有一个线程,那么对于双核心4线程的CPU来说就要浪费了 3/4 的 CPU 性能了,所以在创建线程的时候需要合理的利用 CPU 资源,具体可以看看 AsyncTask 内部的线程池是如何设计的。
加快响应用户的时间如果多个任务时串行执行的话,那么效果肯定不好,在移动端开发中,并发执行多个任务是很常见的操作,最常见的就是多线程下载了。
可以使代码模块化,异步化,简单化在 Android 应用程序开发中的,一般 UI 线程负责去更新界面相关的工作,而一些 IO,网络等操作一般会放在异步的工作线程去执行,这样使得不同的线程各司其职,异步化。
线程之间的安全性问题
问题1:同一进程间的多个线程是可以共享该进程的资源的,当多个线程访问共享变量时,就会线程安全问题。
问题2:为了解决线程之间的同步问题,一般会引入锁机制,对于线程之间抢夺锁时也是有可能造成死锁问题。
问题3:在 JVM 内存模型中,每一个线程都会分配一个虚拟机栈,这个虚拟机栈是需要占用内存空间的,如果无限制的创建线程的话,会耗尽系统的内存。
因为 Thread 构造中只接收 Runnable 类型的接口,需要实现将 Callable 的实现类包装为 FutureTask 之后交给 Thread 类去执行。
Callable callable = new Callable() { @Override public String call() throws Exception { Thread.sleep(1500); return "work done!"; } }; FutureTask futureTask = new FutureTask(callable); Thread thread = new Thread(futureTask); thread.start(); try { //get() 是一个阻塞式的操作,一直等待 call 方法执行完毕。 String resule = futureTask.get(); } catch (ExecutionException e) { e.printStackTrace(); }再来看 FutureTask 的应用:我们观察到 AsyncTask 内部就使用到了 FutureTask ,因为在 doInBackground() 需要有一个返回值,而恰好 Callable 就可以实现子线程执行完有返回值。使用 FutureTask 来封装 WorkRunnable 对象,然后再交给对应的线程池去执行。具体的代码如下:
总结:对于第1,2两种方式是在线程执行完毕后,无法得到执行的结果,而第三种方式是可以获取执行结果的。
也就是 run 执行完毕,或者是内部出现一个异常导致线程提前结束。
暴力终止
suspend() 使线程挂起,并且不会释放线程占有的资源(例如锁),resume() 使挂起的线程恢复。
stop() 暴力停止,立刻释放锁,导致共享资源可能不同步。
以上几个方法已经被 JDK 标记为废弃状态。
interrupt() 安全终止第一种情况:如果线程处于正常运行状态,那么线程的中断状态会被置为 true ,并且线程还是会正常执行,仅此而已。
第二种情况:如果当前线程如果是处于阻塞状态,例如调用了 wait,join,sleep 等方法,那么则会抛出InterruptedException异常。
总结: interrupt() 并不能中断线程,需要线程配合才能实现中断操作。
上面的代码时在线程体中执行一个 while(true)的死循环,然后在其他线程中调用 endThread.interrupt() 观察当前线程是否会执行完毕。
经测试:在调用线程的 interrupt()方法之后,while(true)是不会结束循环的,也就是线程还是一直在运行着。所以说 interrupt() 并不会应用 run 方法的执行
下面再来看看另一个关于 interrupt 方法的使用
在线程体内部 sleep(2000) 并且 try catch 对应的 InterruptedException 异常,如果在其他线程调用了 endThread.interrupt() 那么此处就会抛出 InterruptedException 异常,并且会isInterrupted() 会返回 false 。
public class EndThread2 extends Thread { @Override public void run() { super.run(); System.out.println("isInterrupted:" + isInterrupted()); try { //在其他线程调用 endThread.interrupt() 之后,会抛出 InterruptedException 异常并且线程的 // isInterrupted 会被标记为 false。因此最后输出的结果还是 false Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); System.out.println("catch InterruptedException"); } System.out.println("isInterrupted:" + isInterrupted()); } public static void main(String[] args) throws InterruptedException { final EndThread2 endThread = new EndThread2(); endThread.start(); Thread.sleep(300); //在其他线程中去调用线程的interrupt方法给线程打一个终止的标记 endThread.interrupt(); } }执行结果:
isInterrupted:false java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at endthread.EndThread.run(EndThread.java:28) catch InterruptedException isInterrupted:false将示例01中的 while(true) 修改为 while(!isInterrupted()){},在外界调用了 endThread.interrupt()之后,线程的 isInterrupted() 就会返回 true,标记着你可以结束线程了。
public class EndThread extends Thread { @Override public void run() { super.run(); System.out.println("isInterrupted:"+isInterrupted()); while (!isInterrupted()){ System.out.println("I am Thread body"); } System.out.println("isInterrupted:"+isInterrupted()); } public static void main(String[] args) throws InterruptedException { final EndThread endThread = new EndThread(); endThread.start(); Thread.sleep(10); //在其他线程中去调用线程的interrupt方法给线程打一个终止的标记 endThread.interrupt(); ) } }还有一种方式是设置一个 boolean 类型的变量 mIsExit 标记,当线程体内部判断到 mIsExit 为 false 那么就跳出循环。具体示例代码如下:
public class EndThread extends Thread { //这个变量需要在其他线程中判断,因此需要设置为线程可见的 private volatile boolean mIsExit = true; @Override public void run() { super.run(); while(mIsExit){ System.out.println("I am Thread body"); } } public static void main(String[] args) throws InterruptedException { final EndThread endThread = new EndThread(); endThread.start(); Thread.sleep(3); //设置标记为退出状态 endThread.mIsExit = false; } }start() 方法调用之后会让一个线程进入就绪等待队列,当获取到 CPU 执行权之后会执行线程体 run()方法。
run() 方法只是 Thread 类中一个普通方法,如果手动去调用,跟调用普通方法没有什么区别。
在 Java 中只有 Thread 才能表示一个线程,而 Runnable 只能表示一个任务,任务是需要交给线程去执行的,当出现如下代码时,你看到可能会懵逼,执行结果到底是什么?
//接受一个 runnable 接口 Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("runnable run invoked"); } }) { @Override public void run() { super.run(); System.out.println("thread started"); } }; thread.start();以上方式的输出结果是:
如果线程 run 方法内部调用了 super.run() 那么输出结果如下:
如果线程 run 方法内部不调用 super.run() 那么输出结果如下:
thread started我们可以通过源码来解答这个问题:在创建线程时,如果往构造函数中传入一个 Runnable 对象,那么它会给线程 target 属性赋值,并且在线程体执行时先判断 target 是否为空,不为空,则先执行 Runnable 的 run 方法,再执行当前线程体的子类中的 run 方法。
//Thread.java private Runnable target; public Thread(ThreadGroup group, Runnable target, String name, long stackSize) { init(group, target, name, stackSize); } @Override public void run() { if (target != null) { //如果传入的 Runnable 实例,那么会调用调用 runnable 实例的 run 方法 target.run(); } }Java线程中的 Thread.yield() 方法,译为线程让步。顾名思义,就是说当一个线程使用了这个方法之后,它会放弃自己CPU执行权,让自己或者其它的线程运行,注意是让自己或者其他线程运行,并不是单纯的让给其他线程。yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()`之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!
public class YieldDemo { public static void main(String[] args) { Yieldtask yieldtask = new Yieldtask(); Thread t1 = new Thread(yieldtask, "A"); Thread t2 = new Thread(yieldtask, "B"); Thread t3 = new Thread(yieldtask, "C"); Thread t4 = new Thread(yieldtask, "D"); t1.start(); t2.start(); t3.start(); t4.start(); } public static class Yieldtask implements Runnable { @Override public void run() { for (int i = 0; i Thread.yield(); } } } } }从运行结果可以看到,调用 yield() 方法的线程之后,CPU 执行权不一定会给其他线程抢到,有可能还是当前线程抢到 CPU 执行权。
... A-0 D-4 D-5 D-6//在这里,还是执行 D 这个线程 D-7 D-8 D-9 B-2 B-3 B-4 B-5 C-6 B-6 A-1 A-2 A-3 A-4 ...join() 把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
这里举一个栗子:午饭时间到了,老王(线程A)调用了微波炉热饭的方法,预约了4分钟,当微波炉跑了2分钟,这时老王看到一个美女(线程B)过来,这时主动调用了线程B.join()方法,此时把微波炉让给了美女,这时老王就等待美女热饭,直到热好美女的饭之后,才轮到老王去继续热饭,这就是一个 join() 方法的作用。
下面画了一个草图:
在 Java Thread 类中定义了一个 State 枚举,内部定义以下6个线程状态。
在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(TIME_WAITING,WAITING)和死亡(TERMINATED)五种状态。
我这里绘制一个草图,描述了各个状态之前的切换:
新建一个线程对象,此时并没有执行 start() 方法,这时的线程状态就是处于新建状态。
Thread thread = new Thread(){ public void run() {...} }; 就绪状态(RUNNABLE)start() 方法调用之后会让一个线程进入就绪等待队列,JVM 会为其创建对应的虚拟机栈,本地方法栈和程序计数器。处于这个状态的线程还没开始运行,需要等待 CPU 的执行权。
运行状态(RUNNING)处于就绪状态的线程在抢到 CPU 执行权之后,就处于运行状态,执行该线程的 run 方法。
阻塞状态(BLOCKED)TIME_WAIT/WAIT:
运行的线程执行 wait() /wait(long),join()/join(long)方法,那么这些线程放弃 CPU 执行和线程持有的锁,并且 JVM 会将这些线程放入到等待池中。
BLOCKED:
运行时的线程在获取同步锁时,发现锁被其他线程持有,这时 JVM 会将当前线程放入锁池中。
结束(TERMINATED)线程 run 方法执行完毕,或者线程被异常终止。
以上是关于 Java 多线程的一些基本知识点的总结,有任何不对的地方,请多多指正。
记录于2019年4月7日
上一篇:java基础知识大全
皓盘云建最新版下载v9.0 安卓版
53.38MB |商务办公
ris云客移动销售系统最新版下载v1.1.25 安卓手机版
42.71M |商务办公
粤语翻译帮app下载v1.1.1 安卓版
60.01MB |生活服务
人生笔记app官方版下载v1.19.4 安卓版
125.88MB |系统工具
萝卜笔记app下载v1.1.6 安卓版
46.29MB |生活服务
贯联商户端app下载v6.1.8 安卓版
12.54MB |商务办公
jotmo笔记app下载v2.30.0 安卓版
50.06MB |系统工具
鑫钜出行共享汽车app下载v1.5.2
44.7M |生活服务