Java基础之线程篇

什么是线程?

线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

线程的状态:

  1. 新建(NEW):新创建了一个线程对象。
  2. 可运行(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
  3. 运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
  4. 阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
  • 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
  • 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
  • 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
  1. 死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

实现线程的方式

1.继承Thread类

package com.sk.Multithreading;

public class ThreadDemo {

    public static void main(String[] args) {
        MyThread thread1 = new MyThread("玉无双");
        MyThread thread2 = new MyThread("花辞树");
        MyThread thread3 = new MyThread("醉星河");
        thread1.start();//启动线程,只能调用一次start()方法,否则会抛出java.lang.IllegalThreadStateException
        thread2.start();
        thread3.start();
    }
}

class MyThread extends Thread {
    String name;

    public MyThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println(name+Thread.currentThread().getName());
    }
}

start方法的作用就是将线程由NEW状态,变为RUNABLE状态。start()方法内部调用的是start0()native方法启动。当线程创建成功时,线程处于NEW(新建)状态,如果你不调用start( )方法,那么线程永远处于NEW状态。(每个线程类对象只能调用一次)调用start( )后,才会变为RUNABLE状态,线程才可以运行。(不一定就会立即执行,线程要等待CPU调度)。当线程被cpu调度,就会让线程去执行run方法。

public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

2.实现Runnable接口

Java里的继承都是单一的有局限性,所以我们可以通过实现Runnable 接口来创建线程(Thread类实现了Runnable 接口)。

package com.sk.Multithreading;

public class ThreadDemo {

    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyThread("玉无双"));//传入一个实现Runnable 接口的对象
        Thread thread2 = new Thread(new MyThread("花辞树"));
        Thread thread3 = new Thread(new MyThread("醉星河"));
        thread1.start();
        thread2.start();
        thread3.start();

    }
}

class MyThread implements Runnable {
    String name;

    public MyThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println(name+Thread.currentThread().getName());
    }
}

3.实现Callable接口

Runnable接口有一个缺点:当线程执行完毕后,我们无法获取一个返回值。并且Runnable接口不会向上抛出异常,只能在run()方法里进行try catch,而Callbale可以向上抛异常,并且定义的时候可以设置一个泛型,此泛型的类型就是返回数据的类型。

package com.sk.Multithreading;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> task1 = new FutureTask(new MyThread("玉无双"));
        FutureTask<String> task2 = new FutureTask(new MyThread("花辞树"));
        FutureTask<String> task3 = new FutureTask(new MyThread("醉星河"));
        new Thread(task1).start();
        String s1 = task1.get();
        System.out.println(s1);
        
        new Thread(task2).start();
        String s2 = task2.get();
        System.out.println(s2);
        
        new Thread(task3).start();
        String s3 = task3.get();
        System.out.println(s3);

    }
}

class MyThread implements Callable<String> {
    String name;

    public MyThread(String name) {
        this.name = name;
    }

    @Override
    public String call() throws Exception {
        System.out.println(name+Thread.currentThread().getName());
        return name;
    }
}



4.线程池

4.1实现方式

1、newCachedThreadPool:用来创建一个可以无限扩大的线程池,适用于负载较轻的场景,执行短期异步任务。(可以使得任务快速得到执行,因为任务时间执行短,可以很快结束,也不会造成cpu过度切换)

package com.sk.Multithreading;

import java.util.concurrent.*;

public class ThreadDemo {

    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newCachedThreadPool();
        try {
            for (int i = 1; i <= 10; i++) {//模拟10个任务
                threadPool.execute(() -> System.out.println(Thread.currentThread().getName() + "处理任务"));
                //TimeUnit.MILLISECONDS.sleep(100);//休息100ms
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }

    }
}

效果
在这里插入图片描述
休息100ms,看线程是否可以复用。

在这里插入图片描述

2、newFixedThreadPool:创建一个固定大小的线程池,因为采用无界的阻塞队列,所以实际线程数量永远不会变化,适用于负载较重的场景,对当前线程数量进行限制。(保证线程数可控,不会造成线程过多,导致系统负载更为严重)

package com.sk.Multithreading;

import java.util.concurrent.*;

public class ThreadDemo {

    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        try {
            for (int i = 1; i <= 10; i++) {//模拟10个任务
                threadPool.execute(() -> System.out.println(Thread.currentThread().getName() + "处理任务"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }

    }
}

效果
在这里插入图片描述

3、newSingleThreadExecutor:创建一个单线程的线程池,适用于需要保证顺序执行各个任务。

package com.sk.Multithreading;

import java.util.concurrent.*;

public class ThreadDemo {

    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        try {
            for (int i = 1; i <= 10; i++) {//模拟10个任务
                threadPool.execute(() -> System.out.println(Thread.currentThread().getName() + "处理任务"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }

    }
}


效果
在这里插入图片描述

4、newScheduledThreadPool:适用于执行延时或者周期性任务。

package com.sk.Multithreading;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;

public class ThreadDemo {

    public static void main(String[] args)  {
        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(3);
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        System.out.println("时间:"+sdf.format(new Date()));
        try {
            for (int i = 1; i <= 10; i++) {//模拟10个任务
                threadPool.schedule(new Runnable(){
                    @Override
                    public void run() {
                        System.out.println("时间:" + sdf.format(new Date())+Thread.currentThread().getName()+"处理任务");
                    }
                },3,TimeUnit.SECONDS);//延时3秒后执行
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }

    }
}

效果

在这里插入图片描述

5、newWorkStealingPool:可以传入线程的数量,不传入,则默认使用当前计算机中可用的cpu数量,适用于耗时严重的任务。(Java8新增的)

package com.sk.Multithreading;

import java.util.concurrent.*;

public class ThreadDemo {

    public static void main(String[] args) {
        ExecutorService stealingPool = Executors.newWorkStealingPool();
        try {
            for (int i = 1; i <= 10; i++) {//模拟10个任务
                int finalI = i;
                stealingPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t" + finalI + "处理任务");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            stealingPool.shutdown();
        }
        while (Thread.activeCount() > 2) {

        }
    }
}

效果
在这里插入图片描述

4.2参数详解

1、corePoolSize(核心线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。)

2、maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。

配置线程池最大线程数

  • CPU密集型(任务需要大量运算,没有阻塞,CPU一直全速运行)最大线程数=CPU核数+1
  • IO密集型(任务线程并不是一直在执行任务):最大线程数 = CPU核数*2最大线程数 = CPU核数/(1-阻塞系数)(阻塞系数的值一般在0.8~0.9)

3、keepAliveTime(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。

4、unit(单位)keepAliveTime参数的时间单位。
5、workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。

6、threadFactory(线程工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。

7、handler(拒绝策略4种):当线程池和队列都满了,再加入线程会执行此策略。

  • ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。(默认)
  • ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
  • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
  • ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务

注意:线程池Executors创建线程底层其实调用的是ThreadPoolExecutor 类去创建。阿里开发手册强制:线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

以下是阿里手册规范

线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这
样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2) CachedThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

    /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters and default thread factory and rejected execution handler.
     * It may be more convenient to use one of the {@link Executors} factory
     * methods instead of this general purpose constructor.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
4.3线程池运行流程

1、判断核心线程池是否已满,没满则创建一个新的工作线程来执行任务。满了则加入任务队列中。
2、判断任务队列是否已满,没满则将新提交的任务添加在工作队列,满了则启动最大线程数的线程来执行任务。
3、判断整个线程池是否已满,没满则创建一个新的工作线程来执行任务,已满则执行拒绝策略。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值