Java多线程

目录

什么是进程?

什么是线程?

线程的实现方式?

Thread 类中的start() 和 run()方法有什么区别?

线程NEW状态

线程RUNNABLE状态

线程的RUNNING状态

线程的BLOCKED状态

线程的TERMINATED状态

线程状态转化图

如何知道代码段被哪个线程调用?

线程活动状态

sleep()方法

如何优雅的设置睡眠时间?

停止线程

interrupted 和isInterrupted

yield

线程的优先级

优先级继承特性

谁跑的更快?

 线程种类

守护线程的特点

Java中典型的守护线程

如何设置守护线程        

Java虚拟机退出时Daemon线程中的finally块一定会执行?

结果:

设置线程上下文类加载器

join

线程池

为什么使用线程池

线程池创建,作用

向线程池提交任务

关闭线程池

线程池如何合理设置

Executor

Executor框架的主要成员

FixedThreadPool

源码:

SingleThreadExecutor

源码

CachedThreadPool

源码

工作流程:


什么是进程?

进程是系统中正在运行的一个程序,程序一旦运行就是进程。
进程可以看成程序执行的一个实例。进程是系统资源分配的独立实体,每个进程都拥有独立的地址空间。一个进程无法访问另一个进程的变量和数据结构,如果想让一个进程访问另一个进程的资源,需要使用进程间通信,比如管道,文件,套接字等。

什么是线程?

是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

线程的实现方式?

  • 继承Thread类
  • 实现Runnable接口
  • 使用Callable和Future

Thread 类中的start() 和 run()方法有什么区别?

1.start()方法来启动线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码;通过调用Thread类的start()方法来启动一个线程, 这时此线程是处于就绪状态,并没有运行。 然后通过此Thread类调用方法run()来完成其运行操作的,这里方法run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程终止。然后CPU再调度其它线程。

2.run()方法当作普通方法的方式调用。程序还是要顺序执行,要等待run方法体执行完毕后,才可继续执行下面的代码;程序中只有主线程--这一个线程,其程序执行路径还是只有一条, 这样就没有达到写线程的目的。

线程NEW状态

new创建一个Thread对象时,并没处于执行状态,因为没有调用start方法启动改线程,那么此时的状态就是新建状态。

线程RUNNABLE状态

线程对象通过start方法进入runnable状态,启动的线程不一定会立即得到执行,线程的运行与否要看cpu的调度,我们把这个中间状态叫可执行状态(RUNNABLE)。

线程的RUNNING状态

一旦cpu通过轮询货其他方式从任务可以执行队列中选中了线程,此时它才能真正的执行自己的逻辑代码。

线程的BLOCKED状态

线程正在等待获取锁。

  • 进入BLOCKED状态,比如调用了sleep,或者wait方法
  • 进行某个阻塞的io操作,比如因网络数据的读写进入BLOCKED状态
  • 获取某个锁资源,从而加入到该锁的阻塞队列中而进入BLOCKED状态

线程的TERMINATED状态

TERMINATED是一个线程的最终状态,在该状态下线程不会再切换到其他任何状态了,代表整个生命周期都结束了。


下面几种情况会进入TERMINATED状态:

  • 线程运行正常结束,结束生命周期
  • 线程运行出错意外结束
  • JVM Crash 导致所有的线程都结束

线程状态转化图

如何知道代码段被哪个线程调用?

System.out.println(Thread.currentThread().getName());

线程活动状态

public class Demo extends Thread{
    @Override
    public void run() {
        System.out.println("run is "+Thread.currentThread().isAlive());
    }

    public static void main(String[] args) {
        Demo demo = new Demo();
        System.out.println("begin——"+demo.isAlive());
        demo.start();
        System.out.println("end——"+demo.isAlive());
    }

}

sleep()方法

方法sleep()的作用是在指定的毫秒数内让当前的“正在执行的线程"休眠(暂停执行)。

如何优雅的设置睡眠时间?

jdk1.5 后,引入了一个枚举TimeUnit,对sleep方法提供了很好的封装。
比如要表达2小时22分55秒899毫秒。

        Thread.sleep(8575899L);
        TimeUnit.HOURS.sleep(2);
        TimeUnit.MINUTES.sleep(22);
        TimeUnit.SECONDS.sleep(55);
        TimeUnit.MILLISECONDS.sleep(899);

可以看到表达的含义更清晰,更优雅。

停止线程

run方法执行完成,自然终止。
stop()方法,suspend()以及resume()都是过期作废方法,使用它们结果不可预期。
大多数停止一个线程的操作使用Thread.interrupt()等于说给线程打一个停止的标记,此方法不回去终止一个正在运行的线程,需要加入一个判断才能可以完成线程的停止。

interrupted 和isInterrupted

interrupted:判断当前线程是否已经中断,会清除状态。
isInterrupted:判断线程是否已经中断,不会清除状态。

yield

放弃当前cpu资源,将它让给其他的任务占用cpu执行时间。但放弃的时间不确定,有可能刚刚放弃,马上又获得cpu时间片。

测试代码:(cpu独占时间片)

class MyThread extends  Thread{
    @Override
    public void run() {

        long beginTime = System.currentTimeMillis();
        int count = 0;
        for (int i = 0; i < 50000000; i++) {
            count+=i;
        }
        long endTime =System.currentTimeMillis();
        System.out.println("使用时间:"+(endTime-beginTime)+"毫秒");
    }

    public static void main(String[] args) {
        new MyThread().start();
    }
}

加入yield,再来测试。(cpu让给其他资源导致速度变慢)

class MyThread extends  Thread{
    @Override
    public void run() {

        long beginTime = System.currentTimeMillis();
        int count = 0;
        for (int i = 0; i < 50000000; i++) {
            Thread.yield();
            count+=i;
        }
        long endTime =System.currentTimeMillis();
        System.out.println("使用时间:"+(endTime-beginTime)+"毫秒");
    }

    public static void main(String[] args) {
        new MyThread().start();
    }
}

线程的优先级

在操作系统中,线程可以划分优先级,优先级较高的线程得到cpu资源比较多,也就是cpu有限执行优先级较高的线程对象中的任务,但是不能保证一定优先级高,就先执行。
Java的优先级分为1~10个等级,数字越大优先级越高,默认优先级大小为5。超出范围则抛出:java.lang.lllegalArgumentException 。

优先级继承特性

线程的优先级具有继承性,比如a线程启动b线程,b线程与a优先级是一样的。

谁跑的更快?

设置优先级高低两个线程,累加数字,看谁跑的快,上代码。

public class test {
    public static void main(String[] args) {
        ThreadDemo1 t1 = new ThreadDemo1();
        ThreadDemo2 t2 = new ThreadDemo2();
        t1.setPriority(2);
        t2.setPriority(8);
        t1.start();
        t2.start();
        try {
            TimeUnit.SECONDS.sleep(2); // 主线程休眠 2 秒
            t1.stop();
            t2.stop();
            System.out.println(t1.getName() + "|count: " + t1.getCount());
            System.out.println(t2.getName() + "|count: " + t2.getCount());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class ThreadDemo1 extends Thread {
    private volatile Long count = 0l;

    public Long getCount() {
        return count;
    }

    @Override
    public void run() {
        while (true) {
            count++;
        }
    }
}

class ThreadDemo2 extends Thread {
    private volatile Long count = 0l;

    public Long getCount() {
        return count;
    }

    @Override
    public void run() {
        while (true) {
            count++;
        }
    }
}

 线程种类

Java线程有两种,一种是用户线程,一种的守护线程

守护线程的特点

守护线程是一个比较特殊的线程,主要被用做程序中后台调度以及支持性工作。当Java虚拟机中不存在非守护线程时,守护线程才会随着JVM一同结束工作。

Java中典型的守护线程

GC(垃圾回收器)

如何设置守护线程        

thread.setDaemon(true);

PS:Daemon属性需要再启动线程之前设置,不能再启动后设置。

Java虚拟机退出时Daemon线程中的finally块一定会执行?

Java虚拟机退出时Daemon线程中的finally块并不一定会执行。

代码示例:

   public static void main(String[] args) {
        Thread thread=new Thread(new DaemonRunnable(),"DaemonRunnable");
        thread.setDaemon(true);
        thread.start();

    }
    static class DaemonRunnable implements Runnable{
        @Override
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(3);
            }catch (Exception e){
                e.printStackTrace();
            }finally{
                System.out.println("finally");
            }
        }
    }

结果:

没有任何的输出,说明没有执行fnally。

设置线程上下文类加载器

获取线程上下文类加载器

public classLoader getContextClassLoader();

设置线程类加载器(可以打破Java类加载器的父类委托机制)

public void setContextClassLoader(ClassLoader cl)

join

join是指把指定的线程加入到当前线程,比如join某个线程a,会让当前线程b进入等待,直到a的生命周期结束,此期间b线程是处于blocked状态。

线程池

为什么使用线程池

几乎所有需要异步或者并发执行任务的程序都可以使用线程池。合理使用会给我们带来以下好处。

  • 降低系统消耗:重复利用已经创建的线程降低线程创建和销毁造成的资源消耗。
  • 提高响应速度:当任务到达时,任务不需要等到线程创建就可以立即执行。
  • 提供线程可以管理性:可以通过设置合理分配、调优、监控。

线程池创建,作用

public ThreadPoolExecutor(int corePoolSize,  
                          int maximumPoolSize,  
                          long keepAliveTime,  
                          TimeUnit unit,  
                          BlockingQueue<Runnable> workQueue,  
                          ThreadFactory threadFactory,  
                          RejectedExecutionHandler handler)

参数的作用如下

  1. corePoolSize(核心线程数):线程池中的核心线程数,即使这些线程处于空闲状态,它们也不会被销毁,除非设置了allowCoreThreadTimeOut
  2. maximumPoolSize(最大线程数):线程池中允许的最大线程数。当工作队列已满,并且当前线程数小于maximumPoolSize时,线程池会创建新的线程来处理任务。
  3. keepAliveTime(线程空闲时间):当线程数大于corePoolSize时,多余的空闲线程在终止前等待新任务的最长时间。
  4. unit(时间单位):keepAliveTime参数的时间单位,例如TimeUnit.SECONDS
  5. workQueue(工作队列):用于保存等待执行的任务的队列。当所有核心线程都在工作时,新提交的任务会被添加到这个队列中等待处理。Java提供了几种类型的队列,一般来说可以选择如下阻塞队列:
    • ArrayBlockingQueue:基于数组的有界阻塞队列。
    • LinkedBlockingQueue:基于链表的阻塞队列。
    • SynchronizedQueue:一个不存储元素的阻塞队列。
    • PriorityBlockingQueue:一个具有优先级的阻塞队列。
  6. threadFactory(线程工厂):用于创建新线程的工厂。你可以通过实现ThreadFactory接口来定制线程的创建过程,例如设置线程名称、设置线程优先级等。
  7. handler(拒绝策略):当线程池无法处理新任务时(例如,工作队列已满,并且线程数已达到maximumPoolSize),这个RejectedExecutionHandler会被调用。Java提供了几种预定义的拒绝策略,如AbortPolicy(直接抛出异常)、CallerRunsPolicy(调用者运行任务)、DiscardPolicy(丢弃任务)和DiscardOldestPolicy(丢弃队列中的最旧任务)。你也可以实现自己的拒绝策略。

向线程池提交任务

可以使用execute()submit()两种方式提交任务。
execute():无返回值,所以无法判断任务是否被执行成功。
submit():用于提交需要有返回值的任务。线程池返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()来获取返回值,get()方法会阻塞当前线程知道任务完成。get(long timeout,TimeUnit unit)可以设置超市时间。

关闭线程池

可以通过shutdown()或shutdownNow()来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt来中断线程,所以无法响应终端的任务可以能永远无法停止。
shutdownNow首先将线程池状态设置成STOP,然后尝试停止所有的正在执行或者暂停的线程,并返回等待执行任务的列表。
shutdown只是将线程池的状态设置成shutdown状态,然后中断所有没有正在执行任务的线程。
只要调用两者之一,isShutdown就会返回true,当所有任务都已关闭,isTerminaed就会返回true。-般来说调用shutdown方法来关闭线程池,如果任务不一定要执行完,可以直接调用shutdownNow方法。

Demo如下

import java.util.concurrent.*;

public class ThreadPoolExecutorDemo {

    public static void main(String[] args) {
        // 定义线程池参数  
        int corePoolSize = 5; // 核心线程数  
        int maximumPoolSize = 10; // 最大线程数  
        long keepAliveTime = 60L; // 空闲线程存活时间,单位秒  
        TimeUnit unit = TimeUnit.SECONDS; // 时间单位  
        BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(100); // 工作队列  
        ThreadFactory threadFactory = Executors.defaultThreadFactory(); // 使用默认线程工厂  
        RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy(); // 拒绝策略:抛出异常  

        // 创建线程池  
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                unit,
                workQueue,
                threadFactory,
                handler
        );

        // 提交任务到线程池  
        for (int i = 0; i < 20; i++) {
            final int taskId = i;
            executor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Running task: " + taskId + " with thread: " + Thread.currentThread().getName());
                    // 模拟任务执行时间  
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

        // 关闭线程池,不再接受新任务,但已提交的任务会继续执行  
        executor.shutdown();

        // 等待所有任务执行完毕  
        try {
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                // 等待超时,可以选择取消正在执行的任务  
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            // 当前线程在等待过程中被中断  
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }

    }
}

线程池如何合理设置

配置线程池可以从以下几个方面考虑。
任务是cpu密集型、IO密集型或者混合型
任务优先级,高中低。
·任务时间执行长短。
任务依赖性:是否依赖其他系统资源。
cpu密集型可以配置可能小的线程,比如 n+1个线程。io密集型可以配置较多的线程,如2n个线程。混合型可以拆成io密集型任务和cpu密集型任务如果两个任务执行时间相差大,

否->分解后执行吞吐量将高于串行执行吞吐量。

否->没必要分解。
可以通过Runtime.getRuntime().availableProcessors()来获取cpu个数。
建议使用有界队列,增加系统的预警能力和稳定性。

Executor

从JDK5开始,把工作单元和执行机制分开。工作单元包括Runnable和Callable,而执行机制由Executor框架提供。

Executor框架的主要成员

ThreadPoolExecutor :可以通过工厂类Executors来创建。
可以创建3种类型的ThreadPoolExecutor:SingleThreadExecutor、FixedThreadPool、CachedThreadPool.
ScheduledThreadPoolExecutor :可以通过工厂类Executors来创建。
可以创建2中类型的ScheduledThreadPoolExecutor: ScheduledThreadPoolExecutor、SingleThreadScheduledExecutor
Future接口:Future和实现Future接口的FutureTask类来表示异步计算的结果。
Runnable和Callable:它们的接口实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。Runnable不能返回结果Callable可以返回结果

FixedThreadPool

可重用固定线程数的线程池。

源码:
    public static ExecutorService newFixedThreadPool(int nThreads){return new ThreadPoolExecutor(nThreads,nThreads,
            0L,TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>());}

corePoolSize 和maxPoolSize都被设置成我们设置的nThreads。
当线程池中的线程数大于corePoolSize,keepAliveTime为多余的空闲线程等待新任务的最长时间,超过这个时间后多余的线程将被终止,如果设为0,表示多余的空闲线程会立即终止。
工作流程:
1.当前线程少于corePoolSize,创建新线程执行任务。
2.当前运行线程等于corePoolSize,将任务加入LinkedBlockingQueue。
3.线程执行完1中的任务,会循环反复从LinkedBlockingQueue获取任务来执行。
LinkedBlockingQueue作为线程池工作队列(默认容量Integer.MAX VALUE)。因此可能会造成如下赢下。

  1. 当线程数等于corePoolSize时,新任务将在队列中等待,因为线程池中的线程不会超过corePoolSize。
  2. maxnumPoolSize等于说是一个无效参数。
  3. keepAliveTime等于说也是一个无效参数。
  4. 运行中的FixedThreadPool(未执行shundown或shundownNow))则不会调用拒绝策略。
  5. 由于任务可以不停的加到队列,当任务越来越多时很容易造成OOM。

SingleThreadExecutor

源码
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1,1,
                        0l, TimeUnit.MILLISECONDS,
                        new LinkedBlockingQueue<Runnable>()));
    }

corePoolSize和maxnumPoolSize被设置为1。其他参数和FixedThreadPool相同。
执行流程以及造成的影响同FixedThreadPoo1.

CachedThreadPool

源码
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                60L, TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>());
    }

corePoolSize设置为0,maxmumPoolSize为Integer.MAX_VALUE。keepAliveTime为60秒。

工作流程:
  1. 初始化阶段
    • 当使用 newCachedThreadPool() 方法时,线程池在初始化阶段是空的,没有任何线程在运行。
  2. 新任务的提交
    • 当一个新任务被提交给线程池时,线程池会检查是否有空闲的线程可用。
      • 如果有空闲线程,则直接将任务分配给其中一个空闲线程来执行。
      • 如果没有空闲线程,线程池会立即创建一个新的线程,并将任务分配给该线程来执行。
  3. 工作线程的执行
    • 被分配任务的线程会执行任务的 run() 方法,完成具体的业务逻辑。
    • 任务的执行时间可能会有所不同,取决于任务的复杂性和执行时间。
  4. 线程池的线程数量控制
    • 在 CachedThreadPool 中,线程的数量是不受限制的(理论上)。但实际上,线程数量受到系统资源的限制。
    • 如果当前没有可用的线程,线程池会立即创建一个新的线程。
    • 如果线程池中有闲置的线程,并且这些线程在一段时间内(默认为60秒)没有执行任务,那么这些线程将会被终止并从线程池中移除,以释放系统资源。
  5. 任务队列
    • 尽管 CachedThreadPool 主要通过动态创建和销毁线程来处理任务,但在某些实现中,它也可能使用一个内部队列来管理任务。
    • 当线程池中的线程都在忙碌时,新提交的任务可能会被放入这个队列中等待处理。但请注意,由于 CachedThreadPool 的动态线程特性,这种队列的使用可能并不普遍或显著。
  6. 异常处理
    • 在线程创建与回收过程中,线程池模块负责处理可能发生的异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

非洲养老号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值