您该打基础了~Thread

77 阅读10分钟

Thread用于线程操作,是所有涉及到线程操作(如并发)的基础,最近最火的协程也是基于线程封装起来的

Thread基本使用

  • 继承Thread类

    public class ThreadTest {
        public static void main(String[] args) {
            //获取主线程
            Thread mainThread = Thread.currentThread();
            System.out.println("主线程名称: " + mainThread.getName());
    ​
            LeoThread leoThread = new LeoThread();
            //开启线程
            leoThread.start();
        }
    }
    ​
    class LeoThread extends Thread {
        @Override
        public void run() {
            //具体的业务执行代码
            Thread thread = Thread.currentThread();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("工作线程的名称" + thread.getName());
        }
    }
    
  • 实现Runnable接口

    class ThreadTest2 {
        public static void main(String[] args) {
            //获取主线程
            Thread mainThread = Thread.currentThread();
            System.out.println("主线程名称: " + mainThread.getName());
    ​
            LeoThreadRunnable leoThreadRunnable = new LeoThreadRunnable();
            Thread leoThread = new Thread(leoThreadRunnable);
            //开启线程
            leoThread.start();
    ​
        }
    }
    ​
    ​
    class LeoThreadRunnable implements Runnable {
    ​
        @Override
        public void run() {
            //具体的业务执行代码
            Thread thread = Thread.currentThread();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("工作线程的名称" + thread.getName());
        }
    }
    
  • 匿名内部类

    class ThreadTest3 {
        public static void main(String[] args) {
            Thread leoThread=new Thread(new Runnable() {
                @Override
                public void run() {
                    //具体的业务执行代码
                    Thread thread = Thread.currentThread();
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("工作线程的名称" + thread.getName());
                }
            });
            leoThread.start();
        }
    }
    
  • 使用lambda创建Runnable

    class ThreadTest4 {
        public static void main(String[] args) {
            Thread leoThread=new Thread(() -> {
                //具体的业务执行代码
                Thread thread = Thread.currentThread();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("工作线程的名称" + thread.getName());
            });
            leoThread.start();
        }
    }
    

Thread基本属性

  • 线程的优先级

    线程执行有优先级,优先级越高执行机会越大(并不是一定先执行),线程优先级最高为10,最低为1,默认是5

    
private int            priority;
​
public final static int MIN_PRIORITY = 1;
/**
 * The default priority that is assigned to a thread.
 */
public final static int NORM_PRIORITY = 5;
/**
 * The maximum priority that a thread can have.
 */
public final static int MAX_PRIORITY = 10;
  • 线程的状态

    线程对象共有6种状态

public enum State {
        /**
         * Thread state for a thread which has not yet started.
         * 新建
         */
        NEW,
​
        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         * 运行
         */
        RUNNABLE,
​
        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         * 阻塞
         */
        BLOCKED,
​
        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         * 等待
         */
        WAITING,
​
        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         * 有时间的等待
         */
        TIMED_WAITING,
​
        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         * 终止
         */
        TERMINATED;
    }

Thread构造器

Thread对外提供了多个构造器,接收不同参数,最终都是调用init方法,如下

​
/**
 *  ThreadGroup g 指定当前线程的线程组,未指定时线程组为创建该线程所属的线程组。线程组可以用来管理一组线程,通过activeCount() 来查看活动线程的数量。
 *  Runnable target 指定运行其中的Runnable,一般都需要指定,不指定的线程没有意义,或者可以通过创建Thread的子类并重新run方法      
 *  String name     线程的名称,不指定自动生成。
 *  long stackSize  预期堆栈大小,不指定默认为0,0代表忽略这个属性。与平台相关,不建议使用该属性。
 *  AccessControlContext acc
 *
 */
private void init(ThreadGroup g, Runnable target, String name,
                     long stackSize, AccessControlContext acc) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
​
        this.name = name;
​
        Thread parent = currentThread();
        // Android-removed: SecurityManager stubbed out on Android
        // SecurityManager security = System.getSecurityManager();
        if (g == null) {
            // Android-changed: SecurityManager stubbed out on Android
            /*
            /* Determine if it's an applet or not *
​
            /* If there is a security manager, ask the security manager
               what to do. *
            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();
            // }
        }
​
        // Android-removed: SecurityManager stubbed out on Android
        /*
        /* 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();
​
        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        // Android-changed: Moved into init2(Thread) helper method.
        /*
        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;
        // Android-removed: The priority parameter is unchecked on Android.
        // It is unclear why this is not being done (b/80180276).
        // setPriority(priority);
        // Android-changed: Moved into init2(Thread) helper method.
        // if (parent.inheritableThreadLocals != null)
        //     this.inheritableThreadLocals =
        //         ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        init2(parent);
​
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;
​
        /* Set thread ID */
        tid = nextThreadID();
}

Thread基本方法

属性方法说明
tidgetId()
namegetName()/setName(String name)
prioritygetPriority()/setPriority(int newPriority)
daemonisDaemon()/setDaemon(boolean on)
静态方法说明
Thread.currentThread()获得当前线程的引用。获得当前线程后对其进行操作
Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler()返回线程由于未捕获到异常而突然终止时调用的默认处理程序。
int Thread.activeCount()当前线程所在线程组中活动线程的数目。
static native boolean interrupted()判断当前线程的中断标志位是否设置,调用和请清除标志位
public方法说明
void dumpStack()将当前线程的堆栈跟踪打印至标准错误流
int enumerate(Thread[] tarray)将当前线程的线程组及其子组中的每一个活动线程复制到指定的数组中
Map<Thread,StackTraceElement[]> getAllStackTraces()返回所有活动线程的堆栈跟踪的一个映射
boolean holdsLock(Object obj)当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true
boolean interrupted()测试当前线程是否已经中断
void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)设置当线程由于未捕获到异常而突然终止,并且没有为该线程定义其他处理程序时所调用的默认处理程序。
void sleep(long millis)休眠指定时间
void sleep(long millis, int nanos)休眠指定时间
void yield()暂停当前正在执行的线程对象,并执行其他线程。意义不太大
void checkAccess()判定当前运行的线程是否有权修改该线程
ClassLoader getContextClassLoader()返回该线程的上下文 ClassLoader
long getId()返回该线程的标识符
String getName()返回该线程的名称
int getPriority()返回线程的优先级
StackTraceElement[] getStackTrace()返回一个表示该线程堆栈转储的堆栈跟踪元素数组
Thread.State getState()返回该线程的状态
ThreadGroup getThreadGroup()返回该线程所属的线程组
Thread.UncaughtExceptionHandler getUncaughtExceptionHandler()返回该线程由于未捕获到异常而突然终止时调用的处理程序。
void interrupt()中断线程
boolean isAlive()测试线程是否处于活动状态。
boolean isDaemon()测试该线程是否为守护线程。
boolean isInterrupted()测试线程是否已经中断。
void join()等待该线程终止。
void join(long millis)等待该线程终止的时间最长为 millis 毫秒。
void join(long millis, int nanos)等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
void run()线程启动后执行的方法。
void setContextClassLoader(ClassLoader cl)设置该线程的上下文 ClassLoader。
void setDaemon(boolean on)将该线程标记为守护线程或用户线程。
void start()使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
String toString()返回该线程的字符串表示形式,包括线程名称、优先级和线程组。

线程中断

  • 使用自定义标识来终止线程

  • 使用interrupt() 终止线程

    使用Thread.interrupted() 或者Thread.currentThread().isInterrupted() 代替自定义标志位

    使用thread对象的interrupted()方法通知线程结束

    方法说明
    public void interrupt()中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位
    static native boolean interrupted()判断当前线程的中断标志位是否设置,调用后清楚标志位
    boolean isInterrupted()判断对象关联的线程的标志位是否设置,调用后不清除标志位

线程等待

  • join() 作用是阻塞当前线程的执行,等到被调用join的线程对象执行完毕才执行继续执行当前线程

  • 当thread.join()被调用时,如果调用的线程中持有了thread对象锁会被释放。

    class ThreadTest5 {
        public static void main(String[] args) {
            Thread leoThread = new Thread(() -> {
                //具体的业务执行代码
                Thread thread = Thread.currentThread();
                for (int i = 0; i < 3; i++) {
                    System.out.println("执行" + thread.getName() + i);
                    try {
                        Thread.sleep(1000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("工作线程的名称:" + thread.getName());
            }, "leoThread");
    ​
            leoThread.start();
    ​
            try {
                //当leoThread执行完毕,主线程才继续执行
                leoThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    ​
            System.out.println("主线程的名称:" + Thread.currentThread().getName());
            
            //执行leoThread0
            //执行leoThread1
            //执行leoThread2
            //工作线程的名称:leoThread
            //主线程的名称:xxxxx
        }
    }
    

线程休眠与挂起

  • sleep()

    sleep是让某个线程暂停运行一段时间,其控制范围由当前线程决定的

    在java.lang.Thread类中,提供了sleep()

    sleep()可以将一个线程睡眠,参数可以指定一个时间

    sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常

    sleep方法没有释放锁

  • wait()

    java.lang.Object类中提供了wait(), notify()和notifyAll()方法来操作线程

    wait()可以将一个线程挂起,直到超时或者该线程被唤醒

    wait方法释放了锁,使得其他线程可以使用同步控制块或者方法

    wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用

其实两者都可以让线程暂停一段时间,但是本质的区别是一个线程的运行状态控制,一个是线程之间的通讯的问题

获取线程实例

  • Thread.currentThread()

yield让出执行权

yield 线程的让步,就是 原来执行的这个线程从执行状态到 就绪状态, 顾名思义,就是当一个线程使用了这个方法后,他就会把自己的CPU执行时间让掉,让自己或者其他的线程运行

打个比方: 现在有很多人在排队上厕所,好不容易轮到这个人上厕所了,突然这个人说:“我要和大家来个竞赛,看谁先抢到厕所!”,然后所有的人在同一起跑线冲向厕所,有可能是别人抢到了,也有可能他自己有抢到了

我们还知道线程有个优先级的问题,那么手里有优先权的这些人就一定能抢到厕所的位置吗? 不一定的,他们只是概率上大些,也有可能没特权的抢到了。

这样做的好处是防止当前线程一直霸占 cpu。

ThreadLocal

ThreadLocal 线程局部变量,意思是ThreadLocal中填充的变量属于当前线程的,该变量对于其他线程而言是隔离的,也就是说该变量是当前线程独有的变量

方法说明
T get()
void set(T value)
void remove()
public class ThreadLocalDemo {

    private static ThreadLocal<String> local=new ThreadLocal();

    public static void main(String[] args) {
        new Thread(() -> {
            local.set("ThreadLocal LeoA");
            //打印当前线程中本地内存中本地变量的值
            System.out.println("LeoA ThreadLocal :" + local.get());
            //清除本地内存中的本地变量
            local.remove();
            System.out.println("LeoA ThreadLocal after remove:" + local.get());

        },"LeoA").start();

        new Thread(() -> {
            local.set("ThreadLocal LeoB");
            //打印当前线程中本地内存中本地变量的值
            System.out.println("LeoB ThreadLocal :" + local.get());
            //清除本地内存中的本地变量
            local.remove();
            System.out.println("LeoB ThreadLocal after remove:" + local.get());
        },"LeoB").start();

        //LeoA ThreadLocal :ThreadLocal LeoA
        //LeoA ThreadLocal after remove:null
        
        //LeoB ThreadLocal :ThreadLocal LeoB
        //LeoB ThreadLocal after remove:null
        //结果:两个线程分表获取了自己线程存放的变量,他们之间变量的获取并不会错乱
    }
}
  • ThreadLocalSynchronized区别

    • Synchronized用于线程间数据共享,而ThreadLocal则用于线程间的数据隔离
    • Synchronized是利用锁的机制,使变量或者代码块在某一时刻只能被一个线程访问
    • ThreadLocal为每个变量提供了变量的副本,使得每个线程在某一时刻访问的并不是同一个对象,这样就隔离多线程对数据的数据共享

ThreadLocal其实是与线程绑定的一个变量。ThreadLocal和Synchonized都用于解决多线程并发访问。

  • ThreadLocal源码

    ThreadLocal,向ThreadLocal里面存东西就是向它里面的ThreadLocalMap存东西的,然后ThreadLocal把这个ThreadLocalMap挂到当前的线程底下,这样ThreadLocalMap就只属于这个线程了。

    • void set(T value)

          /**
           * Sets the current thread's copy of this thread-local variable
           * to the specified value.  Most subclasses will have no need to
           * override this method, relying solely on the {@link #initialValue}
           * method to set the values of thread-locals.
           *
           * @param value the value to be stored in the current thread's copy of
           *        this thread-local.
           */
          public void set(T value) {
              //1.获取当前线程
              Thread t = Thread.currentThread();
              //2.获取当前线程的ThreadLocalMap
              ThreadLocalMap map = getMap(t);
              //3.如果map为空就创建并赋值,不为空直接赋值
              if (map != null)
                  map.set(this, value);
              else
                  createMap(t, value);
          }
      
          /**
           * Get the map associated with a ThreadLocal. Overridden in
           * InheritableThreadLocal.
           *
           * @param  t the current thread
           * @return the map
           */
          ThreadLocalMap getMap(Thread t) {
              return t.threadLocals;
          }
      
    • public T get()

      	/**
           * Returns the value in the current thread's copy of this
           * thread-local variable.  If the variable has no value for the
           * current thread, it is first initialized to the value returned
           * by an invocation of the {@link #initialValue} method.
           *
           * @return the current thread's value of this thread-local
           */
          public T get() {
              //1.获取当前线程
              Thread t = Thread.currentThread();
              //2.获取当前线程的ThreadLocalMap
              ThreadLocalMap map = getMap(t);
              //3.如果map不为空,直接获取值
              if (map != null) {
                  ThreadLocalMap.Entry e = map.getEntry(this);
                  if (e != null) {
                      @SuppressWarnings("unchecked")
                      T result = (T)e.value;
                      return result;
                  }
              }
              //4. 如果是数据为null,则初始化,初始化的结果,TheralLocalMap中存放key值为threadLocal,值为null
              return setInitialValue();
          }
      
      
          /**
           * Variant of set() to establish initialValue. Used instead
           * of set() in case user has overridden the set() method.
           *
           * @return the initial value
           */
          private T setInitialValue() {
              T value = initialValue();
              Thread t = Thread.currentThread();
              ThreadLocalMap map = getMap(t);
              if (map != null)
                  map.set(this, value);
              else
                  createMap(t, value);
              return value;
          }
          /**
           * Returns the current thread's "initial value" for this
           * thread-local variable.  This method will be invoked the first
           * time a thread accesses the variable with the {@link #get}
           * method, unless the thread previously invoked the {@link #set}
           * method, in which case the {@code initialValue} method will not
           * be invoked for the thread.  Normally, this method is invoked at
           * most once per thread, but it may be invoked again in case of
           * subsequent invocations of {@link #remove} followed by {@link #get}.
           *
           * <p>This implementation simply returns {@code null}; if the
           * programmer desires thread-local variables to have an initial
           * value other than {@code null}, {@code ThreadLocal} must be
           * subclassed, and this method overridden.  Typically, an
           * anonymous inner class will be used.
           *
           * @return the initial value for this thread-local
           */
          protected T initialValue() {
              return null;
          }
      
    • public void remove()

      	/**
           * Removes the current thread's value for this thread-local
           * variable.  If this thread-local variable is subsequently
           * {@linkplain #get read} by the current thread, its value will be
           * reinitialized by invoking its {@link #initialValue} method,
           * unless its value is {@linkplain #set set} by the current thread
           * in the interim.  This may result in multiple invocations of the
           * {@code initialValue} method in the current thread.
           *
           * @since 1.5
           */
           public void remove() {
               ThreadLocalMap m = getMap(Thread.currentThread());
               if (m != null)
                   m.remove(this);
           }
      
  • ThreadLocal0 使用场景

    • 每个线程需要有自己的单独的实例
    • 实例需要在多个方法中共享,但不希望多线程共享

Thread面试题

  • 创建线程的几种方式

  • 子线程1 需要等待 子线程2 执行完成之后才能执行,如何实现

  • wait()和sleep()的相同点和区别

  • 守护线程和非守护线程的区别

    在JVM退出时,JVM是不会销毁守护线程的,只会管理非守护线程
    如果非守护线程还有在运行的,JVM就不会退出
    如果没有非守护线程了,但是还有守护线程,JVM直接退出
    
  • start()与run()的区别

    start会开启一个新线程
    run方法是用当前线程执行