进程、线程

进程:是操作系统分配资源的基本单位(静态概念),一个运行的程序可以称为一个进程;
线程:是在进程内部,是进程调度执行的基本单位(动态概念),一个进程可以有多个线程。同一进程中的多线程共享进程资源。

设置多线程的的目的是压榨CPU性能,多线程与单线程程序相比,多线程程序能够充分利用CPU。但是线程也不能随便的多,因为切换线程也换消耗CPU资源,线程过多,频繁切换,反而使得CPU利用率降低。

那么,一个程序到底设置多少个线程是比较合适的呢?

  • 一种做法是采取压力测试,进行选择合适的线程数量。
  • 通过公式计算01 线程基本概念 - 图1

    • 01 线程基本概念 - 图2是处理器核数
    • 01 线程基本概念 - 图3是期望CPU利用率
    • 01 线程基本概念 - 图4是等待时间与计算时间的比率,可通过profiler工具进行获取

      创建线程的5种方式

  • new Thread().start()

  • new Thread(Runable).start()
  • new Thread(()->{}).start
  • 线程池,Executors.newCachedThreadPool()
  • FutureTask+Callable

    1. public class Code02_CreateThreadWays {
    2. private static class Thread1 extends Thread {
    3. @Override
    4. public void run() {
    5. System.out.println("创建线程法1: new Thread()");
    6. }
    7. }
    8. public static void main(String[] args) {
    9. Thread thread1 = new Thread1();
    10. Thread thread2 = new Thread(new Runnable() {
    11. @Override
    12. public void run() {
    13. System.out.println("创建线程法2:Thread(Runnable)");
    14. }
    15. });
    16. Thread thread3 = new Thread(() -> System.out.println("创建线程法3:Thread(lambda)"));
    17. ExecutorService executorService = Executors.newCachedThreadPool();
    18. Thread thread4 = new Thread((Runnable) executorService.submit(() -> System.out.println("创建线程法4:线程池")));
    19. Thread thread5 = new Thread(new FutureTask<Void>(new Callable<Void>() {
    20. @Override
    21. public Void call() throws Exception {
    22. System.out.println("创建线程法5:FutureTask+Callable");
    23. return null;
    24. }
    25. }));
    26. thread1.start();
    27. thread2.start();
    28. thread3.start();
    29. thread4.start();
    30. thread5.start();
    31. }
    32. }

    image.png

    线程的状态

  • NEW 新建线程

  • RUNNABLE 可运行状态(包括Running 和Ready状态)
  • WAITING 等待被唤醒
  • TIMED WAITING 隔一段时间后自动唤醒
  • BLOCKED 被阻塞,等待锁(synchronized)
  • TERMINATED 线程结束

image.png

public class Code03_ThreadStates {
    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("2: " + this.getState());

            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.print(i + " ");
            }
            System.out.println();
        }
    }

    public static void main(String[] args) throws Exception {
        Thread t1 = new MyThread();
        System.out.println("1: " + t1.getState());
        t1.start();
        t1.join();
        System.out.println("3: " + t1.getState());

        Thread t2 = new Thread(() -> {
            try {
                LockSupport.park();
                System.out.println("t2 go on!");
                TimeUnit.SECONDS.sleep(5);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        t2.start();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("4: " + t2.getState());

        LockSupport.unpark(t2);
        TimeUnit.SECONDS.sleep(1);
        System.out.println("5: " + t2.getState());

        final Object o = new Object();
        Thread t3 = new Thread(()->{
            synchronized (o) {
                System.out.println("t3 得到了锁 o");
            }
        });

        new Thread(()-> {
            synchronized (o) {
                SleepHelper.sleepSeconds(5);
            }
        }).start();

        SleepHelper.sleepSeconds(1);

        t3.start();
        SleepHelper.sleepSeconds(1);
        System.out.println("6: " + t3.getState());
    }
}

线程的中断(interrupt)

Java 中的线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接终
仅供止该线程的执行, 而是被中断的线程根据中断状态自行处理。

//Thread.java  
public void interrupt()            //t.interrupt() 中断线程(设置线程中断标志位true,并不是中断断线程的运行)
public boolean isInterrupted()     //t.isInterrupted() 查询中断标志位是否被设置
public static boolean interrupted()//Thread.interrupted() 查看“当前”线程是否被中断,如果被中断,恢复标志位

sleep()方法在睡眠的时候,不到时间是没有办法叫醒的,这个时候可以用interrupt设置标志位,然后呢必须得catch InterruptedException来进行处理,决定继续睡或者是别的逻辑,(自动进行中断标志复位)。

interrupt()不能打断正在竞争锁的线程synchronized(),如果想打断正在竞争锁的线程,使用ReentrantLock的lockInterruptibly()

结束线程的方法

  1. 自然结束(能自然结束就尽量自然结束)
  2. stop() suspend() resume() ——》 因为会产生不一致,被废弃
  3. volatile标志
    1. 不适合某些场景(比如还没有同步的时候,线程做了阻塞操作,没有办法循环回去)
    2. 打断时间也不是特别精确,比如一个阻塞容器,容量为5的时候结束生产者,
      但是,由于volatile同步线程标志位的时间控制不是很精确,有可能生产者还继续生产一段儿时间
  4. interrupt() and isInterrupted(比较优雅)

    public void run(){
    try {
      while(!Thread.currentThread().isInterrupted()&& more work to do){
          // 业务代码
      } catch(InterruptionException e) {
          // 异常处理
      } finanly {
          // 释放资源
      }
    }
    

    线程的通知与等待

    wait

    当一个线程调用一个共享变量的wait方法时, 该调用线程会被阻塞挂起, 直到发生
    下面几件事情之一才返回:

  5. 其他线程调用了该共享对象的noti fy或者notifyAll方法;

  6. 其他线程调用了该线程的interrupt方法,该线程抛出InterruptedException 异常返回;
  7. 如果调用wait方法的线程没有事先获取该对象的监视器锁,则调用wait方法时调用线程会抛出IllegalMonitorState Exce ption 异常。
  8. 当线程调用共享对象的wait方法时,当前线程只会释放当前共享对象的锁,当前线程持有的其他共享对象的监视器锁不会释放。
  9. 当一个线程A调用共享对象的wait方法被阻塞挂起后,如果其他线程B中断了该线程(使用了该线程A的interrupt方法), 则该线程会抛出InterruptedException 异常并返回。
synchronized ( obj ) {
 while ( 条件不满足 ) {
     obj.wait();
}
public class Code02_SynchronizedWithWait {

    volatile Queue<String> queue = new LinkedList<>();
    private static final int MAX_SIZE = 10;

    public static void main(String[] args) {
        Code02_SynchronizedWithWait t = new Code02_SynchronizedWithWait();


        new Thread(t::consume, "消费者").start();
        new Thread(t::produce, "生产者").start();


    }

    public void produce() {

        synchronized (this) {
            System.out.println(Thread.currentThread().getName() + " 获取到锁");
            for (int i = 0; i < 20; i++) {
                while (queue.size() == MAX_SIZE) {
                    System.out.println("篮子满了," + Thread.currentThread().getName() + " 阻塞,释放锁");
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + " 生成产第" + (queue.size() + 1) + "馒头");
                queue.add("馒头");
                this.notify();
            }

        }
    }

    public void consume() {

        synchronized (this) {
            System.out.println(Thread.currentThread().getName() + " 获取到锁");
            for (int i = 0; i < 20; i++) {
                while (queue.size() == 0) {
                    System.out.println("篮子空了," + Thread.currentThread().getName() + " 阻塞,释放锁");
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + " 消费" + queue.poll() + ",剩余:" + queue.size());
                this.notify();
            }
        }
    }
}

notify、notifyAll

一个线程调用共享对象的notify()方法后,随机唤醒一个在该共享变量上调用wait 系列方法后被挂起的线程。被唤醒的线程获得锁后继续执行。notify A ll () 方法则会唤醒所有在该共享变量上由于调用wait 系列方法而被挂起的线程。

等待线程执行终止的join方法

等待Join的线程执行完毕再继续执行
01 线程基本概念 - 图7

public class Code03_Join {
    public static void main(String[] args) {
        Object o = new Object();

        @SuppressWarnings("all")
        Thread thread1 = new Thread(()->{
            synchronized (o){
                System.out.println(Thread.currentThread().getName()+"获取到锁");
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"执行完毕");
            }
        },"线程1");
        @SuppressWarnings("all")
        Thread thread2 = new Thread(()->{
            synchronized (o){
                System.out.println(Thread.currentThread().getName()+"获取到锁");
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"执行完毕");
            }
        },"线程2");

        thread1.start();
        thread2.start();
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("全部线程执行完毕");
    }
}

让出CPU执行权的yeild方法

Thread 类中有一个静态的yield 方法,当一个线程调用yield 方法时,实际就是在暗示
线程调度器当前线程请求让出自己的CPU 使用,但是线程调度器可以无条件忽略这个暗示。

我们知道操作系统是为每个线程分配一个时间片来占有CPU 的, 正常情况下当一个线程把分配给自己的时间片使用完后,线程调度器才会进行下一轮的线程调度,而当一个线程调用了Thread 类的静态方法yield 时,是在告诉线程调度器自己占有的时间片中还没有使用完的部分自己不想使用了,这暗示线程调度器现在就可以进行下一轮的线程调度。

当一个线程调用y ield 方法时, 当前线程会让出CPU 使用权,然后处于就绪状态,线
程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能会调度到刚刚让出CPU 的那个线程来获取CPU 执行权。

public class Code04_Yield {

    public static void main(String[] args) {
        new Thread(Code04_Yield::run, "线程1").start();
        new Thread(Code04_Yield::run, "线程2").start();
        new Thread(Code04_Yield::run, "线程3").start();

    }

    public static void run() {
        for (int i = 0; i < 5; i++) {
            //当i = O 时让出CPU执行权,放弃时间片,进行下一轮调度
            if (i % 5 == 0) {
                System.out.println(Thread.currentThread().getName() + "yield cpu……");
                //当前线程让出CPU执行权,放弃时间片,进行下一轮调度
                Thread.yield();
            }

            System.out.println(Thread.currentThread().getName() + " ---- " + i);
        }
        System.out.println(Thread.currentThread().getName() + "结束");
    }
}

死锁

死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的互相等待的现象,在无外力作用的情况下,这些线程会一直相互等待而无法继续运行下去。
image.png
产生死锁条件:

  • 互斥条件:指线程对己经获取到的资源进行排它性使用, 即该资源同时只由一个线程占用。
  • 请求并持有条件:指一个线程己经持有了至少一个资源, 但又提出了新的资源请求,而新资源己被其他线程占有,所以当前线程会被阻塞,但阻塞的同时并不释放自己己经获取的资源。
  • 不可剥夺条件:指线程获取到的资源在自己使用完之前不能被其他线程抢占, 只有在自己使用完毕后才由自己释放该资源。
  • 环路等待条件:指在发生死锁时, 必然存在一个线程→资源的环形链, 即线程集合{TO , TL T2 ,…, Tn }中的TO 正在等待一个Tl 占用的资源, Tl 正在等待T2 占用的资源,……Tn 正在等待己被TO 占用的资源。

只要破坏任意一个条件,死锁情况就会被打破。

守护线程与用户线程

Java 中的线程分为两类,分别为daemon 线程(守护线程〉和user 线程(用户程)。在JVM启动时会调用main 函数, main 函数所在的钱程就是一个用户线程,其实在NM内部同时还启动了好多守护线程, 比如垃圾回收线程。

用户线程与守护线程区别:当最后一个用户线程结束时,JVM会退出。而不会管是否还有守护线程。

将线程的daemon参数设为true,就把该线程设置为了守护线程。

ThreadLocal

ThreadLocal 是JDK 包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal 变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。

当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。

创建一个ThreadLocal 变量后,每个线程都会复制一个变量到自己的本地内存
image.png

ThreadLocal原理

image.png
image.png
Thread 类中有一个threadLocals 和一个inheritableThreadLocals , 它们都是ThreadLocalMap 类型的变量, 而ThreadLocalMap 是一个定制化的Hashmap 。

在默认情况下, 每个线程中的这两个变量都为null ,只有当前线程第一次调用ThreadLocal 的set 或者get 方法时才会创建它们。

image.png

Thread 成员变量

// 线程名称
private volatile String name;    
// 线程优先级
private int priority;  
// 线程是否单步执行
private boolean single_step;
// 是否为守护线程
private boolean daemon = false;
// JVM状态
private boolean stillborn = false;
// 将要运行的Runnable任务
private Runnable target;
// 线程所在线程组
private ThreadGroup group;
// 当前线程的上下文类加载器
private ClassLoader contextClassLoader;
// 用于匿名线程自动编号(自增)
private static int threadInitNumber;
// 当前线程申请的JVM栈大小
private long stackSize;
// 线程id
private long tid;
// 用于生成线程id
private static long threadSeqNumber;
// 线程状态,初始化为0,表示未启动
private volatile int threadStatus = 0;
// 线程最小优先级
public final static int MIN_PRIORITY = 1;
// 线程默认优先级
public final static int NORM_PRIORITY = 5;
// 线程最大优先级
public final static int MAX_PRIORITY = 10;
// 当前线程引用
public static native Thread currentThread();

Thread 状态

  • NEW
  • RUNNABLE
  • BLOCKED
  • WAITING
  • TIMED_WAITING
  • TERMINATED

01 线程基本概念 - 图13

run

重新Runnable中的run(),否则声明都不执行

public void run() {
    if (target != null) {
        target.run();
    }
}

start

public synchronized void start() {
    // 判断线程当前的状态为未启动
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    // 通知线程组当前线程即将启动,并将当前线程添加到线程组列表中,减少线程组中未启动的线程数量
    group.add(this);

    boolean started = false;
    try {
        // 启动线程
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                // 线程启动失败,从线程组中移除该线程并将未启动线程数量增加
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {

        }
    }
}
// 调用run方法
private native void start0();

interrupt

interrupt 方法可以中断线程,除非始终运行当前线程中断本身,否则会调用checkAccess(),可能会抛出SecurityException

public void interrupt() {
    if (this != Thread.currentThread())
        checkAccess();

    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            interrupt0();           // Just to set the interrupt flag
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}