一、线程的介绍

1.1、概念

进程:

  • 你的硬盘上有一个简单的程序,这个程序叫QQ.exe,这是一个程序,这个程序是一个静态的概念,它被扔在硬盘上也没人理他,但是当你双击它,弹出一个界面输入账号密码登录进去了,OK,这个时候叫做一个进程。进程相对于程序来说它是一个动态的概念。

线程:

  • 作为一个进程里面最小的执行单元它就叫一个线程,用简单的话讲一个程序里不同的执行路径就叫做一个线程。一个进程中可以包含多个线程。

单线程:

  • 多个任务依次执行,效率低。

多线程:

  • 多个任务同时执行,往往会有线程安全的问题。

1.2、线程调度方式

分时调度:

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

抢占式调度:

  • 多个线程之间竞争,优先级高的线程竞争优势大,Java 采用这种模式。
  • 线程优先级为 1-10,其中 1 最低,10 最高。主线程的优先级默认为 5,子线程的优先级默认和主线程的优先级一至。
  • 主线程就是 main 方法,从 main 方法开始,一直到 main 方法执行结束。

二、线程的创建及常用方法

2.1 创建线程的几种方式

  • 继承 Thread 类,重写 run 方法,对象,调用 start 方法开启线程。
  • 实现 Runnable 接口,重写 run 方法,封装 Thread 对象,调用 start 方法开启线程。
  • 实现 Callable 接口,重写 call方法,封装 Thread 对象,调用 start 方法开启线程。
    • 与前两种的区别是:存在返回值,并且会抛出异常。
  • 通过线程池获得(后文具体介绍)。

举例如下:

import java.util.concurrent.*;

/**
 * @author xiandongxie
 */
public class ThreadDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getPriority() + "\t" + System.currentTimeMillis());

        // 线程一
        new ThreadDemo()
                .new Thread1()
                .start();

        // 线程二
        new Thread(new ThreadDemo().new Thread2())
                .start();

        // 线程三
        Thread3 thread3 = new ThreadDemo().new Thread3();
        FutureTask<String> futureTask = new FutureTask<>(thread3);
        Thread thread = new Thread(futureTask);
        thread.start();
        // 这是一个阻塞方法,只有拿到 call 的返回值后,才会继续执行
//        String name = futureTask.get();
        // 同样是一个阻塞方法,不过可以设置等待时间
        String name = null;
        try {
            name = futureTask.get(2000, TimeUnit.MILLISECONDS);
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
        System.out.println("futureTask.get() = " + name);

        System.out.println("main end " + System.currentTimeMillis());
    }

    class Thread1 extends Thread {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getPriority() + "\t" + System.currentTimeMillis());
        }
    }

    class Thread2 implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getPriority() + "\t" + System.currentTimeMillis());
        }
    }

    class Thread3 implements Callable<String> {
        @Override
        public String call() throws Exception {
            Thread.sleep(5000);
            System.out.println("call 方法执行完毕...\t" + Thread.currentThread().getPriority() + "\t" + System.currentTimeMillis());
            String name = Thread.currentThread().getName();
            return name;
        }
    }
}

2.2 线程的状态

stage
线程状态

2.3 线程常用的方法

  • run,线程要执行的逻辑代码。
  • start,开启一个线程。
  • Sleep,意思就是睡眠,当前线程暂停一段时间让给别的线程去运行。Sleep是怎么复活的?由你的睡眠时间而定,等睡眠到规定的时间自动复活。sleep 不会释放锁,只是释放 CPU 执行资源
  • Yield,就是当前线程正在执行的时候停止下来进入等待队列,回到等待队列里在系统的调度算法里头呢还是依然有可能把你刚回去的这个线程拿回来继续执行,当然,更大的可能性是把原来等待的那些拿出一个来执行,所以yield的意思是我让出一下CPU,后面你们能不能抢到那我不管。
  • join, 意思就是在自己当前线程加入你调用Join的线程(),本线程等待。等调用的线程运行完了,自己再去执行。t1和t2两个线程,在t1的某个点上调用了t2.join,它会跑到t2去运行,t1等待t2运行完毕继续t1运行(自己join自己没有意义)。
  • wait,是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)
  • wait(long timeout),让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)
  • notify 和 notifyAll ,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。

三、线程池

通过建立池可以有效的利用系统资源,节约系统性能。Java 中的线程池就是一种非常好的实现,从 JDK 1.5 开始 Java 提供了一个线程工厂 Executors 用来生成线程池,通过 Executors 可以方便的生成不同类型的线程池。

线程池的优点:

  • 降低资源消耗。线程的开启和销毁会消耗资源,通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

线程池的使用:

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author xiandongxie
 */
public class ThreadPool {

    //参数初始化 返回Java虚拟机可用的处理器数量
//    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CPU_COUNT = 2;
    //核心线程数量大小
    private static final int corePoolSize = Math.max(2, Math.min(CPU_COUNT - 1, 4));
    //线程池最大容纳线程数
    private static final int maximumPoolSize = CPU_COUNT * 2 + 1;
    //线程空闲后的存活时长
    private static final int keepAliveTime = 30;

    //任务过多后,存储任务的一个阻塞队列
    BlockingQueue<Runnable> workQueue = new SynchronousQueue<>();

    //线程的创建工厂
    ThreadFactory threadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "AdvacnedAsyncTask #" + mCount.getAndIncrement());
        }
    };

    //线程池任务满载后采取的任务拒绝策略: 不执行新任务,直接抛出异常,提示线程池已满
    RejectedExecutionHandler rejectHandler = new ThreadPoolExecutor.AbortPolicy();

    //线程池对象,创建线程
    ThreadPoolExecutor mExecute = new ThreadPoolExecutor(
            corePoolSize,
            maximumPoolSize,
            keepAliveTime,
            TimeUnit.SECONDS,
            workQueue,
            threadFactory,
            rejectHandler
    );

    public static void main(String[] args) {
        System.out.println("main start ..... \nCPU_COUNT = " + CPU_COUNT + "\tcorePoolSize=" + corePoolSize + "\tmaximumPoolSize=" + maximumPoolSize);
        
        ThreadPool threadPool = new ThreadPool();
        ThreadPoolExecutor execute = threadPool.mExecute;
        // 预启动所有核心线程
        execute.prestartAllCoreThreads();

        for (int i = 0; i < 5; i++) {
            execute.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "\tstart..." + System.currentTimeMillis());
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "\tend..." + System.currentTimeMillis());
                }
            });
        }
        execute.shutdown();
        
        System.out.println("main end .....");
    }
}

参数介绍:

  • corePoolSize
    • 线程池的核心线程数。在没有设置 allowCoreThreadTimeOut 为 true 的情况下,核心线程会在线程池中一直存活,即使处于闲置状态。
  • maximumPoolSize
    • 线程池所能容纳的最大线程数。当活动线程(核心线程+非核心线程)达到这个数值后,后续任务将会根据 RejectedExecutionHandler 来进行拒绝策略处理。
  • keepAliveTime
    • 非核心线程闲置时的超时时长。超过该时长,非核心线程就会被回收。若线程池通过 allowCoreThreadTimeOut() 方法设置 allowCoreThreadTimeOut 属性为 true,则该时长同样会作用于核心线程,AsyncTask 配置的线程池就是这样设置的。
  • unit
    • keepAliveTime 时长对应的单位。
  • workQueue
    • 线程池中的任务队列,通过线程池的 execute() 方法提交的 Runnable 对象会存储在该队列中。
    • 是一个阻塞队列 BlockingQueue,虽然它是 Queue 的子接口,但是它的主要作用并不是容器,而是作为线程同步的工具,他有一个特征,当生产者试图向 BlockingQueue 放入(put)元素,如果队列已满,则该线程被阻塞;当消费者试图从 BlockingQueue 取出(take)元素,如果队列已空,则该线程被阻塞。
  • ThreadFactory
    • 线程工厂,功能很简单,就是为线程池提供创建新线程的功能。这是一个接口,可以通过自定义,做一些自定义线程名的操作。
  • RejectedExecutionHandler
    • 当任务无法被执行时(超过线程最大容量 maximum 并且 workQueue 已经被排满了)的处理策略,这里有四种任务拒绝类型:
      1. AbortPolicy: 不执行新任务,直接抛出异常,提示线程池已满,涉及到该异常的任务也不会被执行,线程池默认的拒绝策略就是该策略。
      2. DisCardPolicy: 不执行新任务,也不抛出异常;
      3. DisCardOldSetPolicy: 将消息队列中的第一个任务(即等待时间最久的任务)替换为当前新进来的任务执行;
      4. CallerRunsPolicy: 直接调用execute来执行当前任务;

线程池工作原理:
线程池工作原理

常见的线程池:

  • CachedThreadPool:可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。
  • SecudleThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。
  • SingleThreadPool:只有一条线程来执行任务,适用于有顺序的任务的应用场景。
  • FixedThreadPool:定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程

版权声明:本文为xiexiandong原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/xiexiandong/p/12901098.html