Skip to content

线程

一、线程和进程

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位 线程个数最优公式 N(thread)= (1+T(io)/T(cpu))*Ncpu

进程是操作系统资源分配的基本单位,线程是任务调度和执行的基本单位,进程维护资源,线程是真正的执行体

包含关系:一个进程可以创建多个线程,且最少一个主线程,线程只能属于一个进程,线程是进程的一部分。

二、上下文切换

cpu为线程分配时间片,时间片非常短(毫秒级别),cpu不停的切换线程执行,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态,让我们感觉是多个程序同时运行的。上下文的频繁切换,会带来一定的性能开销

如何减少上下文切换的开销?

  • 无锁并发编程:多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照Hash算法取模分段,不同的线程处理不同段的数据

  • CAS:Java的Atomic包使用CAS算法来更新数据,而不需要加锁。使用最少线程

  • 使用最少线程:避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态

  • 协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。--主要是go语言

三、线程的状态

操作系统的状态

  • NEW:该状态下的线程还没开始执行
  • READY:线程等待获取CPU的时间片
  • RUNNING:线程正在运行
  • WAITING:线程等待
  • TERMINATING:该状态下的线程已经退出

java的状态

  • NEW:该状态下的线程还没开始执行
  • RUNNABLE:该线程正运行中
  • BLOCKED:该状态下的线程被阻塞并且等待一个监听锁,主要是等待synchronized语句块的锁的状态
  • WAITING:该状态下的线程无限期等待另外一条线程执行特定的操作,执行了object.wait()、join()、LockSupport.part(),进入该状态的线程需要等待其它线程做出一些特定的动作(通知或者中断)。
  • TIMED_WAITING:该状态下的线程在特定的时间内等待另外一条线程执行某种操作。执行了Object.wait(long)、Thread.join()、LockSupport.parkNanos()、LockSupport.parkUtil,会在特定时间内自行返回。
  • TERMINATING:该状态下的线程已经退出

四、忙循环

忙循环就是用一个循环让一个线程等待,它不像wait、sleep会放弃cpu的控制权,而是一直在执行当前的程序,这是为了保留CPU的缓存。在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使用它了。

六、线程挂起

线程的挂起是为了使线程进入“非可执行”状态,这个状态下的线程CPU不会分给线程时间片,进入这个状态用来暂停一个线程的运行。

为什么要挂起:cpu分配的时间片非常短,同时也非常珍贵,为了避免资源的浪费。

挂起线程操作:

1、废弃的方法,thread.suspend()该方法不会释放资源,可能会导致死锁,所以废弃。thread.resume()是跟着suspend()存在的,也跟着废弃。

2、可以使用的方法:wait()方法可以挂起线程,会释放资源(包括锁和CPU资源),notify()可以唤醒一个该锁的线程,notifyAll()是唤醒所有锁的线程。这几个方法必须是持有锁对象,必须在同步的方法体内。

使用场景:当所有资源没有准备到位的时候,需要挂起等到所有资源都拿到了,再执行。

七、线程中断

stop()方法已经被废弃,因为该方法会强行关闭线程,会导致线程安全问题。

使用interrupt()方法可以使线程中断,但是要视情况而定,如果不使用线程池,直接使用该方法,但是如果使用线程池,需要根据标记位去判断线程的状态。

/**
 * @Description 中断线程
 * @Author lcy
 * @Date 2020/9/1 21:13
 */
public class InterruptThread {

    /**
     * 要保证原子性操作,如果不使用原子操作的类,需要加volatile
     * 即 private static volatile boolean interrupt = true;
     */
    private static AtomicBoolean interrupt = new AtomicBoolean(true);


    public static void main(String[] args) throws InterruptedException{
        ExecutorService executorService =  new ThreadPoolExecutor(0, 1,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>());

        //手动中断线程的方法
        Thread thread = new Thread(()->{
            while(!Thread.currentThread().isInterrupted()){
                System.out.println("手动的线程正在运行");
            }
            System.out.println("手动的线程结束");
        });

        thread.start();

        //线程池中断
        executorService.execute(()->{
            while(interrupt.get()){
                System.out.println("线程池的线程正在运行");
            }
            System.out.println("线程池的线程结束");
        });
        Thread.sleep(1L);

        //线程中断
        thread.interrupt();
        //让线程池里的这个线程结束 --注意,使用线程池测试的时候,线程池不一定会直接关闭,需要自己配置线程池参数,
        // 让线程池跟着子线程一起关闭
        interrupt.set(false);
    }

}

八、线程优先级

线程优先级的高度,跟数字的大小相反,优先级为1最高,为10最低

线程的优先级告诉程序线程的重要程度,当线程堵塞的时候,程序会尽可能的先运行优先级比较高的线程,优先级比较低的线程被准许运行的概率会比较低。

/**
 * @Description 线程优先级
 * @Author lcy
 * @Date 2020/9/2 9:13
 */
public class ThreadPriority {

    public static void main(String[] args) throws InterruptedException{

        //因为队列的原因,需要三个线程才能测试,有一个进入消息队列。这样另外两个临时线程才会同步
        ExecutorService executorService = new ThreadPoolExecutor(0,3,
                0L,TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(1));

        Object object = new Object();
        new Thread(() -> {
            Thread.currentThread().setName("线程A");
            //默认优先级5
            System.out.println(Thread.currentThread().getName() + "尝试获取锁资源!");
            synchronized (object) {
                System.out.println(Thread.currentThread().getName() + "准备等待!");
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "被唤醒!");
            }
        }).start();

        new Thread(() -> {
            Thread.currentThread().setName("线程B");
            //设置优先级
            Thread.currentThread().setPriority(4);
            System.out.println(Thread.currentThread().getName() + "尝试获取锁资源!");
            synchronized (object) {
                System.out.println(Thread.currentThread().getName() + "准备等待!");
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "被唤醒!");
            }
        }).start();


        //System.out.println("-----------------线程池-----------------");
        Object o = new Object();
        executorService.execute(() -> {
            Thread.currentThread().setName("线程池线程C");
            //默认优先级5
            System.out.println(Thread.currentThread().getName() + "尝试获取锁资源!");
            synchronized (o) {
                System.out.println(Thread.currentThread().getName() + "准备等待!");
                try {
                    o.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "被唤醒!");
            }
        });

        executorService.execute(() -> {
            Thread.currentThread().setName("线程池线程D");
            //默认优先级5
            System.out.println(Thread.currentThread().getName() + "尝试获取锁资源!");
            synchronized (o) {
                System.out.println(Thread.currentThread().getName() + "准备等待!");
                try {
                    o.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "被唤醒!");
            }
        });

        executorService.execute(() -> {
            Thread.currentThread().setName("线程池线程E");
            //默认优先级5
            Thread.currentThread().setPriority(4);
            System.out.println(Thread.currentThread().getName() + "尝试获取锁资源!");
            synchronized (o) {
                System.out.println(Thread.currentThread().getName() + "准备等待!");
                try {
                    o.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "被唤醒!");
            }
        });
        //唤醒线程必须在同步方法里,否则报IllegalMonitorStateException
        Thread.sleep(1000L);
        synchronized (object){
            object.notifyAll();
        }
        synchronized (o){
            o.notifyAll();
        }
    }

}

九、守护线程

线程分为用户线程和守护线程,守护线程是指整个程序中的所有线程用户的守护者,只要有活着的用户线程,守护线程就或者。当jvm当中的最后一个非守护线程结束时,守护线程也随着jvm一起退出。

尽量少使用守护线程,因为不可控制。不要在守护线程当中去进行读写操作、执行计算逻辑等等。