前言
多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具备多线程能力的计算机因有硬件反对而可能在同一时间执行多于一个线程,进而晋升整体解决性能。具备这种能力的零碎包含对称多处理机、多外围处理器以及芯片级多解决或同时多线程处理器。
原理并不简单,但实现起来可没那么简略,在网上看到两张图,我感觉形容多线程很是活泼,给大伙看看
现实多线程:
事实多线程:
是不是感觉很形象?
篇幅所限,这篇文章咱们就先讲讲过程与线程、并发与并行的外围原理以及线程的创立,如果想更深刻的理解多线程,我这里也整顿了一些多线程的学习材料与面试材料,如下:
关注公众号:北游学Java,回复“630”即可支付
一、过程与线程
过程:是代码在数据汇合上的一次运行流动,是零碎进行资源分配和调度的根本单位。
线程:是过程的一个执行门路,一个过程中至多有一个线程,过程中的多个线程共享过程的 资源。
尽管零碎是把资源分给过程,然而CPU很非凡,是被调配到线程的,所以线程是CPU调配的根本单位。
二者关系:
一个过程中有多个线程,多个线程共享过程的堆和办法区资源,然而每个线程有本人的程序计数器和栈区域。
程序计数器:是一块内存区域,用来记录线程以后要执行的指令地址 。
栈:用于存储该线程的局部变量,这些局部变量是该线程公有的,除此之外还用来寄存线程的调用栈祯。
堆:是一个过程中最大的一块内存,堆是被过程中的所有线程共享的。
办法区:则用来寄存 NM 加载的类、常量及动态变量等信息,也是线程共享的 。
二者区别:
过程:有独立的地址空间,一个过程解体后,在保护模式下不会对其它过程产生影响。
线程:是一个过程中的不同执行门路。线程有本人的堆栈和局部变量,但线程之间没有独自的地址空间,一个线程死掉就等于整个过程死掉。
- 简而言之,一个程序至多有一个过程,一个过程至多有一个线程.
- 线程的划分尺度小于过程,使得多线程程序的并发性高。
- 另外,过程在执行过程中领有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
- 每个独立的线程有一个程序运行的入口、程序执行序列和程序的进口。然而线程不可能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行管制。
- 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行局部能够同时执行。但操作系统并没有将多个线程看做多个独立的利用,来实现过程的调度和治理以及资源分配。这就是过程和线程的重要区别
二、并发与并行
并发:是指同一个时间段内多个工作同时都在执行,并且都没有执行完结。并发工作强调在一个时间段内同时执行,而一个时间段由多个单位工夫累积而成,所以说并发的多个工作在单位工夫内不肯定同时在执行 。
并行:是说在单位工夫内多个工作同时在执行 。
在多线程编程实际中,线程的个数往往多于CPU的个数,所以个别都称多线程并发编程而不是多线程并行编程。
并发过程中常见的问题:
1、线程平安问题:
多个线程同时操作共享变量1时,会呈现线程1更新共享变量1的值,然而其余线程获取到的是共享变量没有被更新之前的值。就会导致数据不精确问题。
2、共享内存不可见性问题
Java内存模型(解决共享变量)
Java 内存模型规定,将所有的变量都寄存在主内存中,当线程应用变量时,会把主内存外面的变量复制到本人的工作空间或者叫作工作内存,线程读写变量时操作的是本人工作内存中的变量 。(如上图所示)
(理论工作的java内存模型)
上图中所示是一个双核 CPU 零碎架构,每个核有本人的控制器和运算器,其中控制器蕴含一组寄存器和操作控制器,运算器执行算术逻辅运算。CPU的每个核都有本人的一级缓存,在有些架构外面还有一个所有CPU都共享的二级缓存。 那么Java内存模型外面的工作内存,就对应这里的 Ll或者 L2 缓存或者 CPU 的寄存器
- 线程A首先获取共享变量X的值,因为两级Cache都没有命中,所以加载主内存中X的值,如果为0。而后把X=0的值缓存到两级缓存,线程A批改X的值为1,而后将其写入两级Cache,并且刷新到主内存。线程A操作结束后,线程A所在的CPU的两级Cache内和主内存外面的X的值都是l。
- 线程B获取X的值,首先一级缓存没有命中,而后看二级缓存,二级缓存命中了,所以返回X=1;到这里一切都是失常的,因为这时候主内存中也是X=l。而后线程B批改X的值为2,并将其寄存到线程2所在的一级Cache和共享二级Cache中,最初更新主内存中X的值为2,到这里一切都是好的。
- 线程A这次又须要批改X的值,获取时一级缓存命中,并且X=l这里问题就呈现了,明明线程B曾经把X的值批改为2,为何线程A获取的还是l呢?这就是共享变量的内存不可见问题,也就是线程B写入的值对线程A不可见。
synchronized 的内存语义:
这个内存语义就能够解决共享变量内存可见性问题。进入synchronized块的内存语义是把在synchronized块内应用到的变量从线程的工作内存中革除,这样在synchronized块内应用到该变量时就不会从线程的工作内存中获取,而是间接从主内存中获取。退出synchronized块的内存语义是把在synchronized块内对共享变量的批改刷新到主内存。会造成上下文切换的开销,独占锁,升高并发性
Volatile的了解:
该关键字能够确保对一个变量的更新对其余线程马上可见。当一个变量被申明为volatile时,线程在写入变量时不会把值缓存在寄存器或者其余中央,而是会把值刷新回主内存。当其余线程读取该共享变量时-,会从主内存从新获取最新值,而不是应用以后线程的工作内存中的值。volatile的内存语义和synchronized有相似之处,具体来说就是,当线程写入了volatile变量值时就等价于线程退出synchronized同步块(把写入工作内存的变量值同步到主内存),读取volatile变量值时就相当于进入同步块(先清空本地内存变量值,再从主内存获取最新值)。不能保障原子性
三、创立线程
1、继承Thread类
重写run办法:应用继承形式的益处是,在run()办法内获取以后线程间接应用this就能够了,毋庸应用Thread.currentThread()办法;不好的中央是Java不反对多继承,如果继承了Thread类,那么就不能再继承其余类。另外工作与代码没有拆散,当多个线程执行一样的工作时须要多份工作代码。
public class ThreadRuning extends Thread{ public ThreadRuning(String name){ //重写结构,能够对线程增加名字 super(name); } @Override public void run() { while(true){ System.out.println("good time");//在run办法里,this代表以后线程 System.out.println(this); } } public static void main(String[] args){ ThreadRuning threadRuning = new ThreadRuning("1111"); threadRuning.start(); }}
2、实现Runable接口
实现run办法:解决继承Thread的毛病,没有返回值
public class RunableTest implements Runnable { @Override public void run() { while (true) { System.out.println("good time"); } } public static void main(String[] args) { RunableTest runableTest1 = new RunableTest(); RunableTest runableTest2 = new RunableTest(); new Thread(runableTest1).start(); new Thread(runableTest1).start(); new Thread(runableTest2).start(); }}
3、实现Callable接口
实现call办法:
public class CallTest implements Callable { @Override public Object call() throws Exception { return "hello world"; } public static void main(String[] args){ FutureTask<String> futureTask = new FutureTask<String>(new CallTest()); new Thread(futureTask).start(); try { String result = futureTask.get(); System.out.println(result); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }}
应用继承形式的益处是不便传参,你能够在子类外面增加成员变量,通过set办法设置参数或者通过构造函数进行传递,而如果应用Runnable形式,则只能应用主线程外面被申明为final的变量。不好的中央是Java不反对多继承,如果继承了Thread类,那么子类不能再继承其余类,而Runable则没有这个限度。前两种形式都没方法拿到工作的返回后果,然而Callable形式能够
四、Thread类详解
线程个性:
1、线程能被标记为守护线程,也能够是用户线程
2、每个线程均调配一个name,默认为(Thread-自增数字)的组合
3、每个线程都有优先级.高优先级线程优先于低优先级线程执行. 1-10,默认为5
4、main所在的线程组为main,结构线程的时候没有事实的指定线程组,线程组默认和父线程一样
5、当线程中的run()办法代码外面又创立了一个新的线程对象时,新创建的线程优先级和父线程优先级一样.
6、当且仅当父线程为守护线程时,新创建的线程才会是守护线程.
7、当JVM启动时,通常会有惟一的一个非守护线程(这一线程用于调用指定类的main()办法)
JVM会继续执行线程直到上面状况某一个产生为止:
1)类运行时exit()办法被调用 且 平安机制容许此exit()办法的调用.
2)所有非守护类型的线程均曾经终止,or run()办法调用返回or在run()办法内部抛出了一些可流传性的异样.
Init办法:
/** * Initializes a Thread. * @param g 线程组 * @param target 执行对象 * @param name 线程名 * @param stackSize 新线程栈大小,为0示意疏忽 * @param acc用于继承的访问控制上下文 * @param inheritThreadLocals如果值为true,从结构线程继承可继承线程局部变量的初始值*/private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { if (name == null) { throw new NullPointerException("name cannot be null"); } this.name = name; Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); //如果所属线程组为nullif (g == null) { /* Determine if it's an applet or not */ /* If there is a security manager, ask the security manager //如果有平安治理,查问平安治理须要做的工作 what to do. */ if (security != null) { g = security.getThreadGroup(); } /* If the security doesn't have a strong opinion of the matter use the parent thread group. *///如果平安治理在线程所属父线程组的问题上没有什么强制的要求 if (g == null) { g = parent.getThreadGroup(); } } /* checkAccess regardless of whether or not threadgroup is explicitly passed in. *///无论所属线程组是否显示传入,都要进行查看拜访. g.checkAccess(); /* * Do we have the required permissions? */ if (security != null) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } g.addUnstarted(); this.group = g; this.daemon = parent.isDaemon();//如果父线程为守护线程,则此线程也被 设置为守护线程. this.priority = parent.getPriority();//获取父过程的优先级 if (security == null || isCCLOverridden(parent.getClass())) this.contextClassLoader = parent.getContextClassLoader(); else this.contextClassLoader = parent.contextClassLoader; this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); this.target = target; setPriority(priority); if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; /* Set thread ID 设置线程id*/ tid = nextThreadID();}
构造方法:所有的构造方法都是调用init()办法
public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0);} public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0);} public Thread(Runnable target, AccessControlContext acc) { init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);} public Thread(ThreadGroup group, Runnable target) { init(group, target, "Thread-" + nextThreadNum(), 0);} public Thread(String name) { init(null, null, name, 0);} public Thread(ThreadGroup group, String name) { init(group, null, name, 0);} public Thread(Runnable target, String name) { init(null, target, name, 0);} public Thread(ThreadGroup group, Runnable target, String name, long stackSize) { init(group, target, name, stackSize);}
线程状态:
public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; }
NEW:状态是指线程刚创立, 尚未启动
RUNNABLE:状态是线程正在失常运行中, 当然可能会有某种耗时计算/IO期待的操作/CPU工夫片切换等, 这个状态下产生的期待个别是其余系统资源, 而不是锁, Sleep等
BLOCKED:这个状态下, 是在多个线程有同步操作的场景, 比方正在期待另一个线程的synchronized 块的执行开释, 或者可重入的 synchronized块里他人调用wait() 办法, 也就是这里是线程在期待进入临界区
WAITING:这个状态下是指线程领有了某个锁之后, 调用了他的wait办法, 期待其余线程/锁拥有者调用 notify / notifyAll 一遍该线程能够持续下一步操作, 这里要辨别 BLOCKED 和 WATING 的区别, 一个是在临界点里面期待进入, 一个是在了解点外面wait期待他人notify, 线程调用了join办法 join了另外的线程的时候, 也会进入WAITING状态, 期待被他join的线程执行完结
TIMED_WAITING:这个状态就是无限的(工夫限度)的WAITING, 个别呈现在调用wait(long), join(long)等状况下, 另外一个线程sleep后, 也会进入TIMED_WAITING状态
TERMINATED: 这个状态下示意 该线程的run办法曾经执行结束了, 基本上就等于死亡了(过后如果线程被长久持有, 可能不会被回收)
(在很多文章中都写了running状态,其实源码外面只有六种的,当本人写一个线程通过while始终放弃执行状态,而后应用jconsole工具去查看线程的状态,的确是Runable状态)
Api文档是这么说的:
其实咱们能够了解为两种状态,一个是running,示意正在执行,一个是runable,示意准备就绪了,只是在期待其余的系统资源。而后咱们就能够了解如下图
Start办法:
public synchronized void start() { /** * 此办法并不会被次要办法线程or由虚拟机创立的零碎组线程所调用. * 任何向此办法增加的新性能办法在将来都会被增加到虚拟机中. * 0状态值代表了NEW的状态. */ if (threadStatus != 0) // 线程不能反复start 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();
是一个本地办法,提醒线程调度器以后线程违心放弃以后CPU的应用。如果以后资源不缓和,调度器能够疏忽这个提醒。实质上线程状态始终是RUNNABLE,然而我能够了解为RUNNABLE到RUNNING的转换
sleep办法:
/** * 此办法会引起以后执行线程sleep(长期进行执行)指定毫秒数. * 此办法的调用不会引起以后线程放弃任何监听器(monitor)的所有权(ownership). */public static native void sleep(long millis) throws InterruptedException; public static void sleep(long millis, int nanos)throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && millis == 0)) { millis++; } sleep(millis);}
sleep办法,有一个重载办法,sleep办法会开释cpu的工夫片,然而不会开释锁,调用sleep()之后从RUNNABLE状态转为TIMED_WAITING状态
join办法
/** * 最多期待参数millis(ms)时长以后线程就会死亡.参数为0时则要继续期待. * 此办法在实现上:循环调用以this.isAlive()办法为条件的wait()办法. * 当线程终止时notifyAll()办法会被调用. * 倡议应用程序不要在线程实例上应用wait,notify,notifyAll办法. */ public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; //如果等待时间<0,则抛出异样 if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } //如果等待时间为0 if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } } //等待时间单位为纳秒,其它解释都和下面办法一样 public final synchronized void join(long millis, int nanos) throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && millis == 0)) { millis++; } join(millis); } //办法性能:期待始终到线程死亡. public final void join() throws InterruptedException { join(0); }
join某个线程A,会使得线程B进入期待,晓得线程A完结,或者达到给定的工夫,那么期间线程B处于BLOCKED的状态,而不是线程A
五、其余办法
接下来聊一下Object类的wait,notify和notifyAll办法
wait办法
public final native void wait(long timeout) throws InterruptedException; //本地办法 参数为毫秒public final void wait(long timeout, int nanos) throws InterruptedException {//参数为毫秒和纳秒 if (timeout < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos > 0) { timeout++; } wait(timeout); } public final void wait() throws InterruptedException { wait(0); }
可见wait()和wait(long timeout, int nanos)都在在外部调用了wait(long timeout)办法。
上面次要是说说wait(long timeout)办法
wait办法会引起以后线程阻塞,直到另外一个线程在对应的对象上调用notify或者notifyAll()办法,或者达到了办法参数中指定的工夫。
调用wait办法的以后线程肯定要领有对象的监视器锁。
wait办法会把以后线程T搁置在对应的object上的期待队列中,在这个对象上的所有同步申请都不会失去响应。线程调度将不会调用线程T,在以下四件事产生之前,线程T会被唤醒(线程T是在其代码中调用wait办法的那个线程)
1、当其余的线程在对应的对象上调用notify办法,而在此对象的对应的期待队列中将会任意抉择一个线程进行唤醒。
2、其余的线程在此对象上调用了notifyAll办法
3、其余的线程调用了interrupt办法来中断线程T
4、期待的工夫曾经超过了wait中指定的工夫。如果参数timeout的值为0,不是指实在的等待时间是0,而是线程期待直到被另外一个线程唤醒为止。
被唤醒的线程T会被从对象的期待队列中移除并且从新可能被线程调度器调度。之后,线程T会像平时一样跟其余的线程竞争获取对象上的锁;一旦线程T取得了此对象上的锁,那么在此对象上的所有同步申请都会复原到之前的状态,也就是复原到wait被调用的状况下。而后线程T从wait办法的调用中返回。因而,当从wait办法返回时,对象的状态以及线程T的状态跟wait办法被调用的时候一样。
线程在没有被唤醒,中断或者工夫耗尽的状况下依然可能被唤醒,这叫做伪唤醒。尽管在理论中,这种状况很少产生,然而程序肯定要测试这个可能唤醒线程的条件,并且在条件不满足时,线程持续期待。换言之,wait操作总是呈现在循环中,就像上面这样:
synchronized(对象){ while(条件不满足){ 对象.wait(); } 对应的逻辑解决}
如果以后的线程被其余的线程在以后线程期待之前或者正在期待时调用了interrupt()中断了,那么会抛出InterruptedExcaption异样。直到这个对象下面的锁状态复原到下面形容的状态以前,这个异样是不会抛出的。
要留神的是,wait办法把以后线程搁置到这个对象的期待队列中,解锁也仅仅是在这个对象上;以后线程在其余对象下面上的锁在以后线程期待的过程中依然持有其余对象的锁。
这个办法应该仅仅被持有对象监视器的线程调用。
wait(long timeout, int nanos)办法的实现中只有nanos大于0,那么timeout工夫就加上一毫秒,次要是更准确的管制工夫,其余的跟wait(long timeout)一样
notify办法
public final native void notify(); //本地办法
告诉可能期待该对象的对象锁的其余线程。由JVM(与优先级无关)随机筛选一个处于wait状态的线程。
在调用notify()之前,线程必须取得该对象的对象级别锁
执行完notify()办法后,不会马上开释锁,要直到退出synchronized代码块,以后线程才会开释锁
notify()一次只随机告诉一个线程进行唤醒
notifyAll()办法
public final native void notifyAll();//本地办法
和notify()差不多,只不过是使所有正在期待池中期待同一共享资源的全副线程从期待状态退出,进入可运行状态
让它们竞争对象的锁,只有取得锁的线程能力进入就绪状态
每个锁对象有两个队列:就绪队列和阻塞队列
- 就绪队列:存储将要取得锁的线程
- 阻塞队列:存储被阻塞的线程
六、实例
1、sleep
public class ThreadDemo1 { public static void main(String[] args) { MyThread mt = new MyThread(); //举荐 MyRunnable mr = new MyRunnable(); Thread t2 = new Thread(mr); mt.start();//启动线程 t2.start(); for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "-" + i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }} /** * 实现线程的第一种形式:继承thread类 */class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { if (this.isInterrupted()) { break; } System.out.println(Thread.currentThread().getName() + "-" + i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); this.interrupt(); } } }} /** * 实现线程的第二种形式:实现Runnable接口 */class MyRunnable implements Runnable { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "-" + i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }}
2、join和中断(举荐用标记中断)
public class ThreadDemo2 { public static void main(String[] args){ MyRunable2 mr2 = new MyRunable2(); Thread t = new Thread(mr2);// t.start(); MyRunable3 mr3 = new MyRunable3(); Thread t2 = new Thread(mr3); t2.start(); for (int i = 0; i < 50; i++) { System.out.println(Thread.currentThread().getName()+"--"+i); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } if(i==20){// try { //这些关上用来测试join// t.join();//让t线程执行结束// } catch (InterruptedException e) {// e.printStackTrace();// }// t.interrupt();//中断线程,只是作了一个中断标记,用于测试interrupt办法 mr3.flag = false; //用于测试标记中断 } } }} class MyRunable2 implements Runnable{ @Override public void run() { for (int i = 0; i < 50; i++) { if(Thread.interrupted()){//测试中断状态,此办法会把中断状态革除 //.... break; } System.out.println(Thread.currentThread().getName()+"--"+i); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } } }}<br>//标记中断class MyRunable3 implements Runnable{ public boolean flag = true; public MyRunable3(){ flag = true; } @Override public void run() { int i=0; while(flag){ System.out.println(Thread.currentThread().getName()+"==="+(i++)); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } }}
3、优先级和守护过程
public class ThreadDemo3 { public static void main(String[] args){ MyRunnable4 mr4 = new MyRunnable4(); Thread t = new Thread(mr4); t.setName("Thread-t"); //优先级高能够进步该线程抢点CPU工夫片的概率大 t.setPriority(Thread.MAX_PRIORITY); //线程能够分成守护线程和 用户线程,当过程中没有用户线程时,JVM会退出 t.setDaemon(true);//把线程设置为守护线程 System.out.println(t.isAlive()); t.start(); System.out.println(t.isAlive()); for (int i = 0; i < 50; i++) { System.out.println("main--"+i); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } if (i==5){ Thread.yield();//让出本次CPU执行工夫片 } } }} class MyRunnable4 implements Runnable{ @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println("--"+i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }}
4、生产者与消费者(遗记在哪篇文章看到的了。。道歉)
定义一个接口:
package threadtest.procon; public interface AbstractStorage { void consume(int num); void product(int num);}
定义一个类实现接口,用于寄存生产的货色
package threadtest.procon; import java.util.LinkedList; /** * @author: LUGH1 * @date: 2019-7-4 * @description: */public class Storage implements AbstractStorage{ private final int MAX_SIZE = 100; private LinkedList list = new LinkedList(); @Override public void consume(int num) { synchronized (list){ while (list.size()<num){ System.out.println("【要生产的产品数量】:" + num + "\t【库存量】:"+ list.size() + "\t临时不能执行生产工作!"); try { list.wait(2000); } catch (InterruptedException e) { e.printStackTrace(); } } for(int i=0;i<num;i++){ list.remove(); } System.out.println("【曾经生产产品数】:" + num + "\t【现仓储量为】:" + list.size()); list.notifyAll(); } } @Override public void product(int num) { synchronized (list){ while(list.size()+num > MAX_SIZE){ System.out.println("【要生产的产品数量】:" + num + "\t【库存量】:" + list.size() + "\t临时不能执行生成工作!"); try { list.wait(2000); } catch (InterruptedException e) { e.printStackTrace(); } } for(int i=0;i<num;i++){ list.add(new Object()); } System.out.println("【曾经生产产品数】:" + num + "\t【现仓储量为】:" + list.size()); list.notifyAll(); } }}
生产者类:
package threadtest.procon; /** * @author: LUGH1 * @date: 2019-7-4 * @description: */public class Producer extends Thread { private int num; public AbstractStorage abstractStorage; public Producer(AbstractStorage abstractStorage){ this.abstractStorage = abstractStorage; } public void setNum(int num) { this.num = num; } public void produce(int num){ abstractStorage.product(num); } @Override public void run() { produce(num); }}
消费者类:
package threadtest.procon; /** * @author: LUGH1 * @date: 2019-7-4 * @description: */public class Consumer extends Thread { private int num; public AbstractStorage abstractStorage; public Consumer(AbstractStorage abstractStorage){ this.abstractStorage = abstractStorage; } public void setNum(int num){ this.num = num; } public void consume(int num){ this.abstractStorage.consume(num); } @Override public void run() { consume(num); }}
测试类:
package threadtest.procon; /** * @author: LUGH1 * @date: 2019-7-4 * @description: */public class Test { public static void main(String[] args){ AbstractStorage abstractStorage = new Storage(); // 生产者对象 Producer p1 = new Producer(abstractStorage); Producer p2 = new Producer(abstractStorage); Producer p3 = new Producer(abstractStorage); Producer p4 = new Producer(abstractStorage); Producer p5 = new Producer(abstractStorage); Producer p6 = new Producer(abstractStorage); Producer p7 = new Producer(abstractStorage); // 消费者对象 Consumer c1 = new Consumer(abstractStorage); Consumer c2 = new Consumer(abstractStorage); Consumer c3 = new Consumer(abstractStorage); // 设置生产者产品生产数量 p1.setNum(10); p2.setNum(20); p3.setNum(30); p4.setNum(40); p5.setNum(30); p6.setNum(20); p7.setNum(80); // 设置消费者产品生产数量 c1.setNum(50); c2.setNum(70); c3.setNum(20); c1.start(); c2.start(); c3.start(); p1.start(); p2.start(); p3.start(); p4.start(); p5.start(); p6.start(); p7.start(); } }