From cc221c491126a1be87f2692bf7eece34e8b9b52a Mon Sep 17 00:00:00 2001 From: lhc Date: Wed, 8 Jan 2020 18:10:16 +0800 Subject: [PATCH 1/4] week 05 --- week_05/62/Thread.md | 246 ++++++++++++++++++++++++++++++++++++++ week_05/62/ThreadLocal.md | 36 ++++++ 2 files changed, 282 insertions(+) create mode 100644 week_05/62/Thread.md create mode 100644 week_05/62/ThreadLocal.md diff --git a/week_05/62/Thread.md b/week_05/62/Thread.md new file mode 100644 index 0000000..eea57cd --- /dev/null +++ b/week_05/62/Thread.md @@ -0,0 +1,246 @@ +#Thread + +###线程有优先级,守护线程 +默认和父线程优先级一样,守护线程的两种方式手动设置,以及父线程是守护线程,可自动继承为守护线程。main线程是非守护线程 +###终止线程 +1、Runtime.getRuntime().exit(); +2、所有非守护线程died,run方法中return,或者抛异常 +###创建线程 +1、继承thread,复写run +2、实现Runnable接口 +每个线程都有一个用于标识的名称。多个线程可能具有相同的名称。如果在创建线程时未指定名称,则为其生成新名称。 +Thread 构造方法传入null会直接报空指针 + +JVM退出时Daemon线程中的finally块中的代码不一定会执行。因此不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。 + +线程6中状态: +NEW:线程还未开始,只是进行了一些线程创建的初始化操作,但未调用start()方法. +RUNNABLE:线程在JVM里面处于运行状态(这里就绪和运行同属于运行). +BLOCKED:线程正在等待一个监视器锁,处于阻塞状态. +WAITING:一个线程在等待另一个线程的特定操作(通知or中断),这种等待是无限期的. +TIMED_WAITING:一个线程在等待另一个线程的特定操作,这种等待是有时间限制的.一旦超时则线程自行返回. +TERMINATED:线程已退出.表示线程已经执行完毕. + +###源码分析 参考 https://blog.csdn.net/caoxiaohong1005/article/details/80312396 +```java +/** + * 提示线程调度器当前线程愿意放弃当前CPU的使用。当然调度器可以忽略这个提示。 + * + * 让出CPU是一种启发式的尝试,以改善线程之间的相对进展,否则将过度利用CPU。 + * 它的使用应该与详细的分析和基准测试相结合以确保它实际上具有预期的效果。 + * + * 此方法很少有适用的场景.它可以用用于debug或者test,通过跟踪条件可以重现bug. + * 当设计并发控制结构(如java.util.concurrent.locks包中的并发结构)时,它可能比较有用. + */ + public static native void yield(); +``` + + +```java +/** + * 此方法的调用会引起当前线程的执行;JVM会调用此线程的run()方法. + * 结果就是两个线程可以并发执行:当前线程(从调用的start方法返回)和另一个线程(它在执行run方法). + * 一个线程可以被调用多次. + * 尤其注意:一个线程执行完成后可能并不会再被重新执行. + */ + public synchronized void start() { + /** + * 此方法并不会被主要方法线程or由虚拟机创建的系统组线程所调用. + * 任何向此方法添加的新功能方法在未来都会被添加到虚拟机中. + * 0状态值代表了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 */ + } + } + } +``` + +```java +/** + * 强制线程退出.此时会创建一个新的对象ThreadDeath作为异常. + * 允许对一个还没有start的线程执行此方法.如果当前线程已经start了,则此方法的调用会使其立即停止. + * + * 客户端不应该经常去捕获ThreadDeath异常,除非有一些额外的清除工作要做(注意:在线程死亡前,ThreadDeath的异常在抛出 + * 时会引发try对应的finally代码块的执行).如果catch捕获了ThreadDeath对象,必须重新抛出此异常以保证线程可以真正的死亡. + * + * 最顶级的错误处理器会对其它未被捕获类型的异常进行处理,但是如果未处理异常是线程死亡的实例,则不会打印消息或通知应用程序。 + */ + @Deprecated + public final void stop() { + SecurityManager security = System.getSecurityManager(); + if (security != null) { + checkAccess(); + if (this != Thread.currentThread()) { + security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION); + } + } + // A zero status value corresponds to "NEW", it can't change to + // not-NEW because we hold the lock. + if (threadStatus != 0) { + resume(); // Wake up thread if it was suspended; no-op otherwise + } + + //虚拟机能处理所有的线程 + stop0(new ThreadDeath()); + } +``` + +```java +/** + * 此方法功能:中断当前线程. + * + * 除非当前线程在中断自己(这么做是允许的),此线程的checkAccess()方法被调用且抛出异常SecurityException + * + * 1.如果当前线程由于wait类型方法,join类型方法或者sleep类型的方法的调用被阻塞,则它的中断状态将被清除且会收到一个 + * 中断异常InterruptedException + * + * 2.如果此线程由于java.nio.channels.InterruptibleChannel类中的InterruptibleChannel的I/O操作而被阻塞, + * 则此方法会导致通道被关闭,且线程的中断状态会被重置,同时线程会收到一个异常ClosedByInterruptException. + * + * 3.如果此线程由于java.nio.channels.Selector而阻塞,则线程的中断状态会被重置,且它将立即从阻塞的selection操作返回, + * 且返回值通常是一个非零值,这就和java.nio.channels.Selector#wakeup的wakeup()方法被调用一样. + * + * 4.如果前面的条件都不成立,那么该线程的中断状态将被重置.。 + * + * 中断一个处于非活着状态的线程并不需要产生任何其它影响. + * + * @revised 6.0 + * @spec JSR-51 + */ + public void interrupt() { + if (this != Thread.currentThread()) + checkAccess(); + + //对阻塞锁使用同步机制 + synchronized (blockerLock) { + Interruptible b = blocker; + if (b != null) { + interrupt0(); //只是为了设定中断标识位 + b.interrupt(this);//中断当前线程 + return; + } + } + interrupt0();//只是为了设置中断标识位 + } +``` +```java +/** + * 此方法返回此线程的上下文类加载器.上下文类加载器由当加载类和资源时使用代码创建线程的创造者提供. + * 如果通过方法setContextClassLoader进行上下文类加载器的设定,则默认的上下文类加载器为父线程. + * 原始线程的类加载器通常被设定为:加载应用的类加载器. + * + * 如果安全管理器存在,且调用者的类加载器不为null,且它们不相同,且也不是父子关系,则此方法的调用会导致安全管理的方法 + * checkPermission的调用,用于确定对上下文类加载器的检索是否被允许. + * + * 关于注解@CallerSensitive:这个注解是为了堵住漏洞用的。曾经有黑客通过构造双重反射来提升权限,原理是当时反射只检查固定深度的 + * 调用者的类,看它有没有特权.使用CallerSensitive后,getCallerClass不再用固定深度去寻找actual caller(“我”),而是把所 + * 有跟反射相关的接口方法都标注上CallerSensitive,搜索时凡看到该注解都直接跳过,这样就有效解决了这类的黑客问题. + * @since 1.2 + */ + @CallerSensitive + public ClassLoader getContextClassLoader() { + if (contextClassLoader == null) + return null; + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + ClassLoader.checkClassLoaderPermission(contextClassLoader, + Reflection.getCallerClass()); + } + return contextClassLoader; + } +``` + +```java +/** + * 此枚举表示线程状态.线程状态有如下几种: + * 1.NEW表示:线程还未开始,只是进行了一些线程创建的初始化操作,但未调用start()方法. + * 2.RUNNABLE表示:线程在JVM里面处于运行状态(这里就绪和运行同属于运行). + * 3.BLOCKED表示:线程正在等待一个监视器锁,处于阻塞状态. + * 4.WAITING表示:一个线程在等待另一个线程的特定操作(通知or中断),这种等待是无限期的. + * 5.TIMED_WAITING表示:一个线程在等待另一个线程的特定操作,这种等待是有时间限制的.一旦超时则线程自行返回. + * 6.TERMINATED表示:线程已退出.表示线程已经执行完毕. + * + * 线程在某一时刻,只能处于上述6个状态的某一个.这些状态值是虚拟机状态值,因而并不会反映操作系统的线程状态. + * + * @since 1.5 + * @see #getState + */ + public enum State { + + NEW, + + //线程可以正在运行,也可以处于就绪状态等待获得CPU. + RUNNABLE, + + //在调用完wait()方法后,为了进入同步方法(锁)或者重进入同步方法(锁). + BLOCKED, + + /** + * 一个线程处于wating状态,是因为调用了下面方法中的某一个: + * 1.Object.wait + * 2.Thread.join + * 3.LockSupport.park + * + * 其它线程的特定操作包括 :notify(),notifyAll(),join()等. + */ + WAITING, + + /** + * 线程等待指定时间. + * 这种状态的出现是因为调用了下面方法中的某一个: + * 1.Thread.sleep() + * 2.Object.wait() + * 3.Thread.join() + * 4.LockSupport.parkNanos() + * 5.LockSupport.parkUntil() + */ + TIMED_WAITING, + + //线程完成了执行 + TERMINATED; + } +``` +```java +/** + * 由于未捕获异常而导致线程终止的函数接口处理器. + * 当一个线程由于未捕获异常而终止时,JVM将会使用getUncaughtExceptionHandler来查询此线程的UncaughtExceptionHandler, + * 且会调用处理器handler的uncaughtException()方法,将此线程和其异常作为参数. + * + * 如果一个线程没有它特定的UncaughtExceptionHandler,则它所属的线程组对象充当其UncaughtExceptionHandler. + * 如果线程组对象没有处理异常的指定请求,它可以向前调用getDefaultUncaughtExceptionHandler的默认处理异常的方法. + * + * @see #setDefaultUncaughtExceptionHandler + * @see #setUncaughtExceptionHandler + * @see ThreadGroup#uncaughtException + * @since 1.5 + */ + @FunctionalInterface + public interface UncaughtExceptionHandler { + /** + * 由于未捕获异常而导致线程终止的方法调用. + * 此方法抛出的任何异常都会被JVM忽略. + * @param t the thread + * @param e the exception + */ + void uncaughtException(Thread t, Throwable e); + } +``` \ No newline at end of file diff --git a/week_05/62/ThreadLocal.md b/week_05/62/ThreadLocal.md new file mode 100644 index 0000000..34ab490 --- /dev/null +++ b/week_05/62/ThreadLocal.md @@ -0,0 +1,36 @@ +#ThreadLocal +实现单个线程存储信息,可以线程变得安全 +###内存一出问题 +内部维持一个特殊的hashmap(ThreadLocalMap),key值是弱引用,防止内存溢出,但是value值会一直保存在线程中,对于使用线程池 +这种,线程循环服用,会使value值一直存在,所以有必要在每次使用之后使用remove操作,只有线程被gc才会被回收。 + +###InheritableThreadLocal +```java +if (inheritThreadLocals && parent.inheritableThreadLocals != null) + this.inheritableThreadLocals = + ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); +``` +InheritableThreadLocal 重写了getMap方法 +```java +public T get() { + Thread t = Thread.currentThread(); + //这里实际上就是调用的inheritableThreadLocals,就可以得到父类的local里面对象 + ThreadLocalMap map = getMap(t); + if (map != null) { + ThreadLocalMap.Entry e = map.getEntry(this); + if (e != null) { + @SuppressWarnings("unchecked") + T result = (T)e.value; + return result; + } + } + return setInitialValue(); + } +``` + +开放地址法:容易产生堆积问题;不适于大规模的数据存储;散列函数的设计对冲突会有很大的影响;插入时可能会出现多次冲突的现象,删除的元素是多个冲突元素中的一个,需要对后面的元素作处理,实现较复杂;结点规模很大时会浪费很多空间; + +链地址法:处理冲突简单,且无堆积现象,平均查找长度短;链表中的结点是动态申请的,适合构造表不能确定长度的情况;相对而言,拉链法的指针域可以忽略不计,因此较开放地址法更加节省空间。插入结点应该在链首,删除结点比较方便,只需调整指针而不需要对其他冲突元素作调整。 + +ThreadLocalMap 为什么采用开放地址法? +个人认为由于 ThreadLocalMap 的 hashCode 的精妙设计,使hash冲突很少,并且 Entry 继承 WeakReference, 很容易被回收,并开方地址可以节省一些指针空间;然而恰恰由于开方地址法的使用,使在处理hash冲突时的代码很难懂,比如在replaceStaleEntry,cleanSomeSlots,expungeStaleEntry 等地方,然而真正调用这些方法的几率却比较小 -- Gitee From ca044624c00c9c2a2d80d06d1f1ae006a113314c Mon Sep 17 00:00:00 2001 From: lhc Date: Fri, 10 Jan 2020 17:20:59 +0800 Subject: [PATCH 2/4] week 06 --- week_05/62/ThreadPoolExecutor.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 week_05/62/ThreadPoolExecutor.md diff --git a/week_05/62/ThreadPoolExecutor.md b/week_05/62/ThreadPoolExecutor.md new file mode 100644 index 0000000..9de4393 --- /dev/null +++ b/week_05/62/ThreadPoolExecutor.md @@ -0,0 +1,15 @@ +#ThreadPoolExecutor + 1、如果有多余core线程并小于max线程在运行,也只有当队列满时才会创建,或者调用空闲线程执行任务。 + 2、默认情况下,即使是核心线程也只是在新任务到达时才会创建和启动,但是可以使用方法prestartCoreThread或prestartAllCoreThreads动态地覆盖它。如果使用非空队列构造池,则可能需要预启动线程。 + 3、默认情况下没有定所需的队列,则使用defaultThreadFactory创建线程(非守护线程) + 4、创建线程失败的情况下会返回null,可能造成没有任务任务在执行 + 5、如果运行的线程小于corePoolSize,则执行程序总是希望添加新线程而不是排队。 + 如果正在运行corePoolSize或多个线程,执行程序总是希望对请求进行排队,而不是添加新线程。 + 如果一个请求不能排队,那么将创建一个新线程,除非这个线程超过了maximumPoolSize,在这种情况下,任务将被拒绝。 + 6、拒绝策略 + 在默认的ThreadPoolExecutor中。处理程序在拒绝时抛出运行时RejectedExecutionException异常。 + DiscardPolicy直接抛弃 + DiscardOldestPolicy:抛弃queue中的第一个任务,再次执行该任务。 + CallerRunsPolicy: 直接由执行该方法的线程继续执行该任务,除非调用了shutdown方法,这个任务才会被丢弃,否则继续执行该任务会发生阻塞。 + + 实现RejectedExecutionHandler,也可以自定义实现策略方法。 \ No newline at end of file -- Gitee From 9529ac16d0cdcea85d93fde5e0c516705023c210 Mon Sep 17 00:00:00 2001 From: lhc <592537612@qq.com> Date: Sat, 11 Jan 2020 22:40:27 +0800 Subject: [PATCH 3/4] Default Changelist --- week_05/62/Executors.md | 48 +++++++++++++++++ week_05/62/ThreadPoolExecutor.md | 90 +++++++++++++++++++++++++++++++- 2 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 week_05/62/Executors.md diff --git a/week_05/62/Executors.md b/week_05/62/Executors.md new file mode 100644 index 0000000..c39a70d --- /dev/null +++ b/week_05/62/Executors.md @@ -0,0 +1,48 @@ +#Executors + Executors可以创建的几种线程池简介 + + 1、newFixedThreadPool(int corePoolSize) + 创建一个线程数固定(corePoolSize==maximumPoolSize)的线程池 + 核心线程会一直运行 + 如果一个核心线程由于异常跪了,会新创建一个线程 + 无界队列LinkedBlockingQueue + newSingleThreadExecutor + 2、创建一个线程数固定(corePoolSize==maximumPoolSize==1)的线程池 + 核心线程会一直运行 + 无界队列LinkedBlockingQueue + 所有task都是串行执行的(即同一时刻只有一个任务在执行) + newCachedThreadPool + corePoolSize==0 + maximumPoolSize==Integer.MAX_VALUE + 队列:SynchronousQueue + 3、创建一个线程池:当池中的线程都处于忙碌状态时,会立即新建一个线程来处理新来的任务 + 这种池将会在执行许多耗时短的异步任务的时候提高程序的性能 + 6秒钟内没有使用的线程将会被中止,并且从线程池中移除,因此几乎不必担心耗费资源 + 4、newScheduledThreadPool(int corePoolSize) + 用于执行定时或延迟执行的任务,最典型的:异步操作时的超时回调 + +###方法:封装callable +```java +public static Callable callable(Runnable task) { + if (task == null) + throw new NullPointerException(); + return new RunnableAdapter(task, null); + } + +``` + +```java + static final class RunnableAdapter implements Callable { + final Runnable task; + final T result; + RunnableAdapter(Runnable task, T result) { + this.task = task; + this.result = result; + } + public T call() { + task.run();//这里是真正的task运行的地方 + return result; + } + } + +``` diff --git a/week_05/62/ThreadPoolExecutor.md b/week_05/62/ThreadPoolExecutor.md index 9de4393..90d08c1 100644 --- a/week_05/62/ThreadPoolExecutor.md +++ b/week_05/62/ThreadPoolExecutor.md @@ -7,9 +7,95 @@ 如果正在运行corePoolSize或多个线程,执行程序总是希望对请求进行排队,而不是添加新线程。 如果一个请求不能排队,那么将创建一个新线程,除非这个线程超过了maximumPoolSize,在这种情况下,任务将被拒绝。 6、拒绝策略 - 在默认的ThreadPoolExecutor中。处理程序在拒绝时抛出运行时RejectedExecutionException异常。 + AbortPolicy在默认的ThreadPoolExecutor中。处理程序在拒绝时抛出运行时RejectedExecutionException异常。 DiscardPolicy直接抛弃 DiscardOldestPolicy:抛弃queue中的第一个任务,再次执行该任务。 CallerRunsPolicy: 直接由执行该方法的线程继续执行该任务,除非调用了shutdown方法,这个任务才会被丢弃,否则继续执行该任务会发生阻塞。 + 实现RejectedExecutionHandler,也可以自定义实现策略方法。 - 实现RejectedExecutionHandler,也可以自定义实现策略方法。 \ No newline at end of file + 7、七大参数: + 1 corePoolSize int 核心线程池大小 + 2 maximumPoolSize int 最大线程池大小 + 3 keepAliveTime long 线程最大空闲时间 + 4 unit TimeUnit 时间单位 + 5 workQueue BlockingQueue 线程等待队列 + 6 threadFactory ThreadFactory 线程创建工厂 + 7 handler RejectedExecutionHandler 拒绝策略 + + 8、ctl 表示线程池状态,和工作线程数量 高3位于低29位(高3位代表5中线程状态 RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED) + 通过以下两个方法高3位,低29位赋值 + private static int runStateOf(int c) { return c & ~CAPACITY; } + private static int workerCountOf(int c) { return c & CAPACITY; } + + 9、ThreadPoolExecutor为提供了每个任务执行前后提供了钩子方法,重写beforeExecute(Thread,Runnable) + 和afterExecute(Runnable,Throwable)方法来操纵执行环境; 例如,重新初始化ThreadLocals,收集统计 + 信息或记录日志等。此外,terminated()在Executor完全终止后需要完成后会被调用,可以重写此方法, + 以执行任殊处理。 + +###方法 +```java +public void execute(Runnable command) { + if (command == null) + throw new NullPointerException(); + /* + * Proceed in 3 steps: + * + * 1. If fewer than corePoolSize threads are running, try to + * start a new thread with the given command as its first + * task. The call to addWorker atomically checks runState and + * workerCount, and so prevents false alarms that would add + * threads when it shouldn't, by returning false. + * + * 2. If a task can be successfully queued, then we still need + * to double-check whether we should have added a thread + * (because existing ones died since last checking) or that + * the pool shut down since entry into this method. So we + * recheck state and if necessary roll back the enqueuing if + * stopped, or start a new thread if there are none. + * + * 3. If we cannot queue task, then we try to add a new + * thread. If it fails, we know we are shut down or saturated + * and so reject the task. + */ + int c = ctl.get(); + //当前线程数小于核心线程数 + if (workerCountOf(c) < corePoolSize) { + //添加并执行 + if (addWorker(command, true)) + return; + c = ctl.get(); + } + //当前核心线程池中全部线程都在运行 + //线程池是否处于运行状态,且是否任务插入任务队列成功 + if (isRunning(c) && workQueue.offer(command)) { + int recheck = ctl.get(); + //线程池是否处于运行状态,如果不是则使刚刚的任务出队 + if (! isRunning(recheck) && remove(command)) + reject(command); + else if (workerCountOf(recheck) == 0) + addWorker(null, false); + } + /*3.插入队列不成功,且当前线程数数量小于最大线程池数量,此时则创建新线程执行任务,创建失败抛出异常*/ + else if (!addWorker(command, false)) + reject(command); + } +``` + +addWorker中调用了调用runworker方法, +```java +try { + task.run(); + } catch (RuntimeException x) { + thrown = x; throw x; + } catch (Error x) { + thrown = x; throw x; + } catch (Throwable x) { + thrown = x; throw new Error(x); + } finally { + //处理run方法中产生的异常 + afterExecute(task, thrown); + } +``` + + + \ No newline at end of file -- Gitee From aa1f242e68a033a29951354fb31367e5d3b0341b Mon Sep 17 00:00:00 2001 From: lhc <592537612@qq.com> Date: Sat, 11 Jan 2020 23:52:14 +0800 Subject: [PATCH 4/4] week 05 --- ...\233\277\346\211\223\345\215\260FooBar.md" | 28 +++++++++ ...6_\345\245\207\345\201\266\346\225\260.md" | 63 +++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 "week_05/62/1115\344\272\244\346\233\277\346\211\223\345\215\260FooBar.md" create mode 100644 "week_05/62/1116\351\233\266_\345\245\207\345\201\266\346\225\260.md" diff --git "a/week_05/62/1115\344\272\244\346\233\277\346\211\223\345\215\260FooBar.md" "b/week_05/62/1115\344\272\244\346\233\277\346\211\223\345\215\260FooBar.md" new file mode 100644 index 0000000..f3d7627 --- /dev/null +++ "b/week_05/62/1115\344\272\244\346\233\277\346\211\223\345\215\260FooBar.md" @@ -0,0 +1,28 @@ +class FooBar { + private volatile boolean flag = false; + public void foo(int n) throws InterruptedException { + for (int i = 0; i < n; i++) { + synchronized (this) { + if(flag){ + wait(); + } + System.out.println("foo"); + flag = true; + notify(); + } + } + } + + public void bar(int n) throws InterruptedException { + for (int i = 0; i < n; i++) { + synchronized (this) { + if(!flag){ + wait(); + } + System.out.println("bar"); + flag = false; + notify(); + } + } + } + } \ No newline at end of file diff --git "a/week_05/62/1116\351\233\266_\345\245\207\345\201\266\346\225\260.md" "b/week_05/62/1116\351\233\266_\345\245\207\345\201\266\346\225\260.md" new file mode 100644 index 0000000..3a2914f --- /dev/null +++ "b/week_05/62/1116\351\233\266_\345\245\207\345\201\266\346\225\260.md" @@ -0,0 +1,63 @@ +class ZeroEvenOdd { + private int n; + private AtomicInteger num = new AtomicInteger(0); + + Lock lock = new ReentrantLock(); + Condition zeroC = lock.newCondition(); + Condition othreC = lock.newCondition(); + AtomicBoolean isZero = new AtomicBoolean(true); + + public ZeroEvenOdd(int n) { + this.n = n; + } + + // printNumber.accept(x) outputs "x", where x is an integer. + public void zero(IntConsumer printNumber) throws InterruptedException { + for(;num.get() == n;){ + lock.lock(); + try { + while (!isZero.get()){ + zeroC.wait(); + } + printNumber.accept(num.get()); + num.getAndIncrement(); + isZero.set(false); + othreC.signalAll(); + }finally { + lock.unlock(); + } + } + } + + public void even(IntConsumer printNumber) throws InterruptedException { + for(;num.get() <= n;){ + lock.lock(); + try { + while(isZero.get() || (num.get()&1)==1){ + othreC.wait(); + } + printNumber.accept(num.get()); + num.getAndIncrement(); + zeroC.signal(); + }finally { + lock.unlock(); + } + } + } + + public void odd(IntConsumer printNumber) throws InterruptedException { + for(;num.get() <= n;){ + lock.lock(); + try { + while(isZero.get() || (num.get()&1)==0){ + othreC.wait(); + } + printNumber.accept(num.get()); + num.getAndIncrement(); + zeroC.signal(); + }finally { + lock.unlock(); + } + } + } + } \ No newline at end of file -- Gitee