线程、线程池

介绍

api文档介绍

 

 

 

 

 Thread 是Runnable的实现类,也可以说是其子类
进程是程序从开始到结束的过程
线程是进程进一步划分,是进程不同功能的具体实现

 

构造方法

 

 

 

Thread源码

           public class Thread implements Runnable {
    3         /* Make sure registerNatives is the first thing <clinit> does. */
    4         private static native void registerNatives();
    5         static {
    6                 registerNatives();
    7         }
    8 
    9         private volatile String name;
   10         private int            priority;
   11         private Thread         threadQ;
   12         private long           eetop;
   13 
   14         /* Whether or not to single_step this thread. */
   15         private boolean     single_step;
   16 
   17         /* Whether or not the thread is a daemon thread. */
   18         private boolean     daemon = false;
   19 
   20         /* JVM state */
   21         private boolean     stillborn = false;
   22 
   23         /* What will be run. */
   24         private Runnable target;
   25 
   26         /* The group of this thread */
   27         private ThreadGroup group;
   28 
   29         /* The context ClassLoader for this thread */
   30         private ClassLoader contextClassLoader;
   31 
   32         /* The inherited AccessControlContext of this thread */
   33         private AccessControlContext inheritedAccessControlContext;
   34 
   35         /* For autonumbering anonymous threads. */
   36         private static int threadInitNumber;

 

六种线程状态

在Thread.state查看  

 

 

大概示意图:jvm指定以了6中状态,没有“运行”状态  ,因为抢到CPU的执行权后就交给操作系统去处理了

 

 

 

 线程的优先级

0-10,默认5,  优先级越高,获取cpu时间片的概率越大

     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */

 

查看优先级

Thread.currentThread().getPriority()

 

 常见成员方法

yield() 礼让其他线程,出让cpu的执行权  有可能再次抢到  

setDaemon() 设置为守护线程  主线程执行完,守护线程会陆续结束(不是立刻)

join() 插队   插当前线程的队

 

 

wait和sleep的区别

https://zhuanlan.zhihu.com/p/471109617

相同点:都是用来将线程进入休眠,都可以响应interrupt中断

不同点:

wait必须配合synchronized一起使用,不然在运行时会抛出IllegalMonitorStateException 异常

wait是Object的方法,sleep是Thread的方法

sleep有超时时间,wait只能被动唤醒

wait会主动释放锁,sleep不会释放锁

调用sleep进入TIMED_WATING限时等待,调用wait进入WAITING无限时等待

 

 

 

重写的run()

调用目标对象的run,否则什么都不做

 1     /**
 2      * If this thread was constructed using a separate
 3      * <code>Runnable</code> run object, then that
 4      * <code>Runnable</code> object's <code>run</code> method is called;
 5      * otherwise, this method does nothing and returns.
 6      * <p>
 7      * Subclasses of <code>Thread</code> should override this method.
 8      *
 9      * @see     #start()
10      * @see     #stop()
11      * @see     #Thread(ThreadGroup, Runnable, String)
12      */
13     @Override
14     public void run() {
15         if (target != null) {
16             target.run();
17         }
18     }
    /* What will be run. */
    private Runnable target;

 

线程的Start()

实际调用的是start0()方法,native声明,便是调用本机的操作系统,因为多线程的实现需要依靠底层操作系统的支持 Start0具体由JVM实现,最终是调pthread_create 系统方法来创建的线程,这里会从用户态切换到内核态完成系统资源的分配,线程的创建。

Thread的子类重写了run()方法,调用start()时,由于继承的关系,jvm自动调用的是子类的run()方法

对于实现了Runnable  的它的子类,通过带参Thread(Runnable 对象名)构造方法时便已将该子类对象赋值给了Thread的私有Runnable target对象,调用start时,run方法会判断该target对象是否为空,不为空的话,执行该traget的run方法
 1  /**
 2      * Causes this thread to begin execution; the Java Virtual Machine
 3      * calls the <code>run</code> method of this thread.
 4      * <p>
 5      * The result is that two threads are running concurrently: the
 6      * current thread (which returns from the call to the
 7      * <code>start</code> method) and the other thread (which executes its
 8      * <code>run</code> method).
 9      * <p>
10      * It is never legal to start a thread more than once.
11      * In particular, a thread may not be restarted once it has completed
12      * execution.
13      * 
14      * start用于使线程开始执行,JVM会调用该线程的run()
15      * 结果是会有两个线程并发:start调用的线程,执行run的线程
16      * 不允许多次启动同一个线程
17      * 特别是,线程一旦完成就不能重新启动执行。
18      * 
19      * 一个线程的start只能被调用一次=》线程的生命周期是单行道,不能重来,挂了就没了,但是可以新建线程
20      * 
21      *
22      * @exception  IllegalThreadStateException  if the thread was already
23      *               started.
24      * @see        #run()
25      * @see        #stop()
26      */
27     public synchronized void start() {
28         /**
29          * This method is not invoked for the main method thread or "system"
30          * group threads created/set up by the VM. Any new functionality added
31          * to this method in the future may have to also be added to the VM.
32          *
33          * 线程状态为New,才能到start方法
34          * A zero status value corresponds to state "NEW".
35          */
36         if (threadStatus != 0)
37             throw new IllegalThreadStateException();
38 
39         /* Notify the group that this thread is about to be started
40          * so that it can be added to the group's list of threads
41          * and the group's unstarted count can be decremented. */
42         group.add(this);
43 
44         boolean started = false;
45         try {
46             // 实际执行时start0,一个本地方法
47             start0();
48             started = true;
49         } finally {
50             try {
51                 if (!started) {
52                     group.threadStartFailed(this);
53                 }
54             } catch (Throwable ignore) {
55                 /* do nothing. If start0 threw a Throwable then
56                   it will be passed up the call stack */
57             }
58         }
59     }
60 
61     private native void start0();

 

为什么不能直接调用run()方法?

由start源码可知,线程的运行需要操作系统的支持

真正做事的是start0(0),native声明,便是调用本机的操作系统,因为多线程的实现需要依靠底层操作系统的支持

Start0具体由JVM实现,最终是调pthread_create 系统方法来创建的线程,这里会从用户态切换到内核态完成系统资源的分配,线程的创建,run方法也是JVM调用的。

直接调用run相当于调用一个普通方法而已,并不会创建线程
-参考java3y文章
 

线程的四种创建方式

JDK8提供了9种Thread的构造方法

 

 

 Thread的创建方式大体上分为4种

JDK1.5以前有两种

1,继承Thread类(因此可以直接通过对象名.start()的方式开启线程,受到单继承限制) (一个类继承了Thread类,此类就称为多线程操作类,必须明确的覆写run()方法

2,实现Runnable接口(通过Thread(Runnable 对象名)接收Rannable对象来获取Thread对象,再调用start()方法开启线程)

【都是调用Thread对象的start()方法,1是通过Thread子类对象,2是传入runnable对象实例化Thread子类实例】

JDK1.5以后又有两种
3.因为引入了JUC,java util cancurrent。可以实现Callable接口,重写call方法 这个要说到FutureTask 同样来自JUC

4.可以使用线程池 一般是使用线程池

FutureTask实现了Runnable接口,能够用Callable构造

 

 

 

eg 

1.直接继承Thread

 1                  // 多线程操作类
2 // 继承Thread:,直接通过Thread子类,实例化子类对象并调用start
3 class MyThread extends Thread{ 4 private String name; 5 Public MyThread(Strng name ){ 6 This.name=name; 7 }
// 明确的覆写run指名要进行的操作
8 public void run(){ 9 for(int i=0;i<10;i++){ 10 System.out.println(name+"运行,i="+"i"); 11 } 12 public class ThreadDemo{ 13 public static void main(String []args){ 14 MyThread m1=new MyThread("线程A"); 15 MyThread m2=new MyThread("线程B");
// 线程的执行从Start开始
16 m1.start(); 17 m2.start(); 18 } 19 }

 

2.实现Runnable接口

                class A implements Runnable {
                    static Integer count = 0;
                    //重写run方法
                    @Override
                    public void run() {
                            for (int i = 0; i < 5; i++) {
                                count++;
                                System.out.println(Thread.currentThread().getName() + "\t" + count);
                            }
                    }
                }
                public class WithoutStatic {
                    public static void main(String[] args) {
                        A m1 new A();
                        A m2 new A();
                        //基于Thread的构造方法
                        Thread n1 = new Thread(m1);
                        Thread n2 new Thread(m2);
                        n1.start();
                        n2.start();
                    }
                }

 

3.实现Callable

一个实现了Callable接口的类
class MyThread implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName()+"---Callable.call()");
        return 200;
    }
}

 

匿名内部类写法/lambda表达式写法

                public  static void getVoid() {
                        //不传入Runnable对象
                           new Thread(){
                        //重写run()方法
                                 public void run() {
                                     for (int i = 0; i < 8; i++) {
                                         //业务逻辑写上
                                        System.out.println("+++"+i);
                                    }
                                 }
                           }.start();
}

 

new Thread(new Runnable() { 
    @Override
    public void run() {
        //写上业务逻辑
        for (int i = 0; i <5; i++) {
            System.out.println("======="+i);
        }
    }
}).start();

 

new Thread(() -> {
                try {
                phone.sendSMS();
          } catch (Exception e) {
                e.printStackTrace();
            }

}, "your thread name").start();

Callable接口

 

 

 一个可以返回结果和可能抛出的异常的任务。实现者定义一个名为call的没有参数的方法
A task that returns a result and may throw an exception. Implementors define a single method with no arguments called call.
Callable接口和Runnable接口类似,都是为实例可能被另外的线程执行的类设计的
The Callable interface is similar to Runnable, in that both are designed for classes whose instances are potentially executed by another thread.
但是Runnable接口没有返回值,而且不能抛出检查异常
A Runnable, however, does not return a result and cannot throw a checked exception.

引入

Java从发布的第一个版本开始就可以很方便地编写多线程的应用程序,并在设计中引入异步处理。Thread类、Runnable接口和Java内存管理模型使得多线程编程简单直接。

但Thread类和Runnable接口都不允许声明检查型异常,也不能定义返回值。没有返回值这点稍微有点麻烦。不能声明抛出检查型异常则更麻烦一些。

public void run()方法契约意味着你必须捕获并处理检查型异常。即使你小心地保存了异常信息(在捕获异常时)以便稍后检查,但也不能保证这个类(Runnable对象)的所有使用者都读取异常信息。

你也可以修改Runnable实现的getter,让它们都能抛出任务执行中的异常。但这种方法除了繁琐也不是十分安全可靠,你不能强迫使用者调用这些方法,程序员很可能会调用join()方法等待线程结束然后就不管了。

但是现在不用担心了,以上的问题终于在1.5中解决了。Callable接口和Future接口的引入以及他们对线程池的支持优雅地解决了这两个问题

 唯一方法

 

 

call和run的区别?

Runnable接口

 

 

callable接口  函数式接口

 

不同点

  •  方法名不同
  • run() 没有返回值,call()有
  • run()没有异常声明,call()有
  • 运行Callable任务可拿到一个Future对象。

 

Runnable和Callable相同点

  Runnable和Callable都是函数式接口,都支持lambda表达式

 

FutrueTask

使用Callable构造FutureTask创建线程,FutureTask是Callable的中间人

实现了Runnable接口,也是函数式接口

 

 

一个可取消的异步计算。该类提供Future的基本实现
A cancellable asynchronous computation. This class provides a base implementation of Future,
使用方法启动和取消计算、查询计算是否完成以及检索计算结果
 with methods to start and cancel a computation, query to see if the computation is complete, and retrieve the result of the computation.
 只有计算完成后结果才能被获取。如果计算没有完成,get方法会阻塞。
 The result can only be retrieved when the computation has completed; the get methods will block if the computation has not yet completed.
 一旦计算完成,就不能被重启或者取消,除非计算是被runAndReset()调用执行
 Once the computation has completed, the computation cannot be restarted or cancelled (unless the computation is invoked using runAndReset()).
 FutureTask可以用来包装Callable对象或Runnable对象。因为FutureTask实现了Runnable,一个FutureTask可以被提交给Executor执行
A FutureTask can be used to wrap a Callable or Runnable object. Because FutureTask implements Runnable, a FutureTask can be submitted to an Executor for execution.
除了作为一个独立的类,该类还提供了在创建自定义任务类时可能有用的受保护功能
In addition to serving as a standalone class, this class provides protected functionality that may be useful when creating customized task classes.

Since:
1.5

 

 

未来任务,从主线程中将复杂任务用另外的线程分出去异步运行计算,一个FutureTask对同一个复杂任务只会计算一次,多次调用只会直接返回结果
当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态。
仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法

 

 

计算结果存储在成员变量中

 

 

 

 使用eg

// 一个实现了Callable接口的类
class MyThread implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName()+"---Callable.call()");
        return 200;
    }
}

 

    1 FutureTask<Integer> ft = new FutureTask<Integer>(new MyThread());
    new Thread(ft, "AA").start();
    @Test
    public void futureTaskTest() throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask = new FutureTask<Integer>(() -> 1);
        new Thread(futureTask, "thread").start();
        System.out.println(futureTask.get());
    }

 

线程池

推荐博文:https://javaguide.cn/java/concurrent/java-thread-pool-best-practices.html#%E7%BE%8E%E5%9B%A2%E7%9A%84%E9%AA%9A%E6%93%8D%E4%BD%9C

 

 

 An object that executes submitted Runnable tasks. This interface provides a way of decoupling task submission from the mechanics of how each task will be run, including details of thread use, scheduling, etc. An Executor is normally used instead of explicitly creating threads. For example, rather than invoking new Thread(new(RunnableTask())).start() for each of a set of tasks
一个 执行提交的Runnable任务的 对象。该接口提供了将任务提交与如何运行每个任务的机制解耦的方法,包括线程使用和调度的细节等。通常使用Executor而不是显示侧创建线程。例如,不用为每个一组任务中的每一个都调用new Thread(new (RunnableTask())).start()。
The Executor implementations provided in this package implement ExecutorService, which is a more extensive interface. The ThreadPoolExecutor class provides an extensible thread pool implementation. The Executors class provides convenient factory methods for these Executors.
这个包中提供的Executor实现工具ExecutorService是更加广泛的接口。
ThreadPoolExecutor类提供了可扩展的线程池实现。
Execurots类为这些Executor提供了方便的工厂方法

 

 

Java中的线程池是通过Executor框架实现的
不用显示创建线程,每一个线程都去调用Thread.start方法进行启动
可以控制运行的线程数量,处理过程中将任务放入队列

主要特点

线程复用;控制最大并发数;管理线程

线程池的五种状态

 

好处

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

具体流程

 

 

 

1、在创建了线程池后,初始线程数为0 (有任务的时候才会创建)
2、当调用execute()方法添加一个请求任务时,线程池会做出如下判断:
2.1如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
2.2如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列
2.3如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务(不是阻塞队列中的,而是先解决当前任务);
2.4如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来处理。
3、当一个线程完成(当前)任务后,它会从队列中取下一个任务来执行。
4、当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。 所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小

 

 

 

 

 

 

 

 

 

 

 

饱和拒绝策略

AbortPolicy(默认) 直接抛出RejectedExecutionException异常阻止系统正常运行  直接抛异常
CallerRunsPolicy 回退给调用者进行处理从而降低线程池的任务流量   退回给调用者
DiscardOldestPolicy 丢弃队列中等待最久的任务,尝试提交当前任务   替换等待时间最长的
DiscardPolicy 直接丢弃,不做处理也不返回异常信息   直接丢弃

 

ThreadPoolExecutor

 

 

 

1、corePoolSize:线程池中的常驻核心线程数
2、maximumPoolSize:线程池中能够容纳同时执行的最大线程数,此值必须大于等于1
3、keepAliveTime:多余的空闲线程的存活时间,当前池中线程数量超过corePoolSize时,当空闲时间
      达到keepAliveTime时,多余线程会被销毁直到,只剩下corePoolSize个线程为止
4、unit:keepAliveTime的单位
5、workQueue:任务队列,被提交但尚未被执行的任务
6、threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程,一般默认的即可
7、handler:拒绝策略,表示当队列满了,并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒绝
     请求执行的runnable的策略

手写线程池

ExecutorService threadPool 
    = new ThreadPoolExecutor(
           2,//核心线程数
           5,  //最大线程数
           3L, //空闲线程的存活时间
           TimeUnit.SECONDS,  //时间单位
             new ArrayBlockingQueue<Runnable>(3),//有界阻塞队列
           Executors.defaultThreadFactory(),  //默认的线程工厂
           new ThreadPoolExecutor.DiscardPolicy()//默默丢弃无法处理的任务 只要触发饱和拒绝策略的都直接丢弃
           
           //new ThreadPoolExecutor.AbortPolicy()//四种饱和拒绝策略之默认 :  直接抛出异常RejectedExecutionException
           //new ThreadPoolExecutor.CallerRunsPolicy()//返回给调用者 从哪来回哪去
           //new ThreadPoolExecutor.DiscardOldestPolicy()//抛弃等待时间最长的
    1 public class ManuscriptThreadPool {
    2         public static void main(String[] args) {
    3                 //手动创建线程池,自定义七个参数
    4                 ExecutorService threadPool = new ThreadPoolExecutor(
    5                                 2,
    6                                 5,
    7                                 3L,
    8                                 TimeUnit.SECONDS,
    9                                 new ArrayBlockingQueue<Runnable>(3),//有界阻塞队列
   10                                 Executors.defaultThreadFactory(),
   11                                 //new ThreadPoolExecutor.AbortPolicy()//四种饱和拒绝策略之默认 :  直接抛出异常RejectedExecutionException
   12                                 //new ThreadPoolExecutor.CallerRunsPolicy()//返回给调用者 从哪来回哪去
   13                                 //new ThreadPoolExecutor.DiscardOldestPolicy()//抛弃等待时间最长的
   14                                 new ThreadPoolExecutor.DiscardPolicy()//默默丢弃无法处理的任务 只要触发饱和拒绝策略的都直接丢弃
   15 
   16                 );
   17                 try {
   18                         //8个是Maxsize + Queue 的大小 超出就可能触发饱和拒绝策略 是可能 因说不定哪个线程吃药了
   19                         for (int i = 1; i <=30; i++) {
   20                                 int num = i;
   21                                 threadPool.execute(()->{
   22                                         System.out.println(Thread.currentThread().getName()+"\t"+num+"号业务员办理业务");
   23                                 });
   24                         }
   25                 }catch(Exception e){
   26                         e.printStackTrace();
   27                 }finally{
   28                         //关闭线程池
   29                       threadPool.shutdown();
   30                 }

 

给线程池线程命名方式

主要是操作Factory

1、利用 guava 的 ThreadFactoryBuilder

ThreadFactory threadFactory = new ThreadFactoryBuilder()
                        .setNameFormat(threadNamePrefix + "-%d")
                        .setDaemon(true).build();
ExecutorService threadPool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory)

 

2、自己实现 ThreadFactor

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
 * 线程工厂,它设置线程名称,有利于我们定位问题。
 */
public final class NamingThreadFactory implements ThreadFactory {

    private final AtomicInteger threadNum = new AtomicInteger();
    private final ThreadFactory delegate;
    private final String name;

    /**
     * 创建一个带名字的线程池生产工厂
     */
    public NamingThreadFactory(ThreadFactory delegate, String name) {
        this.delegate = delegate;
        this.name = name; // TODO consider uniquifying this
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread t = delegate.newThread(r);
        t.setName(name + " [#" + threadNum.incrementAndGet() + "]");
        return t;
    }

}

 

摘录自javaGuide大佬: https://javaguide.cn/java/concurrent/java-thread-pool-best-practices.html#_3%E3%80%81%E5%BB%BA%E8%AE%AE%E4%B8%8D%E5%90%8C%E7%B1%BB%E5%88%AB%E7%9A%84%E4%B8%9A%E5%8A%A1%E7%94%A8%E4%B8%8D%E5%90%8C%E7%9A%84%E7%BA%BF%E7%A8%8B%E6%B1%A0

 

 

 

JDK四种自带实现(不推荐)

 

 

 

 

 

 

在工作中单一的/固定数的/可变的三种创建线程池的方法哪个用的多?

一个都不用,我们工作中只能使用自定义的

 

 

 Integer.MAX_VALUE: 0x 7EEEEEEE = 21亿多 造成机器内存溢出

其他原因

  •   实际使用中需要根据自己机器的性能、业务场景来手动配置线程池的参数比如核心线程数、使用的任务队列、饱和策略等等。
  •  我们应该显示地给我们的线程池命名,这样有助于我们定位问题。

 

 

结论

正确使用线程池:使用有界队列+限制线程数量+合理配置参数

不同业务配置不同线程池:一般建议是不同的业务使用不同的线程池,配置线程池的时候根据当前业务的情况对当前线程池进行配置,因为不同的业务的并发以及对资源的使用情况都不同,重心优化系统性能瓶颈相关的业

 

posted on 2023-02-21 23:23  or追梦者  阅读(13)  评论(0编辑  收藏  举报