JAVA多线程——(一)如何实现多线程
一、背景介绍
最近开始了人生的新征程,开始转java开发了,之前也看过一些关于JAVA多线程的一些资料和文档,但是没有系统的进行过整理和总结。借此机会对java多线程以及常见问题进行系统的整理和总结。在这一小节中,主要目的是整理一下多线程的实现方式。在此之前,首先复习一下线程的生命周期,有利于后期对于并发场景的理解
二、线程的生命周期
在介绍线程的生命周期前,先简要的总结一下什么是线程、为什么要使用线程。
1、什么是线程?
在计算机中,一个应用程序是一个进程,一个进程可以并发多个线程,线程是CPU的最小调度单位、
2、为什么要使用线程
-
- 将串行执行转换为并行执行
- 提高系统并发能力
- 提高系统吞吐率,充分利用CPU资源
在介绍完什么是线程后,接下来介绍一下线程的生命周期。如下图所示,线程包含有5个状态:新建状态、就绪状态、运行状态、死亡状态、阻塞状态。
其中分别为:
- 新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
- 就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
- 运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
- 阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
死亡状态:-
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
-
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
-
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
-
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
三、多线程的实现方式
Java 提供了三种创建线程的方法:
-
- 通过实现 Runnable 接口;
- 通过继承 Thread 类本身;
- 通过 Callable 和 Future 创建线程。
1、通过继承Thread类本身
继承thread类,并重写run方法,调用start()方法开始执行。Thread内部也实现了Runnable接口
package thread; public class MyThreadDemo1 extends Thread{ private String name; private Integer age; public MyThreadDemo1(String name, Integer age){ this.name = name; this.age = age; } public void run(){ try { for(int i = 4; i > 0; i--) { System.out.println("Thread: " + name + ", " + i); // 让线程睡眠一会 Thread.sleep(50); } }catch (InterruptedException e) { System.out.println("Thread " + name + " interrupted."); } System.out.println("Thread " + name + " exiting."); } public static void main(String[] args) { MyThreadDemo1 thread1 = new MyThreadDemo1("thread11111", 11); thread1.start(); MyThreadDemo1 thread2 = new MyThreadDemo1("thread222222", 22); thread2.start(); } }
运行结果:
Thread: thread222222, 4 Thread: thread11111, 4 Thread: thread222222, 3 Thread: thread11111, 3 Thread: thread222222, 2 Thread: thread11111, 2 Thread: thread222222, 1 Thread: thread11111, 1 Thread thread222222 exiting. Thread thread11111 exiting.
2、通过实现 Runnable 接口
由于有的类已经继承自某一个类,同事java中限制了只能单继承,所以可以通过实现Runnable接口的方式实现多线程。
通过重写run方法,.创建线程对象,调用start()方法启动线程
package thread; public class MyThreadDemo2 implements Runnable{ private String name; private Integer age; public MyThreadDemo2(String name, Integer age){ this.name = name; this.age = age; } public void run(){ try { for(int i = 4; i > 0; i--) { System.out.println("Thread: " + name + ", " + i); // 让线程睡眠一会 Thread.sleep(50); } }catch (InterruptedException e) { System.out.println("Thread " + name + " interrupted."); } System.out.println("Thread " + name + " exiting."); } public static void main(String[] args) { MyThreadDemo2 mythread1 = new MyThreadDemo2("thread11111", 11); Thread thread1 = new Thread(mythread1); MyThreadDemo2 mythread2 = new MyThreadDemo2("thread222222", 22); Thread thread2 = new Thread(mythread1); thread1.start(); thread2.start(); } }
运行结果:
Thread: thread11111, 4 Thread: thread11111, 4 Thread: thread11111, 3 Thread: thread11111, 3 Thread: thread11111, 2 Thread: thread11111, 2 Thread: thread11111, 1 Thread: thread11111, 1 Thread thread11111 exiting. Thread thread11111 exiting.
3、通过 Callable 和 Future 创建线程
Callable箱规Runnable接口,可以获取线程的返回值。实现方式为:
- 实现Callable接口,并实现call() 方法
- 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
- 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程
- 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
重点关注项:实现后面的Callable<Long>,其中Long为线程返回值得类型,需和创建FutureTask<Long> future1 = new FutureTask<>(mythread1)的类型,以及call() 函数返回值的类型保持一致
package thread; import org.apache.commons.logging.Log; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class MyThreadDemo3 implements Callable<Long> { private String name; private Integer age; public MyThreadDemo3(String name, Integer age){ this.name = name; this.age = age; } public static void main(String[] args) throws ExecutionException, InterruptedException { MyThreadDemo3 mythread1 = new MyThreadDemo3("thread11111", 11); FutureTask<Long> future1 = new FutureTask<>(mythread1); Thread thread1 = new Thread(future1); MyThreadDemo3 mythread2 = new MyThreadDemo3("thread222222", 22); FutureTask<Long> future2 = new FutureTask<>(mythread2); Thread thread2 = new Thread(future2); thread1.start(); thread2.start(); System.out.println("thread1 result:" + future1.get() + "\n"); System.out.println("thread2 result:" + future2.get() + "\n"); } @Override public Long call() throws Exception { try { for(int i = 4; i > 0; i--) { System.out.println("Thread: " + name + ", " + i); // 让线程睡眠一会 Thread.sleep(50); } }catch (InterruptedException e) { System.out.println("Thread " + name + " interrupted."); } System.out.println("Thread " + name + " exiting."); return System.currentTimeMillis(); } }
运行结果:
Thread: thread222222, 4 Thread: thread11111, 4 Thread: thread11111, 3 Thread: thread222222, 3 Thread: thread11111, 2 Thread: thread222222, 2 Thread: thread11111, 1 Thread: thread222222, 1 Thread thread11111 exiting. thread1 result:1651053928927 Thread thread222222 exiting. thread2 result:1651053928934
四、多线程总结
多线程可以提升系统的并发能力、CPU利用率,但是多线程也引入了一些新的问题:多线程如何通信? 多线程如何保证数据一致性?多线程是不是回死锁?滥用多线程会有什么影响?(线程上下文频繁切换,消耗系统资源)。这些问题,会在后面的文章中一一介绍。
posted on 2022-04-27 18:13 1450811640 阅读(241) 评论(0) 编辑 收藏 举报