Java多线程学习笔记-基础篇

阅读 skywang12345Java多线程系列 的笔记与总结。

记录一下,防止读过就忘了。下面大部分都是摘自原博。

1.线程的五种状态

  1. 新建状态(New) : 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。

  2. 就绪状态(Runnable) : 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。

  3. 运行状态(Running) : 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。

  4. 阻塞状态(Blocked) : 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

    (01) 等待阻塞 – 通过调用线程的wait()方法,让线程等待某工作的完成。

    (02) 同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。

    (03) 其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

  5. 死亡状态(Dead) : 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

2.Thread与Runnable

  • Runnable 是一个接口,该接口中只包含了一个run()方法。它的定义如下:
public interface Runnable {
    publicabstractvoidrun();
}
  • Thread 是一个类。Thread本身就实现了Runnable接口。它的声明如下:
public classThreadimplementsRunnable{}

此处参见Thread类的源代码,很清楚。

private Runnable target;

publicThread(Runnabletarget){
	init(null, target, "Thread-" + nextThreadNum(), 0);
}

@Override
public voidrun(){
    if (target != null) {
        target.run();
    }
}

3.start() 和 run()的区别

  • start() : 它的作用是启动一个新线程,新线程会执行相应的run()方法。start()不能被重复调用。

  • run() : run()就和普通的成员方法一样,可以被重复调用。单独调用run()的话,会在当前线程中执行run(),而并不会启动新线程!

原理还是看上面的java源代码。

测试代码:

public class Demo {
    publicstaticvoidmain(String[] args){
        Thread mythread = new MyThread2("mythread");

        System.out.println(Thread.currentThread().getName() + " call mythread.run()");
        mythread.run();

        System.out.println(Thread.currentThread().getName() + " call mythread.start()");
        mythread.start();
    }
}

class MyThread2 extends Thread {
    publicMyThread2(String name){
        super(name);
    }

    publicvoidrun(){
        System.out.println(Thread.currentThread().getName() + " is running");
    }
};

运行结果:

main call mythread.run()
main is running
main call mythread.start()
mythread is running

4.synchronized关键字

4.1.同步锁是依赖于对象而存在

在java中,每一个对象有且仅有一个同步锁。当我们调用某对象的synchronized方法时,就获取了该对象的同步锁。别的线程此时若企图获取该同步锁,就会失败并且需要等待。

我们将synchronized的基本规则总结为下面3条,并通过实例对它们进行说明。

  1. 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。

  2. 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块。

  3. 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。

示例代码1:

classMyRunableimplementsRunnable{
    
    @Override
    publicvoidrun(){
        synchronized(this) {
            try {  
                for (int i = 0; i < 5; i++) {
                    Thread.sleep(100); // 休眠100ms
                    System.out.println(Thread.currentThread().getName() + " loop " + i);  
                }
            } catch (InterruptedException ie) {  
            }
        }  
    }
}

public classDemo1_1{

    publicstaticvoidmain(String[] args){  
        Runnable demo = new MyRunable();     // 新建“Runnable对象”

        Thread t1 = new Thread(demo, "t1");  // 新建“线程t1”, t1是基于demo这个Runnable对象
        Thread t2 = new Thread(demo, "t2");  // 新建“线程t2”, t2是基于demo这个Runnable对象
        t1.start();                          // 启动“线程t1”
        t2.start();                          // 启动“线程t2”
    } 
}

代码1运行结果:

t1 loop 0
t1 loop 1
t1 loop 2
t1 loop 3
t1 loop 4
t2 loop 0
t2 loop 1
t2 loop 2
t2 loop 3
t2 loop 4

示例代码2:

classMyThreadextendsThread{
    
    publicMyThread(String name){
        super(name);
    }

    @Override
    publicvoidrun(){
        synchronized(this) {
            try {  
                for (int i = 0; i < 5; i++) {
                    Thread.sleep(100); // 休眠100ms
                    System.out.println(Thread.currentThread().getName() + " loop " + i);  
                }
            } catch (InterruptedException ie) {  
            }
        }  
    }
}

public classDemo1_2{

    publicstaticvoidmain(String[] args){  
        Thread t1 = new MyThread("t1");  // 新建“线程t1”
        Thread t2 = new MyThread("t2");  // 新建“线程t2”
        t1.start();                          // 启动“线程t1”
        t2.start();                          // 启动“线程t2”
    } 
}

代码2运行结果:

t1 loop 0
t2 loop 0
t1 loop 1
t2 loop 1
t1 loop 2
t2 loop 2
t1 loop 3
t2 loop 3
t1 loop 4
t2 loop 4

区别在于,示例1的两个线程都访问的是demo这个对象的锁,示例2的两个线程访问的是t1,t2各自的锁。

一定要搞清锁是谁的锁,这一点明白之后上面总结的所谓123条完全可以不看。

4.2.synchronized方法 和 synchronized代码块

“synchronized方法”是用synchronized修饰方法,而 “synchronized代码块”则是用synchronized修饰代码块。

synchronized方法示例:

public synchronized voidfoo1(){
    System.out.println("synchronized methoed");
}

synchronized代码块:

public voidfoo2(){
    synchronized (this) {
        System.out.println("synchronized methoed");
    }
}

synchronized代码块中的this是指当前对象。也可以将this替换成其他对象,例如将this替换成obj,则foo2()在执行synchronized(obj)时就获取的是obj的同步锁。

synchronized代码块可以更精确的控制冲突限制访问区域,效率更高

4.3.实例锁 和 全局锁

  • 实例锁 : 锁在某一个实例对象上。如果该类是单例,那么该锁也具有全局锁的概念。

    实例锁对应的就是synchronized关键字。
  • 全局锁 : 该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。

    全局锁对应的就是static synchronized(或者是锁在该类的class或者classloader对象上)。

示例代码3:

// LockTest3.java的源码
classSomething{
    publicstaticsynchronizedvoidcSyncA(){
        try {  
            for (int i = 0; i < 5; i++) {
                Thread.sleep(100); // 休眠100ms
                System.out.println(Thread.currentThread().getName()+" : cSyncA");
            } 
        }catch (InterruptedException ie) {  
        }  
    }
    publicstaticsynchronizedvoidcSyncB(){
        try {  
            for (int i = 0; i < 5; i++) {
                Thread.sleep(100); // 休眠100ms
                System.out.println(Thread.currentThread().getName()+" : cSyncB");
            } 
        }catch (InterruptedException ie) {  
        }  
    }
}

public classLockTest3{

    Something x = new Something();
    Something y = new Something();

    // 比较(03) x.cSyncA()与y.cSyncB()
    privatevoidtest3(){
        // 新建t31, t31会调用 x.isSyncA()
        Thread t31 = new Thread(
                new Runnable() {
                    @Override
                    publicvoidrun(){
                        x.cSyncA();
                    }
                }, "t31");

        // 新建t32, t32会调用 x.isSyncB()
        Thread t32 = new Thread(
                new Runnable() {
                    @Override
                    publicvoidrun(){
                        y.cSyncB();
                    }
                }, "t32");  


        t31.start();  // 启动t31
        t32.start();  // 启动t32
    }

    publicstaticvoidmain(String[] args){
        LockTest3 demo = new LockTest3();

        demo.test3();
    }
}

代码3结果:

t31 : cSyncA
t31 : cSyncA
t31 : cSyncA
t31 : cSyncA
t31 : cSyncA
t32 : cSyncB
t32 : cSyncB
t32 : cSyncB
t32 : cSyncB
t32 : cSyncB

示例代码4:

// LockTest4.java的源码
classSomething{
    publicsynchronizedvoidisSyncA(){
        try {  
            for (int i = 0; i < 5; i++) {
                Thread.sleep(100); // 休眠100ms
                System.out.println(Thread.currentThread().getName()+" : isSyncA");
            }
        }catch (InterruptedException ie) {  
        }  
    }
    publicstaticsynchronizedvoidcSyncA(){
        try {  
            for (int i = 0; i < 5; i++) {
                Thread.sleep(100); // 休眠100ms
                System.out.println(Thread.currentThread().getName()+" : cSyncA");
            } 
        }catch (InterruptedException ie) {  
        }  
    }
}

public classLockTest4{

    Something x = new Something();

    // 比较(04) x.isSyncA()与Something.cSyncA()
    privatevoidtest4(){
        // 新建t41, t41会调用 x.isSyncA()
        Thread t41 = new Thread(
                new Runnable() {
                    @Override
                    publicvoidrun(){
                        x.isSyncA();
                    }
                }, "t41");

        // 新建t42, t42会调用 x.isSyncB()
        Thread t42 = new Thread(
                new Runnable() {
                    @Override
                    publicvoidrun(){
                        x.cSyncA();
                    }
                }, "t42");  


        t41.start();  // 启动t41
        t42.start();  // 启动t42
    }

    publicstaticvoidmain(String[] args){
        LockTest4 demo = new LockTest4();

        demo.test4();
    }
}

代码4结果:

t41 : isSyncA
t42 : cSyncA
t41 : isSyncA
t42 : cSyncA
t41 : isSyncA
t42 : cSyncA
t41 : isSyncA
t42 : cSyncA
t41 : isSyncA
t42 : cSyncA

示例3不能被同时访问。因为cSyncA()和cSyncB()都是static类型,x.cSyncA()相当于Something.isSyncA(),y.cSyncB()相当于Something.isSyncB(),因此它们共用一个同步锁,不能被同时访问。

示例4可以被同时访问。因为isSyncA()是实例方法,x.isSyncA()使用的是对象x的锁;而cSyncA()是静态方法,Something.cSyncA()可以理解对使用的是“类的锁”。因此,它们是可以被同时访问的。

5.线程等待与唤醒

5.1.wait(), notify(), notifyAll()等方法介绍

在Object.java中,定义了wait(), notify()和notifyAll()等接口。wait()的作用是让 当前线程 进入等待状态,同时,wait()也会让 当前线程 释放它所持有的锁。而notify()和notifyAll()的作用,则是唤醒 当前对象 上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。

5.2.wait()和notify()示例

// WaitTest.java的源码
classThreadAextendsThread{

    publicThreadA(String name){
        super(name);
    }

    publicvoidrun(){
        synchronized (this) {
            System.out.println(Thread.currentThread().getName()+" call notify()");
            // 唤醒当前的wait线程
            notify();
        }
    }
}

public classWaitTest{

    publicstaticvoidmain(String[] args){

        ThreadA t1 = new ThreadA("t1");

        synchronized(t1) {
            try {
                // 启动“线程t1”
                System.out.println(Thread.currentThread().getName()+" start t1");
                t1.start();

                // 主线程等待t1通过notify()唤醒。
                System.out.println(Thread.currentThread().getName()+" wait()");
                t1.wait();

                System.out.println(Thread.currentThread().getName()+" continue");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:

main start t1
main wait()
t1 call notify()
main continue

结果说明:

如下图,说明了“主线程”和“线程t1”的流程。

(01) 注意,图中”主线程” 代表“主线程main”。”线程t1” 代表WaitTest中启动的“线程t1”。 而“锁” 代表“t1这个对象的同步锁”。

(02) “主线程”通过 new ThreadA(“t1”) 新建“线程t1”。随后通过synchronized(t1)获取“t1对象的同步锁”。然后调用t1.start()启动“线程t1”。

(03) “主线程”执行t1.wait() 释放“t1对象的锁”并且进入“等待(阻塞)状态”。等待t1对象上的线程通过notify() 或 notifyAll()将其唤醒。

(04) “线程t1”运行之后,通过synchronized(this)获取“当前对象的锁”;接着调用notify()唤醒“当前对象上的等待线程”,也就是唤醒“主线程”。

(05) “线程t1”运行完毕之后,释放“当前对象的锁”。紧接着,“主线程”获取“t1对象的锁”,然后接着运行。

注意:jdk的解释中,说wait()的作用是让“当前线程”等待,而“当前线程”是指正在cpu上运行的线程!

这也意味着,虽然t1.wait()是通过“线程t1”调用的wait()方法,但是调用t1.wait()的地方是在“主线程main”中。而主线程必须是“当前线程”,也就是运行状态,才可以执行t1.wait()。所以,此时的“当前线程”是“主线程main”!因此,t1.wait()是让“主线程”等待,而不是“线程t1”!

4.3.wait() 和 notifyAll()示例

public classNotifyAllTest{

    private static Object obj = new Object();
    publicstaticvoidmain(String[] args){

        ThreadA t1 = new ThreadA("t1");
        ThreadA t2 = new ThreadA("t2");
        ThreadA t3 = new ThreadA("t3");
        t1.start();
        t2.start();
        t3.start();

        try {
            System.out.println(Thread.currentThread().getName()+" sleep(3000)");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        synchronized(obj) {
            // 主线程等待唤醒。
            System.out.println(Thread.currentThread().getName()+" notifyAll()");
            obj.notifyAll();
        }
    }

    static classThreadAextendsThread{

        publicThreadA(String name){
            super(name);
        }

        publicvoidrun(){
            synchronized (obj) {
                try {
                    // 打印输出结果
                    System.out.println(Thread.currentThread().getName() + " wait");

                    // 唤醒当前的wait线程
                    obj.wait();

                    // 打印输出结果
                    System.out.println(Thread.currentThread().getName() + " continue");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行结果:

t1 wait
t3 wait
t2 wait
main sleep(3000)
main notifyAll()
t2 continue
t3 continue
t1 continue

流程图如下:

6.线程让步yield()

yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!

例如:

// YieldTest.java的源码
classThreadAextendsThread{
    publicThreadA(String name){ 
        super(name); 
    } 
    publicsynchronizedvoidrun(){ 
        for(int i=0; i <10; i++){ 
            System.out.printf("%s [%d]:%dn", this.getName(), this.getPriority(), i); 
            // i整除4时,调用yield
            if (i%4 == 0)
                Thread.yield();
        } 
    } 
} 

public classYieldTest{ 
    publicstaticvoidmain(String[] args){ 
        ThreadA t1 = new ThreadA("t1"); 
        ThreadA t2 = new ThreadA("t2"); 
        t1.start(); 
        t2.start();
    } 
}

理论上的结果:

t1 [5]:0
t2 [5]:0
t1 [5]:1
t1 [5]:2
t1 [5]:3
t1 [5]:4
t2 [5]:1
t2 [5]:2
t2 [5]:3
t2 [5]:4
t1 [5]:5
t1 [5]:6
t1 [5]:7
t1 [5]:8
t2 [5]:5
t2 [5]:6
t2 [5]:7
t2 [5]:8
t1 [5]:9
t2 [5]:9

但是,yield()方法不会释放锁,上例中sync…关键字是对各自对象加锁,如果对同一个对象,那么t2会一直阻塞直到t1结束。这一点与wait不同。

7.线程休眠sleep()

sleep() 定义在Thread.java中。

sleep() 的作用是让当前线程休眠,即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待cpu的调度执行。

使用方法:

//让当前线程休眠(阻塞)100ms
Thread.sleep(100);

同yield()一样,sleep不会释放对象锁。如果sleep期间还一直占用对象锁,其他线程会一直阻塞。

8. join()介绍

join() 定义在Thread.java中。join() 的作用:让“主线程”等待“子线程”结束之后才能继续运行。

使用方法:

//在Father线程中调用:
sonThread.join();

则father线程会阻塞到son线程完成。join的原理是用wait做的,因此其行为类似wait。

9.interrupt()和线程终止

首先,Thread中的stop()和suspend()方法,由于固有的不安全性,已经建议不再使用!

interrupt()的作用是中断本线程。具体原理原博客也没说清楚。下面结合实际说说终止线程的方式。

9.1.终止处于“阻塞状态”的线程

当线程由于被调用了sleep(), wait(), join()等方法而进入阻塞状态;若此时调用线程的interrupt()将线程的中断标记设为true。由于处于阻塞状态,中断标记会被清除,同时产生一个InterruptedException异常。将InterruptedException放在适当的位置就能终止线程,形式如下:

@Override
public voidrun(){
    try {
        while (true) {
            // 执行任务...
        }
    } catch (InterruptedException ie) {  
        // 由于产生InterruptedException异常,退出while(true)循环,线程终止!
    }
}

说明:在while(true)中不断的执行任务,当线程处于阻塞状态时,调用线程的interrupt()产生InterruptedException中断。中断的捕获在while(true)之外,这样就退出了while(true)循环!

9.2.终止处于“运行状态”的线程

9.2.1.通过“中断标记”终止运行中的线程

@Override
public voidrun(){
    while (!isInterrupted()) {
        // 执行任务...
    }
}

说明:isInterrupted()是判断线程的中断标记是不是为true。当线程处于运行状态,并且我们需要终止它时;可以调用线程的interrupt()方法,使用线程的中断标记为true,即isInterrupted()会返回true。此时,就会退出while循环。

注意: interrupt()并不会终止处于“运行状态”的线程!它会将线程的中断标记设为true

9.2.2.通过“额外添加标记”终止运行中的线程

private volatile boolean flag= true;
protected voidstopTask(){
    flag = false;
}

@Override
public voidrun(){
    while (flag) {
        // 执行任务...
    }
}

说明:线程中有一个flag标记,它的默认值是true;并且我们提供stopTask()来设置flag标记。当我们需要终止该线程时,调用该线程的stopTask()方法就可以让线程退出while循环。

注意:将flag定义为volatile类型,是为了保证flag的可见性。即其它线程通过stopTask()修改了flag之后,本线程能看到修改后的flag的值。

9.3.通用的终止线程的形式

线程处于“阻塞状态”和“运行状态”的终止方式:

@Override
public voidrun(){
    try {
        // 1. isInterrupted()保证,只要中断标记为true就终止线程。
        while (!isInterrupted()) {
            // 执行任务...
        }
    } catch (InterruptedException ie) {  
        // 2. InterruptedException异常保证,当InterruptedException异常产生时,线程被终止。
    }
}

10.线程优先级和守护线程

java 中的线程优先级的范围是1~10,默认的优先级是5。“高优先级线程”会优先于“低优先级线程”执行。

java 中有两种线程:用户线程和守护线程。可以通过isDaemon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。

用户线程一般用户执行用户级任务,而守护线程也就是“后台线程”,一般用来执行后台任务。需要注意的是:Java虚拟机在“用户线程”都结束后会后退出。

11.综合应用:生产消费者问题

生产/消费者问题是个非常典型的多线程问题,涉及到的对象包括“生产者”、“消费者”、“仓库”和“产品”。他们之间的关系如下:

(01) 生产者仅仅在仓储未满时候生产,仓满则停止生产。

(02) 消费者仅仅在仓储有产品时候才能消费,仓空则等待。

(03) 当消费者发现仓储没产品可消费时候会通知生产者生产。

(04) 生产者在生产出可消费产品时候,应该通知等待的消费者去消费。

代码来自原博客,有改动,使过程更清晰:

// 仓库
classDepot{
    private int capacity;    // 仓库的容量
    private int size;        // 仓库的实际数量

    publicDepot(intcapacity){
        this.capacity = capacity;
        this.size = 0;
    }

    publicsynchronizedvoidproduce(intval){
        try {
            // left 表示“想要生产的数量”(有可能生产量太多,需多次生产)
            int left = val;
            while (left > 0) {
                // 库存已满时,等待“消费者”消费产品。
                while (size >= capacity)
                    wait();
                // 获取“实际生产的数量”(即库存中新增的数量)
                // 如果“库存”+“想要生产的数量”>“总的容量”,则“实际增量”=“总的容量”-“当前容量”。(此时填满仓库)
                // 否则“实际增量”=“想要生产的数量”
                int inc = (size + left) > capacity ? (capacity - size) : left;
                size += inc;
                left -= inc;
                System.out.printf("%s 生产:(%3d)个的过程 --> 还需生产=%3d, 已生产=%3d, 仓库库存=%3dn",
                        Thread.currentThread().getName(), val, left, inc, size);
                // 通知“消费者”可以消费了。
                notifyAll();
            }
        } catch (InterruptedException e) {
        }
    }

    publicsynchronizedvoidconsume(intval){
        try {
            // left 表示“客户要消费数量”(有可能消费量太大,库存不够,需多此消费)
            int left = val;
            while (left > 0) {
                // 库存为0时,等待“生产者”生产产品。
                while (size <= 0)
                    wait();
                // 获取“实际消费的数量”(即库存中实际减少的数量)
                // 如果“库存”<“客户要消费的数量”,则“实际消费量”=“库存”;
                // 否则,“实际消费量”=“客户要消费的数量”。
                int dec = (size < left) ? size : left;
                size -= dec;
                left -= dec;
                System.out.printf("%s 消费:(%3d)个的过程 <-- 还需消费=%3d, 已消费=%3d, 仓库剩余=%3dn",
                        Thread.currentThread().getName(), val, left, dec, size);
                notifyAll();
            }
        } catch (InterruptedException e) {
        }
    }

    publicStringtoString(){
        return "capacity:" + capacity + ", actual size:" + size;
    }
}

// 生产者
classProducer{
    private Depot depot;

    publicProducer(Depot depot){
        this.depot = depot;
    }

    // 消费产品:新建一个线程向仓库中生产产品。
    publicvoidproduce(finalintval){
        new Thread() {
            publicvoidrun(){
                depot.produce(val);
            }
        }.start();
    }
}

// 消费者
classCustomer{
    private Depot depot;

    publicCustomer(Depot depot){
        this.depot = depot;
    }

    // 消费产品:新建一个线程从仓库中消费产品。
    publicvoidconsume(finalintval){
        new Thread() {
            publicvoidrun(){
                depot.consume(val);
            }
        }.start();
    }
}

public classDemo1{
    publicstaticvoidmain(String[] args)throwsInterruptedException{
        Depot mDepot = new Depot(100);
        Producer mPro = new Producer(mDepot);
        Customer mCus = new Customer(mDepot);

        mPro.produce(60);
        //过了一会儿
        Thread.sleep(1000);
        mPro.produce(120);
        //过了一会儿
        Thread.sleep(1000);
        mCus.consume(90);
        //过了一会儿
        Thread.sleep(1000);
        mCus.consume(150);
        //过了一会儿
        Thread.sleep(1000);
        mPro.produce(110);
    }
}

说明:

(01) Producer是“生产者”类,它与“仓库(depot)”关联。当调用“生产者”的produce()方法时,它会新建一个线程并向“仓库”中生产产品。

(02) Customer是“消费者”类,它与“仓库(depot)”关联。当调用“消费者”的consume()方法时,它会新建一个线程并消费“仓库”中的产品。

(03) Depot是“仓库”类,仓库中记录“仓库的容量(capacity)”以及“仓库中当前产品数目(size)”。

“仓库”类的生产方法produce()和消费方法consume()方法都是synchronized方法,进入synchronized方法体,意味着这个线程获取到了该“仓库”对象的同步锁。这也就是说,同一时间,生产者和消费者线程只能有一个能运行。通过同步锁,实现了对“残酷”的互斥访问。

对于生产方法produce()而言:当仓库满时,生产者线程等待,需要等待消费者消费产品之后,生产线程才能生产;生产者线程生产完产品之后,会通过notifyAll()唤醒同步锁上的所有线程,包括“消费者线程”,即我们所说的“通知消费者进行消费”。

对于消费方法consume()而言:当仓库为空时,消费者线程等待,需要等待生产者生产产品之后,消费者线程才能消费;消费者线程消费完产品之后,会通过notifyAll()唤醒同步锁上的所有线程,包括“生产者线程”,即我们所说的“通知生产者进行生产”。

运行结果:

Thread-0 生产:( 60)个的过程 --> 还需生产=  0, 已生产= 60, 仓库库存= 60
Thread-1 生产:(120)个的过程 --> 还需生产= 80, 已生产= 40, 仓库库存=100
Thread-2 消费:( 90)个的过程  还需生产=  0, 已生产= 80, 仓库库存= 90
Thread-3 消费:(150)个的过程  还需生产= 10, 已生产=100, 仓库库存=100
Thread-3 消费:(150)个的过程  还需生产=  0, 已生产= 10, 仓库库存= 50
稿源:SunQiang's Blog (源链) | 关于 | 阅读提示

本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 综合技术 » Java多线程学习笔记-基础篇

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录