Java并发编程——线程基础查漏补缺

Thread

使用Java的同学对Thread应该不陌生了,线程的创建和启动等这里就不讲了,这篇主要讲几个容易被忽视的方法以及线程状态迁移。

wait/notify/notifyAll

首先我们要明白这三个方法是定义在Object类中,他们起到的作用就是允许线程就资源的锁定状态进行通信。这里所说的资源一般就是指的我们常说的共享对象了,也就是说针对共享对象的锁定状态可以通过wait/notify/notifyAll来进行通信。我们先看下如何使用的,并对相应原理进行展开。

wait


wait方法告诉调用线程放弃锁定并进入休眠状态,直到其他某个线程进入同一个监视器(monitor)并调用notify方法。wait方法在等待之前释放锁,并在wait方法返回之前重新获取锁。wait方法实际上和同步锁紧密集成,补充同步机制无法直接实现的功能。
需要注意到wait方法在jdk源码中是final并且是native的本地方法,我们无法去覆盖此方法。
调用wait一般的方式如下:

synchronized(lockObject) {
    while(!condition) {
        lockObject.wait();
    }
    // 这里进行相应处理;
}

注意这里使用while进行条件判断而没有使用if进行条件判断,原因是这里有个很重要的点容易被忽视,下面来自官方的建议:

应该在循环中检查等待条件,原因是处于等待状态的线程可能会收到错误的警报和伪唤醒,如果不在循环条件中等待,程序就会在没有满足结束条件的情况下退出。

notify


notify方法唤醒了同一个对象上调用wait的线程。这里要注意notify并没有放弃对资源的锁定,他告诉等待的线程可以唤醒,但是作用在notify上synchronized同步块完成之前,实际上是不会放弃锁。因此,如果通知线程在同步块内,调用notify方法后,需要在进行10s的其他操作,那么等待的线程将会再至少等待10s。
notify一般的使用方式如下:

synchronized(lockObject) {
    // 确定条件
    lockObject.notify();
    // 如果需要可以加任意代码
}

notifyAll


notifyAll会唤醒在同一个对象上调用wait方法的所有线程。在大多数情况下优先级最高的线程将被执行,但是也是无法完全保证会是这样。其他的与notify相同。

使用例子


下面的代码示例实现了队列空和满时线程阻塞已经非空非满时的通知:

生产者:

class Producer implements Runnable {
    private final List<Integer> taskQueue;
    private final int MAX_CAPACITY;

    public Producer(List<Integer> sharedQueue, int size) {
        this.taskQueue = sharedQueue;
        this.MAX_CAPACITY = size;
    }

    @Override
    public void run() {
        int counter = 0;
        while (true) {
            try {
                produce(counter++);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

    private void produce(int i) throws InterruptedException {
        synchronized (taskQueue) {
            while (taskQueue.size() == MAX_CAPACITY) {
                System.out.println("队列已满,线程" + Thread.currentThread().getName() + "进入等待,队列长度:" + taskQueue.size());
                taskQueue.wait();
            }

            Thread.sleep(1000);
            taskQueue.add(i);
            System.out.println("生产:" + i);
            taskQueue.notifyAll();
        }
    }
}

消费者:

class Consumer implements Runnable {
    private final List<Integer> taskQueue;

    public Consumer(List<Integer> sharedQueue) {
        this.taskQueue = sharedQueue;
    }

    @Override
    public void run() {
        while (true) {
            try {
                consume();
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

    private void consume() throws InterruptedException {
        synchronized (taskQueue) {
            while (taskQueue.isEmpty()) {
                System.out.println("队列已空,线程" + Thread.currentThread().getName() + "进入等待,队列长度:" + taskQueue.size());
                taskQueue.wait();
            }
            Thread.sleep(1000);
            int i = (Integer) taskQueue.remove(0);
            System.out.println("消费:" + i);
            taskQueue.notifyAll();
        }
    }
}

测试代码:

public class ProducerConsumerExampleWithWaitAndNotify {
    public static void main(String[] args) {
        List<Integer> taskQueue = new ArrayList<>();
        int MAX_CAPACITY = 5;
        Thread tProducer = new Thread(new Producer(taskQueue, MAX_CAPACITY), "Producer");
        Thread tConsumer = new Thread(new Consumer(taskQueue), "Consumer");
        tProducer.start();
        tConsumer.start();
    }
}

部分输出如下:

生产:0
生产:1
生产:2
生产:3
生产:4
队列已满,线程Producer进入等待,队列长度:5
消费:0
消费:1
消费:2
消费:3
消费:4
队列已空,线程Consumer进入等待,队列长度:0

yield/join

yield


从字面意思理解yield可以是谦让、放弃、屈服、投降的意思。一个要“谦让”的线程其实是在告诉虚拟机他愿意让其他线程安排到他的前面,这表明他没有说明重要的事情要做了。注意了这只是个提示,并不能保证能起到任何效果。
yield在Thread.java中定义如下:

/**
 * 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.
*/
public static native void yield();

从这里面我们总结出一些重点(有关线程状态后面会讲到):

  • yield方法是一个静态的并且是native的方法。
  • yield告诉当前执行的线程为线程池中其他具有相同优先级的线程提供机会。
  • 不能保证yield会立即使当前正在执行的线程处于可运行状态。
  • 他只能使得线程从运行状态变成可运行状态,而无法做其他状态改变。

yield使用例子:

public class YieldExample {
   public static void main(String[] args) {
      Thread producer = new Producer();
      Thread consumer = new Consumer();
       
      producer.setPriority(Thread.MIN_PRIORITY); // 最低优先级
      consumer.setPriority(Thread.MAX_PRIORITY); // 最高优先级
       
      producer.start();
      consumer.start();
   }
}
 
class Producer extends Thread {
   public void run() {
      for (int i = 0; i < 5; i++) {
         System.out.println("生产者 : 生产 " + i);
         Thread.yield();
      }
   }
}
 
class Consumer extends Thread {
   public void run() {
      for (int i = 0; i < 5; i++) {
         System.out.println("消费者 : 消费 " + i);
         Thread.yield();
      }
   }
}

当注释两个“Thread.yield();”时输出:

消费者 : 消费 0
消费者 : 消费 1
消费者 : 消费 2
消费者 : 消费 3
消费者 : 消费 4
生产者 : 生产 0
生产者 : 生产 1
生产者 : 生产 2
生产者 : 生产 3
生产者 : 生产 4

当不注释两个“Thread.yield();”时输出:

生产者 : 生产 0
消费者 : 消费 0
生产者 : 生产 1
消费者 : 消费 1
生产者 : 生产 2
消费者 : 消费 2
生产者 : 生产 3
消费者 : 消费 3
生产者 : 生产 4
消费者 : 消费 4

join


join方法用于将线程当前执行点连接到另一个线程的执行结束,这样这个线程就不会开始运行直到另一个线程结束。在Thread实例上调用join,则当前运行的线程将会阻塞,直到这个Thread实例完成执行。
简要摘抄Thread.java源码中join的定义:

// Waits for this thread to die.
public final void join() throws InterruptedException

join还有可以传入时间参数的重载方法,这个可以时join的效果在特定时间后无效。当达到超时时间时,主线程和taskThread是同样可能的执行者候选。但是join和sleep一样,依赖于OS进行计时,不应该假定刚好等待指定的时间。
join和sleep一样也通过InterruptedException来响应中断。
join使用示例:

public class JoinExample {
   public static void main(String[] args) throws InterruptedException {
      Thread t = new Thread(new Runnable() {
          public void run() {
              System.out.println("第一个任务启动");
              System.out.println("睡眠2s");
              try {
                  Thread.sleep(2000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              System.out.println("第一个任务完成");
          }
      });
      Thread t1 = new Thread(new Runnable() {
          public void run() {
              System.out.println("第二个任务完成");
          }
      });
      t.start();
      t.join();
      t1.start();
   }
}

输出结果:

第一个任务启动
睡眠2s
第一个任务完成
第二个任务完成

join原理分析


join在Thread.java中有三个重载方法:

public final void join() throws InterruptedException

public final synchronized void join(long millis) throws InterruptedException

public final synchronized void join(long millis, int nanos) throws InterruptedException

查看源码可以得知最终的实现核心部分都在join(long millis)中,我们来分析下这个方法源码:

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);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

首先可以看到这个方法是使用synchronized修饰的同步方法,从这个方法的源码可以看出join的核心就是使用wait来实现的,而外部条件就是isAlive(),可以断定,在非isAlive()时会进行notify。

线程状态

在Thread.java的源代码中就体现出了六种状态:

   /**
     * A thread state.  A thread can be in one of the following states:
     * <ul>
     * <li>{@link #NEW}<br>
     *     A thread that has not yet started is in this state.
     *     </li>
     * <li>{@link #RUNNABLE}<br>
     *     A thread executing in the Java virtual machine is in this state.
     *     </li>
     * <li>{@link #BLOCKED}<br>
     *     A thread that is blocked waiting for a monitor lock
     *     is in this state.
     *     </li>
     * <li>{@link #WAITING}<br>
     *     A thread that is waiting indefinitely for another thread to
     *     perform a particular action is in this state.
     *     </li>
     * <li>{@link #TIMED_WAITING}<br>
     *     A thread that is waiting for another thread to perform an action
     *     for up to a specified waiting time is in this state.
     *     </li>
     * <li>{@link #TERMINATED}<br>
     *     A thread that has exited is in this state.
     *     </li>
     * </ul>
     *
     * <p>
     * A thread can be in only one state at a given point in time.
     * These states are virtual machine states which do not reflect
     * any operating system thread states.
     *
     * @since   1.5
     * @see #getState
     */
    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;
    }

一般我们用如下图来表示状态迁移,注意相关方法。(注意:其中RUNNING和READY是无法直接获取的状态。)
线程状态

下一篇:Java并发编程——线程安全性深层原因

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值