JAVA中也提供了多线程的相关操作。
线程与进程
进程是程序的一次动态执行过程,该过程表示从代码加载、执行到执行完毕的整个过程,该过程同时也是进程产生、工作到销毁的过程。多进程操作系统能同时运行多个进程(程序),由于CPU具备分时机制,所以每个进程都能获得自己的CPU时间片。
而线程和进程一样,都是实现并发的一个基本单位。多线程是指一个进程在执行过程中可以产生多个线程,这些线程可以同时存在、同时运行。一个进程可能包含多个同时执行的线程。
也因此,如果进程结束了,那么进程中对应的线程也会结束,也就是说线程一定要依附于进程才能够存在。
多线程实现
JAVA中一切都是依靠类的定义来实现的,多线程也是这样,如果要实现多线程的程序,就必须要依靠一个线程的主体类。而该线程的主体类在定义时也需要有一些特殊的要求,即此类需要继承Thread类或实现Runable(Callable)接口来完成定义。
继承Thread类
java.lang.Thread是一个负责线程操作的类,任何类只要继承Thread类就可以称为一个线程的主类。同时覆写Thread类中的run方法就是实现线程启动的主方法:
class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for(int i = 0;i < 3;++i) {
System.out.println(this.name + ",i = " + i);
}
}
}
上边的代码中定义了MyThread,并且该类继承自Thread类,并覆写了run方法,作为该线程启动的主方法。但是如果直接调用run方法,并不能启动多线程,多线程启动的唯一方法是Thread类中的start方法。那么可以如此启动多线程:
public class Demo {
public static void main(String args[]) {
MyThread th1 = new MyThread("th1");
MyThread th2 = new MyThread("th2");
MyThread th3 = new MyThread("th3");
th1.start();
th2.start();
th3.start();
}
}
执行结果为:
// First run
th2,i = 0
th3,i = 0
th1,i = 0
th3,i = 1
th2,i = 1
th3,i = 2
th1,i = 1
th2,i = 2
th1,i = 2
// Second run
th1,i = 0
th3,i = 0
th2,i = 0
th3,i = 1
th1,i = 1
th3,i = 2
th2,i = 1
th1,i = 2
th2,i = 2
上边的结果表示两次执行的结果并不一样,这是由于多线程同时工作时,没有固定的时序关系,往往是哪个线程先占用资源就先执行。不过单个线程的主方法执行顺序是固定的。
再看以下start方法的定义:
/**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the {@code run} method of this thread.
* <p>
* The result is that two threads are running concurrently: the
* current thread (which returns from the call to the
* {@code start} method) and the other thread (which executes its
* {@code run} method).
* <p>
* It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed
* execution.
*
* @throws IllegalThreadStateException if the thread was already started.
* @see #run()
* @see #stop()
*/
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方法中要调用start0方法,而且此方法的结构与抽象方法类似,使用了native进行声明。在JAVA开发中有一个技术称为JAVA本地接口(JAVA Native Interface, JNI)技术,该技术的特点是使用JAVA调用本机操作系统提供的函数。而此技术也有缺点,就是不能离开特定的操作系统。如果要想能够执行线程,就需要操作系统来进行资源分配,所以此操作严格来讲主要是由JVM负责根据不同的操作系统而实现的。即使用Thread类的stat方法不仅要启动多线程的执行代码,还要根据不同的操作系统进行资源的分配。
实现Runnable接口
继承Thread能够实现多线程,但由于类只能进行单继承,因此也可以使用Runnable接口实现多线程。
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface {@code Runnable} is used
* to create a thread, starting the thread causes the object's
* {@code run} method to be called in that separately executing
* thread.
* <p>
* The general contract of the method {@code run} is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
该接口中也存在fun方法,也就是说如果想要通过Runnable实现多线程,也需要实现run方法:
class MyThread implements Runnable {
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for(int i = 0;i < 3;++i) {
System.out.println(this.name + ",i = " + i);
}
}
}
看起来似乎很简单,只需要把extends修改为implements就可以了,那么如果调用该主方法呢?因为Runnable中只存在run方法,而不存在start方法,当然不能像Thread那样直接使用start进行调用了。先看下Thread的其中一个构造方法:
public Thread(Runnable target)
//Allocates a new Thread object. This constructor has the same effect as Thread (null, target, gname), where gname is a newly generated name. Automatically generated names are of the form "Thread-"+n, where n is an integer.
//Parameters:
//target - the object whose run method is invoked when this thread is started. If null, this classes run method does nothing.
也就是说,Thread可以通过Runnable对象进行构建,那么便可以通过该构造方法借助匿名对象实现调用:
public class Demo {
public static void main(String args[]) {
MyThread th1 = new MyThread("th1");
MyThread th2 = new MyThread("th2");
MyThread th3 = new MyThread("th3");
new Thread(th1).start();
new Thread(th2).start();
new Thread(th3).start();
}
}
执行结果为:
th3,i = 0
th1,i = 0
th2,i = 0
th1,i = 1
th3,i = 1
th1,i = 2
th2,i = 1
th3,i = 2
th2,i = 2
同样可以实现调用。
而同时Runnable接口使用了“@FunctionalInterface”注解,因此Runnable也是一个函数式接口,因此调用方法也可以实现为:
public class Demo {
public static void main(String args[]) {
for(int idx = 0;idx < 3;++idx) {
new Thread(()->{
for(int i = 0;i < 3;++i) {
System.out.println("th" + i + ",i = " + i);
}
}).start();
}
}
}
这种实现调用的方式使用到了Lambda表达式,不过看起来不是特别直观。
Thread和Runnable两种多线程方式的区别
首先看一下Thread的实现方式:
public class Thread extends Object implements Runnable
从上边的继承关系来看,Thread继承自Object,实现了Runnable,这也就难怪Thread可以直接使用Runnable作为参数进行构造。
在之前的内容中提到代理设计模式:
- 先定义接口,然后在接口中定义抽象方法
- 定义实际类,实现接口,并覆写抽象方法
- 定义代理,代理实现接口,覆写抽象方法,并接收实际类作为参数
- 实际开发中,使用代理接收实际类作为参数,并调用接口的抽象方法,以此来隐藏中间过程。
而Thread和Runnable的关系就有点像代理设计模式的过程:
- 接口为Runnable
- 定义实际类MyThread,该类继承自Thread,并覆写抽象方法
- 定义代理Thread,实现接口,覆写抽象方法
- 实际开发,使用MyThread,并调用接口的抽象方法
也就是说MyThread只是代理了Thread的操作run,来实现多线程的过程。
不过使用Runnable可以更方便地表达出数据共享的概念,比较下面两段代码:
class MyThread extends Thread {
private String name;
private int num = 5;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for(int i = 0;i < 20;++i) {
if (num > 0) {
System.out.println(this.name + ",num = " + num--);
}
}
}
}
public class Demo {
public static void main(String args[]) {
MyThread th1 = new MyThread("th1");
MyThread th2 = new MyThread("th2");
MyThread th3 = new MyThread("th3");
th1.start();
th2.start();
th3.start();
}
}
执行结果为:
th1,num = 5
th3,num = 5
th2,num = 5
th3,num = 4
th1,num = 4
th3,num = 3
th2,num = 4
th3,num = 2
th1,num = 3
th3,num = 1
th2,num = 3
th1,num = 2
th2,num = 2
th1,num = 1
th2,num = 1
另一段代码:
class MyThread implements Runnable {
private String name;
private int num = 5;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for(int i = 0;i < num;++i) {
if (num > 0) {
System.out.println(this.name + ",num = " + num--);
}
}
}
}
public class Demo {
public static void main(String args[]) {
MyThread th = new MyThread("th");
new Thread(th).start();
new Thread(th).start();
new Thread(th).start();
}
}
执行结果为:
th,num = 4
th,num = 5
th,num = 3
th,num = 2
这个例子中,程序想要在线程中维护一份数据,该数据用于共享,而Runnable由于是通过匿名对象进行调用的,因此可以利用同一实例化对象进行构造调用,因此便于进行数据共享。
简单地说,虽然通过匿名对象利用同一MyThread实例化对象构建了多次Thread,但是其指向的还是同一个MyThread,因此对数据的操作自然也是同一份数据了。
如果是这个思路,那么是否可以将第一段代码修改为:
class MyThread extends Thread {
private String name;
private int num = 5;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for(int i = 0;i < 20;++i) {
if (num > 0) {
System.out.println(this.name + ",num = " + num--);
}
}
}
}
public class Demo {
public static void main(String args[]) {
MyThread th = new MyThread("th");
new Thread(th).start();
new Thread(th).start();
new Thread(th).start();
}
}
其执行结果为:
th,num = 3
th,num = 5
th,num = 4
th,num = 1
th,num = 2
看起来结果是一样的,但是这里通过匿名对象重新构建Thread多少觉得怪怪的,因为原有的MyThread本身就具有start方法,这样的实现方法会显得很多余。因此Runnable来实现这种数据共享方式会更恰当。
实现Callable接口
如果Runnable接口就可以实现多线程,那么为什么还要多出来一个Callable接口,原因就在于Callable的call方法是有返回值的。
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
从上边的形式来看,Callable需要覆写的是call方法,而不是run方法,并且call方法的返回值是V类型的,这就是说返回值的类型是可以自定义的。
如何实现多线程的问题解决了,那么怎么调用呢?之前提到Runnable接口可以借用Thread构建匿名对象来调用start方法,这是因为Thread实现了Runnable接口,而Callable怎么调用线程主方法呢?
JAVA提供了java.util.concurrent.FutureTask<V>类:
public class FutureTask<V> extends Object implements RunnableFuture<V>
而RunnableFurure为:
public interface RunnableFuture<V> extends Runnable, Future<V>
同时该类存在一个构造方法:
public FutureTask(Callable<V> callable)
这也就是说该类可以接纳一个Callable实例化对象作为参数进行构建。同时该类的主要方法有:
boolean cancel(boolean mayInterruptIfRunning) // Attempts to cancel execution of this task.
protected void done() // Protected method invoked when this task transitions to state isDone (whether normally or via cancellation).
V get() // Waits if necessary for the computation to complete, and then retrieves its result.
V get(long timeout, TimeUnit unit) // Waits if necessary for at most the given time for the computation to complete, and then retrieves its result, if available.
boolean isCancelled() // Returns true if this task was cancelled before it completed normally.
boolean isDone() // Returns true if this task completed.
void run() // Sets this Future to the result of its computation unless it has been cancelled.
protected boolean runAndReset() // Executes the computation without setting its result, and then resets this future to initial state, failing to do so if the computation encounters an exception or is cancelled.
protected void set(V v) // Sets the result of this future to the given value unless this future has already been set or has been cancelled.
protected void setException(Throwable t) // Causes this future to report an ExecutionException with the given throwable as its cause, unless this future has already been set or has been cancelled.
String toString() // Returns a string representation of this FutureTask.
那么利用Callable接口就可以实现如下多线程:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
class MyThread implements Callable<String> {
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public String call() throws Exception {
for(int i = 0;i < 3;++i) {
System.out.println(this.name + ",i = " + i);
}
return "Call over";
}
}
public class Demo {
public static void main(String args[]) throws Exception{
MyThread th1 = new MyThread("th1");
MyThread th2 = new MyThread("th2");
FutureTask<String> task1 = new FutureTask<String>(th1);
FutureTask<String> task2 = new FutureTask<String>(th2);
new Thread(task1).start();
new Thread(task2).start();
System.out.println(task1.get());
System.out.println(task2.get());
}
}
执行结果为:
th1,i = 0
th1,i = 1
th2,i = 0
th1,i = 2
th2,i = 1
th2,i = 2
Call over
Call over
这里说明下相关的关系:
- 首先MyThread实现Callable接口,覆写call方法,不过要保证call方法返回值和Callable接口的V值类型一致
- 然后用创建出的MyThread对象构建FutureTask对象,要保证FutureTask和Callable的V值类型一致
- 由于FutureTask实现了RunnableFuture,RunnableFuture又继承自Runnable,因此便可以使用FutureTask实例化对象构建Thread的匿名对象,然后调用Thread的start方法,实现多线程调用
- 同时FutureTask的get方法如下,要求在调用处进行异常处理,由于是在main中调用,同时在main中未进行异常处理,但是用throws修饰main,表示main的异常也不处理,而直接交给JVM处理,才避免了在main中进行FutureTask的get方法的异常处理
public V get() throws InterruptedException,ExecutionException
这样看起来,还是使用Runnable更方便一点。
线程状态
对于进程来说,每个进程都会有其自己的状态。而线程也具有自己的线程状态,即创建、就绪、运行、堵塞和终止。
创建
在程序中使用构造方法创建一个线程对象后,新的线程对象便处于新建状态,此时,该对象已经具有相应的生存空间和其它资源,只是还处于不可运行状态。
就绪
新建线程对象后,调用该线程的start方法就可以启动线程。当线程启动时,线程就进入就绪状态。此时,线程需要进入线程队列排队以获取CPU服务,此时表明该线程具备了运行条件。
运行
当就绪状态的线程被调用并获得CPU资源时,线程就进入了运行状态。此时,自动调用该线程对象的run方法。
堵塞
一个正在执行的线程在某些特殊情况下,如被挂起或者需要执行比较耗时的输入输出操作时,将让出CPU并暂时中止自己的执行,进入堵塞状态。在可执行状态下,如果调用sleep、suspend、wait等方法,线程都将进入堵塞状态。堵塞时,线程不能进入排队队列,只有当引起堵塞的原因被消除后,线程才可以转入就绪状态。
终止
线程调用stop或run方法执行结束后,就处于终止状态。处于终止状态的线程不具有继续运行的能力。
线程的生命周期
既然提到线程的状态,那么作为补充就必须要提到线程的生命周期。
线程从创建到终止的状态变化就对应了线程的生命周期。也可以通过某些方法手动改变线程的状态:
- suspend方法:暂时挂起线程,线程将进入堵塞状态
- resume方法:恢复挂起的线程,线程转入就绪状态
- stop方法:停止线程,线程将进入终止状态
不过上面几种方法不建议使用,因为可能会导致死锁。
在实际开发中会使用标志位来停止线程:
class MyThread implements Runnable {
private int num;
private boolean flag = true;
@Override
public void run() {
while(flag) {
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Num is " + num++);
}
}
public void stop() {
flag = false;
}
}
public class Demo {
public static void main(String args[]) throws Exception{
MyThread th = new MyThread();
new Thread(th,"th").start();
Thread.sleep(1000);
th.stop();
}
}
线程常用操作方法
线程的命名
由于多线程的存在,所有线程程序的执行,每次的执行结果都可能是不同的,因此不同的线程如果想要进行区分,就必须依靠某个标识符来进行区分,这个标识符就是线程的名字。
通常情况下,线程的名字会在其启动前进行定义,一般不建议对已经启动的线程更改名称,或者是为不同的线程设置相同的名称。
如果想要对一个线程命名,可以在构造时传入参数,或者是使用相关方法:
Thread(Runnable target, String name) // Allocates a new Thread object.
Thread(Runnable target, String name) // Allocates a new Thread object.
final String getName() // Returns this thread's name.
final void setName(String name) // Changes the name of this thread to be equal to the argument name.
static Thread currentThread() // Returns a reference to the currently executing thread object.
代码示例为:
class MyThread implements Runnable {
@Override
public void run() {
for(int i = 0;i < 3;++i) {
System.out.println(Thread.currentThread().getName() + ",i = " + i);
}
}
}
public class Demo {
public static void main(String args[]) throws Exception{
MyThread tmp = new MyThread();
new Thread(tmp,"th1").start();
MyThread tmp2 = new MyThread();
Thread th = new Thread(tmp2);
System.out.println("name is " + th.getName());
th.setName("th2");
th.start();
}
}
执行结果为:
name is Thread-0
th1,i = 0
th2,i = 0
th1,i = 1
th2,i = 1
th1,i = 2
th2,i = 2
上面的代码说明:
- 如果没有显式给线程命名,会自动命名
- 可以使用currentThread方法获取当前的线程名
线程的休眠
线程的休眠是指让程序的执行速度变慢。
public static void sleep(long millis) throws InterruptedException
上述休眠的单位为ms。
class MyThread implements Runnable {
@Override
public void run() {
for(int i = 0;i < 3;++i) {
try {
Thread.sleep(1000);
} catch (Exception e) {
System.out.println("Exception occur.");
}
System.out.println(Thread.currentThread().getName() + ",i = " + i);
}
}
}
public class Demo {
public static void main(String args[]){
MyThread tmp = new MyThread();
new Thread(tmp,"th").start();
}
}
上面的代码可以让该线程每隔1s打印一次。
线程的优先级
如果查看Thread的相关方法,会发现存在跟优先级相关的方法和属性。
static final int MAX_PRIORITY //The maximum priority that a thread can have.
static final int MIN_PRIORITY // The minimum priority that a thread can have.
static final int NORM_PRIORITY // The default priority that is assigned to a thread.
final void setPriority(int newPriority) // Changes the priority of this thread.
final int getPriority() // Returns this thread's priority.
在JAVA的线程操作中,所有的线程在运行前都会保持就绪状态,此时线程的执行顺序便和线程的优先级相关。
class MyThread implements Runnable {
@Override
public void run() {
for(int i = 0;i < 3;++i) {
try {
Thread.sleep(1000);
} catch (Exception e) {
System.out.println("Exception occur.");
}
System.out.println(Thread.currentThread().getName() + ",i = " + i);
}
}
}
public class Demo {
public static void main(String args[]){
MyThread tmp = new MyThread();
Thread th1 = new Thread(tmp,"th1");
System.out.println(th1.getPriority());
th1.start();
Thread th2 = new Thread(tmp,"th2");
System.out.println(th2.getPriority());
th2.setPriority(Thread.MAX_PRIORITY);
System.out.println(th2.getPriority());
th2.start();
}
}
执行结果为:
5
5
10
th2,i = 0
th1,i = 0
th2,i = 1
th1,i = 1
th2,i = 2
th1,i = 2
上面的代码在线程休眠结束后,会进入就绪状态,此时会重新根据优先级大小判定执行顺序。也可以看出,默认的优先级为5,最大优先级为10。
线程的同步和死锁
如果同时启动多个线程,由于多线程之间的资源竞争,很难保证各个线程之间的执行顺序。而某些场景中,如银行账户、影院票务等,必须要保证多个线程之间的执行顺序,否则就会发生问题。
线程的同步
考虑下面的例子:
class MyThread implements Runnable {
private int num = 5; // > 0
@Override
public void run() {
for(int i = 0;i < 10;++i) {
if (this.num > 0) {
try {
Thread.sleep(1000);
} catch (Exception e) {
System.out.println("Exception occur.");
}
System.out.println(Thread.currentThread().getName() + ",num = " + this.num--);
}
}
}
}
public class Demo {
public static void main(String args[]){
MyThread tmp = new MyThread();
new Thread(tmp,"th1").start();
new Thread(tmp,"th2").start();
new Thread(tmp,"th3").start();
}
}
执行结果可能是:
th1,num = 4
th2,num = 5
th3,num = 3
th1,num = 2
th2,num = 1
th3,num = 0
th1,num = -1
上面的代码中,多个线程是要共同维护一份变量num的,并在该值不大于0的时候,停止线程主方法的操作。但是实际结果中却出现了0和-1,这是不应该的。这就是由于线程之间不同步造成的。
因此才要保证各个线程之间的操作是同步的。而所谓的同步就是一个代码块中的多个操作在同一时间段内只能有一个线程进行,其它线程要等待该线程完成后才可以继续执行。
JAVA中可以通过synchronized关键字来实现同步操作:
- 同步代码块:利用synchronized包装的代码块,但是需要指定同步对象,一般设置为this
- 同步方法:利用synchronized定义的方法
利用同步代码块实现线程同步:
class MyThread implements Runnable {
private int num = 5; // > 0
@Override
public void run() {
for(int i = 0;i < 10;++i) {
synchronized(this) {
if (this.num > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
System.out.println("Exception occur.");
}
System.out.println(Thread.currentThread().getName() + ",num = " + this.num--);
}
}
}
}
}
public class Demo {
public static void main(String args[]){
MyThread tmp = new MyThread();
new Thread(tmp,"th1").start();
new Thread(tmp,"th2").start();
new Thread(tmp,"th3").start();
}
}
执行结果可能是:
th1,num = 5
th1,num = 4
th3,num = 3
th3,num = 2
th3,num = 1
使用同步方法来实现线程同步:
class MyThread implements Runnable {
private int num = 5; // > 0
@Override
public void run() {
for(int i = 0;i < 10;++i) {
this.fun();
}
}
public synchronized void fun() {
if (this.num > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
System.out.println("Exception occur.");
}
System.out.println(Thread.currentThread().getName() + ",num = " + this.num--);
}
}
}
public class Demo {
public static void main(String args[]){
MyThread tmp = new MyThread();
new Thread(tmp,"th1").start();
new Thread(tmp,"th2").start();
new Thread(tmp,"th3").start();
}
}
执行结果可能是:
th1,num = 5
th1,num = 4
th1,num = 3
th1,num = 2
th1,num = 1
线程的死锁
线程的死锁指两个线程在彼此等待,从而造成了程序的停止。
class A {
public synchronized void fun(B b) {
System.out.println("A fun");
b.foo();
}
public synchronized void foo() {
System.out.println("A foo");
}
}
class B {
public synchronized void fun(A a) {
System.out.println("B fun");
a.foo();
}
public synchronized void foo() {
System.out.println("B foo");
}
}
public class Demo implements Runnable {
private static A a = new A();
private static B b = new B();
public static void main(String args[]){
new Demo();
System.out.println("Main over");
}
public Demo() {
new Thread(this).start();
b.fun(a);
}
public void run() {
a.fun(b);
}
}
执行结果为:
B fun
A fun
上边的A和B中的方法都是同步方法,因此执行时a和b彼此等待,因此出现死锁。
生产者和消费者
生产者和消费者案例是多线程中的经典案例。
class Purse {
private int sum;
public Purse(int num) {
sum = num;
}
public synchronized void add() {
++sum;
System.out.println(Thread.currentThread().getName() + ",sum is " + sum);
}
public synchronized void sub() {
if(sum > 0) {
--sum;
System.out.println(Thread.currentThread().getName() + ",sum is " + sum);
}
}
public int getPurse() {
return sum;
}
public void setPurse(int num) {
sum = num;
}
}
class Producer implements Runnable {
private Purse pr = null;
public Producer(Purse tmp) {
this.pr = tmp;
}
@Override
public void run() {
for(int i = 0;i < 5;++i) {
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
pr.add();
}
}
}
class Consumer implements Runnable {
private Purse pr = null;
public Consumer(Purse tmp) {
this.pr = tmp;
}
@Override
public void run() {
for(int i = 0;i < 5;++i) {
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
pr.sub();
}
}
}
public class Demo {
public static void main(String args[]) {
Purse pr = new Purse(20);
Producer pro1 = new Producer(pr);
Consumer con1 = new Consumer(pr);
Consumer con2 = new Consumer(pr);
new Thread(pro1,"Producer 1").start();
new Thread(con1,"Consumer 1").start();
new Thread(con2,"Consumer 2").start();
}
}
执行结果为:
Consumer 1,sum is 19
Producer 1,sum is 20
Consumer 2,sum is 19
Consumer 1,sum is 18
Producer 1,sum is 19
Consumer 2,sum is 18
Producer 1,sum is 19
Consumer 1,sum is 18
Consumer 2,sum is 17
Producer 1,sum is 18
Consumer 1,sum is 17
Consumer 2,sum is 16
Consumer 1,sum is 15
Producer 1,sum is 16
Consumer 2,sum is 15
而同时也可以建立通知机制,来进行线程间的状态通知。这种通知主要使用以下方法:
final void notify() // Wakes up a single thread that is waiting on this object's monitor.
final void notifyAll() // Wakes up all threads that are waiting on this object's monitor.
final void wait() // Causes the current thread to wait until it is awakened, typically by being notified or interrupted.
那么上边的代码就可以修改为:
class Purse {
private int sum;
private boolean flag = true; // True: Product False:Consume
public Purse(int num) {
sum = num;
}
public synchronized void add() {
if (flag == false) {
try {
super.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
++sum;
System.out.println(Thread.currentThread().getName() + ",sum is " + sum + ",flag is " + flag);
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
this.flag = false;
super.notifyAll();
}
public synchronized void sub() {
if (flag == true) {
try {
super.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
if(sum > 0) {
--sum;
System.out.println(Thread.currentThread().getName() + ",sum is " + sum + ",flag is " + flag);
}
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
this.flag = true;
super.notifyAll();
}
public int getPurse() {
return sum;
}
public void setPurse(int num) {
sum = num;
}
public boolean getFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
class Producer implements Runnable {
private Purse pr = null;
public Producer(Purse tmp) {
this.pr = tmp;
}
@Override
public void run() {
for(int i = 0;i < 5;++i) {
pr.add();
}
}
}
class Consumer implements Runnable {
private Purse pr = null;
public Consumer(Purse tmp) {
this.pr = tmp;
}
@Override
public void run() {
for(int i = 0;i < 5;++i) {
pr.sub();
}
}
}
public class Demo {
public static void main(String args[]) {
Purse pr = new Purse(20);
Producer pro1 = new Producer(pr);
Producer pro2 = new Producer(pr);
Consumer con1 = new Consumer(pr);
Consumer con2 = new Consumer(pr);
new Thread(pro1,"Producer 1").start();
new Thread(pro1,"Producer 2").start();
new Thread(con1,"Consumer 1").start();
new Thread(con2,"Consumer 2").start();
}
}
执行结果可能为:
Producer 1,sum is 21,flag is true
Consumer 2,sum is 20,flag is false
Producer 2,sum is 21,flag is true
Consumer 2,sum is 20,flag is false
Producer 2,sum is 21,flag is true
Consumer 1,sum is 20,flag is false
Producer 1,sum is 21,flag is true
Consumer 1,sum is 20,flag is false
Producer 1,sum is 21,flag is true
Consumer 1,sum is 20,flag is false
Producer 1,sum is 21,flag is true
Consumer 1,sum is 20,flag is false
Producer 2,sum is 21,flag is true
Consumer 2,sum is 20,flag is false
Producer 2,sum is 21,flag is true
Consumer 2,sum is 20,flag is false
Producer 2,sum is 21,flag is true
Consumer 2,sum is 20,flag is false
Consumer 1,sum is 19,flag is true
Producer 1,sum is 20,flag is true
但是上边的代码也会存在如下问题:
- 当生产者的线程全部结束后,剩下的消费者线程会设置flag为true,那么未执行完毕的消费者线程会集体陷入等待
- 当消费者的线程全部结束后,剩下的生产者线程会设置flag为false,那么未执行完毕的生产者线程会集体陷入等待
因此最好还是根据生产者、消费者数目和线程主方法终止条件完善判断条件,防止程序陷入无限等待。