Java 并发编程之多线程基础(一)

你真的了解线程吗?创建线程的常用方式有哪些?为什么不能重复调用 Start 方法?什么是单继承的局限?生产者与消费者如何实现?

1. 进程与线程

1.1 进程

进程是系统资源(CPU、内存等)分配的最小单位,它是程序执行时的一个实例。

通常情况下,进程和程序、应用可以看作是同一个概念,但一个程序可能有多个进程,例如,你可以在一台电脑上同时打开多个 QQ。打开的每一个 QQ 都对应一个进程,但所有的这些进程都属于程序 QQ。如下图:

每一个程序运行时系统都会为其创建一个进程,并为其分配资源,把该进程放入进程就绪队列,当进程调度器选中它时,就会为它分配 CPU 时间,此时程序才开始真正运行。因此,从微观上来看,程序的每一次运行,都可以看作是 “代码加载→代码执行→执行完毕” 一个完整的过程 。

1.2 线程

线程是 CPU 调度的最小单位,是“轻量级”的进程。

1.3 进程与线程的关系
  • 进程是线程的载体,一个进程至少有一个线程
  • 系统分配给每一个进程的资源在进程间是不共享,但每一个进程中的所有线程共享系统分配给该进程的资源
  • 进程的创建资源消耗较大,线程的创建几乎不需要任何资源,因为它所需要的资源,在创建进程的时候已经加载进来了
  • 相比于进程间通信,线程间通信十分简单

举个生活中的例子,进程相当于双向四车道的马路,线程相当于这条马路上的一条车道。

2. 常用的创建线程的方法

通常情况下,创建线程的方法有两种:

  • 继承 Thread 类
  • 实现 Runnable 接口
2.1 继承 Thread 类
2.1.1 语法
class 类名称 extends Thread{
    属性...;
    方法...;
    public void run(){
        线程主体;
    }
}
复制代码
2.1.2 实例
//源码:
public class ThreadByExtends_201810252239 extends Thread {
	
	public ThreadByExtends_201810252239() {}
	
	public ThreadByExtends_201810252239(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}
	
	public static void main(String[] args) {
		ThreadByExtends_201810252239 t1 = new ThreadByExtends_201810252239("THREAD-A-天王盖地虎");
		t1.start();
	}
	
}

//执行结果:
THREAD-A-天王盖地虎  0
THREAD-A-天王盖地虎  1
THREAD-A-天王盖地虎  2
THREAD-A-天王盖地虎  3
THREAD-A-天王盖地虎  4
复制代码
2.2 实现 Runnable 接口
2.2.1 语法
class 类名称 implements Runnable{
    属性...;
    方法...;
    public void run(){
        线程主体;
    }
}
复制代码
2.2.2 实例
//源码:
public class ThreadByImplements_201810260617 implements Runnable {

	@Override
	public void run() {
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

	public static void main(String[] args) {
		ThreadByImplements_201810260617 r1 = new ThreadByImplements_201810260617();
		Thread t1 = new Thread(r1, "THREAD-A-小鸡炖蘑菇");
		t1.start();
	}

}

//执行结果:
THREAD-A-小鸡炖蘑菇  0
THREAD-A-小鸡炖蘑菇  1
THREAD-A-小鸡炖蘑菇  2
THREAD-A-小鸡炖蘑菇  3
THREAD-A-小鸡炖蘑菇  4
复制代码
2.3 相关问题
2.3.1 通过继承 Thread 类创建线程时,为什么一定要覆写 run 方法?

查看 run 方法在 Thread 类中的定义:

//源码:
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}
复制代码

由 run 方法定义可知,默认情况下,run 方法的执行最终调用的是 runnable 接口中的方法,所以如果通过继承 Thread 实现线程的时候不覆写 run 方法,那这个线程最终什么事也没做。

2.3.2 启动线程的时候,为什么调用的是 start 方法而不是 run 方法?能直接调用 run 方法吗?

查看 start 方法在 Thread 类中的定义:

//源码:
public synchronized void start() {
    /**
     * 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".
     */
    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();
复制代码

由 start 方法定义可知,start 方法最终调用的是 start0 方法,在 start0 方法声明处使用了 native 关键字,该关键字表示调用本机操作系统函数,因此此处线程的启动必须通过 start 方法。

如果直接调用 run 方法会出现什么情况?

//源码:
public class ThreadByExtends_201810261951 extends Thread{

	public ThreadByExtends_201810261951() {}
	
	public ThreadByExtends_201810261951(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

	public static void main(String[] args) {
		ThreadByExtends_201810261951 t1 = new ThreadByExtends_201810261951("THREAD-A-宝塔镇河妖");
		t1.start();
		ThreadByExtends_201810261951 t2 = new ThreadByExtends_201810261951("THREAD-B-铁锅炖大鹅");
		t2.run();
	}

}

//执行结果:
main  0
main  1
main  2
THREAD-A-宝塔镇河妖  0
THREAD-A-宝塔镇河妖  1
main  3
main  4
THREAD-A-宝塔镇河妖  2
THREAD-A-宝塔镇河妖  3
THREAD-A-宝塔镇河妖  4
复制代码

由执行结果可知,当直接调用 run 方法的时候,实际上和普通方法的调用并没有任何区别——方法的运行是在 run 方法的调用线程,而不是子线程。

2.3.3 为什么不能重复调用 start 方法?

如果直接调用两次 start 方法会出现什么情况呢?

//源码:
public ThreadByExtends_201810262030() {}
	
	public ThreadByExtends_201810262030(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

	public static void main(String[] args) {
		ThreadByExtends_201810262030 t1 = new ThreadByExtends_201810262030("THREAD-A-宝塔镇河妖");
		t1.start();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t1.start();
	}

}

//执行结果:
THREAD-A-宝塔镇河妖  0
THREAD-A-宝塔镇河妖  1
THREAD-A-宝塔镇河妖  2
THREAD-A-宝塔镇河妖  3
THREAD-A-宝塔镇河妖  4
Exception in thread "main" java.lang.IllegalThreadStateException
	at java.lang.Thread.start(Thread.java:705)
	at com.smart.www.a_thread_and_runnable.ThreadByExtends_201810262030.main(ThreadByExtends_201810262030.java:42)
复制代码

由执行结果可知,当直接调用两次 start 方法时,程序会抛出 IllegalThreadStateException 异常。

接下来,看看 start 方法在 Thread 类中是如何定义的:

//源码:
public synchronized void start() {
    /**
     * 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".
     */
    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();
复制代码

由 start 方法定义可知,当 threadStatus != 0 时,程序会抛出 IllegalThreadStateException()。那 threadStatus 对应的是什么呢?查看 threadStatus 在 Thread 中的定义:

//源码:
/* Java thread status for tools,
 * initialized to indicate thread 'not yet started'
 */

private volatile int threadStatus = 0;
复制代码

由 threadStatus 变量的注释可知,当线程还没有启动的时候,threadStatus = 0。另外,由 start 方法定义中的注释可知,当 threadStatus = 0 时,线程对应的状态是 NEW。那这个 NEW 是在哪里定义的呢?查看 Thread 类定义可知,NEW 是枚举类 State 的一个对象,枚举类 State 定义如下:

//源码:
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;
}
复制代码

由枚举类 State 定义可知,当 Thread 启动之后,便会进入其他状态。当线程执行完毕的时候,线程的状态会变成 TERMINATED 而不是 NEW,因此,重复调用 start 方法时,程序会抛异常。

为了验证上面的分析,将之前的代码略作修改:

//源码:
public class ThreadByExtends_201810262030 extends Thread{

    public ThreadByExtends_201810262030() {}
	
	public ThreadByExtends_201810262030(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

	public static void main(String[] args) {
		ThreadByExtends_201810262030 t1 = new ThreadByExtends_201810262030("THREAD-A-宝塔镇河妖");
		System.out.println(t1.getState());
		t1.start();
		System.out.println(t1.getState());
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(t1.getState());
		t1.start();
	}

}

//执行结果:
NEW
RUNNABLE
THREAD-A-宝塔镇河妖  0
THREAD-A-宝塔镇河妖  1
THREAD-A-宝塔镇河妖  2
THREAD-A-宝塔镇河妖  3
THREAD-A-宝塔镇河妖  4
TERMINATED
Exception in thread "main" java.lang.IllegalThreadStateException
	at java.lang.Thread.start(Thread.java:705)
	at com.smart.www.a_thread_and_runnable.ThreadByExtends_201810262030.main(ThreadByExtends_201810262030.java:45)
复制代码

由执行结果可知,当线程还没有调用 start 方法的时候,此时 State 对应的是 NEW;当线程调用 start 方法之后,此时 State 对应的是 RUNNABLE;当线程执行完毕之后,此时 State 对应的是 TERMINATED,即:当再次调用 start 方法时,State 对应的不是 NEW,故抛异常。

2.3.4 两种创建线程方式的联系及区别
2.3.4.1 联系

观察 Thread 类部分代码:

//源码:
public class Thread implements Runnable {
…
/* What will be run. */
private Runnable target;
…

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) {
    …
    this.target = target;
    …
}

@Override
public void run() {
    if (target != null) {
        target.run();
    }
}
复制代码

由 Thread 类定义可知,Thread 与 Runnable 实现类是代理的关系,Thread 是 Runnable 实现类的代理,这也解释了为什么两种实现方式最终都需要通过 Thread 启动线程。

2.3.4.2 区别
  1. 继承 Thread 有单继承局限;
  2. 资源共享方式不同。

两种创建线程的方式在资源共享方面区别还是挺大的,不过由于资源共享涉及到同步问题。因此,将此部分概念放到 《死锁与同步》模块详述。

2.3.5 多个 Thread 使用同一个 Runnable 对象,最终创建的线程是同一个吗?它们之间什么关系?多个 Thread 使用多个 Runnable 呢?
2.3.5.1 多个 Thread 单个 Runnable

多个 Thread 使用同一个 Runnable 对象时,最终会创建多个 Thread 对象。由于多个 Thread 使用的是同一个 Runnable,所以,最终多个 Thread 之间数据共享,如:

//源码:
public class ThreadByImplements_201810262032 implements Runnable {

	private int bandit = 10;
	
	@Override
	public void run() {
		for (int i = 0; i < 25; i++) {
			if (bandit > 0) {
				System.out.println(Thread.currentThread().getName() + "  杀了  " + (bandit--) + "  土匪  " + this.hashCode());
			}
		}
	}

	public static void main(String[] args) {
		ThreadByImplements_201810262032 r1 = new ThreadByImplements_201810262032();
		new Thread(r1,"THREAD-张飞").start();
		new Thread(r1,"THREAD-赵云").start();
		new Thread(r1,"THREAD-吕布").start();
		new Thread(r1,"THREAD-董卓").start();
		new Thread(r1,"THREAD-孙尚香").start();
		new Thread(r1,"THREAD-华佗").start();
		new Thread(r1,"THREAD-曹操").start();
		new Thread(r1,"THREAD-夏侯惇").start();
		new Thread(r1).start();
		new Thread(r1).start();
	}

}

//执行结果:
THREAD-张飞  杀了  10  土匪  1578680387
THREAD-孙尚香  杀了  6  土匪  1578680387
THREAD-华佗  杀了  4  土匪  1578680387
THREAD-董卓  杀了  7  土匪  1578680387
THREAD-吕布  杀了  8  土匪  1578680387
THREAD-赵云  杀了  9  土匪  1578680387
THREAD-董卓  杀了  1  土匪  1578680387
THREAD-华佗  杀了  2  土匪  1578680387
THREAD-孙尚香  杀了  3  土匪  1578680387
THREAD-张飞  杀了  5  土匪  1578680387
复制代码

上面的执行结果不仅验证了推论——多个 Thread 使用同一个 Runnable 时,多个 Thread 之间是数据共享的,同时还说明 Thread 对象的个数是由创建 Thread 对象的次数决定的,跟 Runnable 个数没有关系。

2.3.5.2 多个 Thread 多个 Runnable

多个 Thread 使用多个 Runnable 对象时,最终也会创建多个 Thread 对象。同样的,由于多个 Thread 没有使用同一个 Runnable 对象,所以,最终多个 Thread 之间数据不共享,如:

//源码:
public class ThreadByImplements_201810262032 implements Runnable {

	private int bandit = 10;
	
	@Override
	public void run() {
		for (int i = 0; i < 25; i++) {
			if (bandit > 0) {
				System.out.println(Thread.currentThread().getName() + "  杀了  " + (bandit--) + "  土匪  " + this.hashCode());
			}
		}
	}

	public static void main(String[] args) {
		ThreadByImplements_201810262032 r1 = new ThreadByImplements_201810262032();
		ThreadByImplements_201810262032 r2 = new ThreadByImplements_201810262032();
		ThreadByImplements_201810262032 r3 = new ThreadByImplements_201810262032();
		ThreadByImplements_201810262032 r4 = new ThreadByImplements_201810262032();
		ThreadByImplements_201810262032 r5 = new ThreadByImplements_201810262032();
		ThreadByImplements_201810262032 r6 = new ThreadByImplements_201810262032();
		ThreadByImplements_201810262032 r7 = new ThreadByImplements_201810262032();
		ThreadByImplements_201810262032 r8 = new ThreadByImplements_201810262032();
		ThreadByImplements_201810262032 r9 = new ThreadByImplements_201810262032();
		ThreadByImplements_201810262032 r10 = new ThreadByImplements_201810262032();
		new Thread(r1,"THREAD-张飞").start();
		new Thread(r2,"THREAD-赵云").start();
		new Thread(r3,"THREAD-吕布").start();
		new Thread(r4,"THREAD-董卓").start();
		new Thread(r5,"THREAD-孙尚香").start();
		new Thread(r6,"THREAD-华佗").start();
		new Thread(r7,"THREAD-曹操").start();
		new Thread(r8,"THREAD-夏侯惇").start();
		new Thread(r9).start();
		new Thread(r10).start();
	}

}

//执行结果:
THREAD-赵云  杀了  10  土匪  258726645
THREAD-孙尚香  杀了  10  土匪  1513896901
THREAD-孙尚香  杀了  9  土匪  1513896901
THREAD-董卓  杀了  10  土匪  786514993
THREAD-吕布  杀了  10  土匪  441874245
THREAD-曹操  杀了  10  土匪  679081647
THREAD-曹操  杀了  9  土匪  679081647
THREAD-曹操  杀了  8  土匪  679081647
THREAD-张飞  杀了  10  土匪  1642210515
THREAD-曹操  杀了  7  土匪  679081647
THREAD-夏侯惇  杀了  10  土匪  1583687396
THREAD-吕布  杀了  9  土匪  441874245
THREAD-吕布  杀了  8  土匪  441874245
THREAD-董卓  杀了  9  土匪  786514993
THREAD-华佗  杀了  10  土匪  1785098644
THREAD-孙尚香  杀了  8  土匪  1513896901
…
复制代码
2.3.6 什么是单继承局限?

一言以蔽之:只能继承一个父类。因为 Thread 是普通类,因此通过继承 Thread 类创建线程的时候,不能再继承其他类。相比于继承 Thread 类创建线程的方法,通过实现 Runnable 创建线程的方法显得更灵活,因为 Runnable 的实现类可以同时实现多个接口。

2.3.7 Thread.currentThread() 和 this.currentThread() 联系和区别

说实话,当我第一次被问到这个问题的时候有点懵逼,因为我也没有仔细想过这个问题。就像你女朋友有天突然问你“如果我跟你妈同时掉进河里,你先救谁?”一样,只是觉得有些突兀。其实只要稍微看下 currentThread 方法在 Thread 类中的定义就可以一目了然了。

//源码:
/**
 * Returns a reference to the currently executing thread object.
 *
 * @return  the currently executing thread.
 */
public static native Thread currentThread();
复制代码

这个方法就是一个静态的调用本地方法的方法,没了。很多人不懂的地方可能不在于方法的定义上,而在于它们方法的使用上。

在使用上二者的区别在于,前者能用在所有的类(Thread 类、非 Thread 类)中,后者只能用在 Thread 中,与此同时,两者在 Thread 类中的使用效果并没有什么区别。

如果你还是不懂,那这样问可能就懂了:currentThread()、this.currentThread() 和 Thread.currentThread() 有什么区别?

//源码:
public class ThreadByExtend_201809052254 extends Thread {
	
	public ThreadByExtend_201809052254() {}
	
	public ThreadByExtend_201809052254(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		System.out.println(currentThread().getName());
		System.out.println(this.currentThread().getName());
		System.out.println(Thread.currentThread().getName());
	}
	
	public static void main(String[] args) {
		ThreadByExtend_201809052254 t1 = new ThreadByExtend_201809052254("THREAD-A-天王盖地虎");
		t1.start();
	}
	
}

//执行结果:  
THREAD-A-天王盖地虎
THREAD-A-天王盖地虎
THREAD-A-天王盖地虎


//源码:
public class ThreadByImplement_201809060656 implements Runnable {

	@Override
	public void run() {
		//此处如果使用此函数系统会提示 The method currentThread() is undefined for the type ThreadByImplement_201809052225
//		System.out.println(currentThread().getName());
		//此处如果使用此函数系统会提示 The method currentThread() is undefined for the type ThreadByImplement_201809052225
//		System.out.println(this.currentThread().getName());
		//此函数无影响
		System.out.println(Thread.currentThread().getName());
	}
	
	public static void main(String[] args) {
		ThreadByImplement_201809060656 r1 = new ThreadByImplement_201809060656();
		Thread t1 = new Thread(r1,"THREAD-A-天王盖地虎");
		Thread t2 = new Thread(r1,"THREAD-B-小鸡炖蘑菇");
		Thread t3 = new Thread(r1,"THREAD-C-宝塔镇河妖");
		t1.start();
		t2.start();
		t3.start();
	}

}

//执行结果:
THREAD-A-天王盖地虎
THREAD-C-宝塔镇河妖
THREAD-B-小鸡炖蘑菇
复制代码

这种问题产生的根本原因是基本概念不清,直接导致思考问题的出发点偏离正轨,进而导致行动错误,最终导致一些“神奇”的问题。

2.3.8 为什么不能调用 Stop?如何停止线程?

查看 stop 方法在 Thread 类中的定义:

//源码:
/**
 * …
 * @deprecated This method is inherently unsafe.  Stopping a thread with
 *       Thread.stop causes it to unlock all of the monitors that it
 *       has locked (as a natural consequence of the unchecked
 *       <code>ThreadDeath</code> exception propagating up the stack).  If
 *       any of the objects previously protected by these monitors were in
 *       an inconsistent state, the damaged objects become visible to
 *       other threads, potentially resulting in arbitrary behavior.  Many
 *       uses of <code>stop</code> should be replaced by code that simply
 *       modifies some variable to indicate that the target thread should
 *       stop running.  The target thread should check this variable
 *       regularly, and return from its run method in an orderly fashion
 *       if the variable indicates that it is to stop running.  If the
 *       target thread waits for long periods (on a condition variable,
 *       for example), the <code>interrupt</code> method should be used to
 *       interrupt the wait.
 */
@Deprecated
public final void stop() {}
复制代码

由 stop 方法注释(This method is inherently unsafe)可知,该方法在操作时会产生死锁的问题,因此不建议使用。

在 stop 方法的注释中,除了介绍为什么该方法不建议使用之外,还介绍了如何停止线程——通过变量控制,如:

//源码:
public class ThreadByExtends_201810281058 extends Thread{

	private boolean mStop = false;
	private int mNumber = 0;
	
	public ThreadByExtends_201810281058() {}
	
	public ThreadByExtends_201810281058(String n) {
		super(n);
	}
	
	public boolean isStop() {
		return mStop;
	}

	public void setStop(boolean stop) {
		this.mStop = stop;
	}

	@Override
	public void run() {
		super.run();
		while (!mStop) {
			System.out.println(Thread.currentThread().getName() + "  " + (++mNumber));
		}
		System.out.println(Thread.currentThread().getName() + "  " + "停止了");
	}

	public static void main(String[] args) {
		ThreadByExtends_201810281058 t1 = new ThreadByExtends_201810281058("THREAD-A-天王盖地虎");
		t1.start();
		try {
			Thread.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t1.setStop(true);
	}

}

//执行结果:
…
THREAD-A-天王盖地虎  26
THREAD-A-天王盖地虎  27
THREAD-A-天王盖地虎  28
THREAD-A-天王盖地虎  29
THREAD-A-天王盖地虎  30
THREAD-A-天王盖地虎  31
THREAD-A-天王盖地虎  停止了
复制代码

由上面的示例可知,通过控制变量的方法完全可以达到停止线程的目的。

2.3.9 除了这种创建线程的方法,还有其他方法吗?是什么?

创建线程的方法除了上面提到的两种之外,还有其他方法,如: Callable + Future

//源码:
public class ThreadByCallable_201810281535 implements Callable<Integer>{

	private int result;
	
	@Override
	public Integer call() throws Exception {
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
			result = i;
		}
		return result;
	}
	
	public static void main(String[] args) {
		ExecutorService executor = Executors.newCachedThreadPool();
		ThreadByCallable_201810281535 c1 = new ThreadByCallable_201810281535();
        Future<Integer> result = executor.submit(c1);
        executor.shutdown();
        try {
			System.out.println("Result " + result.get());
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
	}

}

//执行结果:
pool-1-thread-1  0
pool-1-thread-1  1
pool-1-thread-1  2
pool-1-thread-1  3
pool-1-thread-1  4
Result 4
复制代码

由于其他创建线程的方法涉及本文未提到的知识,故在此不详述。

3. 线程的状态(生命周期)

任何一个线程都有五种状态,即:创建、就绪、运行、阻塞、终止。线程各状态之间的转换如下图所示:

3.1 创建状态

使用 new 关键字创建完线程对象之后,线程就进入该状态。也就是说,系统给 Thread 对象分配了内存之后,这个对象就处于这个状态了。

3.2 就绪状态

当 Thread 对象调用 start 方法之后,线程就进入该状态。处于这个状态的线程将进入线程队列排队,等待 CPU 的调度。

3.3 运行状态

当线程队列中的线程被 CPU 调度的时候,线程就进入该状态。此时将执行线程的 run 方法。

3.4 阻塞状态

当处于运行状态的线程调用 suspend、sleep、wait 等方法时,线程就进入该状态。处于阻塞状态的线程不能进入线程队列排队,只有当引起阻塞的原因消失之后,线程才能进入线程队列排队。

3.5 阻终止状态

当线程调用 stop 方法或者 run 方法执行完毕之后,线程就进入该状态。处于该状态的线程不能再运行,详情请参考《2.3.3 为什么不能重复调用 start 方法?》。

4. 线程常用方法

4.1 线程名
4.1.1 语法
public final String getName()
复制代码
4.1.2 示例
//源码:
public class ThreadByExtends_201810281917 extends Thread{
	
	public ThreadByExtends_201810281917() {}
	
	public ThreadByExtends_201810281917(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

	public static void main(String[] args) {
		ThreadByExtends_201810252239 t1 = new ThreadByExtends_201810252239("THREAD-A-天王盖地虎");
		ThreadByExtends_201810252239 t2 = new ThreadByExtends_201810252239("THREAD-B-宝塔镇河妖");
		ThreadByExtends_201810252239 t3 = new ThreadByExtends_201810252239("THREAD-C-小鸡炖蘑菇");
		ThreadByExtends_201810252239 t4 = new ThreadByExtends_201810252239();
		ThreadByExtends_201810252239 t5 = new ThreadByExtends_201810252239();
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
	}

}

//执行结果:
THREAD-A-天王盖地虎  0
Thread-0  0
Thread-0  1
THREAD-B-宝塔镇河妖  0
THREAD-B-宝塔镇河妖  1
THREAD-B-宝塔镇河妖  2
THREAD-C-小鸡炖蘑菇  0
THREAD-C-小鸡炖蘑菇  1
THREAD-B-宝塔镇河妖  3
Thread-0  2
Thread-0  3
Thread-0  4
Thread-1  0
Thread-1  1
Thread-1  2
Thread-1  3
THREAD-A-天王盖地虎  1
THREAD-A-天王盖地虎  2
THREAD-A-天王盖地虎  3
THREAD-A-天王盖地虎  4
Thread-1  4
THREAD-B-宝塔镇河妖  4
THREAD-C-小鸡炖蘑菇  2
THREAD-C-小鸡炖蘑菇  3
THREAD-C-小鸡炖蘑菇  4
复制代码
4.2 状态检查
4.2.1 语法
public final native boolean isAlive()
复制代码
4.2.2 示例
//源码:
public class ThreadByExtends_201810281928 extends Thread{
	
	public ThreadByExtends_201810281928() {}
	
	public ThreadByExtends_201810281928(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

	public static void main(String[] args) {
		ThreadByExtends_201810252239 t1 = new ThreadByExtends_201810252239("THREAD-A-天王盖地虎");
		System.out.println("Is Alive" + " " + t1.isAlive());
		t1.start();
		System.out.println("Is Alive" + " " + t1.isAlive());
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("Is Alive" + " " + t1.isAlive());
	}

}

//执行结果:
Is Alive false
Is Alive true
THREAD-A-天王盖地虎  0
THREAD-A-天王盖地虎  1
THREAD-A-天王盖地虎  2
THREAD-A-天王盖地虎  3
THREAD-A-天王盖地虎  4
Is Alive false
复制代码
4.3 强制运行
4.3.1 语法
public final native boolean isAlive()
复制代码
4.3.2 示例
//源码:
//1. Normal
public class ThreadByExtends_201810281950 extends Thread{
	
	public ThreadByExtends_201810281950() {}
	
	public ThreadByExtends_201810281950(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

	public static void main(String[] args) {
		outputNormal();
//		outputJoin();
	}
	
	private static void outputNormal(){
		ThreadByExtends_201810281950 t1 = new ThreadByExtends_201810281950("THREAD-A-天王盖地虎");
		t1.start();
		for (int i = 0; i < 10; i++) {
			System.out.println("Main" + " " + i);
		}
	}
	
	private static void outputJoin(){
		ThreadByExtends_201810281950 t1 = new ThreadByExtends_201810281950("THREAD-A-天王盖地虎");
		t1.start();
		for (int i = 0; i < 10; i++) {
			if(i == 5){
				try {
					t1.join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("Main" + " " + i);	
		}
	}

}

//执行结果:
Main 0
Main 1
Main 2
THREAD-A-天王盖地虎  0
THREAD-A-天王盖地虎  1
THREAD-A-天王盖地虎  2
Main 3
Main 4
THREAD-A-天王盖地虎  3
THREAD-A-天王盖地虎  4
Main 5
THREAD-A-天王盖地虎  5
THREAD-A-天王盖地虎  6
THREAD-A-天王盖地虎  7
Main 6
Main 7
Main 8
THREAD-A-天王盖地虎  8
Main 9
THREAD-A-天王盖地虎  9

//源码:
//2. Join
public class ThreadByExtends_201810281950 extends Thread{
	
	public ThreadByExtends_201810281950() {}
	
	public ThreadByExtends_201810281950(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

	public static void main(String[] args) {
//		outputNormal();
		outputJoin();
	}
	
	private static void outputNormal(){
		ThreadByExtends_201810281950 t1 = new ThreadByExtends_201810281950("THREAD-A-天王盖地虎");
		t1.start();
		for (int i = 0; i < 10; i++) {
			System.out.println("Main" + " " + i);
		}
	}
	
	private static void outputJoin(){
		ThreadByExtends_201810281950 t1 = new ThreadByExtends_201810281950("THREAD-A-天王盖地虎");
		t1.start();
		for (int i = 0; i < 10; i++) {
			if(i == 5){
				try {
					t1.join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("Main" + " " + i);	
		}
	}

}

//执行结果:
Main 0
Main 1
THREAD-A-天王盖地虎  0
THREAD-A-天王盖地虎  1
Main 2
Main 3
Main 4
THREAD-A-天王盖地虎  2
THREAD-A-天王盖地虎  3
THREAD-A-天王盖地虎  4
THREAD-A-天王盖地虎  5
THREAD-A-天王盖地虎  6
THREAD-A-天王盖地虎  7
THREAD-A-天王盖地虎  8
THREAD-A-天王盖地虎  9
Main 5
Main 6
Main 7
Main 8
Main 9
复制代码

由上面的示例可知,当线程对象调用 join 方法之后,其他正在运行的线程必须让出 CPU,等待此线程完成之后才可以继续运行。

4.4 休眠
4.4.1 语法
public static native void sleep(long millis)
复制代码
4.4.2 示例
//源码:
public class ThreadByExtends_201810282125 extends Thread{

	public ThreadByExtends_201810282125() {}
	
	public ThreadByExtends_201810282125(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5; i++) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

	public static void main(String[] args) {
		ThreadByExtends_201810282125 t1 = new ThreadByExtends_201810282125("THREAD-A-天王盖地虎");
		t1.start();
	}	

}

//执行结果:
THREAD-A-天王盖地虎  0
THREAD-A-天王盖地虎  1
THREAD-A-天王盖地虎  2
THREAD-A-天王盖地虎  3
THREAD-A-天王盖地虎  4
复制代码

从上面的执行结果里是不是感觉不到线程休眠的效果,那就用心去感受吧,少年。

4.5 中断执行
4.5.1 语法
public void interrupt()
复制代码
4.5.2 示例
//源码:
public class ThreadByExtends_201810282135 extends Thread{

	public ThreadByExtends_201810282135() {}
	
	public ThreadByExtends_201810282135(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		System.out.println(Thread.currentThread().getName() + "  1.开始休眠");
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			System.out.println(Thread.currentThread().getName() + "  *.休眠中断");
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + "  2.休眠结束");
	}

	public static void main(String[] args) {
		ThreadByExtends_201810282135 t1 = new ThreadByExtends_201810282135("THREAD-A-天王盖地虎");
		t1.start();
		try {
			Thread.sleep(200);
			t1.interrupt();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}

//执行结果:
THREAD-A-天王盖地虎  1.开始休眠
THREAD-A-天王盖地虎  *.休眠中断
java.lang.InterruptedException: sleep interrupted
THREAD-A-天王盖地虎  2.休眠结束
	at java.lang.Thread.sleep(Native Method)
	at com.smart.www.a_thread_and_runnable.ThreadByExtends_201810282135.run(ThreadByExtends_201810282135.java:31)
复制代码
4.6 后台运行
4.6.1 语法
public final void setDaemon(boolean on)
复制代码
4.6.2 示例
//源码:
public class ThreadByExtends_201810282143 extends Thread{

	public ThreadByExtends_201810282143() {}
	
	public ThreadByExtends_201810282143(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		while (true) {
			System.out.println(Thread.currentThread().getName());
		}
	}

	public static void main(String[] args) {
		ThreadByExtends_201810282143 t1 = new ThreadByExtends_201810282143("THREAD-A-天王盖地虎");
		t1.setDaemon(true);
		t1.start();
		try {
			Thread.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(t1.getState());
	}

}

//执行结果:
…
THREAD-A-天王盖地虎
THREAD-A-天王盖地虎
RUNNABLE
THREAD-A-天王盖地虎
THREAD-A-天王盖地虎
…
复制代码

由上面的示例可知,线程主体里面的执行的是死循环,因此,正常情况下,Eclipse 的 Console 会一直输出线程的名字。但由于在启动线程之前调用了 setDaemon 方法,所以,虽然线程的主体是一个死循环,但 Java 进程依然可以结束。

下图就是上面示例控制台输出的数据,不难看出,此时的 Java 进程已经结束,但从日志输出中可以看出,此时的线程处于 RUNNABLE 状态,即:虽然进程结束了,但后台线程仍然在运行。

4.7 优先级
4.7.1 语法
public final void setPriority(int newPriority)
复制代码
4.7.2 示例
//源码:
public class ThreadByExtends_201810292043 extends Thread {

	public ThreadByExtends_201810292043() {}
	
	public ThreadByExtends_201810292043(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}
	
	public static void main(String[] args) {
		ThreadByExtends_201810292043 t1 = new ThreadByExtends_201810292043("THREAD-A-天王盖地虎");
		ThreadByExtends_201810292043 t2 = new ThreadByExtends_201810292043("THREAD-B-宝塔镇河妖");
		ThreadByExtends_201810292043 t3 = new ThreadByExtends_201810292043("THREAD-C-小鸡炖蘑菇");
		t1.setPriority(MIN_PRIORITY);
		t2.setPriority(NORM_PRIORITY);
		t3.setPriority(MAX_PRIORITY);
		t1.start();
		t2.start();
		t3.start();
	}

}

//执行结果:
THREAD-B-宝塔镇河妖  0
THREAD-B-宝塔镇河妖  1
THREAD-B-宝塔镇河妖  2
THREAD-C-小鸡炖蘑菇  0
THREAD-C-小鸡炖蘑菇  1
THREAD-C-小鸡炖蘑菇  2
THREAD-A-天王盖地虎  0
THREAD-A-天王盖地虎  1
THREAD-A-天王盖地虎  2
THREAD-A-天王盖地虎  3
THREAD-C-小鸡炖蘑菇  3
THREAD-B-宝塔镇河妖  3
THREAD-B-宝塔镇河妖  4
THREAD-C-小鸡炖蘑菇  4
THREAD-A-天王盖地虎  4
复制代码

由上面的示例可知,虽然线程的给各线程都设置了优先级,但并不代表优先级高就一定先执行,最终决定哪个线程先执行的还是 CPU。

4.8 礼让
4.8.1 语法
public static native void yield()
复制代码
4.8.2 示例
//源码:
public class ThreadByExtends_201810292053 extends Thread {

	public ThreadByExtends_201810292053() {}
	
	public ThreadByExtends_201810292053(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
			if(i == 2){
				System.out.println(Thread.currentThread().getName() + "  " + "线程礼让");
				Thread.currentThread().yield();
			}
		}
	}
	
	public static void main(String[] args) {
		ThreadByExtends_201810292053 t1 = new ThreadByExtends_201810292053("THREAD-A-天王盖地虎");
		ThreadByExtends_201810292053 t2 = new ThreadByExtends_201810292053("THREAD-B-宝塔镇河妖");
		ThreadByExtends_201810292053 t3 = new ThreadByExtends_201810292053("THREAD-C-小鸡炖蘑菇");
		t1.start();
		t2.start();
		t3.start();
	}

}

//执行结果:
THREAD-A-天王盖地虎  0
THREAD-B-宝塔镇河妖  0
THREAD-B-宝塔镇河妖  1
THREAD-C-小鸡炖蘑菇  0
THREAD-C-小鸡炖蘑菇  1
THREAD-B-宝塔镇河妖  2
THREAD-A-天王盖地虎  1
THREAD-B-宝塔镇河妖  线程礼让
THREAD-C-小鸡炖蘑菇  2
THREAD-C-小鸡炖蘑菇  线程礼让
THREAD-B-宝塔镇河妖  3
THREAD-A-天王盖地虎  2
THREAD-A-天王盖地虎  线程礼让
THREAD-B-宝塔镇河妖  4
THREAD-C-小鸡炖蘑菇  3
THREAD-A-天王盖地虎  3
THREAD-C-小鸡炖蘑菇  4
THREAD-A-天王盖地虎  4
复制代码

由上面的示例可知,每当线程主体满足条件(i == 2)时,都会让出 CPU,让其他线程先执行,然后直接进入线程队列等待调度。

4.9 相关问题
4.9.1 线程自动命名是如何实现的?

创建线程对象的时候,如果不在构造方法里面传线程名,那么系统将会自动为其命名,如:

//源码:
public class ThreadByExtends_201810292216 extends Thread {

	public ThreadByExtends_201810292216() {}
	
	public ThreadByExtends_201810292216(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		System.out.println(Thread.currentThread().getName());
	}
	
	public static void main(String[] args) {
		ThreadByExtends_201810292216 t1 = new ThreadByExtends_201810292216("THREAD-A-天王盖地虎");
		ThreadByExtends_201810292216 t2 = new ThreadByExtends_201810292216();
		ThreadByExtends_201810292216 t3 = new ThreadByExtends_201810292216();
		t1.start();
		t2.start();
		t3.start();
	}

}

//执行结果:
THREAD-A-天王盖地虎
Thread-1
Thread-0
复制代码

一起看下 Thread 构造方法的定义:

//源码:
public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}
复制代码

Thread 构造方法中调用了 nextThreadNum 方法,nextThreadNum 定义如下:

//源码:
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}
复制代码

每次,当构造方法中没有传入线程名的时候,threadInitNumber 都会 +1,因此最终实现了上面例子所示的结果。

4.9.2 子线程可以比主线程晚消失吗?

可以,如:

//源码:
public class ThreadByExtends_201810292234 extends Thread {
	
	public ThreadByExtends_201810292234() {}
	
	public ThreadByExtends_201810292234(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String[] args) {
		ThreadByExtends_201810292234 t1 = new ThreadByExtends_201810292234("THREAD-A-天王盖地虎");
		t1.start();
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

}

//执行结果:
main  0
THREAD-A-天王盖地虎  0
main  1
main  2
main  3
main  4
THREAD-A-天王盖地虎  1
THREAD-A-天王盖地虎  2
THREAD-A-天王盖地虎  3
THREAD-A-天王盖地虎  4
复制代码
4.9.3 多个线程同时强制运行(join)的时候,哪个先执行?

主线程中,有多个线程同时强制运行时,此时主线程会让出 CPU,直至强制运行的线程执行完才开始继续执行。强制运行线程的执行顺序并没有“先来后到”之分,都是由 CPU 决定的,CPU 想调哪个就执行哪个,即:此时的线程和普通的线程没有任何区别。

//源码:
public class ThreadByExtends_201810281950 extends Thread{
	
	public ThreadByExtends_201810281950() {}
	
	public ThreadByExtends_201810281950(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

	public static void main(String[] args) {
		outputJoinMulti1();
	}
	private static void outputJoinMulti1(){
		ThreadByExtends_201810281950 t1 = new ThreadByExtends_201810281950("THREAD-A-天王盖地虎");
		ThreadByExtends_201810281950 t2 = new ThreadByExtends_201810281950("THREAD-B-宝塔镇河妖");
		ThreadByExtends_201810281950 t3 = new ThreadByExtends_201810281950("THREAD-C-小鸡炖蘑菇");
		t1.start();
		t2.start();
		t3.start();
		for (int i = 0; i < 10; i++) {
			if(i == 5){
				try {
					t1.join();
					t2.join();
					t3.join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("Main" + " " + i);	
		}
	}

}

//执行结果:
…
THREAD-B-宝塔镇河妖  6
Main 3
THREAD-A-天王盖地虎  2
Main 4
THREAD-B-宝塔镇河妖  7
THREAD-C-小鸡炖蘑菇  3
THREAD-B-宝塔镇河妖  8
THREAD-C-小鸡炖蘑菇  4
THREAD-A-天王盖地虎  3
THREAD-A-天王盖地虎  4
THREAD-C-小鸡炖蘑菇  5
THREAD-B-宝塔镇河妖  9
THREAD-C-小鸡炖蘑菇  6
THREAD-A-天王盖地虎  5
THREAD-C-小鸡炖蘑菇  7
THREAD-A-天王盖地虎  6
THREAD-C-小鸡炖蘑菇  8
THREAD-A-天王盖地虎  7
THREAD-C-小鸡炖蘑菇  9
THREAD-A-天王盖地虎  8
THREAD-A-天王盖地虎  9
Main 5
Main 6
Main 7
Main 8
Main 9
复制代码
4.9.4 后台运行(daemon)的作用?Thread.setDaemon 之后就可以执行完了?

由 《4.6 后台运行》 的讲解可知,线程在启动之前,如果调用了 setDaemon 方法,那即使线程主体内是死循环,此时的 Java 进程也可以结束,但此时的线程并没有结束——依然在运行(RUNNABLE)。

4.9.5 主方法的优先级
//源码:
public class ThreadByExtends_201810292043 extends Thread {

	public ThreadByExtends_201810292043() {}
	
	public ThreadByExtends_201810292043(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}
	
	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName() + "  " + Thread.currentThread().getPriority());
	}

}

//执行结果:
main  5
复制代码
//源码:
/**
 * The minimum priority that a thread can have.
 */
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;
复制代码

由 Thread 源码可知,线程优先级 Priority = 5 对应着 NORM_PRIORITY。

4.9.6 线程礼让,如果是多个线程而非两个,会出现什么情况?

Thread 类的 yield 方法并不会产生阻塞,只是让出这一次的 CPU 时间片。让出 CPU 时间片之后,Thread 对象立刻又到线程队列中排队等待调度了,而最终等待调度的线程哪个先执行,还是由 CPU 决定。因此,在主线程中,如果有多个线程礼让,礼让之后,哪个线程先执行,最终由 CPU 决定。

5. 同步与死锁

5.1 同步

说到线程,同步是一个不得不说的话题,为什么呢?因为多个线程访问同一资源时,如果不进行同步处理,那就会出问题。大家喜闻乐见的当然是卖票问题,如:

//源码:
public class ThreadByImplements_201810302136 implements Runnable{

	private int mTicket = 5;
	
	@Override
	public void run() {
		for (int i = 0; i < 25; i++) {
			if(mTicket > 0){
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "  " + (mTicket--));
			}
		}
	}
	
	public static void main(String[] args) {
		ThreadByImplements_201810302136 r1 = new ThreadByImplements_201810302136();
		Thread t1 = new Thread(r1,"THREAD-A-天王盖地虎");
		Thread t2 = new Thread(r1,"THREAD-B-宝塔镇河妖");
		Thread t3 = new Thread(r1,"THREAD-C-小鸡炖蘑菇");
		t1.start();
		t2.start();
		t3.start();
	}

}

//执行结果:
THREAD-A-天王盖地虎  3
THREAD-C-小鸡炖蘑菇  5
THREAD-B-宝塔镇河妖  4
THREAD-B-宝塔镇河妖  2
THREAD-A-天王盖地虎  2
THREAD-C-小鸡炖蘑菇  1
THREAD-B-宝塔镇河妖  0
THREAD-A-天王盖地虎  -1
复制代码

其实简单分析一下就能明白为什么会出现上面的执行结果。在线程主体中,判断票数是否大于零,如果票数大于零,则卖,反之,则不卖。这样就会出现一种情况:票数为 1 的时候,一个线程刚把票买走,系统还没有来得及减,另外一个线程又进来了,由于系统此时还没有来得及减,所以此时票数还是大于零,结果后来的线程也买到票了。

要解决上面的问题,必须使用同步。所谓同步是指在同一时间段内,只能有一个线程对数据进行操作,其他线程只能等这个线程操作完之后才可以操作。

线程同步的方法有两种:同步代码块和同步方法。接下来,分别用两种方式对上面的例子进行处理。

5.1.1 同步代码块
5.1.1.1 语法
synchronized (同步对象) {
	//需要同步的代码块
}
复制代码
5.1.1.2 实例
//源码:
public class ThreadByImplements_201810302208 implements Runnable {

	private int mTicket = 5;
	
	@Override
	public void run() {
		for (int i = 0; i < 25; i++) {
			synchronized (this) {
				if(mTicket > 0){
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "  " + (mTicket--));
				}
			}
		}
	}
	
	public static void main(String[] args) {
		ThreadByImplements_201810302208 r1 = new ThreadByImplements_201810302208();
		Thread t1 = new Thread(r1,"THREAD-A-天王盖地虎");
		Thread t2 = new Thread(r1,"THREAD-B-宝塔镇河妖");
		Thread t3 = new Thread(r1,"THREAD-C-小鸡炖蘑菇");
		t1.start();
		t2.start();
		t3.start();
	}

}

//执行结果:
THREAD-A-天王盖地虎  5
THREAD-A-天王盖地虎  4
THREAD-A-天王盖地虎  3
THREAD-A-天王盖地虎  2
THREAD-A-天王盖地虎  1
复制代码
5.1.2 同步方法
5.1.2.1 语法
synchronized 方法返回值 方法名(参数列表){}
复制代码
5.1.2.2 实例
//源码:
public class ThreadByImplements_201810302220 implements Runnable {

	private int mTicket = 5;
	
	@Override
	public void run() {
		for (int i = 0; i < 25; i++) {
			saleTicket();
		}
	}
	
	private synchronized void saleTicket(){
		if(mTicket > 0){
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "  " + (mTicket--));
		}
	}
	
	public static void main(String[] args) {
		ThreadByImplements_201810302220 r1 = new ThreadByImplements_201810302220();
		Thread t1 = new Thread(r1,"THREAD-A-天王盖地虎");
		Thread t2 = new Thread(r1,"THREAD-B-宝塔镇河妖");
		Thread t3 = new Thread(r1,"THREAD-C-小鸡炖蘑菇");
		t1.start();
		t2.start();
		t3.start();
	}

}

//执行结果:
THREAD-A-天王盖地虎  5
THREAD-A-天王盖地虎  4
THREAD-A-天王盖地虎  3
THREAD-A-天王盖地虎  2
THREAD-A-天王盖地虎  1
复制代码
5.2 死锁

同步解决了资源共享时数据错乱的问题,但并不代表同步处理越多越好。例如,周五的时候,你和女票在家做了一顿饭。吃完饭,女票让你刷锅,你让女票打扫卫生。小仙女可不想打扫卫生,于是说:“等你刷完锅,我就开始打扫卫生”。你一眼识破了女票诡计,于是也说了句:“等你扫完地,我就开始刷锅”。就这样,到了第二天,你们的锅还没有刷,卫生还没有打扫,这实际上就是死锁的概念。

所谓死锁是指两个线程在等待对方先执行完,造成了程序的停滞。接下来就根据上面例子的示范一下,死锁是如何出现的:

//源码:
public class ThreadByImplements_201810302344 implements Runnable {

	//由于是两个 Runnable 对象,所以必须使用静态成员变量才能实现资源共享
	private static GirlFriend gf = new GirlFriend();
	private static BoyFriend bf = new BoyFriend();
	private boolean speakFirst = false;
	
	@Override
	public void run() {
		if(speakFirst){
			synchronized (gf) {
				//1.先下手为强
				gf.say();
				try {
					//2.等待对方刷碗
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized (bf) {
					//3.对方刷完碗,自己开始打扫卫生
					gf.clean();
				}
			}
		}else{
			synchronized (bf) {
				//a.以其人之道,还治其人之身
				bf.say();
				try {
					//b.等待对方打扫卫生
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized (gf) {
					//c.对方打扫完卫生,自己开始刷锅
					bf.wash();
				}
			}
		}
	}

	public static void main(String[] args) {
		ThreadByImplements_201810302344 gfRunnable = new ThreadByImplements_201810302344();
		ThreadByImplements_201810302344 bfRunnable = new ThreadByImplements_201810302344();
		gfRunnable.speakFirst = true;
		bfRunnable.speakFirst = false;
		Thread gfThread = new Thread(gfRunnable);
		Thread bfThread = new Thread(bfRunnable);
		gfThread.start();
		bfThread.start();
	}

}

class GirlFriend {
	
	public void say(){
		System.out.println("女生对男生说:你先刷锅,我再打扫卫生");
	}
	
	public void clean(){
		System.out.println("女生开始打扫卫生");
	}
	
}

class BoyFriend {
	
	public void say(){
		System.out.println("男生对女生说:你先打扫卫生,我再刷锅");
	}
	
	public void wash(){
		System.out.println("男生开始刷锅");
	}
	
}

//执行结果:
女生对男生说:你先刷锅,我再打扫卫生
男生对女生说:你先打扫卫生,我再刷锅
//后面代码不再执行,程序进入死锁状态
复制代码

程序最终执行效果就是下面这个样子:

5.3 相关问题
5.3.1 两种创建线程的方式均能实现资源共享吗?如果能,怎么做?如果不能,为什么?
5.3.1.1 继承 Thread 类

通过继承 Thread 实现线程时,Thread 子类中定义的普通变量不可共享,静态变量可共享,但此时存在“资源共享,但不同步”问题。

接下来,对以下几种情况进行详细分析:

序号处理方式
1定义普通变量
2定义普通变量,并通过 synchronized 关键字同步当前对象
3定义静态变量
4定义静态变量,并通过 synchronized 关键字同步当前对象

5.3.1.1.1 定义普通变量

//源码:
public class ThreadByExtend_201809072104 extends Thread {

	private int bandit = 5;
	
	public ThreadByExtend_201809072104() {}
	
	public ThreadByExtend_201809072104(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		
		for (int i = 0; i < 25; i++) {
			if (bandit > 0) {
				System.out.println(Thread.currentThread().getName() + "  杀了  " + (bandit--) + "  土匪");
			}
		}
		
	}
	
	public static void main(String[] args) {
		ThreadByExtend_201809072104 t1 = new ThreadByExtend_201809072104("THREAD-张飞");
		ThreadByExtend_201809072104 t2 = new ThreadByExtend_201809072104("THREAD-赵云");
		ThreadByExtend_201809072104 t3 = new ThreadByExtend_201809072104("THREAD-吕布");
		t1.start();
		t2.start();
		t3.start();
	}

}

//执行结果:
THREAD-张飞  杀了  5  土匪
THREAD-张飞  杀了  4  土匪
THREAD-张飞  杀了  3  土匪
THREAD-张飞  杀了  2  土匪
THREAD-张飞  杀了  1  土匪
THREAD-吕布  杀了  5  土匪
THREAD-赵云  杀了  5  土匪
THREAD-赵云  杀了  4  土匪
THREAD-赵云  杀了  3  土匪
THREAD-赵云  杀了  2  土匪
THREAD-吕布  杀了  4  土匪
THREAD-赵云  杀了  1  土匪
THREAD-吕布  杀了  3  土匪
THREAD-吕布  杀了  2  土匪
THREAD-吕布  杀了  1  土匪

//第一种方式:定义普通变量(不做任何处理)
//结论:资源不共享
复制代码

5.3.1.1.2 定义普通变量,并通过 synchronized 关键字同步当前对象

//源码:
public class ThreadByExtend_201809072104 extends Thread {

	private int bandit = 5;
	
	public ThreadByExtend_201809072104() {}
	
	public ThreadByExtend_201809072104(String n) {
		super(n);
	}
	
	@Override
	public void run() {
	
		for (int i = 0; i < 25; i++) {
			synchronized (this) {
				if (bandit > 0) {
					System.out.println(Thread.currentThread().getName() + "  杀了  " + (bandit--) + "  土匪  " + this.hashCode());
				}
			}
		}
		
	}
	
	public static void main(String[] args) {
		ThreadByExtend_201809072104 t1 = new ThreadByExtend_201809072104("THREAD-张飞");
		ThreadByExtend_201809072104 t2 = new ThreadByExtend_201809072104("THREAD-赵云");
		ThreadByExtend_201809072104 t3 = new ThreadByExtend_201809072104("THREAD-吕布");
		t1.start();
		t2.start();
		t3.start();
	}

}

//执行结果:
THREAD-张飞  杀了  5  土匪  1584869782
THREAD-张飞  杀了  4  土匪  1584869782
THREAD-张飞  杀了  3  土匪  1584869782
THREAD-张飞  杀了  2  土匪  1584869782
THREAD-赵云  杀了  5  土匪  1751905002
THREAD-赵云  杀了  4  土匪  1751905002
THREAD-赵云  杀了  3  土匪  1751905002
THREAD-吕布  杀了  5  土匪  736376815
THREAD-吕布  杀了  4  土匪  736376815
THREAD-吕布  杀了  3  土匪  736376815
THREAD-赵云  杀了  2  土匪  1751905002
THREAD-张飞  杀了  1  土匪  1584869782
THREAD-赵云  杀了  1  土匪  1751905002
THREAD-吕布  杀了  2  土匪  736376815
THREAD-吕布  杀了  1  土匪  736376815

//第二种方式:定义普通变量,并通过 synchronized 关键字同步当前对象
//结论:资源不共享
//synchronized 关键字的主要作用是同步,而同步的前提是资源共享,所以,此处的同步处理完全是没必要的
复制代码

5.3.1.1.3 定义静态变量

//源码:
public class ThreadByExtend_201809072104 extends Thread {

	private static int bandit = 5;
	
	public ThreadByExtend_201809072104() {}
	
	public ThreadByExtend_201809072104(String n) {
		super(n);
	}
	
	@Override
	public void run() {
	
		for (int i = 0; i < 25; i++) {
			if (bandit > 0) {
				System.out.println(Thread.currentThread().getName() + "  杀了  " + (bandit--) + "  土匪  " + this.hashCode());
			}
		}
		
	}
	
	public static void main(String[] args) {
		ThreadByExtend_201809072104 t1 = new ThreadByExtend_201809072104("THREAD-张飞");
		ThreadByExtend_201809072104 t2 = new ThreadByExtend_201809072104("THREAD-赵云");
		ThreadByExtend_201809072104 t3 = new ThreadByExtend_201809072104("THREAD-吕布");
		t1.start();
		t2.start();
		t3.start();
	}

}

//执行结果:
THREAD-赵云  杀了  5  土匪  576357660
THREAD-赵云  杀了  2  土匪  576357660
THREAD-赵云  杀了  1  土匪  576357660
THREAD-吕布  杀了  3  土匪  1584869782
THREAD-张飞  杀了  4  土匪  366287508

//第三种方式:定义静态变量
//结论:达到资源共享的目的,但存在“资源共享,但不同步”问题
复制代码

5.3.1.1.4 定义静态变量,并通过 synchronized 关键字同步当前对象

//源码:
public class ThreadByExtend_201809072104 extends Thread {

	private static int bandit = 5;
	
	public ThreadByExtend_201809072104() {}
	
	public ThreadByExtend_201809072104(String n) {
		super(n);
	}
	
	@Override
	public void run() {
	
		for (int i = 0; i < 25; i++) {
			synchronized (this) {
				if (bandit > 0) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "  杀了  " + (bandit--) + "  土匪  " + this.hashCode());
				}
			}
		}
		
	}
	
	public static void main(String[] args) {
		ThreadByExtend_201809072104 t1 = new ThreadByExtend_201809072104("THREAD-张飞");
		ThreadByExtend_201809072104 t2 = new ThreadByExtend_201809072104("THREAD-赵云");
		ThreadByExtend_201809072104 t3 = new ThreadByExtend_201809072104("THREAD-吕布");
		t1.start();
		t2.start();
		t3.start();
	}

}

//执行结果:
THREAD-吕布  杀了  5  土匪  1584869782
THREAD-赵云  杀了  4  土匪  576357660
THREAD-张飞  杀了  5  土匪  366287508
THREAD-赵云  杀了  3  土匪  576357660
THREAD-吕布  杀了  3  土匪  1584869782
THREAD-张飞  杀了  2  土匪  366287508
THREAD-赵云  杀了  1  土匪  576357660
THREAD-吕布  杀了  -1  土匪  1584869782
THREAD-张飞  杀了  0  土匪  366287508

//第四种方式:定义静态变量,并通过 synchronized 关键字同步当前对象(验证第三种方式提到的问题)
//结论:未解决“资源共享,但不同步”问题
复制代码
5.3.1.2 实现 Runnable 接口

通过实现 Runnable 接口实现线程时,可以方便地实现资源共享,但此时的“资源共享、同步”存在倾斜性。

接下来,对以下几种情况进行详细分析:

序号处理方式
1定义普通变量,只创建一个 Runnable 实现类,不同的 Thread 使用同一个 Runnable 实现类(验证资源共享)
2定义普通变量,创建三个 Runnable 实现类,不同的 Thread 使用不同的 Runnable 实现类(验证资源共享)
3定义普通变量,只创建一个 Runnable 实现类,不同的 Thread 使用同一个 Runnable 实现类(验证资源同步)
4定义普通变量,只创建一个 Runnable 实现类,不同的 Thread 使用同一个 Runnable 实现类(验证 Synchronized 关键字)
5定义静态变量,只创建一个 Runnable 实现类,不同的 Thread 使用同一个 Runnable 实现类(验证资源共享)
6定义静态变量,创建三个 Runnable 实现类,不同的 Thread 使用不同的 Runnable 实现类(验证资源共享)
7定义静态变量,只创建一个 Runnable 实现类,不同的 Thread 使用同一个 Runnable 实现类(验证资源同步)
8定义静态变量,创建三个 Runnable 实现类,不同的 Thread 使用不同的 Runnable 实现类(验证资源同步)
9定义静态变量,只创建一个 Runnable 实现类,不同的 Thread 使用同一个 Runnable 实现类(验证 Synchronized 关键字)
10定义静态变量,创建三个 Runnable 实现类,不同的 Thread 使用不同的 Runnable 实现类(验证 Synchronized 关键字)

5.3.1.2.1 定义普通变量,只创建一个 Runnable 实现类,不同的 Thread 使用同一个 Runnable 实现类(验证资源共享)

//源码
public class ThreadByImplement_201809072056 implements Runnable {

	private int bandit = 5;
	
	@Override
	public void run() {
		for (int i = 0; i < 25; i++) {
			if (bandit > 0) {
				System.out.println(Thread.currentThread().getName() + "  杀了  " + (bandit--) + "  土匪  " + this.hashCode());
			}
		}
	}

	public static void main(String[] args) {
	
		ThreadByImplement_201809072056 r1 = new ThreadByImplement_201809072056();
		Thread t1 = new Thread(r1,"THREAD-张飞");
		Thread t2 = new Thread(r1,"THREAD-赵云");
		Thread t3 = new Thread(r1,"THREAD-吕布");
		t1.start();
		t2.start();
		t3.start();
		
	}

}

//执行结果
THREAD-赵云  杀了  5  土匪  1008269275
THREAD-赵云  杀了  2  土匪  1008269275
THREAD-张飞  杀了  4  土匪  1008269275
THREAD-吕布  杀了  3  土匪  1008269275
THREAD-赵云  杀了  1  土匪  1008269275

//第一种方式:定义普通变量,只创建一个 Runnable 实现类,不同的 Thread 使用同一个 Runnable 实现类(验证资源共享)
//结论:资源共享
复制代码

5.3.1.2.2 定义普通变量,创建三个 Runnable 实现类,不同的 Thread 使用不同的 Runnable 实现类(验证资源共享)

//源码:
public class ThreadByImplement_201809072056 implements Runnable {

	private int bandit = 5;
	
	@Override
	public void run() {
		for (int i = 0; i < 25; i++) {
			if (bandit > 0) {
				System.out.println(Thread.currentThread().getName() + "  杀了  " + (bandit--) + "  土匪  " + this.hashCode());
			}
		}
	}

	public static void main(String[] args) {
	
		ThreadByImplement_201809072056 r1 = new ThreadByImplement_201809072056();
		ThreadByImplement_201809072056 r2 = new ThreadByImplement_201809072056();
		ThreadByImplement_201809072056 r3 = new ThreadByImplement_201809072056();
		Thread t1 = new Thread(r1,"THREAD-张飞");
		Thread t2 = new Thread(r2,"THREAD-赵云");
		Thread t3 = new Thread(r3,"THREAD-吕布");
		t1.start();
		t2.start();
		t3.start();
		
	}

}

//执行结果:
THREAD-张飞  杀了  5  土匪  366287508
THREAD-张飞  杀了  4  土匪  366287508
THREAD-张飞  杀了  3  土匪  366287508
THREAD-赵云  杀了  5  土匪  576357660
THREAD-赵云  杀了  4  土匪  576357660
THREAD-赵云  杀了  3  土匪  576357660
THREAD-吕布  杀了  5  土匪  1584869782
THREAD-赵云  杀了  2  土匪  576357660
THREAD-赵云  杀了  1  土匪  576357660
THREAD-张飞  杀了  2  土匪  366287508
THREAD-张飞  杀了  1  土匪  366287508
THREAD-吕布  杀了  4  土匪  1584869782
THREAD-吕布  杀了  3  土匪  1584869782
THREAD-吕布  杀了  2  土匪  1584869782
THREAD-吕布  杀了  1  土匪  1584869782

//第二种方式:定义普通变量,创建三个 Runnable 实现类,不同的 Thread 使用不同的 Runnable 实现类(验证资源共享)
//结论:资源不共享
复制代码

5.3.1.2.3 定义普通变量,只创建一个 Runnable 实现类,不同的 Thread 使用同一个 Runnable 实现类(验证资源同步)

//源码:
public class ThreadByImplement_201809072056 implements Runnable {

	private int bandit = 5;
	
	@Override
	public void run() {
		if (bandit > 0) {
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "  杀了  " + (bandit--) + "  土匪  " + this.hashCode());
		}
	}

	public static void main(String[] args) {
	
		ThreadByImplement_201809072056 r1 = new ThreadByImplement_201809072056();
		Thread t1 = new Thread(r1,"THREAD-张飞");
		Thread t2 = new Thread(r1,"THREAD-赵云");
		Thread t3 = new Thread(r1,"THREAD-吕布");
		t1.start();
		t2.start();
		t3.start();
		
	}

}

//执行结果:
THREAD-赵云  杀了  4  土匪  1584869782
THREAD-吕布  杀了  3  土匪  1584869782
THREAD-张飞  杀了  5  土匪  1584869782
THREAD-吕布  杀了  2  土匪  1584869782
THREAD-张飞  杀了  2  土匪  1584869782
THREAD-赵云  杀了  1  土匪  1584869782
THREAD-张飞  杀了  0  土匪  1584869782
THREAD-吕布  杀了  -1  土匪  1584869782

//第三种方式:定义普通变量,只创建一个 Runnable 实现类,不同的 Thread 使用同一个 Runnable 实现类(验证资源同步)
//结论:资源共享,但不同步
复制代码

5.3.1.2.4 定义普通变量,只创建一个 Runnable 实现类,不同的 Thread 使用同一个 Runnable 实现类(验证 Synchronized 关键字)

//源码:
public class ThreadByImplement_201809072056 implements Runnable {

	private int bandit = 5;
	
	@Override
	public void run() {
		synchronized (this) {
			if (bandit > 0) {
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "  杀了  " + (bandit--) + "  土匪  " + this.hashCode());
			}
		}
	}

	public static void main(String[] args) {
	
		ThreadByImplement_201809072056 r1 = new ThreadByImplement_201809072056();
		Thread t1 = new Thread(r1,"THREAD-张飞");
		Thread t2 = new Thread(r1,"THREAD-赵云");
		Thread t3 = new Thread(r1,"THREAD-吕布");
		t1.start();
		t2.start();
		t3.start();
		
	}

}

//执行结果:
THREAD-张飞  杀了  5  土匪  576357660
THREAD-张飞  杀了  4  土匪  576357660
THREAD-张飞  杀了  3  土匪  576357660
THREAD-张飞  杀了  2  土匪  576357660
THREAD-张飞  杀了  1  土匪  576357660

//第四种方式:定义普通变量,只创建一个 Runnable 实现类,不同的 Thread 使用同一个 Runnable 实现类(验证 Synchronized 关键字)
//结论:Synchronized 成功解决“资源共享,但不同步”问题
复制代码

此处能解决“资源共享,但不同步”问题,主要原因是保证了在同一时间段内,只能有一个线程对数据进行操作。

5.3.1.2.5 定义静态变量,只创建一个 Runnable 实现类,不同的 Thread 使用同一个 Runnable 实现类(验证资源共享)

//源码:
public class ThreadByImplement_201809072056 implements Runnable {

	private static int bandit = 5;
	
	@Override
	public void run() {
		if (bandit > 0) {
			System.out.println(Thread.currentThread().getName() + "  杀了  " + (bandit--) + "  土匪  " + this.hashCode());
		}
	}

	public static void main(String[] args) {
	
		ThreadByImplement_201809072056 r1 = new ThreadByImplement_201809072056();
		Thread t1 = new Thread(r1,"THREAD-张飞");
		Thread t2 = new Thread(r1,"THREAD-赵云");
		Thread t3 = new Thread(r1,"THREAD-吕布");
		t1.start();
		t2.start();
		t3.start();
		
	}

}

//执行结果:
THREAD-赵云  杀了  4  土匪  366287508
THREAD-赵云  杀了  2  土匪  366287508
THREAD-赵云  杀了  1  土匪  366287508
THREAD-吕布  杀了  5  土匪  366287508
THREAD-张飞  杀了  3  土匪  366287508

//第五种方式:定义静态变量,只创建一个 Runnable 实现类,不同的 Thread 使用同一个 Runnable 实现类(验证资源共享)
//结论:资源共享
复制代码

5.3.1.2.6 定义静态变量,创建三个 Runnable 实现类,不同的 Thread 使用不同的 Runnable 实现类(验证资源共享)

//源码:
public class ThreadByImplement_201809072056 implements Runnable {

	private static int bandit = 5;
	
	@Override
	public void run() {
		if (bandit > 0) {
			System.out.println(Thread.currentThread().getName() + "  杀了  " + (bandit--) + "  土匪  " + this.hashCode());
		}
	}

	public static void main(String[] args) {
	
		ThreadByImplement_201809072056 r1 = new ThreadByImplement_201809072056();
		ThreadByImplement_201809072056 r2 = new ThreadByImplement_201809072056();
		ThreadByImplement_201809072056 r3 = new ThreadByImplement_201809072056();
		Thread t1 = new Thread(r1,"THREAD-张飞");
		Thread t2 = new Thread(r2,"THREAD-赵云");
		Thread t3 = new Thread(r3,"THREAD-吕布");
		t1.start();
		t2.start();
		t3.start();
		
	}

}

//执行结果:
THREAD-赵云  杀了  4  土匪  576357660
THREAD-赵云  杀了  2  土匪  576357660
THREAD-张飞  杀了  3  土匪  366287508
THREAD-吕布  杀了  5  土匪  1584869782
THREAD-赵云  杀了  1  土匪  576357660

//第六种方式:定义静态变量,创建三个 Runnable 实现类,不同的 Thread 使用不同的 Runnable 实现类(验证资源共享)
//结论:资源共享
复制代码

5.3.1.2.7 定义静态变量,只创建一个 Runnable 实现类,不同的 Thread 使用同一个 Runnable 实现类(验证资源同步)

//源码:
public class ThreadByImplement_201809072056 implements Runnable {

	private static int bandit = 5;
	
	@Override
	public void run() {
		if (bandit > 0) {
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "  杀了  " + (bandit--) + "  土匪  " + this.hashCode());
		}
	}

	public static void main(String[] args) {
	
		ThreadByImplement_201809072056 r1 = new ThreadByImplement_201809072056();
		Thread t1 = new Thread(r1,"THREAD-张飞");
		Thread t2 = new Thread(r1,"THREAD-赵云");
		Thread t3 = new Thread(r1,"THREAD-吕布");
		t1.start();
		t2.start();
		t3.start();
		
	}

}

//执行结果:
THREAD-赵云  杀了  3  土匪  1578680387
THREAD-张飞  杀了  5  土匪  1578680387
THREAD-吕布  杀了  4  土匪  1578680387
THREAD-张飞  杀了  2  土匪  1578680387
THREAD-吕布  杀了  2  土匪  1578680387
THREAD-赵云  杀了  2  土匪  1578680387
THREAD-赵云  杀了  1  土匪  1578680387
THREAD-吕布  杀了  -1  土匪  1578680387
THREAD-张飞  杀了  0  土匪  1578680387

//第七种方式:定义静态变量,只创建一个 Runnable 实现类,不同的 Thread 使用同一个 Runnable 实现类(验证资源同步)
//结论:资源共享,但不同步
复制代码

5.3.1.2.8 定义静态变量,创建三个 Runnable 实现类,不同的 Thread 使用不同的 Runnable 实现类(验证资源同步)

//源码:
public class ThreadByImplement_201809072056 implements Runnable {

	private static int bandit = 5;
	
	@Override
	public void run() {
		if (bandit > 0) {
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "  杀了  " + (bandit--) + "  土匪  " + this.hashCode());
		}
	}

	public static void main(String[] args) {
	
		ThreadByImplement_201809072056 r1 = new ThreadByImplement_201809072056();
		ThreadByImplement_201809072056 r2 = new ThreadByImplement_201809072056();
		ThreadByImplement_201809072056 r3 = new ThreadByImplement_201809072056();
		Thread t1 = new Thread(r1,"THREAD-张飞");
		Thread t2 = new Thread(r2,"THREAD-赵云");
		Thread t3 = new Thread(r3,"THREAD-吕布");
		t1.start();
		t2.start();
		t3.start();
		
	}

}

//执行结果:
THREAD-吕布  杀了  4  土匪  1751905002
THREAD-张飞  杀了  5  土匪  576357660
THREAD-赵云  杀了  3  土匪  1584869782
THREAD-吕布  杀了  1  土匪  1751905002
THREAD-赵云  杀了  2  土匪  1584869782
THREAD-张飞  杀了  2  土匪  576357660

//第八种方式:定义静态变量,创建三个 Runnable 实现类,不同的 Thread 使用不同的 Runnable 实现类(验证资源同步)
//结论:资源共享,但不同步
复制代码

5.3.1.2.9 定义静态变量,只创建一个 Runnable 实现类,不同的 Thread 使用同一个 Runnable 实现类(验证 Synchronized 关键字)

//源码:
public class ThreadByImplement_201809072056 implements Runnable {

	private static int bandit = 5;
	
	@Override
	public void run() {
        synchronized (this) {
			if (bandit > 0) {
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "  杀了  " + (bandit--) + "  土匪  " + this.hashCode());
			}
		}
	}

	public static void main(String[] args) {
	
		ThreadByImplement_201809072056 r1 = new ThreadByImplement_201809072056();
		Thread t1 = new Thread(r1,"THREAD-张飞");
		Thread t2 = new Thread(r1,"THREAD-赵云");
		Thread t3 = new Thread(r1,"THREAD-吕布");
		t1.start();
		t2.start();
		t3.start();
		
	}

}

//执行结果:
THREAD-张飞  杀了  5  土匪  366287508
THREAD-张飞  杀了  4  土匪  366287508
THREAD-张飞  杀了  3  土匪  366287508
THREAD-张飞  杀了  2  土匪  366287508
THREAD-张飞  杀了  1  土匪  366287508

//第九种方式:定义静态变量,只创建一个 Runnable 实现类,不同的 Thread 使用同一个 Runnable 实现类(验证 Synchronized 关键字)
//结论:Synchronized 成功解决“资源共享,但不同步”问题
复制代码

此处能解决“资源共享,但不同步”问题,和《5.3.1.2.4 定义普通变量,只创建一个 Runnable 实现类,不同的 Thread 使用同一个 Runnable 实现类(验证 Synchronized 关键字)》原因一样——保证了在同一时间段内,只能有一个线程对数据进行操作。

5.3.1.2.10 定义静态变量,创建三个 Runnable 实现类,不同的 Thread 使用不同的 Runnable 实现类(验证 Synchronized 关键字)

//源码:
public class ThreadByImplement_201809072056 implements Runnable {

	private static int bandit = 5;
	
	@Override
	public void run() {
        synchronized (this) {
			if (bandit > 0) {
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "  杀了  " + (bandit--) + "  土匪  " + this.hashCode());
			}
		}
	}

	public static void main(String[] args) {
	
		ThreadByImplement_201809072056 r1 = new ThreadByImplement_201809072056();
		ThreadByImplement_201809072056 r2 = new ThreadByImplement_201809072056();
		ThreadByImplement_201809072056 r3 = new ThreadByImplement_201809072056();
		Thread t1 = new Thread(r1,"THREAD-张飞");
		Thread t2 = new Thread(r2,"THREAD-赵云");
		Thread t3 = new Thread(r3,"THREAD-吕布");
		t1.start();
		t2.start();
		t3.start();
		
	}

}

//执行结果:
THREAD-赵云  杀了  4  土匪  736376815
THREAD-吕布  杀了  5  土匪  443403783
THREAD-张飞  杀了  3  土匪  1751905002
THREAD-赵云  杀了  2  土匪  736376815
THREAD-吕布  杀了  1  土匪  443403783
THREAD-张飞  杀了  0  土匪  1751905002
THREAD-赵云  杀了  -1  土匪  736376815

//第十种方式:定义静态变量,创建三个 Runnable 实现类,不同的 Thread 使用不同的 Runnable 实现类(验证 Synchronized 关键字)
//结论:Synchronized 未能解决“资源共享,但不同步”问题
复制代码

综合以上分析,可得:在实际开发中,如果用到线程并涉及资源共享,通过实现 Runnable 接口的方式来实现线程是一个不错的选择

5.3.2 Synchronized 关键字都能修饰哪些元素(代码块、方法、类)?

Synchronized 关键字可以修饰代码块和方法,不能修饰类,如:

//源码:
public class ThreadByImplements_201810312214 implements Runnable {

	private int mTicket = 5;
	
	@Override
	public void run() {
		for (int i = 0; i < 25; i++) {
			saleTickets();
			saleTickets1();
		}
	}

	public static void main(String[] args) {
		ThreadByImplements_201810312214 r1 = new ThreadByImplements_201810312214();
		Thread t1 = new Thread(r1,"THREAD-A-天王盖地虎");
		Thread t2 = new Thread(r1,"THREAD-B-宝塔镇河妖");
		Thread t3 = new Thread(r1,"THREAD-C-小鸡炖蘑菇");
		t1.start();
		t2.start();
		t3.start();
	}

	private void saleTickets(){
		synchronized (this) {
			if(mTicket > 0){
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "  " + (mTicket--));
			}
		}
	}
	
	private synchronized void saleTickets1(){
		synchronized (this) {
			if(mTicket > 0){
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "  " + (mTicket--));
			}
		}
	}
	
}

//执行结果:
THREAD-A-天王盖地虎  5
THREAD-A-天王盖地虎  4
THREAD-A-天王盖地虎  3
THREAD-A-天王盖地虎  2
THREAD-A-天王盖地虎  1
复制代码

在上面的示例中,如果在 class 前面添加 Synchronized 关键字,开发工具会提示:Illegal modifier for the class ThreadByImplements_201810312214; only public, abstract & final are permitted,而代码块和方法是可以用 Synchronized 关键字修饰的。

6. 生产者与消费者

6.1 程序基本实现(问题引出)

生产者与消费者是线程操作的经典案例,即:生产者不断生产,消费者不断取走生产者生产的产品。接下来,我们以买早餐为例讲解如何实现生产者与消费者。

假设,一个早餐店只卖下面两种套餐:

  • 煎饼果子,无糖豆浆
  • 锅盔辣子荚膜,豆腐脑

接下来,用程序实现这个过程:

//源码:
//早餐
public class Breakfast_201811011939 {

	private String food;
	private String drinking;
	
	public Breakfast_201811011939(){}
	
	public Breakfast_201811011939(String f, String d){
		this.food = f;
		this.drinking = d;
	}

	public String getFood() {
		return food;
	}

	public void setFood(String food) {
		this.food = food;
	}

	public String getDrinking() {
		return drinking;
	}

	public void setDrinking(String drinking) {
		this.drinking = drinking;
	}
}

//早餐店
public class Restaurant_201811011951 implements Runnable {

	private Breakfast_201811011939 breakfast;
	
	public Restaurant_201811011951(Breakfast_201811011939 b){
		this.breakfast = b;
	}
	
	@Override
	public void run() {
		boolean isFirstSetMeal = false;
		for (int i = 0; i < 25; i++) {
			if(isFirstSetMeal){
				this.breakfast.setFood("煎饼果子");
				try {
					Thread.sleep(100);
				} catch (Exception e) {
					e.printStackTrace();
				}
				this.breakfast.setDrinking("无糖豆浆");
				isFirstSetMeal = false;
			}else{
				this.breakfast.setFood("锅盔辣子荚膜");
				try {
					Thread.sleep(100);
				} catch (Exception e) {
					e.printStackTrace();
				}
				this.breakfast.setDrinking("豆腐脑");
				isFirstSetMeal = true;
			}
		}
	}

}

//顾客
public class Consumer_201811011958 implements Runnable {

	private Breakfast_201811011939 breakfast;
	
	public Consumer_201811011958(Breakfast_201811011939 b){
		this.breakfast = b;
	}
	
	@Override
	public void run() {
		for (int i = 0; i < 25; i++) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("套餐: " + breakfast.getFood() + "  " + breakfast.getDrinking() + "  " + (i+1));
		}
	}

}

//Main
public class Main_201811012005 {

	public static void main(String[] args) {
		Breakfast_201811011939 breakfast = new Breakfast_201811011939();
		Restaurant_201811011951 restaurant = new Restaurant_201811011951(breakfast);
		Consumer_201811011958 consumer = new Consumer_201811011958(breakfast);
		new Thread(restaurant).start();
		new Thread(consumer).start();
	}

}

//执行结果:
套餐: 煎饼果子  豆腐脑  1
套餐: 锅盔辣子荚膜  无糖豆浆  2
套餐: 煎饼果子  豆腐脑  3
套餐: 锅盔辣子荚膜  无糖豆浆  4
套餐: 煎饼果子  豆腐脑  5
套餐: 锅盔辣子荚膜  无糖豆浆  6
套餐: 煎饼果子  豆腐脑  7
套餐: 锅盔辣子荚膜  无糖豆浆  8
套餐: 煎饼果子  豆腐脑  9
套餐: 锅盔辣子荚膜  无糖豆浆  10
套餐: 煎饼果子  豆腐脑  11
套餐: 锅盔辣子荚膜  无糖豆浆  12
套餐: 煎饼果子  豆腐脑  13
套餐: 锅盔辣子荚膜  无糖豆浆  14
套餐: 煎饼果子  豆腐脑  15
套餐: 锅盔辣子荚膜  无糖豆浆  16
套餐: 煎饼果子  豆腐脑  17
套餐: 锅盔辣子荚膜  无糖豆浆  18
套餐: 煎饼果子  豆腐脑  19
套餐: 锅盔辣子荚膜  无糖豆浆  20
套餐: 煎饼果子  豆腐脑  21
套餐: 锅盔辣子荚膜  无糖豆浆  22
套餐: 煎饼果子  豆腐脑  23
套餐: 锅盔辣子荚膜  无糖豆浆  24
套餐: 锅盔辣子荚膜  无糖豆浆  25
复制代码

由执行结果可知,上面的程序有两个问题:

  • 信息错乱
    Restaurant 线程刚添加完 Food 信息还没有来得及添加 Drinking 信息,Consumer 线程就过来取 Breakfast 了,此时信息就会错乱。
  • 连续存储
    Restaurant 添加了 N 次 Breakfast 信息之后,Consumer 线程才过来取 Breakfast 或者 Consumer 线程过来取了 N 次信息之后,Restaurant 才往 Breakfast 里面添加新的信息,此时就会造成连续存取的问题。
6.2 解决信息错乱问题——添加同步

信息错乱明显是因为资源共享未同步造成的,所以解决方法当然是同步。

接下来,根据分析结果对上述代码进行修改:

//源码:
//早餐
public class Breakfast_201811012009 {

	private String food;
	private String drinking;
	
	public Breakfast_201811012009(){}
	
	public Breakfast_201811012009(String f, String d){
		this.food = f;
		this.drinking = d;
	}
	
	public synchronized void setBreakfast(String f, String d) {
		setFood(f);
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		setDrinking(d);
	}
	
	public synchronized void getBreakfast(){
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("套餐: " + getFood() + "  " + getDrinking());
	}

	public String getFood() {
		return food;
	}

	public void setFood(String food) {
		this.food = food;
	}

	public String getDrinking() {
		return drinking;
	}

	public void setDrinking(String drinking) {
		this.drinking = drinking;
	}
}

//早餐店
public class Restaurant_201811012032 implements Runnable {

	private Breakfast_201811012009 breakfast;
	
	public Restaurant_201811012032(Breakfast_201811012009 b){
		this.breakfast = b;
	}
	
	@Override
	public void run() {
		boolean isFirstSetMeal = false;
		for (int i = 0; i < 25; i++) {
			if(isFirstSetMeal){
				this.breakfast.setBreakfast("煎饼果子", "无糖豆浆" + "  " + (i+1));
				isFirstSetMeal = false;
			}else{
				this.breakfast.setBreakfast("锅盔辣子荚膜", "豆腐脑" + "  " + (i+1));
				isFirstSetMeal = true;
			}
		}
	}

}

//顾客
public class Consumer_201811012018 implements Runnable {

	private Breakfast_201811012009 breakfast;
	
	public Consumer_201811012018(Breakfast_201811012009 b){
		this.breakfast = b;
	}
	
	@Override
	public void run() {
		for (int i = 0; i < 25; i++) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			this.breakfast.getBreakfast();
		}
	}

}

//Main
public class Main_201811012005 {

	public static void main(String[] args) {
        Breakfast_201811012009 breakfast = new Breakfast_201811012009();
		Consumer_201811012018 restaurant = new Consumer_201811012018(breakfast);
		Restaurant_201811012032 consumer = new Restaurant_201811012032(breakfast);
		new Thread(restaurant).start();
		new Thread(consumer).start();
	}
    
}

//执行结果:
套餐: 锅盔辣子荚膜  豆腐脑  1
套餐: 煎饼果子  无糖豆浆  2
套餐: 锅盔辣子荚膜  豆腐脑  3
套餐: 煎饼果子  无糖豆浆  4
套餐: 锅盔辣子荚膜  豆腐脑  5
套餐: 煎饼果子  无糖豆浆  6
套餐: 煎饼果子  无糖豆浆  8
套餐: 锅盔辣子荚膜  豆腐脑  9
套餐: 煎饼果子  无糖豆浆  10
套餐: 锅盔辣子荚膜  豆腐脑  11
套餐: 煎饼果子  无糖豆浆  12
套餐: 煎饼果子  无糖豆浆  16
套餐: 锅盔辣子荚膜  豆腐脑  17
套餐: 煎饼果子  无糖豆浆  18
套餐: 锅盔辣子荚膜  豆腐脑  19
套餐: 煎饼果子  无糖豆浆  20
套餐: 锅盔辣子荚膜  豆腐脑  21
套餐: 煎饼果子  无糖豆浆  22
套餐: 锅盔辣子荚膜  豆腐脑  23
套餐: 煎饼果子  无糖豆浆  24
套餐: 锅盔辣子荚膜  豆腐脑  25
套餐: 锅盔辣子荚膜  豆腐脑  25
套餐: 锅盔辣子荚膜  豆腐脑  25
套餐: 锅盔辣子荚膜  豆腐脑  25
套餐: 锅盔辣子荚膜  豆腐脑  25
复制代码

由执行结果可知,经过同步处理之后,信息错乱的问题解决了,但连续存取的问题还在。

之所以在执行结果中会漏掉某些数字,如:7,13,14,15,是因为 Restaurant 线程添加完 Breakfast 信息,Consumer 线程没有及时取走,于是信息展示的时候,漏掉了这些数字;
之所以在执行结果中会出现连续几次数字一样的情况,如:25 连续出现了 5 次,是因为 Restaurant 线程已经执行完毕,即:此时 Breakfast 信息已经不更改,而 Consumer 线程还没有执行完毕。其实归根结底还是因为 Restaurant 线程和 Consumer 线程没有交替操作 Breakfast 对象。

那如何才能使 Restaurant 线程和 Consumer 线程交替操作 Breakfast 对象呢?——加入等待和唤醒。

6.3 解决连续存取问题——添加等待与唤醒

在 Object 类中定义了线程等待(wait)与唤醒(notify)的方法:

//Wait 线程等待
/**
 * Causes the current thread to wait until another thread invokes the
 * {@link java.lang.Object#notify()} method or the
 * {@link java.lang.Object#notifyAll()} method for this object.
 * …
 */
public final void wait() throws InterruptedException {
    wait(0);
}

//Notify 唤醒第一个等待的线程
/**
 * Wakes up a single thread that is waiting on this object's
 * monitor. 
 * …
 */
public final native void notify();
复制代码

那等待与唤醒如何添加呢?

可以通过一个标志位来控制,假设标志位是一个 Boolean 变量,当标志位内容为 true 时,表示可以 Restaurant 线程可以添加 Breakfast 信息,此时如果 CPU 调度了 Consumer 线程则等待,反之亦然。

接下来,根据分析结果对上述代码进行修改:

//源码:
//早餐
public class Breakfast_201811012300 {

	private String food;
	private String drinking;
	private boolean canCookBreakfast = true;
	
	public Breakfast_201811012300(){}
	
	public Breakfast_201811012300(String f, String d){
		this.food = f;
		this.drinking = d;
	}
	
	public synchronized void setBreakfast(String f, String d) {
		if(!canCookBreakfast){
			try {
				super.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		setFood(f);
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		setDrinking(d);
		canCookBreakfast = false;
		super.notify();
	}
	
	public synchronized void getBreakfast(){
		if(canCookBreakfast){
			try {
				super.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("套餐: " + getFood() + "  " + getDrinking());
		canCookBreakfast = true;
		super.notify();
	}

	public String getFood() {
		return food;
	}

	public void setFood(String food) {
		this.food = food;
	}

	public String getDrinking() {
		return drinking;
	}

	public void setDrinking(String drinking) {
		this.drinking = drinking;
	}
}

//早餐店
public class Restaurant_201811012300 implements Runnable {

	private Breakfast_201811012300 breakfast;
	
	public Restaurant_201811012300(Breakfast_201811012300 b){
		this.breakfast = b;
	}
	
	@Override
	public void run() {
		boolean isFirstSetMeal = false;
		for (int i = 0; i < 25; i++) {
			if(isFirstSetMeal){
				this.breakfast.setBreakfast("煎饼果子", "无糖豆浆" + "  " + (i+1));
				isFirstSetMeal = false;
			}else{
				this.breakfast.setBreakfast("锅盔辣子荚膜", "豆腐脑" + "  " + (i+1));
				isFirstSetMeal = true;
			}
		}
	}

}

//顾客
public class Consumer_201811012300 implements Runnable {

	private Breakfast_201811012300 breakfast;
	
	public Consumer_201811012300(Breakfast_201811012300 b){
		this.breakfast = b;
	}
	
	@Override
	public void run() {
		for (int i = 0; i < 25; i++) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			this.breakfast.getBreakfast();
		}
	}

}

//Main
public class Main_201811012005 {

	public static void main(String[] args) {
        Breakfast_201811012300 breakfast = new Breakfast_201811012300();
		Consumer_201811012300 restaurant = new Consumer_201811012300(breakfast);
		Restaurant_201811012300 consumer = new Restaurant_201811012300(breakfast);
		new Thread(restaurant).start();
		new Thread(consumer).start();
	}

}

//执行结果:
套餐: 锅盔辣子荚膜  豆腐脑  1
套餐: 煎饼果子  无糖豆浆  2
套餐: 锅盔辣子荚膜  豆腐脑  3
套餐: 煎饼果子  无糖豆浆  4
套餐: 锅盔辣子荚膜  豆腐脑  5
套餐: 煎饼果子  无糖豆浆  6
套餐: 锅盔辣子荚膜  豆腐脑  7
套餐: 煎饼果子  无糖豆浆  8
套餐: 锅盔辣子荚膜  豆腐脑  9
套餐: 煎饼果子  无糖豆浆  10
套餐: 锅盔辣子荚膜  豆腐脑  11
套餐: 煎饼果子  无糖豆浆  12
套餐: 锅盔辣子荚膜  豆腐脑  13
套餐: 煎饼果子  无糖豆浆  14
套餐: 锅盔辣子荚膜  豆腐脑  15
套餐: 煎饼果子  无糖豆浆  16
套餐: 锅盔辣子荚膜  豆腐脑  17
套餐: 煎饼果子  无糖豆浆  18
套餐: 锅盔辣子荚膜  豆腐脑  19
套餐: 煎饼果子  无糖豆浆  20
套餐: 锅盔辣子荚膜  豆腐脑  21
套餐: 煎饼果子  无糖豆浆  22
套餐: 锅盔辣子荚膜  豆腐脑  23
套餐: 煎饼果子  无糖豆浆  24
套餐: 锅盔辣子荚膜  豆腐脑  25
复制代码

由上面的执行结果可知,加入等待与唤醒之后,连续存取的问题得到解决,即:Restaurant 线程添加完 Breakfast 信息之后,只有当 Consumer 线程取走 Breakfast 之后才添加新的 Breakfast 信息。当 Restaurant 线程添加完 Breakfast 信息之后,如果 CPU 又调度了一次 Restaurant 线程,这个时候,Restaurant 线程就会等待(wait),直至被 Notify(切换至 Consumer 线程,Consumer 线程取走 Breakfast 信息之后,通知(Notify)第一个等待的线程,此时 Restaurant 线程才被唤醒),当 CPU 重复调用 Consumer 线程时,处理的逻辑是一样,因此不在此赘述。


参考文档

1)《Java 开发实战经典》
2)《Thinking in Java》
3)Android Developer Document
4)Java Tutorials

转载于:https://juejin.im/post/5bdbd723e51d45051d48ce9d

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值