关于java:敞开心扉一起聊聊Java多线程

2次阅读

共计 11229 个字符,预计需要花费 29 分钟才能阅读完成。

@TOC

敞开心扉,一起聊聊 Java 多线程(结尾有福利~)

明天!咱们来聊一聊 多线程 ~

咱们都晓得,不管在是面试还是工作中,多线程都是一些陈词滥调的话题,

置信正在浏览得你,脑海中未然浮现出多线程的相干常识,那么,咱们来一起回顾下吧 ~

== 留神:本片博文后面内容重点在于回顾,前面内容重点解说线程的生命周期以及线程的源码分析 ==

一、线程的实现形式

  • 继承 Thread 类,重写 run 办法
  • 实现 Runnable 接口,重写 run 办法
  • 实现 Callable 接口重写 run 办法,通过 FutureTask 包装器获取返回值

1. 继承 Thread 类,重写 run 办法

/**
 * 多线程
 *      继承 Thread 形式
 * @author zhaojun
 */
public class MyThread extends Thread {public MyThread(String name) {
        // 反对自定义线程名称
        super(name);
    }

    @Override
    public void run() {System.out.println(Thread.currentThread().getName() + "->" + Thread.currentThread().getId());
    }

    /**
     * 测试
     */
    public static void main(String[] args) {new MyThread("thread_1").start();
        new MyThread("thread_2").start();
        System.out.println(Thread.currentThread().getName() + "->" + Thread.currentThread().getId());
    }

}

测试后果如下:

2. 实现 Runnable 接口,重写 run 办法

/**
 * 多线程
 *      实现 Runnable 形式
 * @author zhaojun
 */
public class MyRunnable implements Runnable {

    @Override
    public void run() {System.out.println(Thread.currentThread().getName() + "->" + Thread.currentThread().getId());
    }

    /**
     * 测试
     */
    public static void main(String[] args) {new Thread(new MyRunnable(), "runnable_1").start();
        new Thread(new MyRunnable(), "runnable_2").start();
        System.out.println(Thread.currentThread().getName() + "->" + Thread.currentThread().getId());
    }

}

测试后果如下:

3. 实现 Callable 接口重写 run 办法,通过 FutureTask 包装器获取返回值

/**
 * 多线程
 *      实现 Callable 形式, 利用 FutureTask 获取返回值
 * @author zhaojun
 */
public class MyCallable implements Callable<String> {

    // Callable 接口反对指定泛型,对应 call 返回值类型为指定泛型
    @Override
    public String call() throws Exception {System.out.println(Thread.currentThread().getName() + "->" + Thread.currentThread().getId());
        return Thread.currentThread().getName() + "线程以后运行状态为:" + Thread.currentThread().getState();
    }

    /**
     * 测试
     */
    public static void main(String[] args) throws Exception {FutureTask<String> task1 = new FutureTask<>(new MyCallable());
        new Thread(task1, "callable_1").start();
        System.out.println(task1.get());

        FutureTask<String> task2 = new FutureTask<>(new MyCallable());
        new Thread(task2, "callable_2").start();
        System.out.println(task2.get());

        System.out.println(Thread.currentThread().getName() + "->" + Thread.currentThread().getId());
    }

}

测试后果如下:

二、线程的生命周期

  • 线程 初始 状态:NEW
  • 线程 运行 状态:RUNNABLE
  • 线程 阻塞 状态:BLOCKED
  • 线程 期待 状态:WAITING
  • 超时期待 状态:TIMED_WAITING
  • 线程 终止 状态:TERMINATED

这并不是笔者胡乱假造的,而是 jdk 源码中定义的(Thread 类中保护的一个枚举类),源码如下并加以翻译:

/**
 * 多线程
 *      源码定义 - 翻译
 * @author zhaojun
 */
public enum State {

    /**
     * 线程初始状态
     *      线程被构建,还未调用 start 办法
     */
    NEW,

    /**
     * 线程运行状态
     *      JAVA 线程把操作系统中的(就绪和运行)两种状态对立称为 运行中
     */
    RUNNABLE,

    /**
     * 线程阻塞状态
     *      示意线程进入期待状态,即线程因为某种原因放弃了 CPU 使用权,阻塞也分为几种状况:*       1. 期待阻塞:运行的线程执行 wait 办法,JVM 会把以后线程放入到期待队列
     *       2. 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其余线程锁占用了,那么 JVM 会把以后的线程放入到锁池中
     *       3. 其余阻塞:运行的线程执行 Thread.sleep 或者 join 办法,或者收回了 I/ O 申请时,*                  JVM 会把以后线程设置为阻塞状态,当 sleep 完结 /join 线程终止、I/ O 处理完毕则线程复原
     */
    BLOCKED,

    /**
     * 线程期待状态
     */
    WAITING,

    /**
     * 线程超时期待状态
     *      超时之后主动返回
     */
    TIMED_WAITING,

    /**
     * 线程终止状态
     *      示意以后线程执行结束
     */
    TERMINATED;
}

三、线程状态转换

此处重点讲下,线程状态如何变更为:
       1.TIME_WAITING:线程超时期待状态
       2.WAITING:线程期待状态
       3.BLOCKED:线程阻塞状态

/**
 * 多线程
 *      状态转换 - 代码演示
 * @author zhaojun
 */
public class ThreadStatus {public static void main(String[] args) {

        /**
         * Thread -> TIME_WAITING:线程超时期待状态
         */
        Thread timeWaiting = new Thread(() -> {while (true) {
                try {
                    // sleep 99s
                    TimeUnit.SECONDS.sleep(99);
                } catch (InterruptedException e) {e.printStackTrace();
                }
            }
        }, "Time_Waiting_Thread");


        /**
         * Thread -> WAITING:线程期待状态
         */
        Thread waiting = new Thread(() -> {while (true) {synchronized (ThreadStatus.class) {
                    try {
                        // wait
                        ThreadStatus.class.wait();} catch (InterruptedException e) {e.printStackTrace();
                    }
                }
            }
        }, "Waiting_Thread");


        /**
         * Thread -> BLOCKED:线程阻塞状态
         */
        Thread blocked_thread_01 = new Thread(new BlockedThread(), "Blocked_Thread_01");
        Thread blocked_thread_02 = new Thread(new BlockedThread(), "Blocked_Thread_02");


        /**
         * 启动
         */
        timeWaiting.start();
        waiting.start();
        blocked_thread_01.start();
        blocked_thread_02.start();}


    /**
     * 定义阻塞线程类
     */
    static class BlockedThread extends Thread {
        @Override
        public void run() {synchronized (BlockedThread.class) {while (true) {
                    try {TimeUnit.SECONDS.sleep(99);
                    } catch (InterruptedException e) {e.printStackTrace();
                    }
                }
            }
        }
    }

}

测试后果如下:

首先运行 main 办法,在以后测试类任意地位右击,点击Open in Terminal

输出 jps -l 命令,查看所有 java 过程对应pid,查找 ThreadStatus 对应 pid

输出 jstack pid命令,查看其堆栈信息:


从上图中,能够很清晰的看出:
线程 Waiting_Thread 通过 wait();,状态 ==->== 期待状态;
线程 Time_Waiting_Thread 通过sleep(),状态 ==->== 超时期待状态;
线程 Blocked_Thread_01 先取得锁,而后通过 sleep(),状态 ==->== 超时期待状态;
线程 Blocked_Thread_02 未取得锁,状态 ==->== 阻塞状态;

四、线程的启动

经典面试题

为什么启动一个线程调用 start 办法,而不是 run 办法呢?

源码分析

接下来,带大家来看一下 start()办法,在源码中如何定义的:

    public synchronized void start() {if (threadStatus != 0)
            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);

        boolean started = false;
        try {start0(); // -.- 眼光会聚这里

            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 */
            }
        }
    }

    private native void start0(); // -.- 眼光会聚这里

public class Thread implements Runnable {

    /* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives();

    static {registerNatives();
    }

}

咱们发现 start()实际上调用了 start0()来启动线程,而且 start0()是由 native 润饰的本地办法
这里先记住,start0()这个办法是在 Thread 的动态块中来注册的。

到这里,我须要给各位开发小伙伴科普一个文件 Thread.c
该文件定义了各个操作系统平台要用的对于线程的公共数据以及操作,代码如下:

#include "jni.h"
#include "jvm.h"

#include "java_lang_Thread.h"

#define THD "Ljava/lang/Thread;"
#define OBJ "Ljava/lang/Object;"
#define STE "Ljava/lang/StackTraceElement;"

#define ARRAY_LENGTH(a) (sizeof(a)/sizeof(a[0]))

static JNINativeMethod methods[] = {{"start0",           "()V",        (void *)&JVM_StartThread},  // -.- 眼光会聚这里
    {"stop0",            "(" OBJ ")V", (void *)&JVM_StopThread},
    {"isAlive",          "()Z",        (void *)&JVM_IsThreadAlive},
    {"suspend0",         "()V",        (void *)&JVM_SuspendThread},
    {"resume0",          "()V",        (void *)&JVM_ResumeThread},
    {"setPriority0",     "(I)V",       (void *)&JVM_SetThreadPriority},
    {"yield",            "()V",        (void *)&JVM_Yield},
    {"sleep",            "(J)V",       (void *)&JVM_Sleep},
    {"currentThread",    "()" THD,     (void *)&JVM_CurrentThread},
    {"countStackFrames", "()I",        (void *)&JVM_CountStackFrames},
    {"interrupt0",       "()V",        (void *)&JVM_Interrupt},
    {"isInterrupted",    "(Z)Z",       (void *)&JVM_IsInterrupted},
    {"holdsLock",        "(" OBJ ")Z", (void *)&JVM_HoldsLock},
    {"getThreads",        "()[" THD,   (void *)&JVM_GetAllThreads},
    {"dumpThreads",      "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
};

#undef THD
#undef OBJ
#undef STE

JNIEXPORT void JNICALL
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls) // 发现 registerNatives()办法定义在这里
{(*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}

从代码中看到,start0()会执行 JVM_StartThread 这个办法,那么问题来了,JVM_StartThread又是什么呢?俗话说,码如其名,先从名字推断应该是在 JVM 层启动一个线程,既然有了猜测,咱们无妨去验证下。

== 留神:这里须要下载 hotspot 的源码,它是 JVM 的具体实现,有趣味的小伙伴能够自行下载 ==

不过,大家莫慌,针对线程启动的源码我会附上
咱们找到 jvm.cpp 文件,源码如下:

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_StartThread");
  JavaThread *native_thread = NULL;

...

native_thread = new JavaThread(&thread_entry, sz);

...

从代码中能够看出,JVM_ENTRY用来定义 JVM_StartThread 函数的,在这个函数中创立了一个真正的和平台无关的本地线程,之后 new 了一个javaThread,看看其具体做了什么:

再找到 thread.cpp文件,源码如下:

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
  Thread()
#if INCLUDE_ALL_GCS
  , _satb_mark_queue(&_satb_mark_queue_set),
  _dirty_card_queue(&_dirty_card_queue_set)
#endif // INCLUDE_ALL_GCS
{if (TraceThreadEvents) {tty->print_cr("creating thread %p", this);
  }
  initialize();
  _jni_attach_state = _not_attaching_via_jni;
  set_entry_point(entry_point);
  // Create the native thread itself.
  // %note runtime_23
  os::ThreadType thr_type = os::java_thread;
  thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
                                                     os::java_thread;
  os::create_thread(this, thr_type, stack_sz);
  _safepoint_visible = false;

}

此函数有两个参数:

1.函数名称,线程创立胜利之后依据函数名称调用对应函数;

2. 以后过程内 已有的线程数量 <br/>
在上述代码 19 行,os::create_thread,是调用平台创立线程的办法从而来进行创立线程,接下来就是线程的启动了,代码如下:

void Thread::start(Thread* thread) {trace("start", thread);
  // Start is different from resume in that its safety is guaranteed by context or
  // being called from a Java method synchronized on the Thread object.
  if (!DisableStartThread) {if (thread->is_Java_thread()) {
      // Initialize the thread state to RUNNABLE before starting this thread.
      // Can not set it after the thread started because we do not know the
      // exact thread state at that time. It could be in MONITOR_WAIT or
      // in SLEEPING or some other state.
      java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
                                          java_lang_Thread::RUNNABLE);
    }
    os::start_thread(thread);
  }
}

上述代码 14 行,os::start_thread(thread)就是平台启动线程的办法,最终会调用 Thread.cpp文件中的 JavaThread::run()办法,至此,一个线程的启动就实现了。

五、线程的终止

  • 暴力终止法:stop()
  • 自定义标记位终止
  • 优雅中断法:interrupt()

1. 暴力终止法:stop()


如图所示,官网已将 stop()定义为过期办法,并不倡议应用;
stop()办法在完结一个线程时并不会保障线程的资源失常释 放,因而会导致程序可能呈现一些不确定的状态。

JDK 官网文档 中定义弃用这些办法的起因,链接如下:
Why Are Thread.stop, Thread.suspend, Thread.resume and Runtime.runFinalizersOnExit Deprecated?
<br/>

2. 自定义标记位终止

首先咱们晓得,一个线程完结与否取决于以后线程的 run()办法是否执行结束,咱们可自定义标记位来控制线程的完结,代码如下:

/**
 * 多线程
 *      线程终止:自定义标记位
 */
public class ThreadFlagDemo extends Thread {

    // 标记位
    private volatile boolean isFinish = false;

    private static int i;

    @Override
    public void run() {while (!isFinish) {i++;}

        System.out.println("i:"+i);
    }

    /**
     * 测试
     */
    public static void main(String[] args) throws InterruptedException {ThreadFlagDemo thread = new ThreadFlagDemo();
        thread.start();
        Thread.sleep(1000);

        // 变更标记位,完结线程
        thread.isFinish = true;

    }

}

如代码所示,咱们能够通过自定义一个标记位,来完结死循环以致 run()完结,从而来控制线程的完结。

3. 优雅中断法:interrupt()

这里需明确一点,调用 interrupt() 办法,示意向以后线程打个招呼,通知其能够中断线程了,至于什么时候终止,取决于以后线程本人,其实原理跟自定义标记位类似,只是打一个进行的标记,并不会去真的进行线程。

/**
 * 多线程
 *      线程终止:interrupt()办法
 */
public class ThreadInterruptDemo {

    private static int i;

    /**
     * 测试
     */
    public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {// isInterrupted()默认为 false
            while (!Thread.currentThread().isInterrupted()) {i++;}
            System.out.println("i:"+i);

        });
        thread.start();
        TimeUnit.SECONDS.sleep(1);

        // 将 isInterrupted()设置为 true
        thread.interrupt();}

}

这种通过标记位或中断操作的形式可能使线程在终止时能够继续执行外部逻辑,而不是立刻进行线程,所以,这种中断线程的形式更加的优雅平安,举荐此种形式

六、线程的复位

  • Thread.interrupted()
  • 通过抛出 InterruptedException 异样

1.Thread.interrupted()

/**
 * 多线程
 *      线程复位:Thread.interrupted()办法
 */
public class ThreadInterruptedReset {

    /**
     * 测试
     */
    public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {String threadName = Thread.currentThread().getName();
            while (true) {if(Thread.currentThread().isInterrupted()) {System.out.println(threadName + ":before ->" + Thread.currentThread().isInterrupted());

                    // 线程复位
                    Thread.interrupted();

                    System.out.println(threadName + ":after ->" + Thread.currentThread().isInterrupted());

                    break; // 完结
                }
            }
        }, "Thread_Interrupted");
        thread.start();
        TimeUnit.SECONDS.sleep(1);

        // 将 isInterrupted()设置为 true
        thread.interrupt();}

}

执行流程如下:<br/>

  1. 执行 main() 办法,标识 "main" 主线程 启动,代码自上而下执行
  2. "Thread_Interrupted" 线程 启动,while 循环开启,isInterrupted()默认 false,… 以后处在死循环中;
  3. 与此同时,"main" 主线程 sleep 1s 完结后,将 isInterrupted() 设置为 true;
  4. 此时,"Thread_Interrupted" 线程 中,因为 isInterrupted()以后被设置为 true,执行 if 块代码,before -> true;
  5. 通过 Thread.interrupted()进行复位,after -> false,最终执行 break,完结循环完结线程。

执行流程如下:

2. 通过抛出 InterruptedException 异样

/**
 * 多线程
 *      线程复位:InterruptedException 异样
 */
public class ThreadExeptionReset {

    /**
     * 测试
     */
    public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {String threadName = Thread.currentThread().getName();
            while (true) {if(Thread.currentThread().isInterrupted()) {
                    try {System.out.println(threadName + ":before ->" + Thread.currentThread().isInterrupted());
                        TimeUnit.SECONDS.sleep(10);

                    } catch (InterruptedException e) {System.out.println(threadName + ":after ->" + Thread.currentThread().isInterrupted());
                        e.printStackTrace();
                        break; // 完结
                    }
                }
            }
        }, "Thread_InterruptedException");
        thread.start();
        TimeUnit.SECONDS.sleep(1);

        // 将 isInterrupted()设置为 true
        thread.interrupt();}

}

执行流程如下:

  1. 执行 main() 办法,标识 "main" 主线程 启动,代码自上而下执行
  2. "Thread_InterruptedException" 线程 启动,while 循环开启,isInterrupted()默认 false,… 以后处在死循环中;
  3. 与此同时,"main" 主线程 sleep 1s 完结后,将 isInterrupted() 设置为 true;
  4. 此时,"Thread_InterruptedException" 线程 中,因为 isInterrupted()以后被设置为 true,执行 if 块代码,before -> true;
  5. 接着执行 sleep(),抛出InterruptedException 异样 进行复位,after -> false,最终执行 break,完结循环完结线程。

执行流程如下:

福利:梁博 -《呈现又来到》

今日举荐单曲,梁博学生的《呈现又来到》,放松情绪,努力学习~
 
差点遗记件比写博客还要重要的事件 ~
这里有 博哥(梁博)的粉丝么,据说往年有博哥的演唱会,私信我,约起哈 ~ ❤

正文完
 0