首页 > 编程语言 >【JUC】并发编程初探

【JUC】并发编程初探

时间:2022-12-15 16:22:38浏览次数:35  
标签:lang JUC java Thread 编程 t1 线程 初探 wait

目录

1、Java——天生的多线程

在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换,让使用者感觉到这些线程在同时执行。

当前的main函数就是一个 JVM 进程。 打印出来的 6 条线程信息就是进程中的多条线程。

示例代码: ThreadPrint.java

6 条线程分别是干什么的?

public class ThreadPrint {
    public static void main(String[] args) throws JsonProcessingException, InterruptedException {
        // 获取Java线程管理MXBean
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        // 不需要获取同步的monitor和synchronizer信息,仅获取线程和线程堆栈信息
        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
        // 遍历线程信息,仅打印线程ID和线程名称信息
        for (ThreadInfo threadInfo : threadInfos) {
            System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.
                    getThreadName());
        }
        Thread.sleep(1000000);//使当前线程休眠,进入阻塞状态
    }
}

image-20221016172224562

1.1 main:主线程

1.2 Reference Handle

引用处理的线程

  • 强,软,弱,虚四种引用,在GC过程中有不同表现
  • JVM深入分析

1.3 Finalizer

JVM垃圾回收箱相关的内容

  • 只有当开始一轮垃圾收集的时候,才会开始调用finalize方法(两轮标记:GC Roots引用链不可达,没有重写或已调用finalize方法没有必要执行finalize方法)
  • daemon,prop=10 是一个高优先级的守护线程
  • jvm在垃圾收集的时候,会将失去引用的对象封装到Fianlizer对象(Reference)中,放入到F-queue队列中,由Finalizer线程执行finalize方法

1.4 Signal Dispatcher

信号分发器

通过cmd 发送jstack,传到了jvm进程,这时候信号分发器就要发挥作用了

1.5 Attach Listenner

进程间的通信,附加监听器

简单来说,Attach Listenner是JDK中的一个工具类提供的jvm进程之间通信的工具

进程中的通信:

  • cmd中,java -version
  • jvm中,jstack、jmap、dump

开启该线程的两个方式:

(1)通过JVM参数开启:-XX:StartAttachListenner

(2)延迟开启:cmd -- java -version ==》 jvm适时开启AL线程

1.6 Monitor Ctrl-Break

与JVM关系不大,IDEA通过反射的方式,开启一个随着我们运行的jvm进程开启与关闭的一个监听线程。

1.7 线程

1.7.1 查看线程的方法

image-20221008205811397

1.7.2 上述代码线程的状态

"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000000001a932800 nid=0x3bf8 runnable [0x000000001b96e000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:170)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
        - locked <0x00000000d67070b8> (a java.io.InputStreamReader)
        at java.io.InputStreamReader.read(InputStreamReader.java:184)
        at java.io.BufferedReader.fill(BufferedReader.java:161)
        at java.io.BufferedReader.readLine(BufferedReader.java:324)
        - locked <0x00000000d67070b8> (a java.io.InputStreamReader)
        at java.io.BufferedReader.readLine(BufferedReader.java:389)
        at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x0000000019a39000 nid=0x283c waiting on condition [0x0000000000000000]  prio=5 延迟开启的问题。
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000019a38800 nid=0x1dd4 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

// Finalizer线程
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000017ae8800 nid=0x3708 in Object.wait() [0x0000000019e9f000] (Finalizer 专注垃圾收集,垃圾收集 -- 并行收集,不阻碍用户线程,低优先级线程。 prio=8 他是一个守护线程啊。而且这个线程目前并没有真正的开启,不足以发生minorgc或者是 full gc、)
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d6108e98> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
        - locked <0x00000000d6108e98> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

// Reference Handler线程
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000017ae1800 nid=0x1ee0 in Object.wait() [0x000000001999f000] (引用处理线程-GC相关线程:GC 很重要啊,优先级还挺高)
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d6106b40> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x00000000d6106b40> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

// main线程
"main" #1 prio=5 os_prio=0 tid=0x00000000028f4800 nid=0x25ac waiting on condition [0x00000000028ef000] (操作系统面向的是JVM 进程,JVM 进程里面向的是 我们的main函数,所以对于我们的操作系统如何看待我们的main函数优先级,无所谓。 只要os 给我们jvm进程足够公平的优先级就行。)
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at com.boot.jdk.ThreadPrint.main(ThreadPrint.java:20)

2、线程的优先级和守护线程

2.1 线程的优先级

在Java线程中,通过一个整型成员变量priority来控制优先级,优先级的范围从1~10

在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分配CPU时间片的数量要多于优先级低的线程。

设置线程优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。

示例代码: ThreadPrority.java

public class CodeTest {
    public static void main(String[] args) throws JsonProcessingException {
        System.out.println(Thread.currentThread().getName()
                +"("+Thread.currentThread().getPriority()+ ")");

        Thread t1=new MyThread("t1");    // 新建t1
        Thread t2=new MyThread("t2");    // 新建t2
        t1.setPriority(1);                // 设置t1的优先级为1
        t2.setPriority(10);                // 设置t2的优先级为10
        t1.start();                        // 启动t1
        t2.start();                        // 启动t2
    }
}

class MyThread extends Thread{
    public MyThread(String name) {
        super(name);
    }

    @SneakyThrows
    public void run(){
        for (int i=0; i<5; i++) {
            System.out.println(Thread.currentThread().getName()
                    +"("+Thread.currentThread().getPriority()+ ")"
                    +", loop "+i);
        }
    }
};

输出:

image-20221008211628592

2.2 setPriority()

setPriority()方法,是JVM提供的一个方法,并且能够调用本地方法——setPriority0()。

我们发现优先级貌似没有起作用,为什么呢?

(1)现在的计算机都是多核的,t1,t2 会让哪个cpu处理不好说,虽然可能两个线程的优先级有高低,但是可能两个线程由不同的CPU同时提供资源执行

(2)优先级不代表先后顺序,优先级针对的是CPU时间片的长短问题。哪怕优先级低,也可能先拿到CPU时间片,只不过这个时间片比高优先级的线程的时间片短。

(3)目前工作中,实际项目中,不必要使用setPriority方法。我们现在都是用 hystrix, sential也好,一些开源的信号量控制工具,都能够实现线程资源的合理调度。这个 setPriority方法,很难控制。实际的运行环境太复杂。

2.2 守护线程

Daemon线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这 意味着,当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出(即使守护线程的代码还没有执行完成)。可以通过调用Thread.setDaemon(true)将线程设置为Daemon线程

示例代码: ThreadDaemon.java

public class ThreadDaemon {
    public static void main(String[] args) {
        Thread thread = new Thread(new DaemonThread(),"Daemon Thread!");
        thread.setDaemon(true); // 设置为守护线程
        thread.start();
        // main 线程退出
    }
    static class DaemonThread implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally { //finally 不能够保证我们的守护线程的最终执行
                System.out.println("FINISH!");
            }
        }
    }
}

在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。

注意:必须先设置为守护线程再调用start()方法

public final void setDaemon(boolean on) {
    checkAccess();
    if (isAlive()) {
// 告诉我们,必须要先设置线程是否为守护线程,然后再调用start方法。如果你先调用start。 isAlive = true.
        throw new IllegalThreadStateException();//非法线程状态异常
    }
    daemon = on;
}

3、线程的状态及状态之间的转化

3.1 线程上下文切换

线程上下文切换(Thread Context Switch)

当CPU不在执行当前的线程,转而执行另一个线程的代码时,要进行线程上下文的切换

有一下一些原因:

  • 线程的CPU时间片用完了
  • 垃圾回收
  • 有更高优先级的线程需要运行
  • 线程自己调用了sleep,yield,wait,join,park,synchronized,lock等方法

当Context Switch发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条jvm指令的执行地址,是线程私有的

  • 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
  • Context Switch频繁切换会影响性能

3.1 线程状态及状态之间的转换

从Java API层面来描述,根据Thread.State枚举,分为六种状态

示例源码: Thread.State.java

public enum State {
    NEW,//线程还未开始

    RUNNABLE,//调用start进入RUNABLE状态

    BLOCKED,

    WAITING,

    TIMED_WAITING,

    TERMINATED;
}

基于源码的状态转换图

  • NEW线程刚被创建,但是还没有调用start()方法,Thread.start 之后,他会进入一个就绪状态,还没有分配到 cpu的执行权。 当cpu的时间片切换到他的时候,他才会开始执行,进入running状态。

  • RUNABLE当调用了start()方法之后,注意,Java API层面RUNABLE状态涵盖了操作系统层面的【可运行状态】READY、【运行状态】RUNING和【阻塞状态】(由于BIO导致的线程阻塞,在Java中无法区分,仍然认为是可运行的)

  • BLOCKED:只针对sync锁,如果有synchronized关键字(无论修饰在方法上还是在代码块中),如果竞争锁失败,都会进入BLOCKED状态

  • WAITING:Objeck.wait()方法、Thread.join()方法、LockSupport.park()方法三个方法都会进入WAITING状态

  • TIMED_WAITING:Thread.sleep()、Object.wait(long)、Thread.join(long)、LockSupport.parkNanos()、LockSupport.parkUntil()方法

    BLOCKEDWAITINGTIMED_WAITING都是Java API层面对【阻塞状态】的细分

  • TERMINATED当前线程执行结束

3.2 实战——Stack log(堆日志)解读线程状态

示例代码 : ReadStackLog.Java

import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.SneakyThrows;

public class ReadStackLog {
    public static void main(String[] args) throws JsonProcessingException {
        new Thread(new TimeWaiting (), "TimeWaitingThread").start();
        new Thread(new Waiting(), "WaitingThread").start();
        // 使用两个Blocked线程,一个获取锁成功,另一个被阻塞
        new Thread(new Blocked(), "BlockedThread-1").start();
        new Thread(new Blocked(), "BlockedThread-2").start();
    }
}
// 该线程不断地进行睡眠
 class TimeWaiting implements Runnable {
    @SneakyThrows
    @Override
    public void run() {
        while (true) {
            Thread.sleep(1000000);
        }
    }
}
// 该线程在Waiting.class实例上等待
 class Waiting implements Runnable {
    @Override
    public void run() {
        while (true) {
            synchronized (Waiting.class) {
                try {
                    Waiting.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
// 该线程在Blocked.class实例上加锁后,不会释放该锁
 class Blocked implements Runnable {
    @SneakyThrows
    public void run() {
        synchronized (Blocked.class) {
            while (true) {
                Thread.sleep(1000000);
            }
        }
    }
}

JPS + jstack 命令可进行查看上述代码线程的状态

"BlockedThread-2" #15 prio=5 os_prio=0 tid=0x000000001b956000 nid=0x22d8 waiting for monitor entry [0x000000001d0be000] (发现死锁,一直不会释放的话)
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.boot.jdk.Blocked.run(ReadStackLog.java:45)
        - waiting to lock <0x00000000d67bca20> (a java.lang.Class for com.boot.jdk.Blocked)
        at java.lang.Thread.run(Thread.java:745)

"BlockedThread-1" #14 prio=5 os_prio=0 tid=0x000000001b955000 nid=0x4c4c waiting on condition [0x000000001cfbf000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at com.boot.jdk.Blocked.run(ReadStackLog.java:45)
        - locked <0x00000000d67bca20> (a java.lang.Class for com.boot.jdk.Blocked)
        at java.lang.Thread.run(Thread.java:745)

"WaitingThread" #13 prio=5 os_prio=0 tid=0x000000001b954800 nid=0x39cc in Object.wait() [0x000000001cebf000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d67ba680> (a java.lang.Class for com.boot.jdk.Waiting)
        at java.lang.Object.wait(Object.java:502)
        at com.boot.jdk.Waiting.run(ReadStackLog.java:31)
        - locked <0x00000000d67ba680> (a java.lang.Class for com.boot.jdk.Waiting)
        at java.lang.Thread.run(Thread.java:745)

"TimeWaitingThread" #12 prio=5 os_prio=0 tid=0x000000001b951800 nid=0x3820 waiting on condition [0x000000001cdbe000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at com.boot.jdk.TimeWaiting.run(ReadStackLog.java:20)
        at java.lang.Thread.run(Thread.java:745)


"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000000001b6c9000 nid=0x1210 runnable [0x000000001c6be000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:170)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
        - locked <0x00000000d67070b8> (a java.io.InputStreamReader)
        at java.io.InputStreamReader.read(InputStreamReader.java:184)
        at java.io.BufferedReader.fill(BufferedReader.java:161)
        at java.io.BufferedReader.readLine(BufferedReader.java:324)
        - locked <0x00000000d67070b8> (a java.io.InputStreamReader)
        at java.io.BufferedReader.readLine(BufferedReader.java:389)
        at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001a72b000 nid=0x4ea8 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001a6d2800 nid=0x3d94 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001a6b1800 nid=0x4254 in Object.wait() [0x000000001ab8f000] (一直处于WAITING状态,只有进行垃圾收集的时候,才会被notify。 notify就会用到我们的 signal Dispatcher线程)
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d6108e98> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
        - locked <0x00000000d6108e98> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x00000000187c1000 nid=0x48a8 in Object.wait() [0x000000001a68f000] (引用处理线程处于WAITING状态。加载新的类或引用时,才会再次开启该线程。)
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d6106b40> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x00000000d6106b40> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

4、线程初始化

4.1 概述

一个新构造的线程对象是由其parent线程来进行空间分配的,而child线程继承了parent是否为Daemon、优先级和加载资源的contextClassLoader以及可继承的ThreadLocal,同时还会分配一个唯一的(sync)ID来标识这个child线程。(在下方源码的解读中可以很清晰地看清)。至此,一个能够运行的线程对象就初始化好了,在堆内存中等待着运行。

线程对象在初始化完成之后,调用start()方法就可以启动这个线程。线程start()方法的含义是:当前线程(即parent线程)同步告知Java虚拟机,只要线程规划器空闲,应立即启动调用start()方法的线程。

4.2 init()源码

 /**
     * Initializes a Thread. 初始化线程
     *
     * @param g 线程组
     * @param target 调用run()方法的Runnable对象
     * @param name 创建线程的名字
     * @param stackSize 新线程所需的堆栈大小,或0表示该参数将被忽略。
     * @param acc 继承AccessControlContext,如果为空,则使用AccessController.getContext()
     * @param inheritThreadLocals 如果为true,从构造线程继承可继承线程局部变量的初始值
*/
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;

    Thread parent = currentThread();
    SecurityManager security = System.getSecurityManager();//防止group为null
    if (g == null) {
        /* Determine if it's an applet or not */

        /* If there is a security manager, ask the security manager
               what to do. */直接得到security manager的ThreadGroup
        if (security != null) {
            g = security.getThreadGroup();
        }

        /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
        if (g == null) {
            g = parent.getThreadGroup();//父类的ThreadGroup
        }
    }
//上述代码,尊重线程初始化传入的ThreadGroup;次选System security manager的ThreadGroup;再次选parent的ThreadGroup


    /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
    g.checkAccess();

    /*
         * Do we have the required permissions?
         */
    if (security != null) {
        if (isCCLOverridden(getClass())) {
            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }//检查权限
    }

    g.addUnstarted();//NEW状态的线程,添加到ThreadGroup中

    this.group = g;
    this.daemon = parent.isDaemon();//是否为守护线程
    this.priority = parent.getPriority();//优先级
    //新的线程属性依赖于父类线程
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext =
        acc != null ? acc : AccessController.getContext();
    this.target = target;
    setPriority(priority);
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
        ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;

    /* Set thread ID */
    tid = nextThreadID();//设置线程ID,该方法加锁了,以保证ThreadID的唯一性
}

4.3 start()源码

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)//不为NEW状态,抛出异常
        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);//添加Thread至group中

    boolean started = false;
    try {
        //start0 完全执行之前,线程处于READY状态
        start0();
        //完成后,只要CPU分配执行权,线程就进入RUNNGING状态
        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 */
            //start0 这个异常,会直接反馈给调用线程。即Main函数里面的thread.start方法,防止thread.start方法感知不到异常,导致程序的错误继续执行
        }
    }
}

private native void start0();

4.4 start()和run()的区别

方法名 static 功能说明 注意
start() 启动一个新线程,在新的线程运行run方法中的代码 start方法只是让线程进入就绪,里面的代码不一定立刻执行(CPU时间片还没有分给他)。每个线程对象的start方法只能调用一次,如果调用多次会出现IllegalThreadStateException
run() 新线程启动后会调用的方法 如果在构造Thread对象时传递了Runable参数,则线程启动后会调用Runable中的run方法,否则默认不执行任何操作。但可以创建Thread的子类对象,来覆盖默认行为

(1)调用run()方法

代码:

public static void main(String[] args) {
    Thread t1 = new Thread("t1") {
        @Override
        public void run() {
            log.debug(Thread.currentThread().getName());
            FileReader.read(Constants.MP4_FULL_PATH);
        }
    };
    t1.run();
    log.debug("do other things ...");
}

输出:

19:39:14 [main] c.TestStart - main
19:39:14 [main] c.FileReader - read [1.mp4] start ...
19:39:18 [main] c.FileReader - read [1.mp4] end ... cost: 4227 ms
19:39:18 [main] c.TestStart - do other things ...

分析:

程序仍在main线程执行,FileReader.read()方法调用还是同步的

(2)调用start()方法

代码:将上述代码的t1.run()改为t1.start()

public static void main(String[] args) {
    Thread t1 = new Thread("t1") {
        @Override
        public void run() {
            log.debug(Thread.currentThread().getName());
            FileReader.read(Constants.MP4_FULL_PATH);
        }
    };
    t1.start();
    log.debug("do other things ...");
}

输出:

19:41:30 [main] c.TestStart - do other things ...
19:41:30 [t1] c.TestStart - t1
19:41:30 [t1] c.FileReader - read [1.mp4] start ...
19:41:35 [t1] c.FileReader - read [1.mp4] end ... cost: 4542 ms

分析:

程序在t1线程执行,FileReader.read()方法调用是异步的

(3)小结

  • 直接调用run方法,是在主线程中执行了run方法,并没有启动新的线程
  • 使用start方法,是启动新线程,通过新线程间接执行run方法中的代码

5、Thread.sleep()与Thread.yield()

方法名 static 功能说明 注意
sleep(long n) static 让当前执行的线程休眠n毫秒,休眠时让出CPU的时间片给其他线程
yield() static 提示线程调度器让出当前线程对CPU的使用

5.1 sleep

(1)调用sleep会让当前线程从Running状态进入Timed Waiting状态(阻塞)

(2)其他线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出InterruptedException

(3)睡眠结束后的线程未必会立刻得到执行

(4)建议用 TimeUnit 的 sleep 代替 Thread 的sleep 来获得更好的可读性

5.2 sleep()源码

/**
     * Causes the currently executing thread to sleep (temporarily cease
     * execution) for the specified number of milliseconds, subject to
     * the precision and accuracy of system timers and schedulers. The thread
     * does not lose ownership of any monitors.
     
     翻译:根据系统计时器和调度器的精度和准确性,使当前正在执行的线程休眠(临时停止执行)指定的毫秒数。线程不会失去任何监视器的所有权。
     
     *
     * @param  millis
     *         the length of time to sleep in milliseconds 休眠时长
     *
     * @throws  IllegalArgumentException
     *          if the value of {@code millis} is negative
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     
     翻译:如果有线程中断了当前线程。抛出此异常时,当前线程的中断状态将被清除。
     
*/
// native方法,本地方法,一个Native Method就是一个java调用非java代码的接口。一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C
public static native void sleep(long millis) throws InterruptedException;

示例代码:

import com.fasterxml.jackson.core.JsonProcessingException;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;

public class SleepRelaseCPU {
    public static void main(String[] args) {

        //创建100个线程,每个线程睡500s
        for(int i = 0; i < 100; i++) {
            Thread thread = new Thread(new SubThread(),"Daemon Thread!"+i);
            thread.start();
        }
    }
    static class SubThread implements Runnable {
        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName());
                Thread.sleep(500000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println("FINISH!");
            }
        }
    }
}

jsp+jstack 查看线程的状态:线程处于TIMED_WAITING状态,不占用任何CPU

image-20221009162742345

由上源码可知:

  • 是否释放锁?—— 不释放锁
  • 是否对中断敏感?—— 对中断敏感
  • 是否释放CPU?—— 释放CPU

5.2 yield

(1)调用 yield 会让当前线程从Running进入Runable就绪状态,然后调度执行其他线程

(2)具体的实现依赖于操作系统的任务调度器

6、Object.wait()

只有锁对象才能调用 wait()、notify(),wait()、notify()方法之能在同步泰马快中使用

【重点】wait() 方法辨析

(1)为什么不是线程调用 wait() 方法,而是锁对象调用 wait()

由于每个对象都拥有 monitor对象(即锁),所以让当前线程等待获取某个对象的锁,就应该是通过这个锁对象来操作,而不是线程对象操作,当然线程对象也可以调用 wait() 方法,是因为其也可以作为锁对象

(2)锁对象调用 wait() 方法的含义

调用某个对象的 wait() 方法,相当于让当前线程释放此锁对象的 monitor,进入等待状态,等待后续再次获得此对象的锁。

(3)wait() 方法的调用要在同步代码块中

某个锁对象调用 wait方法,是指持有该锁的当前线程要等待,在 notify 之后竞争获取CPU时间片,所以当前线程必须拥有该对象的 monitor对象,因此 wait() 方法必须在同步块或者同步方法中进行,如果没有当前线程没有持有该锁对象就调用 wait() 方法,抛出 IllegaMontiorStateException。

6.1 wait()源码

/**
	* The current thread must own this object's monitor. The thread
     * releases ownership of this monitor and waits until another thread
     * notifies threads waiting on this object's monitor to wake up
     * either through a call to the {@code notify} method or the
     * {@code notifyAll} method. The thread then waits until it can
     * re-obtain ownership of the monitor and resumes execution.
     
     翻译:当前线程必须拥有此对象的监视器。线程释放这个监视器的所有权并等待,直到另一个线程通过调用{@code notify}方法或{@code notifyAll}方法通知正在等待这个对象的监视器的线程苏醒。然后,线程等待,直到它能够重新获得监视器的所有权并继续执行。
     
     * @throws  InterruptedException if any thread interrupted the
     *             current thread before or while the current thread
     *             was waiting for a notification.  The <i>interrupted
     *             status</i> of the current thread is cleared when
     *             this exception is thrown.
      翻译:如果有线程中断了当前线程。抛出此异常时,当前线程的中断状态将被清除。
     
*/
public final void wait() throws InterruptedException {
    wait(0);
}

public final native void wait(long timeout) throws InterruptedException;

可知:

  • 是否释放锁? —— 释放
  • 是否对中断敏感? —— 对中断敏感
  • 是否释放CPU? —— 释放CPU

6.2 wait()方法的使用

让当前线程等待。wait方法会使当前线程释放锁,等到其他线程调用该锁的notify方法时再继续运行。

object.wati(); object 是以锁对象的含义而非以线程的名义调用 wait() 方法。

示例代码:

import java.util.Date;

public class WaitTest {
    public static void main(String[] args) {
        ThreadA t1=new ThreadA("t1");
        System.out.println("t1:"+t1);
        synchronized (t1) {
            try {
                //启动线程
                System.out.println(Thread.currentThread().getName()+" start t1");
                t1.start();
                //主线程等待t1通过notify唤醒。
                System.out.println(Thread.currentThread().getName()+" wait()"+ new Date());
                t1.wait();// 不是使t1线程等待,而是当前执行该语句的线程等待
                // t1在此处不是指线程,只是指锁对象
                System.out.println(Thread.currentThread().getName()+" continue"+ new Date());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

class ThreadA extends Thread{
    public ThreadA(String name) {
        super(name);
    }
    @Override
    public void run() {
        synchronized (this) {
            System.out.println("this:"+this);
            try {
                Thread.sleep(2000);//使当前线程阻塞1秒
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" call notify()");
            this.notify();
        }
    }
}

输出:

image-20221008185448052

结论:

代码中t1.wait()是让运行这行代码的线程等待,而不是让t1这个线程等待。哪条线程等待取决于 wait() 方法在哪个线程栈中。

7、Thread.join()

方法名 static 功能说明 注意
join() 等待线程运行结束
join(long n) 等待线程运行结束,最多等待n毫秒

【重点】join() 方法辨析

(1)怎么理解join方法?

Thread.join 方法,他底层代码调用的是 Object的 wait方法。那么想要唤醒join方法,就需要使用 object的notify以及 notifyall,且需在持有 thread锁 的同步代码块中

t1.join() 底层是 t1.wait(),且 wait方法 在 t1 为锁对象的同步代码块中,即当前线程在执行 join() 方法的过程中,会持有 t1锁 运行至 wait() 方法之后,释放 t1锁,进入 t1锁 的等待池,直到再次持有 t1锁。

在java中,Thread类线程执行完run()方法后,一定会自动执行notifyAll()方法。因为线程在die的时候会释放持用的资源和锁,自动调用自身的notifyAll方法。所以在 t1 线程运行完毕后,会 notifyAll 所有持有 t1线程对象为锁 的线程。

从整体上来看,就是 主线程(或是调用t1.join()方法的线程)须得在 t1线程 运行完毕后,才继续往下执行。

(2)怎么理解当前线程?

t1.start() 只有实现的 run() 方法在 t1线程栈 中运行,t1 以线程对象的身份调用 join(),以锁对象的身份调用 wait(),都是在 main线程栈 中运行。所以是当前线程等待

7.1 join()源码

join(): void

/**
     * Waits for this thread to die. 等待该线程死亡
     *
     * <p> An invocation of this method behaves in exactly the same
     * way as the invocation
     *
     * <blockquote>
     * {@linkplain #join(long) join}{@code (0)}
     * </blockquote>
     *
     * @throws  InterruptedException 对中断敏感,中断抛出异常
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
public final void join() throws InterruptedException {
    join(0);
}

join(long): void

/**
     * Waits at most {@code millis} milliseconds for this thread to
     * die. A timeout of {@code 0} means to wait forever.
*/
public final synchronized void join(long millis)
    throws InterruptedException {
    long base = System.currentTimeMillis();// 时间戳
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (millis == 0) {
        while (isAlive()) {//线程是存活的
            wait(0);
            //这个wait是调用Object的wait方法,隐含的意义是:this.wait()——>更为准确的说法是,当前线程类调用了wait方法(Thread类有一个当前的线程,而Thread类是Object的一个子类,this是一个object类的引用指向了Thread实例对象),即当前线程释放了CPU,而且是当前线程(Thread类)的对象是锁对象被释放了。
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

由此,

  • 是否释放锁?—— 具体要看当前的锁对象是谁。如果是调用join方法的锁对象,则释放(下面具体探究)
  • 是否对中断敏感? —— 对中断敏感
  • 是否释放了CPU? —— 释放了CPU

7.2 对join方法是否释放了锁的探究

join 方法是否释放了锁,取决于join方法的底层实现中,wait() 方法等待的是 以调用join() 方法的线程对象 为锁对象的锁,wait() 方法之后,释放了线程对象的 moinitor,如果当前线程仅持有 当前线程对象的monitor,则 join 之后也会释放锁

package com.boot.jdk;

import lombok.SneakyThrows;

public class JoinRelase {
    static Object object = new Object();
    public static void main(String[] args) throws InterruptedException {

        // 创建两个线程
        for(int i = 0; i < 2; i++) {
            Thread thread = new Thread(new SubThread(),"Daemon Thread!"+i);
            thread.setName("thread-" + i);
            thread.start();
            Thread.sleep(100);
        }
    }
    
    //synchronize(object),这种情况下,线程2被阻塞
    //即如果当前锁对象不是调用join方法的锁对象,则不释放
    //这里在底层调用当前线程thread对象的wait方法,当前线程thread释放了锁,所持有的锁对象是当前线程对象,也被释放了,而object锁对象并没有并没有释放
    //object锁并没有释放
    static class SubThread implements Runnable {
        @SneakyThrows
        @Override
        public void run() {
            synchronized (object)) {
                System.out.println("获取到锁!!!ThreadName: " + Thread.currentThread().getName());
                Thread.currentThread().join();//当前线程join
            }
        }
    }
    
    //synchronize(Thread.currentThread()),这种情况下,线程2未被阻塞
    //如果当前对象是调用join方法的锁对象,则释放
    //底层是调用当前线程thread的wait方法,thread对象释放了锁
    //thread锁得到了释放
    static class SubThread implements Runnable {
        @SneakyThrows
        @Override
        public void run() {
            synchronized (Thread.currentThread()) {
                System.out.println("获取到锁!!!ThreadName: " + Thread.currentThread().getName());
                Thread.currentThread().join();////当前线程join
            }
        }
    }
}

8、线程中断与通讯方式

8.1 线程中断

方法名 static 功能说明 注意
interrupt() 打断线程 如果被打断的线程正在sleep(),wait(),join()会导致被打断的线程抛出 InterruptedException,并清除打断标记;如果打断的正在运行的线程,则会设置打断标记;park 的线程被打断,也会设置打断标记
interrupted() static 判断当前线程是否被打断 会清除打断标记

中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作。中断好比其他线程对该线程打了个招呼,其他线程通过调用该线程的interrupt()方法对其进行中断操作。

线程通过检查自身是否被中断来进行响应,线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()对当前线程的中断标识位进行复位。如果该线程已经处于终结状态,即使该线程被中断过,在调用该线程对象的isInterrupted()时依旧会返回false。

从Java的API中可以看到,许多声明抛出InterruptedException的方法(例如Thread.sleep(long millis)方法)这些方法在抛出InterruptedException之前,Java虚拟机会先将该线程的中断标识位清除,然后抛出InterruptedException,此时调用isInterrupted()方法将会返回false。

代码:

package com.boot.jdk;
import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.SneakyThrows;

import java.util.concurrent.TimeUnit;

public class ThreadInterrupted {
    public static void main(String[] args) throws InterruptedException {
        // sleepThread不停的尝试睡眠
        Thread sleepThread = new Thread(new SleepRunner(), "SleepThread");
        sleepThread.setDaemon(true);
        Thread busyThread = new Thread(new BusyRunner(), "BusyThread");
        busyThread.setDaemon(true);
        sleepThread.start();
        busyThread.start();
        // 休眠5秒,让sleepThread和busyThread充分运行
        TimeUnit.SECONDS.sleep(5);
        sleepThread.interrupt();
        busyThread.interrupt();
        //sleep方法响应中断,肯定会中断sleep。在抛出异常之前,会清理掉我们的 中断标志。 会返回false,因为当前线程已经停止了。
        System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted());

        // busy thread ,没有立即响应中断,只是他的中断标志位 显示 被中断,这个是isInterrupted会返回true。
        System.out.println("BusyThread interrupted is " + busyThread.isInterrupted());
        // 防止sleepThread和busyThread立刻退出
        TimeUnit.SECONDS.sleep(5);
    }

    static class SleepRunner implements Runnable {
        @SneakyThrows
        @Override
        public void run() {
            while (true) {
                try {
                    // 先清除标志,后抛异常、(sleep)
                    TimeUnit.SECONDS.sleep(100);
                } catch (Exception e) {
                    System.out.println("======");
                }
            }
        }
    }
    static class BusyRunner implements Runnable {
        @Override
        public void run() {
            while (true) {
            }
        }
    }

}

输出:

sleep先清除标志,后抛出异常

SleepThread interrupted is false
======
BusyThread interrupted is true

8.2 线程间的通讯方式

(1) volitate 、synchronize、lock。(都保证可见性)

(2)wait、notify、await() 、 signal

(3)管道输入、输出流(过时了)

  • 管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介为内存。
  • 管道输入/输出流主要包括了如下4种具体实现:PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前两种面向字节,而后两种面向字符。

(4)Thread.join() : 隐式唤醒。等待其他线程执行完成,其他线程会发送唤醒信号。

(5)ThradLocal() ---》支持子线程集成的一种形式。埋点。

(6)线程中断

标签:lang,JUC,java,Thread,编程,t1,线程,初探,wait
From: https://www.cnblogs.com/DarkSki/p/16985331.html

相关文章

  • 数据化运营初探
    最近在研读《数据化运营系统方法与实践案例》,特在此做信息记录。先明确这次分析到底需要达成什么目的,在了解业务的基础上,明确应该从什么角度去切入,应该从哪些指标......
  • SECTION 15 函数和函数式编程(二)
    OverridetheentrypointofanimageIntroducedinGitLabandGitLabRunner9.4.Readmoreaboutthe extendedconfigurationoptions.Beforeexplainingtheav......
  • Java Socket网络编程
    1.TCP流式SocketTCP是TCP/IP体系结构中位于传输层的面向连接的协议,提供可靠的字节流传输。通信双方需要建立连接,发送端的所有数据按顺序发送,接受端按顺序接收。......
  • Go--并发编程
    摘抄(有删改):https://www.topgoer.cn/docs/golang/chapter09-1一、并发介绍1.1进程和线程进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位......
  • python并发编程之asyncio协程(三)
    协程实现了在单线程下的并发,每一个协程共享线程的几乎全部的资源,除了协程本身私有的上下文栈;协程的切换属于程序级别的切换,对于操做系统来讲是无感知的,所以切换速度更快、......
  • 如何学习编程语言大揭秘(以C++为例)!!一
    在网上C++的教程多如牛毛,都有一个特点就是让读者扮演第三人称,如观看电影那样,效果最好的是让读者戴上3D眼镜可以有身临其境的感觉,但还是第三人称。第三人称教程的缺点有:很......
  • 极客编程python入门-类和实例
    类和实例面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各......
  • R语言代做编程辅导ASSIGNMENT THREE - BASIC R SKILS(附答案)
    全文链接:http://tecdat.cn/?p=30904PROBLEM1)ComparingVectorsDescription:Giventwovectors,thelongerwillbedetereminedandreturned.Intheeventofat......
  • unix网络编程2.6——高并发服务器(七)基于epoll&&reactor实现web_socket服务器
    目录系列文章unix网络编程1.1——TCP协议详解(一)unix网络编程2.1——高并发服务器(一)基础——io与文件描述符、socket编程与单进程服务端客户端实现unix网络编程2.2——高并......
  • R语言代做编程辅导因子实验设计STA305/1004 Assignment(附答案)
    全文链接:http://tecdat.cn/?p=30896(AdaptedfromWu,Hamada,2009)Thefollowingexperimentwasperformedatapulpmill.Plantperformanceisbasedonpulpbri......