线程、多线程以及线程池

多线程

并发和并行

并行:指两个或多个事件在同一时刻发生(同时执行)。
并发:指两个或多个事件在同一个时间段内发生(交替执行)。

image-20230915192121416

注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。

进程和线程

进程

​ 进程(Process)是指正在运行的程序,如QQ,是程序一次动态执行过程。它对应了从代码加载、执行并执行完毕的一个完整过程,这个过程也是进程本身从产生、发展到消亡的过程。操作系统同时管理一个计算机系统中的多个进程,让计算机系统中的多个进程轮流使用CPU资源,或者共享资源。

特点:

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

image-20230915192232540

线程

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

​ 线程是进程内部的一个执行单元,是可以完成一个独立任务的顺序控制流程。一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

image-20230915192303796

进程和线程的区别

进程与线程的区别

  • 进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
  • 线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。

多线程的由来

​ 摩尔定律是由英特尔(Intel)创始人之一戈登·摩尔(Gordon Moore)提出来的。其内容为:当价格不变时,集成电路上可容纳的元器件的数目,约每隔18-24个月便会增加一倍,性能也将提升一倍。换言之,每一美元所能买到的电脑性能,将每隔18-24个月翻一倍以上。这一定律揭示了信息技术进步的速度。然而,“摩尔定律”的时代将会退出,因为研究和实验室的成本需求十分高昂,而有财力投资在创建和维护芯片工厂的企业很少。而且制程也越来越接近半导体的物理极限,将会难以再缩小下去。这时,我们不再追求更快的处理器了,而是着眼于更多的处理器(多核处理器),而且让他们一致保持工作。

Java在当时很超前,它是第一个支持并发程序设计的主流语言。它的出发点是,当时多核处理器还很神秘,而且Web编程才刚刚起步,处理器要花很长的时间等待服务器响应,需要并发程序设计来确保界面不会冻住

​ 我们已经很熟悉计算机操作系统中的多任务:在同一刻运行多个程序的能力。如:边打游戏边听歌。现在,我们的电脑都是拥有多个CPU的,但是,并发执行的进程数目并不是由CPU数目制约的。操作系统将CPU的时间片段分配给每个进程,给人并行的感觉。

​ 多线程程序是扩展了多任务的概念:一个程序同时执行多个任务。通常,每一个任务称为一个线程(Thread),它是线程控制的简称。可以同时运行一个以上线程的程序称为多线程程序(Multithreaded)。

1:因为一个进程中的多个线程是并发运行的,那么从微观角度看也是有先后顺序的,哪个线程执行完全取决于CPU 的调度,程序员是干涉不了的。而这也就造成的多线程的随机性。
2:Java 程序的进程里面至少包含两个线程,主进程也就是 main()方法线程,另外一个是垃圾回收机制线程。每当使用 java 命令执行一个类时,实际上都会启动一个 JVM,每一个 JVM 实际上就是在操作系统中启动了一个线程,java 本身具备了垃圾的收集机制,所以在 Java 运行时至少会启动两个线程。
3:由于创建一个线程的开销比创建一个进程的开销小的多,那么我们在开发多任务运行的时候,通常考虑创建多线程,而不是创建多进程。

多线程和多进程的区别

本质的区别在于每个进程拥有自己的一套变量,而线程则是共享数据。线程之间共享数据是有风险的(后边分析),然而共享变量使线程之间的通信比进程之间通信更有效、更容易。此外,在有的操作系统中,与进程相比,线程更"轻量级",创建、撤销一个线程比启动新的进程开销要小的多。

线程调度

  1. 分时调度
    所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
  2. 抢占式调度
    优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

创建线程类

​ 每个程序至少自动拥有一个线程,称为主线程。当程序加载到内存时启动主线程。java程序中public static void main(String[] args)是主线程的入口,运行java程序时会执行这个方法。

  • 将一个类声明为Thread的子类。 这个子类应该重写Thread 类的方法run。 然后可以分配并启动子类的实例。

使用Thread类

java.lang.Thread 类代表线程。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。

public class Thread implements Runnable 

创建线程

继承Thread类,并重写run()方法。

package com.itlaobing.demo.thread;


public class MyThread extends Thread{

    //定义指定线程名称的构造方法
    public MyThread(String name) {
        //调用父类的String参数的构造方法,指定线程的名称
        super(name);
    }
    /**
     * 重写run方法,完成该线程执行的逻辑
     */
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println(getName()+":正在执行!"+i);
        }
    }

}

@Test
public void testMyThread(){
    //创建自定义线程对象
    MyThread thread = new MyThread("myThread");
    //开启线程
    thread.start();
    //在主方法中执行for循环
    for (int i = 0; i < 200; i++){
        System.out.println("main()线程" + i);
    }
}

实现Runnable接口的方式创建

java.lang.Runnable 也是非常常见的一种创建线程的方式,我们只需要重写run方法即可。任何实现Runnable接口的类,其实例都能被线程执行。

public interface Runnable 

创建线程任务

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
}
@Test
public void testMyRunnable() throws InterruptedException {
    //创建线程任务对象
    MyRunnable runnable = new MyRunnable();
    //创建线程对象
    Thread thread = new Thread(runnable, "皇家一号线程");
    //启动线程
    thread.start();
    //线程休眠5S
    Thread.sleep(5000);
    for (int i = 0; i < 50; i++) {
        System.out.println("main线程 " + i);
    }

}

上面代码中MyRunnable可以是一个内部类。具体实现自己补充。

Runnable对象仅仅作为Thread对象的targetRunnable实现类里包含的

run()方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该

Thread线程负责执行其targetrun()方法。

ThreadRunnable区别

实现Runnable接口比继承Thread类所具有的优势:

  1. 可以避免java中的单继承的局限性。
  2. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
  3. 实现了Runable接口的话,很容易的实现资源共享(一个实例多个线程运行)。(适合多个相同的程序代码的线程去共享同一个资源。)
  4. 线程池只能放入实现RunnableCallable类线程,不能直接放入继承Thread的类。

实现Callable接口【应用】

java.util.concurrent.Callable接口类似于Runnable,因为两者的实例都是为另一个线程执行的类设计的。

但是, Runnable不返回结果,也不能抛出检查异常。

public interface Callable<V> {
    // 计算结果,如果无法计算结果,则抛出一个异常
    V call() throws Exception;
}

java.util.concurrent.FutureTask可用于包装一个CallableRunnable对象。具有启动和取消计算、查询计算是否完成以及检索计算结果的方法。

构造函数:

public FutureTask(Runnable runnable, V result)
public FutureTask(Callable<V> callable)

常用方法:

// 如有必要,等待计算完成,然后检索其结果。get 如果计算尚未完成,这些方法将阻塞
public V get() 
// 如有必要,最多等待给定时间以完成计算,然后检索其结果(如果可用)
public V get(long timeout, TimeUnit unit)
import java.util.concurrent.Callable;

public class MyCallable implements Callable<Integer>{

	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for (int i = 0; i < 100; i++) {
			sum += i;
        }
		return sum;
	}

}

public static void main(String[] args) {
    // 线程开启之后需要执行里面的call方法
    MyCallable call = new MyCallable();

    // //可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象
    FutureTask<Integer> future = new FutureTask<Integer>(call);

    Thread thread = new Thread(future);

    thread.start();

    try {
        // main 线程阻塞, 等待 callable 线程执行完毕, 返回结果
        int a = future.get();
        System.out.println(a);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }

}

三种实现方式的对比

  • 实现Runnable、Callable接口
    • 好处: 扩展性强,实现该接口的同时还可以继承其他的类。很容易的实现资源共享(一个实例多个线程运行)
    • 缺点: 编程相对复杂,不能直接使用Thread类中的方法
  • 继承Thread类
    • 好处: 编程比较简单,可以直接使用Thread类中的方法。资源共享麻烦
    • 缺点: 可以扩展性较差,不能再继承其他的类

线程池

线程池的优势

总体来说,线程池有如下的优势:

(1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

(2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

(3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

线程池的使用

线程池的真正实现类是 ThreadPoolExecutor,其构造方法有如下4种:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}
 
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         threadFactory, defaultHandler);
}
 
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), handler);
}
 
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

可以看到,其需要如下几个参数:

  • corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。

  • maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。

  • keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将

  • allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。

  • unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。

  • workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。

  • threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。

  • handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。
    线程池的使用流程如下:

    // 创建线程池
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,
                                                 MAXIMUM_POOL_SIZE,
                                                 KEEP_ALIVE,
                                                 TimeUnit.SECONDS,
                                                 sPoolWorkQueue,
                                                 sThreadFactory);
    // 向线程池提交任务
    threadPool.execute(new Runnable() {
        @Override
        public void run() {
            ... // 线程执行的任务
        }
    });
    // 关闭线程池
    threadPool.shutdown(); // 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程
    threadPool.shutdownNow(); // 设置线程池的状态为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
    

    线程池的工作原理

    下面来描述一下线程池工作的原理,同时对上面的参数有一个更深的了解。其工作原理流程图如下:

通过上图,相信大家已经对所有参数有个了解了。下面再对任务队列、线程工厂和拒绝策略做更多的说明。

线程池的参数

任务队列(workQueue)

任务队列是基于阻塞队列实现的,即采用生产者消费者模式,在 Java 中需要实现 BlockingQueue 接口。但 Java 已经为我们提供了 7 种阻塞队列的实现:

  • ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。

  • LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列,在未指明容量时,容量默认为 Integer.MAX_VALUE。

  • PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现 Comparable 接口也可以提供 Comparator 来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。

  • DelayQueue:类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。

  • SynchronousQueue: 一个不存储元素的阻塞队列,消费者线程调用 take() 方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用 put() 方法的时候也会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。

  • LinkedBlockingDeque: 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出)。

  • LinkedTransferQueue: 它是ConcurrentLinkedQueue、LinkedBlockingQueue 和 SynchronousQueue 的结合体,但是把它用在 ThreadPoolExecutor 中,和 LinkedBlockingQueue 行为一致,但是是无界的阻塞队列。

    注意有界队列和无界队列的区别:如果使用有界队列,当队列饱和时并超过最大线程数时就会执行拒绝策略;而如果使用无界队列,因为任务队列永远都可以添加任务,所以设置 maximumPoolSize 没有任何意义。

线程工厂(threadFactory)

线程工厂指定创建线程的方式,需要实现 ThreadFactory 接口,并实现 newThread(Runnable r) 方法。该参数可以不用指定,Executors 框架已经为我们实现了一个默认的线程工厂:


private static class DefaultThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;
 
    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                              Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                      poolNumber.getAndIncrement() +
                     "-thread-";
    }
 
    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

拒绝策略(handler)

当线程池的线程数达到最大线程数时,需要执行拒绝策略。拒绝策略需要实现 RejectedExecutionHandler 接口,并实现 rejectedExecution(Runnable r, ThreadPoolExecutor executor) 方法。不过 Executors 框架已经为我们实现了 4 种拒绝策略:

AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常。
CallerRunsPolicy:由调用线程处理该任务。
DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务。

功能线程池

嫌上面使用线程池的方法太麻烦?其实Executors已经为我们封装好了 4 种常见的功能线程池,如下:

定长线程池(FixedThreadPool)
定时线程池(ScheduledThreadPool )
可缓存线程池(CachedThreadPool)
单线程化线程池(SingleThreadExecutor)

定长线程池(FixedThreadPool)

创建方法的源码:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(),
                                  threadFactory);
}
  • 特点:只有核心线程,线程数量固定,执行完立即回收,任务队列为链表结构的有界队列。
  • 应用场景:控制线程最大并发数。

使用示例:

// 1. 创建定长线程池对象 & 设置线程池线程数量固定为3
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
  public void run() {
     System.out.println("执行任务啦");
  }
};
// 3. 向线程池提交任务
fixedThreadPool.execute(task);

定时线程池(ScheduledThreadPool )

创建方法的源码:

private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;
 
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue());
}
 
public static ScheduledExecutorService newScheduledThreadPool(
        int corePoolSize, ThreadFactory threadFactory) {
    return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
                                   ThreadFactory threadFactory) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue(), threadFactory);
}
  • 特点:核心线程数量固定,非核心线程数量无限,执行完闲置 10ms 后回收,任务队列为延时阻塞队列。
  • 应用场景:执行定时或周期性的任务。

使用示例:

// 1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
  public void run() {
     System.out.println("执行任务啦");
  }
};
// 3. 向线程池提交任务
scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
scheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务

可缓存线程池(CachedThreadPool)

创建方法的源码:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>(),
                                  threadFactory);
}
  • 特点:无核心线程,非核心线程数量无限,执行完闲置 60s 后回收,任务队列为不存储元素的阻塞队列。
  • 应用场景:执行大量、耗时少的任务。

使用示例:

// 1. 创建可缓存线程池对象
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
  public void run() {
     System.out.println("执行任务啦");
  }
};
// 3. 向线程池提交任务
cachedThreadPool.execute(task);

单线程化线程池(SingleThreadExecutor)

创建方法的源码:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>(),
                                threadFactory));
}
  • 特点:只有 1 个核心线程,无非核心线程,执行完立即回收,任务队列为链表结构的有界队列。
  • 应用场景:不适合并发但可能引起 IO 阻塞性及影响 UI 线程响应的操作,如数据库操作、文件操作等。

使用示例:

// 1. 创建单线程化线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
  public void run() {
     System.out.println("执行任务啦");
  }
};
// 3. 向线程池提交任务
singleThreadExecutor.execute(task);

对比

image-20230917162628840

总结:

Executors 的 4 个功能线程池虽然方便,但现在已经不建议使用了,而是建议直接通过使用 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

其实 Executors 的 4 个功能线程有如下弊端:

  • FixedThreadPool 和 SingleThreadExecutor:主要问题是堆积的请求处理队列均采用 LinkedBlockingQueue,可能会耗费非常大的内存,甚至 OOM。
  • CachedThreadPool 和 ScheduledThreadPool:主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM。

Thread类

构造方法

Thread中常用的构造方法有:

  • public Thread() :分配一个新的线程对象。
  • public Thread(String name) :分配一个指定名字的新的线程对象。
  • public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
  • public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
@Test
public void newInstance(){
    Thread thread = new Thread();
    Thread thread1 = new Thread("thread - 1a");
}

Fields

//线程可以拥有的最小优先级
public final static int MIN_PRIORITY = 1;
//分配给线程的默认优先级
public final static int NORM_PRIORITY = 5;
//线程可以拥有的最大优先级
public final static int MAX_PRIORITY = 10;

这几个属性用于表示线程的优先级(后续会介绍)

常用方法

Thread类提供了大量控制和操作线程的方法,其中常用的有:

  • public static Thread currentThread() :返回对当前正在执行的线程对象的引用。

    Thread.currentThread(); // 执行这句代码的线程
    
  • public void run(): 表示线程的任务。

    • 如果这个线程是使用单独的Runnable运行对象构造的,则Runnable对象的run方法; 否则,此方法不执行任何操作并返回。所有Thread的子类应该覆盖(重写)此方法
  • public synchronized void start(): 线程开始执行;

    • Java虚拟机调用此线程的run方法。结果是两个线程同时运行:当前线程(从调用返回到start方法)和另一个线程(执行其run方法), 多次调用此方法是不合法的。
  • public static native void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。 线程不会丢失任何CPU的所有权。

    //线程休眠5S
    Thread.sleep(5000);
    
  • public static void sleep(long millis, int nanos) :使当前正在执行的线程以指定的毫秒数加上指定的纳秒数暂停(暂时停止执行)

  • public final String getName():返回此线程的名称

    Thread.currentThread().getName();//获取当前线程名称
    
  • public final synchronized void setName(String name):将此线程的名称更改为等于参数name

  • public final int getPriority():返回此线程的优先级

  • public final void setPriority(int newPriority):更改此线程的优先级,1~10之间

  • public final native boolean isAlive():测试这个线程是否活着。 如果一个线程已经启动并且尚未死亡,那么线程是活着的

  • public final void join():等待线程死亡,等同于join(0)

  • public final synchronized void join(long millis):等待这个线程死亡的时间最多为millis毫秒。 0的超时意味着永远等待

    • join可以理解为当我们调用某个线程的这个方法时,这个方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。(让父线程等待子线程结束之后才能继续运行)

      public static void main(String[] args){
          MyThread thread = new MyThread();
          thread.start();
          try {
              // main 执行, main 等待 thread 执行完
              thread.join();
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
      }
      
  • public void interrupt():中断这个线程,线程的中断状态标记为 true

    • @Deprecated(since="1.2") public final void stop() 立即终止线程(废弃的)
  • public static boolean interrupted():测试当前线程是否中断。 该方法可以清除线程的中断状态(设置中断状态为False) 。 换句话说,如果这个方法被连续调用两次,那么第二个调用将返回false(除非当前线程再次中断,在第一个调用已经清除其中断状态之后,在第二个调用之前已经检查过)。

  • public boolean isInterrupted():测试这个线程是否被中断。 线程的中断状态不受此方法的影响。

  • public static native void yield():导致当前执行线程处于让步状态。如果有其他可运行线程具有至少与此线程同样高的优先级,那么这些线程接下来会被调度。并不一定会让出去。

  • public State getState():返回此线程的状态,返回值是Thread的一个内部类,枚举类State。线程状态可以是:

    • NEW 尚未启动的线程处于此状态。
    • RUNNABLE 在Java虚拟机中执行的线程(可以运行的线程)处于此状态。
    • BLOCKED 被阻塞等待监视器锁定的线程处于此状态。
    • WAITING 正在等待另一个线程执行特定动作的线程处于此状态。
    • TIMED_WAITING 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。 (sleep(1000),join(1000))
    • TERMINATED 已退出的线程处于此状态。

线程状态

线程状态具体含义
NEW一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread()只有线程象,没有线程特征。
RUNNABLE当我们调用线程对象的start方法,那么此时线程对象进入了RUNNABLE状态。那么此时才是真正的在JVM进程中创建了一个线程,线程一经启动并不是立即得到执行,线程的运行与否要听令与CPU的调度,那么我们把这个中间状态称之为可执行状态(RUNNABLE)也就是说它具备执行的资格,但是并没有真正的执行起来而是在等待CPU的度。
BLOCKED当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
WAITING一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种,分别是调用Object.wait()、join()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。
TIMED_WAITING一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long)、join(long)。
TERMINATED一个完全运行完成的线程的状态。也称之为终止状态、结束状态

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有六种状态。在API中 java.lang.Thread.State 这个枚举中给出了六种线程状态:

image-20230915192403242

image-20230915192426815

我们不需要去研究这几种状态的实现原理,我们只需知道在做线程操作中存在这样的状态。那我们怎么去理解这几个状态呢,新建与被终止还是很容易理解的,我们就研究一下线程从Runnable(可运行)状态与非运行状态之间的转换问题。

image-20230915194219961

则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
| WAITING | 一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种,分别是调用Object.wait()、join()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。 |
| TIMED_WAITING | 一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long)、join(long)。 |
| TERMINATED | 一个完全运行完成的线程的状态。也称之为终止状态、结束状态 |

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有六种状态。在API中 java.lang.Thread.State 这个枚举中给出了六种线程状态:

[外链图片转存中…(img-twWC30uN-1694939295158)]

[外链图片转存中…(img-UNv1CTSS-1694939295158)]

我们不需要去研究这几种状态的实现原理,我们只需知道在做线程操作中存在这样的状态。那我们怎么去理解这几个状态呢,新建与被终止还是很容易理解的,我们就研究一下线程从Runnable(可运行)状态与非运行状态之间的转换问题。

[外链图片转存中…(img-uDG16Mv2-1694939295159)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值