• 多线程总结


    并发并行

    并行:同一时刻同时处理多个任务。简单理解:一边......一边......

    并发:同一时间段内,多个任务交替执行。简单理解:一会......一会......

    进程和线程

    进程是指正在运行的程序。

    特点
    • 进程是系统运行的基本单位
    • 每个进程都有自己的独立空间,一组资源
    • 每一个进程内部数据和状态都是完全独立的
    • 每一个应用程序运行时都会产生一个进程

    线程

    线程是进程中执行运算的最小单位,一个进程执行过程中可以产生多个线程,线程必须在某个进程内执行。

    一个进程中至少有一个线程,也可以有多个线程,这样的程序就称为多线程程序。

    进程和线程的区别

    进程:有独立的内存空间,进程中的数据存放空间(堆和栈)是独立的,至少有一个进程。

    线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小得多。

    资源分配给进程,同一进程的所有线程共享该进程的所有资源(如内存)。

    处理器分配给线程,即真正在处理器上运行的是线程。

    Java程序中至少包含两个线程:一个main()线程,另外一个是垃圾回收机制线程。

    多线程和多进程的区别

    本质区别在于,每个进程拥有一套自己的变量,而线程则是共享数据。

    线程调度

    分时调度:所有线程轮流拥有CPU的使用权,平均分配每个线程占用CPU的时间。

    抢占式调度:优先让优先级高的线程使用CPU,如果优先级相同,会随机选择一个线程。

    创建线程

    每个程序至少自动拥有一个线程,称为主线程,当程序加载到内存中时启动主线程,main()就是主线程的入口。

    创建线程的三种方法

    继承Thread类,重写run(),通过start()启动线程。

    1. public class MyThread extends Thread{
    2. public MyThread(String name){
    3. super(name);
    4. }
    5. @Override
    6. public void run() {
    7. //run方法里面写线程具体的任务
    8. for (int i = 0; i < 10; i++){
    9. System.out.println(Thread.currentThread().getName());
    10. }
    11. }
    12. }
    1. public class MyThreadTest {
    2. public static void main(String[] args) {
    3. //创建MyThread对象
    4. MyThread myThread = new MyThread("我的线程");
    5. //开启一个新的线程 实际上是去执行重写的run方法
    6. myThread.start();
    7. for (int i = 0; i < 100; i++){
    8. System.out.println("主线程运行了" + i + "次");
    9. }
    10. }
    11. }

    实现Runnable接口的方式创建

    实现Runnale接口,重写run()方法

    1. public class RunnableDemo {
    2. public static void main(String[] args) {
    3. Thread.currentThread().setName("主线程");
    4. Thread t = new Thread(new MyRunnaable());
    5. t.setName("子线程");
    6. t.start();
    7. //子线程运行后,主线程继续运行
    8. for (int i = 0; i < 10; i ++){
    9. System.out.println(Thread.currentThread().getName() + "运行了" + i + "次");
    10. }
    11. }
    12. }
    13. class MyRunnaable implements Runnable{
    14. @Override
    15. public void run() {
    16. //写子线程要执行的任务
    17. //输出1-100
    18. for (int i = 0; i < 100; i++){
    19. System.out.println(Thread.currentThread().getName() + "运行了" + i + "次");
    20. }
    21. }
    22. }

    Runnnable对象仅仅作为Thread对象的target,Runnale实现类里包含的run()仅仅作为线程的执行体,实际的线程对象依然是Thread实例,只是该实例负责执行该其target的run()。

    Thread和Runnable的区别

    实现Runnable接口比继承Thread的优势
    1. 适合多个相同的程序代码的线程去共享一个资源
    2. 避免单继承的局限性
    3. 增加程序的健壮性,实现解耦,代码可以被多个线程共享
    4. 实现Runnale接口很容易实现资源共享
    5. 线程池也只能放入实现Runnable或Callable类线程,不能直接放入继承Thread的类

    实现Callable接口的方式创建

    1. import java.util.concurrent.Callable;
    2. import java.util.concurrent.ExecutionException;
    3. import java.util.concurrent.Future;
    4. import java.util.concurrent.FutureTask;
    5. public class CallableDemo implements Callable {
    6. @Override
    7. public Integer call() throws Exception {
    8. int sum = 0;
    9. for (int i = 1; i <= 10; i++){
    10. sum += i;
    11. }
    12. return sum;
    13. }
    14. public static void main(String[] args) throws ExecutionException, InterruptedException {
    15. Callable callable = new CallableDemo();
    16. FutureTask futureTask = new FutureTask<>(callable);
    17. Thread thread = new Thread(futureTask);
    18. thread.start();
    19. System.out.println(futureTask.get());
    20. }
    21. }

    Callable和Runnale比较相似,Runnable不返回结果,也不能抛出检查异常,Callable没有返回结果。

    Callable和Runnable的区别

    • Callable的call()有返回值,Runnale的run()没有返回值。
    • call()可以抛出检查异常,run()不能。
    • Callable接口的结果可以通过Future对象来获取。

    FutureTask类

    FutureTask是一个实现了Future和Runnable的类,它可以表示一个异步计算的结果,FutureTask可以包装一个Callable和Runnale对象,通过get()获取异步计算的结果。

    1. Callable call = new Myacallable();
    2. FutureTask task = new FutureTask(call);
    3. Thread thread = new Thread(task);
    4. thread.start();
    构造方法

    public FutureTask(Runnable runnable, V result) :创建一个FutureTask在运行时执行给定的Runnable,并且让get在完成时返回结果。

    public FutureTask ( Callable < V > callable ):创建一个FutureTask在运行时执行给定的callable。

    Thread

    构造方法

    public  Thread():创建一个新线程

    public Thread(String name):分配一个指定名字的线程

    public Thread(Runnable runnable):分配一个带有指定目标的线程

    public Thread(Runnable runnable):分配一个带有指定目标的线程,并指定名字

    常用方法
    public static Thread currentThread(): 返回对当前正在执行的线程
    public void run() : 表示线程的任务,如果线程是使用Runnable对象构造的,则执行Runnale的run(),否则此方法不执行任何操作并返回,所有Thread子类都应该覆盖此方法。
    public synchronized void start():线程开始执行。
    public static native void sleep(long millis) :让线程睡眠指定的毫秒数,也就是暂停执行,在此期间,线程不会丢失任何的CPU使用权。
    public final String getName():返回此线程的名称。
    public final synchronized void setName(String name):修改线程名称。
    public final int getPriority():返回线程的优先级。
    public final void setPriority(int newPriority):更改线程优先级,1-10之间,优先级高的只是比优先级低的获得CPU使用权的概率更高,并不意味着一定会得到CPU使用权。
    public final native boolean isAlive() :线程是否活着,就是一个线程已经启动并且尚未结束。
    public final void join() :等待线程死亡,等于join(0)
    public void interrupt() :中断这个线程
    public static boolean interrupted() :测试当前线程是否中断。该方法可以清除线程的中断状态(设置中断状态为false),如果这个方法被调用两次,那么第二次调用返回false,
    public static boolean interrupted() :测试当前线程是否中断。线程的中断状态不受此方法影响。
    public static native void yield():导致当前线程处于让步状态。但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程如果有其他可运行线程拥有与此线程相等的优先级,那么这些线程会被调度,虽然是让步,但是此线程也会和其他线程抢夺CPU使用权,并不一定让的出去。与sleep方法类似,但是不能又用户指定暂停时间。
    public State getState() :返回此线程的状态,返回值是 Thread 的⼀个内部类,枚举类 State 。线程状态可以是:
    new:已经创建好线程了,但是尚未启动。
    Runnable:可以运行的线程处于此状态。就是调用start()方法后的状态,具备执行资格,等待CPU调用。
    Blocked:被阻塞的线程处于此状态。
    Waiting:正在等待其他线程执行指定动作的线程处于此状态。
    TIMED_WAITING :正在等待另⼀个线程执⾏动作达到指定等待时间的线程于此状态 ( sleep(1000) , join(1000) ),只等待规定的时间,超过规定时间就会和其他线程抢占资源。
    TERMINATED 已退出的线程处于此状态。
    Thread.sleep(long millis) :当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。
    obj.wait() , 当前线程调用对象的wait()方法 ,当前线程释放对象锁 ,进入等待队列 。依靠
    notify()/notifyAll() 唤醒或者 wait(long timeout) timeout时间到自动唤醒。
    obj.notify() 唤醒在此对象监视器上等待的单个线程,选择是任意性的。 notifyAll() 唤醒在此对象监视器上等待的所有线程。

    守护线程

    守护线程类似于一个默默无闻的小跟班,依赖于它所守护的那个线程,它守护的那个线程结束了,它也就结束了。如果所有线程都执行完毕了,没有要执行的了,它也会结束。

    守护线程的终止自身无法控制,因此不能把IO、File等重要操作逻辑分配给它。

    守护线程的作用

    GC垃圾回收线程:

    就是⼀个经典的守护线程,当我们的程序中不再有任何运⾏的Thread,程序就不会再产⽣垃圾,垃圾回收器也就⽆事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会⾃动离开。它始终在低级别的状态中运⾏,⽤于实时监控和管理系统中的可回收资源。
    守护线程应用场景:为其他线程提供服务支持;任何情况下,程序结束时这个线程必须正常且要立刻关闭,就可以作为守护线程来使用。
    守护线程实例:
    1. public class WatchThread extends Thread{
    2. //创建守护线程,检查C盘容量,少于1G发出预警
    3. File file = new File("C:");
    4. @Override
    5. public void run() {
    6. while (true){
    7. long space = file.getFreeSpace();
    8. System.out.println("C盘剩余空间:" + space / 1024 / 1024 / 1024 + "G");
    9. if(space < 1024 * 1024 * 1024){
    10. System.out.println("C盘空间不足");
    11. }
    12. try {
    13. //暂停5秒后继续监视
    14. sleep(5000);
    15. } catch (InterruptedException e) {
    16. e.printStackTrace();
    17. }
    18. }
    19. }
    20. public static void main(String[] args) {
    21. WatchThread thread = new WatchThread();
    22. //设置为守护线程,在start之前设置
    23. thread.setDaemon(true);
    24. thread.start();
    25. }
    26. }

    线程同步

    为了解决线程安全问题,可以使用线程同步的思想,最常见的方案就是加锁。意思还是每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动释放锁,然后其他先才能加锁再进来。

    使用多个线程访问同一资源的时候,就会出现线程安全问题,java提供了同步机制 synchronized来解决。
    java提供的三种同步机制
    1. 同步代码块
    2. 同步方法
    3. 锁机制
    同步代码块

    就是把访问共享数据的代码锁起来

    1. synchronized(锁对象){
    2. // ...访问共享数据的代码...
    3. }

    锁对象:对象的同步锁只是一个概念,可以想象成在任意对象上标记了一个锁。

    如何选择锁对象:

    • 建议把共享资源作为锁对象,不要将随便无关的对象作为锁对象。
    • 对于实例方法,建议把this作为锁对象。
    • 对于静态方法,建议把类的字节码(类名.class)作为锁对象。

    CountDownLatch:一种同步辅助工具,允许一个或多个线程等待,直到其他线程中执行的一组操作完成。

    1. CountDownLatch latch = new CountDownLatch(10);
    2. latch.countDown(); // 每一个线程运行结束 数量减一
    3. latch.await(); // 等待这个计数器中数字清空
    同步方法

    使用synchronized修饰的方法就是同步方法,保证线程A执行的时候其他线程只能在方法外等待。就是把一个方法锁住,一个线程调用这个方法,另一个线程就执行不了,只有等上一个线程执行结束,下一个线程调用才能继续执行。

    同步方法的锁对象:实例方法的锁对象是类的字节码对象(类名.class),静态方法的锁对象是this,也就是方法的调用者。

    同步方法和同步代码块的区别

    同步方法是将方法中的所有代码锁住,同步代码块只是把方法中的部分代码锁住。

    创建一个 FutureTask 在运行时执行给定的厂家
    Runnable ,并安排 get 在成功完成时返回给定的结果
    创建一个 FutureTask 在运行时执行给定的
    Runnable ,并安排 get 在成功完成时返回给定的结果。
  • 相关阅读:
    Spring5依赖注入(DI)Set方式注入收录
    用哈希简单封装unordered_map和unordered_set
    git服务器宕机后,怎么用本地仓库重新建立gitlab服务器(包括所有历史版本)
    反射的机制
    PyTorch搭建卷积神经网络(CNN)进行视频行为识别(附源码和数据集)
    YOLOv8血细胞检测(12):EMA基于跨空间学习的高效多尺度注意力、效果优于ECA、CBAM、CA | ICASSP2023
    VM系列振弦读数模块采集测量数据的一般步骤
    叠氮N3修饰Ag2S量子点|巯基SH偶联Ag2Se量子点|生物素Biotin改性Ag2Te量子点
    盲盒小程序 跨平台兼容性测试策略:打造无缝体验
    tf.logging
  • 原文地址:https://blog.csdn.net/m0_57218914/article/details/133246796