![]() |
逆袭的芒果 · 短视频剪辑是如何赚钱的?有哪些途径? - 脉脉· 1 月前 · |
![]() |
坚韧的松鼠 · “杭州西湖日”捧出“西湖十景”· 1 月前 · |
![]() |
耍酷的骆驼 · 在家作麵包-基礎流程篇(內有詳細說明及製作流 ...· 2 月前 · |
![]() |
豪爽的香烟 · Category:奧摩語族- ...· 2 月前 · |
多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。
这里定义和线程相关的另一个术语 - 进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。
下图显示了一个线程完整的生命周期。
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
如果就绪状态的线程获取 CPU 资源,就可以执行 run() ,此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
创建一个线程Java 提供了三种创建线程的方法:
通过实现 Runnable 接口;
为了实现 Runnable,一个类只需要执行一个方法调用 run(),声明如下:
在创建一个实现 Runnable 接口的类之后,你可以在类中实例化一个线程对象。
Thread 定义了几个构造方法,下面的这个是我们经常使用的:
新线程创建之后,你调用它的 start() 方法它才会运行。
继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。
该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。
编译以上程序运行结果如下: Creating Thread-1 Starting Thread-1 Creating Thread-2 Starting Thread-2 Running Thread-1 Thread: Thread-1, 4 Running Thread-2 Thread: Thread-2, 4 Thread: Thread-1, 3 Thread: Thread-2, 3 Thread: Thread-1, 2 Thread: Thread-2, 2 Thread: Thread-1, 1 Thread: Thread-2, 1 Thread Thread-1 exiting. Thread Thread-2 exiting. Thread 方法 下表列出了Thread类的一些重要方法:
1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
4. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
1. 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
2. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
线程间通信
通过对多线程的使用,可以编写出非常高效的程序。不过请注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。
请记住,上下文的切换开销也很重要,如果你创建了太多的线程,CPU 花费在上下文的切换的时间将多于执行程序的时间!
dg5uw
159***[email protected]
1、线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。(是什么)
2、那么,我们为什么需要用到线程池呢?每次用的时候手动创建不行吗?
在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。(为什么)
线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快;另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。(什么用)
3、线程池都是通过线程池工厂创建,再调用线程池中的方法获取线程,再通过线程去执行任务方法。
Executors :线程池创建工厂类4、这里介绍两种使用线程池创建线程的方法
1):使用Runnable接口创建线程池
使用线程池中线程对象的步骤:
1、创建线程池对象Test.java 代码如下:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test { public static void main(String[] args) { //创建线程池对象 参数5,代表有5个线程的线程池 ExecutorService service = Executors.newFixedThreadPool(5); //创建Runnable线程任务对象 TaskRunnable task = new TaskRunnable(); //从线程池中获取线程对象 service.submit(task); System.out.println("----------------------"); //再获取一个线程对象 service.submit(task); //关闭线程池 service.shutdown();TaskRunnable.java 接口文件如下:
public class TaskRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println("自定义线程任务在执行"+i);2)使用Callable接口创建线程池
Callable接口:与Runnable接口功能相似,用来指定线程的任务。其中的call()方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常。
ExecutorService:线程池类
<T> Future<T> submit(Callable<T> task):获取线程池中的某一个线程对象,并执行线程中的 call() 方法
Future 接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用
使用线程池中线程对象的步骤: 1、创建线程池对象 2、创建 Callable 接口子类对象 3、提交 Callable 接口子类对象 4、关闭线程池Test.java 代码如下:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test{ public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(3); TaskCallable c = new TaskCallable(); //线程池中获取线程对象,调用run方法 service.submit(c); //再获取一个 service.submit(c); //关闭线程池 service.shutdown();TaskCallable.java 接口文件如下:
import java.util.concurrent.Callable; public class TaskCallable implements Callable<Object>{ @Override public Object call() throws Exception { for (int i = 0; i < 1000; i++) { System.out.println("自定义线程任务在执行"+i); return null; }#0 dg5uw
159***[email protected]
线程池练习:返回两个数相加的结果
要求:通过线程池中的线程对象,使用Callable接口完成两个数求和操作
Future 接口:用来记录线程任务执行完毕后产生的结果。
线程池创建与使用:get() 获取 Future对象中封装的数据结果
ThreadPoolDemo.java 文件代码如下:
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class ThreadPoolDemo { public static void main(String[] args) throws InterruptedException, ExecutionException { //创建线程池对象 ExecutorService threadPool = Executors.newFixedThreadPool(2); //创建一个Callable接口子类对象 //MyCallable c = new MyCallable(); MyCallable c = new MyCallable(100, 200); MyCallable c2 = new MyCallable(10, 20); //获取线程池中的线程,调用Callable接口子类对象中的call()方法, 完成求和操作 //<Integer> Future<Integer> submit(Callable<Integer> task) // Future 结果对象 Future<Integer> result = threadPool.submit(c); //此 Future 的 get 方法所返回的结果类型 Integer sum = result.get(); System.out.println("sum=" + sum); //再演示 result = threadPool.submit(c2); sum = result.get(); System.out.println("sum=" + sum); //关闭线程池(可以不关闭)MyCallable.java 接口文件代码如下:
import java.util.concurrent.Callable; public class MyCallable implements Callable<Integer> { //成员变量 int x = 5; int y = 3; //构造方法 public MyCallable(){ public MyCallable(int x, int y){ this.x = x; this.y = y; @Override public Integer call() throws Exception { return x+y; }#0 0℃space
960***[email protected]
#0 第二是就绪状态。当调用了线程对象的 start 方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。 第三是运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行 run 函数当中的代码。 第四是阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait 等方法都可以导致线程阻塞。 第五是死亡状态。如果一个线程的 run 方法执行结束或者调用 stop 方法后,该线程就会死亡。对于已经死亡的线程,无法再使用 start 方法令其进入就绪。 实现并启动线程有两种方法
1、写一个类继承自 Thread 类,重写 run 方法。用 start 方法启动线程2、写一个类实现 Runnable 接口,实现 run 方法。用 new Thread(Runnable target).start() 方法来启动 多线程原理:相当于玩游戏机,只有一个游戏机(cpu),可是有很多人要玩,于是,start 是排队!等 CPU 选中你就是轮到你,你就 run(),当 CPU 的运行的时间片执行完,这个线程就继续排队,等待下一次的run()。
调用 start() 后,线程会被放到等待队列,等待 CPU 调度,并不一定要马上开始执行,只是将这个线程置于可动行状态。然后通过 JVM,线程 Thread 会调用 run() 方法,执行本线程的线程体。先调用 start 后调用 run,这么麻烦,为了不直接调用 run?就是为了实现多线程的优点,没这个 start 不行。
1.start() 方法来启动线程,真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕,可以直接继续执行下面的代码;通过调用 Thread 类的 start() 方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。 然后通过此 Thread 类调用方法 run() 来完成其运行操作的, 这里方法 run() 称为线程体,它包含了要执行的这个线程的内容, run 方法运行结束, 此线程终止。然后 CPU 再调度其它线程。2.run() 方法当作普通方法的方式调用。程序还是要顺序执行,要等待 run 方法体执行完毕后,才可继续执行下面的代码; 程序中只有主线程——这一个线程, 其程序执行路径还是只有一条, 这样就没有达到写线程的目的。 记住:多线程就是分时利用 CPU,宏观上让所有线程一起执行 ,也叫并发。
public class Test { public static void main(String[] args) { Runner1 runner1 = new Runner1(); Runner2 runner2 = new Runner2(); // Thread(Runnable target) 分配新的 Thread 对象。 Thread thread1 = new Thread(runner1); Thread thread2 = new Thread(runner2); // thread1.start(); // thread2.start(); thread1.run(); thread2.run(); class Runner1 implements Runnable { // 实现了Runnable接口,jdk就知道这个类是一个线程 public void run() { for (int i = 0; i < 100; i++) { System.out.println("进入Runner1运行状态——————————" + i); class Runner2 implements Runnable { // 实现了Runnable接口,jdk就知道这个类是一个线程 public void run() { for (int i = 0; i < 100; i++) { System.out.println("进入Runner2运行状态==========" + i);#0 (02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。 (03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。 5. 死亡状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
#0 cz
109***[email protected]
#0 我v成为v我v成为
201***[email protected]
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; public class CompletableFutureDemo { public static void main(String[] args) throws InterruptedException { long l = System.currentTimeMillis(); CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> { System.out.println("执行耗时操作..."); timeConsumingOperation(); return 100; completableFuture.whenComplete((result, e) -> { System.out.println("结果:" + result); System.out.println("主线程运算耗时:" + (System.currentTimeMillis() - l) + " ms"); new CountDownLatch(1).await(); static void timeConsumingOperation() { try { Thread.sleep(3000); } catch (Exception e) { e.printStackTrace();控制台输出:
执行耗时操作... 主线程运算耗时:74 ms 结果:100可以发现耗时操作没有占用主线程的时间片,达到了异步调用的效果。我们也不需要引入任何第三方的依赖,这都是依赖于 java.util.concurrent.CompletableFuture 的出现。CompletableFuture 提供了近 50 多个方法,大大便捷了 java 多线程操作,和异步调用的写法。
#0