java线程基础


记得之前学java的时候,最琢磨不定的就是多线程。状态比较多,执行起来也是随心所欲,弄得我焦头烂额。所以很多时候有意的避开使用多线程,但是对于一个java开发人员来说,多线程是必备的基本功。而且对于一些耗时操作基本是必选的。

虽然现在也会照着葫芦画瓢,使用线程池执行并发操作,但是很多多线程的基本知识还是一知半解。

线程的创建和启动

java使用Thread 表示线程,所有的线程对象都必须是Thread类或者子类的实例。

There are two ways to create a new thread of execution

继承Thread类

One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread。An instance of the subclass can then be allocated and started.

  1. 定义Thread子类,重写 run 方法。
  2. 创建子类的实例(线程对象)
  3. 调用线程对象的 start 方法启动线程
实现Runnable接口

The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, and started

  1. 实现Runnable接口,重写run方法
  2. 创建实现类的实例并作为参数传入Thread 构造器中,创建Thread对象。
  3. 调用线程对象的 start 方法启动线程

以该方式创建对象时,我们通常会调用以下方法来创建实例。

  public Thread(Runnable target) 
  public Thread(Runnable target, String name) 
  public Thread(ThreadGroup group, Runnable target)

当然除此之外还有其他方法,上面提到的这些构造方法都会调用init方法,用于线程实例的变量初始化工作。

 init(g, target, name, stackSize, null, true);

下面是init方法的部分源码:

/**
     * Initializes a Thread.
     *
     * @param g the Thread group
     * @param target the object whose run() method gets called
     * @param name the name of the new Thread
     * @param stackSize the desired stack size for the new thread, or
     *        zero to indicate that this parameter is to be ignored.
     * @param acc the AccessControlContext to inherit, or
     *            AccessController.getContext() if null
     * @param inheritThreadLocals if {@code true}, inherit initial values for
     *            inheritable thread-locals from the constructing thread
     */
    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();
        
      ..................................

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        .........................................
        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();
    }

需要关注以下几处代码:

  //是否是守护线程
  this.daemon = parent.isDaemon();
  //优先级
  this.priority = parent.getPriority();

子线程和父线程默认具有相同的优先级和 isDaemon

if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

子线程会继承父线程的 threadLocal。

demo
public class ThreadCreateDemo {

    public static void main(String[] args) {
    	//Thread子类
        new MyThread().start();
        //实现Runnable接口(lambda表达式)
        new Thread(()->  System.out.println("implements the Runnable interface  is over")).start();

    }
}

class MyThread extends Thread{
    @Override
    public void run(){
        System.out.println("subclass  of Thread is over");
    }
}

subclass of Thread is over
implements the Runnable interface is over

线程状态

学习多线程,必不可少的是一张状态转化图,描述线程在不同状态间的转换和线程的整个运行周期。下图来自《并发编程的艺术》一书
在这里插入图片描述
java中使用Thread.State枚举类来表示线程可能具有的状态。

	 private volatile int threadStatus = 0;
	 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;
	    }
  • 新建(NEW):使用new关键字创建线程就处于新建状态。和其他对象一样虚拟机会为该对象分配内存,初始化成员变量,执行初始化操作。线程对象被构建,但是start()方法没有被调用。

  • 可运行(RUNNABLE) :java中 就绪状态和运行状态被统称为“运行中"。所在该状态的线程可能获取到了cpu,也可能在等待cpu资源(其他资源已经全部获取)。

  • 阻塞(BLOCKED) : 该状态的线程等待对象监视器(对象锁)进入 synchronized同步代码【块/方法】, 或者是调用了Object.wait()方法后重新进入synchronized同步代码【块/方法】(被唤醒之后会重新获取对监视器)。

  • 等待(WAITING): 线程处于该状态 可能是调用了下面的方法 :

    • Object.wait()
    • Thread.join()
    • LockSupport.park()
      该状态的线程需要等待其他线程的一些特定的动作(notify,interrupt,LockSupport.unpark(Thread))
  • 超时等待(TIMED_WAITING):调用以下方法时指定等待时间。

    • Thread.sleep(ong)
    • Object.wait(long)
    • join(long)
    • LockSupport.parkNanos(long)
    • LockSupport.parkUntil(long)
  • 死亡(TERMINATED): 线程执行完成后就是死亡状态。下面任一一种情况都视为线程执行完成

    • run方法正常执行完成(中间可能会有异常处理)
    • 线程抛出未捕获的Exception或Error

测试程序,获取线程的运行状态

public class ThreadStatus {

    public static void main(String[] args) throws InterruptedException {
	
        Object object = new Object();//创建一个对象,用来获取对象锁
        Thread thread = new Thread(() -> {
            try {
                //休眠保证 主线程可以先执行,同时获取 调用sleep(time)方法后的状态
                System.out.println("休眠。。。。。。。。。");
                Thread.sleep(3000);
                synchronized (object) {
                    object.wait();//
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
            }
        }, "threadStatusThread");
        thread.start();

        //获取对象锁,阻塞子线程进入 synchronized 代码块
        synchronized (object) {
            for (int i = 0; i < 5; i++) {
                Thread.sleep(1000);
                System.out.println("for......"+thread.getName() + "\t 当前状态: \t" + thread.getState());
            }
        }


        //等待子线程调用wait方法
        Thread.sleep(1000);

        //唤醒子线程
        synchronized (object) {
        	//获取 调用wait方法后的线程状态
            System.out.println(thread.getName() + "\t 当前状态: \t" + thread.getState());
            object.notify();
        }

    }
}

休眠。。。。。。。。。
for…threadStatusThread 当前状态: TIMED_WAITING
for…threadStatusThread 当前状态: BLOCKED
for…threadStatusThread 当前状态: BLOCKED
for…threadStatusThread 当前状态: BLOCKED
for…threadStatusThread 当前状态: BLOCKED
threadStatusThread 当前状态: WAITING

从上面结果可以看到 子线程(threadStatusThread )在等待object对象监视器 时线程状态为 BLOCK。
子线程调用 Thread.sleep(3000);方法休眠时,子线程状态为TIMED_WAITING。
子线程调用 wait方法后线程进入等待状态,线程状态为WAITING。

Thread 常用方法
1.sleep
Thread.sleep(10*1000);//休眠十秒

The thread does not lose ownership of any monitors

控制当前执行的线程休眠指定的时间(单位是毫秒)。时间精确性取决于系统计时器和调度程序的精度和准确性。 该方式不会释放线程拥有的对象监视器。
当其他线程试图使用interrupt方法中断该线程时,该线程时会抛出InterruptedException异常同时会清空该线程上的中断状态。

2.wait

这是基类Object类中定义的方法。wait方法有三种重载形式:

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

前两种最终会调用第三个wait方法实现功能,可以看到第三个方法使用native修饰。

    /**
     * Causes the current thread to wait until either another thread invokes the
     * {@link java.lang.Object#notify()} method or the
     * {@link java.lang.Object#notifyAll()} method for this object, or a
     * specified amount of time has elapsed.
     * <p>
     * The current thread must own this object's monitor.
     * <p>
     * This method causes the current thread (call it <var>T</var>) to
     * place itself in the wait set for this object and then to relinquish
     * any and all synchronization claims on this object. Thread <var>T</var>
     * becomes disabled for thread scheduling purposes and lies dormant
     * until one of four things happens:
     * <ul>
     * <li>Some other thread invokes the {@code notify} method for this
     * object and thread <var>T</var> happens to be arbitrarily chosen as
     * the thread to be awakened.
     * <li>Some other thread invokes the {@code notifyAll} method for this
     * object.
     * <li>Some other thread {@linkplain Thread#interrupt() interrupts}
     * thread <var>T</var>.
     * <li>The specified amount of real time has elapsed, more or less.  If
     * {@code timeout} is zero, however, then real time is not taken into
     * consideration and the thread simply waits until notified.
     * </ul>
     * The thread <var>T</var> is then removed from the wait set for this
     * object and re-enabled for thread scheduling. It then competes in the
     * usual manner with other threads for the right to synchronize on the
     * object; once it has gained control of the object, all its
     * synchronization claims on the object are restored to the status quo
     * ante - that is, to the situation as of the time that the {@code wait}
     * method was invoked. Thread <var>T</var> then returns from the
     * invocation of the {@code wait} method. Thus, on return from the
     * {@code wait} method, the synchronization state of the object and of
     * thread {@code T} is exactly as it was when the {@code wait} method
     * was invoked.
     * <p>
     * A thread can also wake up without being notified, interrupted, or
     * timing out, a so-called <i>spurious wakeup</i>.  While this will rarely
     * occur in practice, applications must guard against it by testing for
     * the condition that should have caused the thread to be awakened, and
     * continuing to wait if the condition is not satisfied.  In other words,
     * waits should always occur in loops, like this one:
     * <pre>
     *     synchronized (obj) {
     *         while (&lt;condition does not hold&gt;)
     *             obj.wait(timeout);
     *         ... // Perform action appropriate to condition
     *     }
     * </pre>
     *
     * <p>If the current thread is {@linkplain java.lang.Thread#interrupt()
     * interrupted} by any thread before or while it is waiting, then an
     * {@code InterruptedException} is thrown.  This exception is not
     * thrown until the lock status of this object has been restored as
     * described above.
     *
     * <p>
     * Note that the {@code wait} method, as it places the current thread
     * into the wait set for this object, unlocks only this object; any
     * other objects on which the current thread may be synchronized remain
     * locked while the thread waits.
     * <p>
     * This method should only be called by a thread that is the owner
     * of this object's monitor. See the {@code notify} method for a
     * description of the ways in which a thread can become the owner of
     * a monitor.
     *
     * @param      timeout   the maximum time to wait in milliseconds.
     * @throws  IllegalArgumentException      if the value of timeout is
     *               negative.
     * @throws  IllegalMonitorStateException  if the current thread is not
     *               the owner of the object's monitor.
     * @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.
     * @see        java.lang.Object#notify()
     * @see        java.lang.Object#notifyAll()
     */
    public final native void wait(long timeout) throws InterruptedException;

The current thread must own this object’s monitor

在调用wait方法之前首先需要获取到对象监视器,否则会抛出异常IllegalMonitorStateException。 同样notify也(notifyAll)必须获得对象监视器,不同的调用notify方法后不会立即释放对象监视器。

	synchronized

同步监视器的获取通过synchronized关键字: 同步代码块、同步方法。

If the current thread is {@linkplain java.lang.Thread#interrupt() interrupted} by any thread before or while it is waiting, then an {@code InterruptedException} is thrown

如果因调用wait方法导致等待的线程,被其他线程调用interrupt方法,则wait方法会抛出InterruptedException,并会(清除)中断状态。

This method causes the current thread (call it T) to place itself in the wait set for this object and then to relinquish any and all synchronization claims on this object

调用wait()方法后,当前线程(我们假设为 线程T)会进入此对象的等待队列中 并会释放此对象的对象监视器(使其他线程可以获取对象锁进行notify)。

调用了wait方法的线程会一直等待,直到出现以下几种情况(结束等待):

  • 其他线程调用了这个对象的notify方法
  • 其他线程调用了这个对象的notifyAll方法
  • 其他线程调用了线程T 的interrupt方法
  • 指定的等待时间已经过去。如果 timeout = 0,那么就没有超时时间,就会一直等待。

A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup,and this method should always be used in a loop

除了上面几种情况之外,线程也可能出现假醒。所以我们需要判断线程被唤醒的原因是不是符合我们的预期的唤醒条件,如果不符合就继续等待,所以wait方法 应当放在循环里面。

 synchronized (obj) {
       while (<condition does not hold>)
            obj.wait();
        ... // Perform action appropriate to condition
}

小结:
通过synchronized关键字获取到对象监视器的对象我们称为 obj对象。
调用wait方法的线程我们称为 线程A。

  • 线程(A)调用obj的wait方法前需要获取此对象(obj)的对象监视器 (synchronized(obj)),否则抛出异常IllegalMonitorStateException
  • (对象obj)执行wait方法后,线程(A)会释放持有该对象(obj)同步监视器并加入此对象(obj)同步监视器的等待队列。
  • 其他线程调用了此对象(obj)的notify方法(也需要通过synchronized(obj) 获取对象监视器)后,会随机唤醒一个在此对象(obj)监视器上等待的线程(线程A),该线程(线程A)会从此对象(obj)的对象监视器等待队列上移除。
  • 需要保证 wait方法在notify 方法之前执行,否则将会一直等待。
3.notify | notifyAll

notify : 唤醒在此对象监视器上等待的线程
notifyAll:唤醒在此对象监视器上等待的所有线程

    /**
     * Wakes up a single thread that is waiting on this object's
     * monitor. If any threads are waiting on this object, one of them
     * is chosen to be awakened. The choice is arbitrary and occurs at
     * the discretion of the implementation. A thread waits on an object's
     * monitor by calling one of the {@code wait} methods.
     * <p>
     * The awakened thread will not be able to proceed until the current
     * thread relinquishes the lock on this object. The awakened thread will
     * compete in the usual manner with any other threads that might be
     * actively competing to synchronize on this object; for example, the
     * awakened thread enjoys no reliable privilege or disadvantage in being
     * the next thread to lock this object.
     * <p>
     * This method should only be called by a thread that is the owner
     * of this object's monitor. A thread becomes the owner of the
     * object's monitor in one of three ways:
     * <ul>
     * <li>By executing a synchronized instance method of that object.
     * <li>By executing the body of a {@code synchronized} statement
     *     that synchronizes on the object.
     * <li>For objects of type {@code Class,} by executing a
     *     synchronized static method of that class.
     * </ul>
     * <p>
     * Only one thread at a time can own an object's monitor.
     *
     * @throws  IllegalMonitorStateException  if the current thread is not
     *               the owner of this object's monitor.
     * @see        java.lang.Object#notifyAll()
     * @see        java.lang.Object#wait()
     */
    public final native void notify();

If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation

notify 方法 唤醒线程是随机的取决于底层实现。

This method should only be called by a thread that is the owner of this object’s monitor.
Only one thread at a time can own an object’s monitor.

notify / notifyAll方法调用需要获得对象监视器。

4.currentThread

本地方法,获取当前正在执行此处代码的线程对象引用

     
     /**
     * Returns a reference to the currently executing thread object.
	 */
    public static native Thread currentThread();

5.interrupt 相关

每个线程都有一个表示线程是否中断的属性。该状态可以通过下面两个方法获取

	//实例方法
	 public boolean isInterrupted() {
        return isInterrupted(false);
    }
	
    //静态方法,该方式会清除线程的中断状态(第二次调用一定返回false)。所以该方法常用于复位操作
	public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

需要注意的是interrupted 方法,会清除中断标识。

调用interrupt方法会将线程的中断标识置为中断,并会根据线程当前所处状态做出不同的相应。下面有专门的章节详细介绍。

public void interrupt()
6. join

Waits for this thread to die.

当线程A调用了线程B的join方法时,线程A就会一直等待,直到线程B执行完成(TERMINATED)。

public class ThreadJoinDemo {

    public static void main(String[] args) {
        Thread mainThread = Thread.currentThread();

        //创建一个子线程
        Thread subThread = new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"    is start....");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //获取主现成的状态。。
            System.out.println(mainThread.getName()+"\t "+mainThread.getState());
            System.out.println(Thread.currentThread().getName()+"    is over....");},"subThread..");
        //启动
        subThread.start();
        try {
            //先执行子线程,主线程等待
            subThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"    is over....");
    }
}

subThread… is start…
main WAITING
subThread… is over…
main is over…

可以看到,调用了join方法后主线程进入等待状态(释放对象锁),子线程执行完成之后主线程被唤醒继续执行(子线程在休眠的时候主线程并未继续执行)。

join 方法有两种有参和无参,参数用来控制等待时间

  • join()
  • join(long millis)
/**
* Waits for this thread to die.
* @throws  InterruptedException
*          if any thread has interrupted the current thread. The interrupted status of the current 
* 		   thread  is cleared when this exception is thrown.
*/
 public final void join() throws InterruptedException {
        join(0);
    }
/**
*Waits at most millis milliseconds for this thread to die. A timeout of 0 means to wait forever. 
* This implementation uses a loop of this.wait calls conditioned on this.isAlive. 
* As a thread terminates the this.notifyAll method is invoked. 
* It is recommended that applications not use wait, notify, or notifyAll on Thread instances.
*/
 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 方法,导致线程等待,直到调用了 notifyAll 、notify方法
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

根据文档注释,我们需要注意一下几点:

  1. A timeout of 0 means to wait forever.

    参数为 0 时表示永远等待 没有超时时间。

  2. As a thread terminates the this.notifyAll method is invoked

    当线程终止时会调用 this.notifyAll 方法,以通知所有等待在该线程对象上的线程。

  3. It is recommended that applications not use wait, notify, or notifyAll on Thread instances.

    开发时不建议调用 线程对象的 wait、notify、notifyAll 方法

我们在回到 join的()方法,它调用了 join(0)方法,最终会执行以下循环操作:

 while (isAlive()) {
         //调用 wait 方法,导致线程(调用 object.wait()方法的线程)等待,直到调用了 notifyAll 、notify方法
          wait(0);
 }

上面提到过线程终止以后会调用自身的 notifyAll方法,这样就会唤醒在该线程对象上等待的线程。

刚开始看源码的时候,我经常处于一种似懂非懂的玄妙状态。我总结了一下使我困惑的那些点:

  1. wait 会使哪个线程等待?
    join 方法 有下面这样一句注释

    Waits for this thread to die.

    等待这个线程死亡,而这个就是指调用 join方法的线程。所以调用代码threadX.join() 的线程会等待线程threadX死亡。
    从源码就可以印证, 调用代码 threadX.join() 就类似于直接调用了 threadX.wait(0)(此时线程对像threadX作为同步监视器)。根据上面wait方法的介绍我们知道,调用 obj.wait 方法的线程会等待并释放对象监视器。

  2. wait 方法的获取不是需要获取对象监视器吗?
    介绍wait 方法的时候强调过,使用wait方法必须获取到 对象的监视器(对象锁)。 我们可以看到 join(long millis)方法使用synchronized修饰。

     public final synchronized void join(long millis)
    
  3. 为什么会使用 while循环呢
    因为将线程唤醒并不一定是因为线程终止后调用notifyAll 方法,可能因为其他线程调用notifyAll 、notify方法 或者假醒。

线程终止时 调用 notifyAll方法的源码:

/src/share/vm/runtime/thread.cpp

void JavaThread::run() {
  ...
  thread_main_inner();
}

void JavaThread::thread_main_inner() {
  ...
  this->exit(false);
  delete this;
}


void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
  ...
  // Notify waiters on thread object. This has to be done after exit() is called
  // on the thread (if the thread is the last thread in a daemon ThreadGroup the
  // group should have the destroyed bit set before waiters are notified).
  ensure_join(this);
  ...
}

static void ensure_join(JavaThread* thread) { 
  ............
  lock.notify_all(thread);
  // Ignore pending exception (ThreadDeath), since we are exiting anyway
  thread->clear_pending_exception();
}

线程退出时会唤醒在此线程同步监视器上等待的线程。

6. setDaemon

是否将线程设置为守护线程(daemon thread)。守护进程的作用就是为了用户线程(user thread.)提供服务的。


public class ThreadCreateDemo {

    public static void main(String[] args) {
    	//创建守护线程
        Thread thread = new MyThread();
        thread.setDaemon(true);
        thread.start();
        //实现Runnable接口的线程
        new Thread(()->  System.out.println("implements the Runnable interface  is over")).start();
       
//        new Thread(()-> {
//            try {
//                Thread.sleep(2000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//        }).start();

    }
}

class MyThread extends Thread{
    @Override
    public void run(){
        //查看守护线程创建的子线程
        Thread subThread = new Thread(()->  System.out.println(" .............."));
        System.out.println(subThread.getName()+"  isDeamon: "+ subThread.isDaemon());

        //自旋
        for(;;){
            System.out.println("doSomething.....");
        }
    }

Thread-2 isDeamon: true
doSomething…
doSomething…
doSomething…
doSomething…
implements the Runnable interface is over
doSomething…
doSomething…
doSomething…
Process finished with exit code 0

去掉上面的注释在执行一遍就会发现,虚拟机会在2s后退出。
从上面可以看到:

  • 非守护线程执行完成后,虚拟机就退出(不是立即退出)
  • 守护线程创建的子线程,默认也是守护线程

注意:

  1. 该方法的调用必修在 start方法之前,否则会抛出异常。
  2. 当虚拟机只有守护线程在运行时,虚拟机就会退出
  3. 用户线程 创建的子线程默认为 用户线程,守护线程创建的子线程默认为守护线程。
过时的方法
  1. stop相关
    强制线程停止执行 。该方法是用来关闭线程的。该方式会释放对象上持有的全部锁,这样会导致它解锁它已锁定的所有监视器
    。如果先前受这些监视器保护的任何对象处于不一致状态,则这种状态不一致的损坏对象对其他线程可见,可能导致结果混乱。
    该方法可以用来停止还没有被启动的方法。如果线程已经启动,会立即终止线程。(该方式很暴力,有点像 kill 命令)

    stop的许多用法应该替换为修改某个变量以指示目标线程应该停止运行。
    常用的两个方式的:

    1. interrupt
    2. volatile
  2. destroy
    该方法的本来目的是为了关闭线程,同时又不释放线程持有的锁,但是这样会导致死锁。该方法也没有具体的实现,只是抛出一个异常

	@Deprecated
    public void destroy() {
        throw new NoSuchMethodError();
    }
线程通信
synchronized

wait、notify、notifyAll 方法调用需要获取对象监视器。

	synchronized (obj)

获取方式是通过synchronized 关键字实现的,synchronized 是一个重量级锁。

Java中的每一个对象都可以作为锁,这是为什么呢?
Java对象头里的Mark Word里默认存储对象的HashCode、分代年龄和锁标记位。

在64位虚拟机下,Mark Word是64bit大小的,其存储结构如下:
在这里插入图片描述
synchronized正是通过锁标志来判断当前对象锁是否可用,在获取锁的过程中可能会涉及到锁的升级。

Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级。

wait/notify
	public class WaitNotifyDemo {
	
	    public static void main(String[] args) {
	        Object object = new Object();
	        Thread thread=new Thread(()->{
	            try {
	                object.wait();
	            } catch (InterruptedException e) {
	                e.printStackTrace();
	            }
	            System.out.println(Thread.currentThread().getName()+"  over");
	        },"waitThread");
	        thread.start();
	
	        try {
	            Thread.sleep(1000);
	        } catch (InterruptedException e) {
	            e.printStackTrace();
	        }
	        object.notify();//唤醒
	
	    }
	}
		

没有获取对象锁,就执行wait方法,会抛出异常IllegalMonitorStateException

Exception in thread "waitThread" java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at thread.WaitNotifyDemo.lambda$main$0(WaitNotifyDemo.java:10)
	at java.lang.Thread.run(Thread.java:748)

对wait和notify操作加锁以后就可以正常执行了

//wait
synchronized(object){
	object.wait();
}

synchronized(object){
	object.notify();
}

还有如果父线程中 notify方法不幸比子线程wait方法先执行,那么子线程将一直等待。

LockSupport

用于创建锁和其他同步类的基本线程阻塞原语。它提供了park/unpark方法用于线程的阻塞和解除阻塞。

AQS的等待队列中线程的挂起和唤醒就是通过LockSupport实现的。

LockSupport调用 park方法尝试获取许可(permit),当许可存在时立即返回继续执行代码,否则线程阻塞。unpark 方法会为线程提供许可。这是为什么这样unpark可以park方法之前执行。

Unlike with Semaphores though, permits do not accumulate. There is at most one。
only one permit is associated with each thread

需要注意的是许可不会累加,所以即便调用了很多次的unpark方法线程也只会存在一个许可。

The park method may also return at any other time, for “no reason”, so in general must be invoked within a loop that rechecks conditions upon return

park方法也可能会有假醒现象,所以 跟wait方法一样 park 方法也应当放在循环里面。

在AQS中 LockSupport.park 方法就是使用循环中。具体可以参考 acquireQueued 方法。

与wait/notify相比, LockSupport 具有以下优点:

  • LockSupport不需要获取对象监视器(需要获取许可)。
  • unpark函数可以先于park调用,所以不需要担心线程间的执行的先后顺序。
  • 代码更简洁

使用LockSupport实现简易版的非重入锁。

public class LockDemo {
    public static void main(String[] args) {
        LockDemo lockDemo = new LockDemo();
        for(int i=0;i<3;i++){
            new Thread(()->{
                lockDemo.lock();
                try{
                    System.out.println(Thread.currentThread().getName()+"        out");
                }finally {
                    lockDemo.unlock();
                }
            }).start();
        }
    }

    // 独占锁 的锁状态。 true: 有锁状态,false: 无锁
    private AtomicBoolean isLock = new AtomicBoolean(false);
    //维护等待队列
    private List<Thread> waitList = new ArrayList<>(20);

    public void lock(){
        //获取当前线程
        Thread currentThread = Thread.currentThread();
        waitList.add(currentThread);
        boolean isInterrupt=false;

        //如果获取锁失败则将当前线程阻塞
        while(currentThread != waitList.get(0) || !isLock.compareAndSet(false,true) ){
            LockSupport.park();
            //线程阻塞期间是否中断过
            if(Thread.interrupted())
                isInterrupt=true;
        }
        waitList.remove(0);

        //响应中断
        if(isInterrupt){
            currentThread.interrupt();
        }
    }

    public void unlock(){
        //设置为无锁状态
        isLock.set(false);
        //唤醒下一个线程
        if(!waitList.isEmpty())
            LockSupport.unpark(waitList.get(0));
    }
}

// Thread-0 ,Thread-1, Thread-2
Thread-0 out
Thread-1 out
Thread-2 out

Condition

在jdk1.5之后,除了使用synchronized 关键字可以实现线程同步之外还可以使用 Lock 类实现同步。

与synchronized相比,使用Lock有更大的自主权,可以指定锁的公平性,是jdk层面的锁(使用底层使用队列 和 LockSupport实现 )。Lock使用 Lock实例作为锁对象,synchronized用的锁是存在Java对象头里的。

当我们使用Lock 而不是 synchronized 实现同步锁时,就需要使用 Condition (await/signal) 来替换对象监视器方法(wait/notify)

Lock 常用实现类:

  • ReentrantLock :可重入独占锁。
  • ReentrantReadWriteLock。 可重入读写锁
 /**
     * Returns a new {@link Condition} instance that is bound to this
     * {@code Lock} instance.
     *
     * <p>Before waiting on the condition the lock must be held by the
     * current thread.
     * A call to {@link Condition#await()} will atomically release the lock
     * before waiting and re-acquire the lock before the wait returns.
     *
     * <p><b>Implementation Considerations</b>
     *
     * <p>The exact operation of the {@link Condition} instance depends on
     * the {@code Lock} implementation and must be documented by that
     * implementation.
     *
     * @return A new {@link Condition} instance for this {@code Lock} instance
     * @throws UnsupportedOperationException if this {@code Lock}
     *         implementation does not support conditions
     */
Condition newCondition();

使用Condition 的awaitsignalsignalAll 方法可以实现线程通信,功能和使用方式都与 wait、notify、notifyAll 一致。同样的 await方法 也会抛出异常InterruptedException。

下面是通Condition实现的简单的阻塞队列,就是简单的生产者消费者模式。

public class BlockingArrayDemo<T> {
    final Lock lock = new ReentrantLock(); //锁
    final Condition notFull = lock.newCondition();//条件: 队列不满
    final Condition notEmpty = lock.newCondition();//条件:队列不空
    final Object [] items = new Object[1]; // 队列存储元素
    int putIndex, takeIndex, count;

    public static void main(String[] args) {
        BlockingArrayDemo blockingArray = new BlockingArrayDemo();
        new Thread(()->{
            for (int i=0;i<5;i++){
                try {
                    System.out.println("take:   "+blockingArray.take());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        for (int i=0;i<5;i++){
            try {
                Thread.sleep(1000);
                blockingArray.put("num: "+i);
                System.out.println("put num:    "+i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 入队
     * @param o
     * @throws InterruptedException
     */
    public void put(T o) throws InterruptedException {
        //获取锁
        lock.lock();
        try{
            // 队列已满等待
            while(count==items.length)
                notFull.await();

            items[putIndex] = o;
            putIndex = (putIndex++) % items.length;
            count++;
            //添加元素,此时队列非空
            notEmpty.signal();
        }finally {
            //释放锁
            lock.unlock();
        }
    }

    /**
     * 出队
     * @return
     * @throws InterruptedException
     */
    public T take()throws InterruptedException{
        //获取锁
        lock.lock();
        try{
            // 队列已空等待
            while(count==0)
                notEmpty.await();
            T takeObj = (T) items[takeIndex];
            takeIndex = (takeIndex++) % items.length;
            count--;
            //已移除元素,此时队列非满
            notFull.signal();
            return takeObj;
        }finally {
            lock.unlock();
        }
    }
 }

put num: 0
take: num: 0
put num: 1
take: num: 1
put num: 2
take: num: 2
put num: 3
take: num: 3
put num: 4
take: num: 4

可以看到 使用方式和wait/notify 是一致的,本就是synchronized / (wait/notify) 替换方案。当然完全可以将Lock替换为synchronized,将Condition 替换为 普通对象

  Object notFull = new Object();
  Object notEmpty = new Object();
  public  synchronized void put(T o) throws InterruptedException {
         while(count==items.length)
                notFull.wait();
            items[putIndex] = o;
            putIndex = (putIndex++) % items.length;
            count++;
            //添加元素,此时队列非空
            notEmpty.signal();
  }

有兴趣可以了解一下博客: ReentrantLock 源码浅读阻塞队列-ArrayBlockingQueue源码解析

join
线程中断
1. java 中有关线程中断的方法
public boolean isInterrupted() //测试线程是否中断

public void interrupt() //中断线程

public static boolean interrupted() //返回中断状态并会清空中断标志位

中断是一个线程的标志位属性,它表示一个运行中的线程是否被其他线程执行了中断操作。每个线程都一个状态位用于标识当前线程对象是否是中断状态。

判断当前线程是否中断的方法

  1. 实例方法,返回是否中断,并且不会清除中断状态位

      public boolean isInterrupted() {
            return isInterrupted(false);
        }
    
  2. 类方法,返回当前线程的中断状态,同时会重置中断状态(true—> false)。也就是 说连续调用两次该方法,第二次会返回false(除非在两次操作的过程中当前线程再一次被中断)

        public static boolean interrupted() {
            return currentThread().isInterrupted(true);
        }
    

中断线程

/**
     * Interrupts this thread.
     *
     * <p> Unless the current thread is interrupting itself, which is
     * always permitted, the {@link #checkAccess() checkAccess} method
     * of this thread is invoked, which may cause a {@link
     * SecurityException} to be thrown.
     *
     * <p> If this thread is blocked in an invocation of the {@link
     * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
     * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
     * class, or of the {@link #join()}, {@link #join(long)}, {@link
     * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
     * methods of this class, then its interrupt status will be cleared and it
     * will receive an {@link InterruptedException}.
     *
     * <p> If this thread is blocked in an I/O operation upon an {@link
     * java.nio.channels.InterruptibleChannel InterruptibleChannel}
     * then the channel will be closed, the thread's interrupt
     * status will be set, and the thread will receive a {@link
     * java.nio.channels.ClosedByInterruptException}.
     *
     * <p> If this thread is blocked in a {@link java.nio.channels.Selector}
     * then the thread's interrupt status will be set and it will return
     * immediately from the selection operation, possibly with a non-zero
     * value, just as if the selector's {@link
     * java.nio.channels.Selector#wakeup wakeup} method were invoked.
     *
     * <p> If none of the previous conditions hold then this thread's interrupt
     * status will be set. </p>
     *
     * <p> Interrupting a thread that is not alive need not have any effect.
     *
     * @throws  SecurityException
     *          if the current thread cannot modify this thread
     *
     * @revised 6.0
     * @spec JSR-51
     */
    public void interrupt()
不同状态下调用interrupt线程的响应

在上面说过 线程有6中状态。使用枚举类 Thread.State来描述线程的状态。
在这里插入图片描述

调用interrupt 方法会唤醒处于等待的线程(sleep,wait,park)

【hotspot\src\share\vm\runtime\thread.hpp】

  ParkEvent * _ParkEvent ;                     // for synchronized()
  ParkEvent * _SleepEvent ;                    // for Thread.sleep
  ParkEvent * _MutexEvent ;                    // for native internal Mutex/Monitor
  ParkEvent * _MuxEvent ;                      // for low-level muxAcquire-muxRelease

【hotspot\src\os\linux\vm\os_linux.cpp】

void os::interrupt(Thread* thread) {
  assert(Thread::current() == thread || Threads_lock->owned_by_self(),
    "possibility of dangling Thread pointer");
		
  OSThread* osthread = thread->osthread();

  if (!osthread->interrupted()) {
  	//设置中断状态
    osthread->set_interrupted(true);
    // More than one thread can get here with the same value of osthread,
    // resulting in multiple notifications.  We do, however, want the store
    // to interrupted() to be visible to other threads before we execute unpark().
    OrderAccess::fence();
    
    //唤醒sleep 
    ParkEvent * const slp = thread->_SleepEvent ;
    if (slp != NULL) slp->unpark() ;
  }

  // For JSR166. Unpark even if interrupt status already was set
  //unsafe.unpark() 
  //唤醒 LockSupport.park()
  if (thread->is_Java_thread())
    ((JavaThread*)thread)->parker()->unpark();
	
  //用于synchronized同步块和Object.wait() 唤醒	
  ParkEvent * ev = thread->_ParkEvent ;
  if (ev != NULL) ev->unpark() ;

}
  1. NEW, TERMINATED
    这两个状态的线程,实际上都算是未存活的线程。NEW,没有调用start()方法,线程没有真正的启动。TERMINATED,线程执行完成。这两种状态下获取中断状态没有意义,调用isIinterrupted始终返回false。
    在Thread.interrupted()这个静态方法上有这么一段注释。同样的isInterrupted方法也有相同的注释。

     	 * A thread interruption ignored because a thread was not alive
         * at the time of the interrupt will be reflected by this method
         * returning false.
    

    中断方法 interrupt的一段注释。说明了对于NEW,TERMINATED这样未存活的线程,调用interrupt方法是不起作用的。

    Interrupting a thread that is not alive need not have any effect.

  2. WAITING/TIMED_WAITING

    *<p>If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int)
    * methods of the Object class, or of the join(), join(long), join(long, int), sleep(long),
    *  or sleep(long, int), methods of this class, then its interrupt status will be cleared and 
    * it will receive an InterruptedException.
    *<p>
    

    诸如调用wait ,wait(long),join,sleep(long)等方法将线程阻塞,调用interrupt 方法会清除中断标志位,同时会接受到InterruptedException异常。

    上面的这些方法都会显式的抛出异常,所以我们在调用的时候需要try-catch处理。

     *          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 static native void sleep(long millis) throws InterruptedException;
    

    等待的线程被中断抛出异常。在异常捕获中可以根据需要做一些操作,来响应中断。

    public class ThreadBlockInterruptDemo {
    
        public static void main(String[] args) {
            Object object = new Object();
            Thread thread=new Thread(()->{
                try {
                    synchronized (object){
                        try {
                            object.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                             System.out.println("isInterrupted:   "+Thread.currentThread().isInterrupted());
                            return ; //结束执行
                        }
                    }
                }finally {
                    System.out.println("执行结束。。。");
                }
            },"threadStop");
            thread.start();
            try {
                Thread.sleep(200); //保证子线程已经执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            thread.interrupt();
        }
    }
    
    

    isInterrupted: false
    java.lang.InterruptedException
    执行结束。。。
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Object.java:502)
    at thread.ThreadBlockInterruptDemo.lambda$main$0(ThreadBlockInterruptDemo.java:11)
    at java.lang.Thread.run(Thread.java:748)

    使用LockSupport挂起线程被中断并不会抛出InterruptedException,但是线程的中断标志位为true,并且该会被唤醒。 在juc的工具类中大量使用了LockSupport,比如AQS。因为LockSupport的这种特性所以AQS会对中断线程操作进行额外的处理。

    	public class LockSupportDemo {
    	    public static void main(String[] args) {
    	        Thread thread=new Thread(()->{
    	            LockSupport.park();
    	            //自旋直到 线程的中断位为 interrupted
    	           for(;;){
    	               System.out.println(Thread.currentThread().getName()+"   \t interrupt \t"+Thread.currentThread().isInterrupted());
    	                if(Thread.currentThread().isInterrupted())
    	                    break;
    	            }
    	            System.out.println(Thread.currentThread().getName()+" over");
    	        },"lockSupport");
    	        thread.start();
    	        thread.interrupt();
    	    }
    	}
    

    lockSupport interrupt true
    lockSupport over

  3. BLOCKED / RUNNABLE
    处于这两种状态的线程,被调用interrupt方法后,只会修改中断状态为以中断。

         * <p> If none of the previous conditions hold then this thread's interrupt
         * status will be set. </p>
    

    所以可以使用中断状态来优雅的关闭线程执行。

    优雅关闭线程执行

    
    public class ThreadStop {
    
        public static void main(String[] args) {
    
            Thread thread=new Thread(()->{
                try {
                    for (; ; ) {
                        System.out.println("执行中。。。。。");
                        //doSomething
                        if (Thread.currentThread().isInterrupted())//线程执行时,如果线程的中断标识已经是中断,结束线程执行
                            break;
    
                    }
                }finally {
                    System.out.println("执行结束。。。");
                }
            },"threadStop");
            thread.start();
    
            try {
                Thread.sleep(200); //保证子线程已经执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            thread.interrupt();//中断线程
        }
    
    }
    
    

小结:
1. runnable状态的线程被中断,只会修改中断状态(interruptedStatus)。需要根据isInterrupted的返回结果来处理
2. 因wait,sleep 等方法导致等待的线程被中断,会抛出异常InterruptedException 并清除中断状态。需要在catch中处理
3. LockSupport.park() 导致线程状态为WAITING的线程被中断,会唤醒线程(相当于调用了LockSupport.unpark)

总结
方法调用
  1. As a thread terminates the this.notifyAll method is invoked

    线程退出之前会调用this.notifyAll方法,唤醒在此线程同步监视器上等待的线程。

  2. wait 、notify、notifyAll :

    The current thread must own this object’s monitor

    调用 waitnotifynotifyAll 这些方法需要获取对象同步监视器。

  3. wait:

    The thread releases ownership of this monitor and waits

    调用obj.wait方法只会释放 obj的对象监视器。

  4. sleep:

    The thread does not lose ownership of any monitors

    不会释放它所拥有的对象监视器

  5. setDaemon(true)

    The Java Virtual Machine exits when the only threads running are all daemon threads.

    当没有前台线程运行时(只有守护线程)JVM 退出。

  6. wait、LockSupport.park(),Condition.await 方法的调用最好是放在循序那语句中:

       while(condition)
          // condition.await();
          // LockSupport.park();
          //obj.wait()
    
线程中断:
  1. runnable状态的线程被中断,只会修改中断状态(interruptedStatus)。自定义中断处理逻辑
  2. 因 wait,sleep (有参和无参)等方法调用导致线程等待的线程被中断,会抛出异常InterruptedException 并清除中断状态。需要在catch中处理
  3. LockSupport.park() 导致线程状态为WAITING的线程被中断,线程会被唤醒(相当于调用了LockSupport.unpark),但是不会抛出异常
  4. 调用 interrupt 方法就会唤醒线程来响应线程中断。 所以这也是 对 2、3两条内容的解释了。

参考文献:

  1. 博客: Java并发之线程中断
  2. 并发编程的艺术
  3. 疯狂java讲义
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值