java多线程总结 - 基础篇

男娘i 2021-06-10 20:39 608阅读 0赞

进程与线程区别

进程是资源分配的最小单位,进程中会有多个线程。引入线程的目的是因为计算机cpu上下文切换会很频繁,而进程的上下文切换相对耗时耗资源。
而线程是在进程中的,线程是cpu调度的最小单位,进程中的多个线程共享一块内存。线程的上下文切换成本更低。

线程的状态

New、Runnable(包含运行态和就绪态)、Waiting、TimeWaiting、Blocked、Terminated

线程的start与run方法的区别

start:新创建一个线程并执行run方法
run:在当前线程执行run方法
start方法源码:

  1. /** * Causes this thread to begin execution; the Java Virtual Machine * calls the <code>run</code> method of this thread. * <p> * The result is that two threads are running concurrently: the * current thread (which returns from the call to the * <code>start</code> method) and the other thread (which executes its * <code>run</code> method). * <p> * It is never legal to start a thread more than once. * In particular, a thread may not be restarted once it has completed * execution. * @exception IllegalThreadStateException if the thread was already * started. * @see #run() * @see #stop() */
  2. public synchronized void start() {
  3. /** * 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". */
  4. if (threadStatus != 0)
  5. throw new IllegalThreadStateException();
  6. /* 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. */
  7. group.add(this);
  8. boolean started = false;
  9. try {
  10. // 执行本地方法,新创建一个线程,并执行run方法
  11. start0();
  12. started = true;
  13. } finally {
  14. try {
  15. if (!started) {
  16. group.threadStartFailed(this);
  17. }
  18. } catch (Throwable ignore) {
  19. /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */
  20. }
  21. }
  22. }
  23. private native void start0();

JVM对应的源码:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/63dd5b7d50eb/src/share/native/java/lang/Thread.c
调用了JVM_StartThread方法:

  1. { "start0", "()V", (void *)&JVM_StartThread},

http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/176a7e5cc060/src/share/vm/prims/jvm.cpp
JVM_StartThread方法,new JavaThread(&thread_entry, sz):

  1. JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  2. JVMWrapper("JVM_StartThread");
  3. JavaThread *native_thread = NULL;
  4. // We cannot hold the Threads_lock when we throw an exception,
  5. // due to rank ordering issues. Example: we might need to grab the
  6. // Heap_lock while we construct the exception.
  7. bool throw_illegal_thread_state = false;
  8. // We must release the Threads_lock before we can post a jvmti event
  9. // in Thread::start.
  10. {
  11. // Ensure that the C++ Thread and OSThread structures aren't freed before
  12. // we operate.
  13. MutexLocker mu(Threads_lock);
  14. // Since JDK 5 the java.lang.Thread threadStatus is used to prevent
  15. // re-starting an already started thread, so we should usually find
  16. // that the JavaThread is null. However for a JNI attached thread
  17. // there is a small window between the Thread object being created
  18. // (with its JavaThread set) and the update to its threadStatus, so we
  19. // have to check for this
  20. if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
  21. throw_illegal_thread_state = true;
  22. } else {
  23. // We could also check the stillborn flag to see if this thread was already stopped, but
  24. // for historical reasons we let the thread detect that itself when it starts running
  25. jlong size =
  26. java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
  27. // Allocate the C++ Thread structure and create the native thread. The
  28. // stack size retrieved from java is signed, but the constructor takes
  29. // size_t (an unsigned type), so avoid passing negative values which would
  30. // result in really large stacks.
  31. size_t sz = size > 0 ? (size_t) size : 0;
  32. // !!!! 调用了JVM_StartThread的new JavaThread
  33. native_thread = new JavaThread(&thread_entry, sz);
  34. // At this point it may be possible that no osthread was created for the
  35. // JavaThread due to lack of memory. Check for this situation and throw
  36. // an exception if necessary. Eventually we may want to change this so
  37. // that we only grab the lock if the thread was created successfully -
  38. // then we can also do this check and throw the exception in the
  39. // JavaThread constructor.
  40. if (native_thread->osthread() != NULL) {
  41. // Note: the current thread is not being used within "prepare".
  42. native_thread->prepare(jthread);
  43. }
  44. }
  45. }

run方法源码:单纯在当前线程中执行run方法

  1. /** * If this thread was constructed using a separate * <code>Runnable</code> run object, then that * <code>Runnable</code> object's <code>run</code> method is called; * otherwise, this method does nothing and returns. * <p> * Subclasses of <code>Thread</code> should override this method. * * @see #start() * @see #stop() * @see #Thread(ThreadGroup, Runnable, String) */
  2. @Override
  3. public void run() {
  4. if (target != null) {
  5. target.run();
  6. }
  7. }

创建线程有哪些方式?

1、继承Thread类(Thread类也实现了Runnable接口),重写run方法
2、实现Runnable接口,重写run方法,Thread threadA = new Thread(Runnable r);
两者本质上都是创建Thread对象,通过start方法,创建一个新线程并执行thread对象的run方法。

如何处理线程的返回值?

可以通过JUC包下的FutureTask,我们知道Thread的run方法是没有返回值的,而FutureTask实现了Callable接口,同时也实现了Runnable接口。Callable接口是有返回值的。

  1. public interface Callable<V> {
  2. /** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */
  3. V call() throws Exception;
  4. }

FutureTask通过实现Callable接口,将Runnable的run方法关联起来,并将线程的返回值通过set()方法保存在一个outcome的对象中。通过get()方法可以获取到这个返回值。
下面是FutureTask类的run方法部分代码:

  1. public void run() {
  2. try {
  3. // 取得callable的实现
  4. Callable<V> c = callable;
  5. if (c != null && state == NEW) {
  6. V result;
  7. boolean ran;
  8. try {
  9. // 执行call方法,将返回值存在临时变量result
  10. result = c.call();
  11. ran = true;
  12. } catch (Throwable ex) {
  13. result = null;
  14. ran = false;
  15. setException(ex);
  16. }
  17. if (ran)
  18. // 将result保存到outcome字段
  19. set(result);
  20. }
  21. }
  22. }
  23. //保存到outcome字段
  24. protected void set(V v) {
  25. outcome = v;
  26. }

sleep与wait的区别

sleep是Thread类的方法,wait是Object类的方法。
sleep方法可以随时被调用,而wait方法只能在synchronized中使用(你只有持有锁,才能释放锁)
sleep是定时等待,有时间设定,在此期间,线程只释放cpu,不释放同步资源锁。
wait是无限期等待,释放cpu,同时释放所持有的的锁,只有等notify或notifyAll时被唤醒。

notify与notifyAll的区别

锁池:当一个线程获取到对象锁之后,其他线程去竞争这把锁之后,会进入阻塞状态,并进入锁池。等待被唤醒。
notify:随机唤醒一个阻塞线程去竞争锁
notifyAll:唤醒所有阻塞线程,去竞争锁

yield函数

  1. /** * A hint to the scheduler that the current thread is willing to yield * its current use of a processor. The scheduler is free to ignore this * hint. * * <p> Yield is a heuristic attempt to improve relative progression * between threads that would otherwise over-utilise a CPU. Its use * should be combined with detailed profiling and benchmarking to * ensure that it actually has the desired effect. * * <p> It is rarely appropriate to use this method. It may be useful * for debugging or testing purposes, where it may help to reproduce * bugs due to race conditions. It may also be useful when designing * concurrency control constructs such as the ones in the * {@link java.util.concurrent.locks} package. */
  2. public static native void yield();

yield函数主要用于告诉cpu,我愿意将cpu资源让给其他线程,但这也可能被调度器忽略。主要用于多线程锁相关的问题。

interrupt函数

interrupt是中断的意思,例如我们鼠标和键盘向计算机输入信息,就是通过中断实现的。这里的interrupt是向线程发起一个中断信号,这样就可以将线程进行标记。如果接收到终端信号的线程处于阻塞状态,则抛出InterruptException异常,后续可以对该线程的中断进行相应的处理。
在这里插入图片描述

发表评论

表情:
评论列表 (有 0 条评论,608人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java线基础知识总结

    一、前言 线程安全是并发编程中的重要关注点,应该注意到的是,造成线程安全问题的主要原因有两点,一是存在共享数据,二是存在多条线程操作共享数据。当存在多个线程操作共享数据时

    相关 java线总结 - 基础

    进程与线程区别 进程是资源分配的最小单位,进程中会有多个线程。引入线程的目的是因为计算机cpu上下文切换会很频繁,而进程的上下文切换相对耗时耗资源。 而线程是在进程中