初始化与清理
1.finalize(),System.gc()方法一旦垃圾回收期准备好释放对象占用的存储空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会释放对象占用的存储空间。而System.gc()表面上是强制进行垃圾回收,但并不意味着System.gc()执行后,Java虚拟机就立即执行垃圾回收的动作。它的作用在于告诉虚拟机,程序员希望此时进行一次垃圾回收,而真正的时间还是看虚拟机的条件,不同的虚拟机有不同的对策,所以System.gc()可能在自动gc原本不会进入gc的位置上进入gc。
2.垃圾回收器如何工作和其他语言一样,在堆上分配对象的带价十分昂贵。而Java的所有对象都是在堆上分配的。而垃圾回收器却使Java从堆分配空间的速度,可以和其他语言从堆栈上分配空间的速度相媲美。
C++里的堆相当于一个大院子,每个对象都负责管理自己的地盘,对象可能被销毁,但地盘必须加以重用。在Java虚拟机中,堆的实现截然不同,它更像一个传送带,每分配一个对象,它就往前移一格。Java的堆指针只是简单地移动到尚未分配的区域。当垃圾回收器工作时,一面回收空间,一面使堆中的对象紧凑排列,实现了一种高速的,有无限空间可供分配的堆模型。
3.常见的垃圾回收机制
(1)引用计数是一种简单但速度很慢的垃圾回收技术。每个对象都含有一个引用计数器,当有引用连接至对象时,引用计数加1。当引用离开作用域或被置为null时,引用计数减1。虽然管理引用计数的开销不大,但这项开销在整个程序生命周期中将持续发生。垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象引用计数为0时,就释放其占用的空间。
(2)在一些更快的模式中,他们依据的思想是:对任何活的对象,一定能追溯到其存活在堆栈或静态存储区之中的引用。这个引用链条可能会穿过数个层次对象。由此,如果从堆栈和静态存储区开始,遍历所有的引用,就能找到所有活的对象,对于发现的每个引用,必须追踪它所引用的对象,然后是此对象包含的所有引用,如此反复进行,直到根源于堆栈和静态存储区的引用所以形成的网络全部被访问未为止。在这种方式下,Java虚拟机采用一种自适应的垃圾回收技术。至于如何处理找到的存活对象,取决于不同Java虚拟机的实现。
(3)停止-复制:先暂停程序的运行,然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的对象全部都是垃圾。当对象被复制到新堆时,它们是一个挨着一个的,所以新堆保持紧凑排列,然后就可以按前述方法简单,直接的分配了。缺点是一浪费空间,两个堆之间要来回倒腾,二是当程序进入稳定态时,可能只会产生极少的垃圾,甚至不产生垃圾,尽管如此,复制式回收器仍会将所有内存自一处复制到另一处。
(4)标记-清扫:同样是从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存活的对象。每当它找到一个存活的对象,就会给对象一个标记,这个过程中不会回收任何对象。只有全部标记工作完成的时候,清理动作才会开始。在清理过程中,没有标记的对象会被释放,不会发生任何复制动作。所以剩下的堆空间是不连续的,垃圾回收器如果要希望得到连续空间的话,就得重新整理剩下的对象。
(5)Java虚拟机的自适应技术就是Java虚拟机会进行见识,如果所有对象都很稳定,垃圾回收器的效率降低的话,就切换到”标记-清扫”方式;同样,Java虚拟机会跟踪”标记-清扫”的效果,要是对空间出现很多碎片,就会切换回”停止-复制”方式。这就是”自适应”技术。
内部类
1.使用.this和.new如果你需要生成对外部类对象的引用,可以使用外部类的名字紧跟圆点和this。有时你可能想告诉某些对象,去创建其某个内部类对象。要实现此目的,你必须在new表达式中提供其他外部类对象的引用,这时需要使用.new
语法。
public class NotNew { public class Inner{} public static void main(String[] args) { NotNew dn = new NotNew(); NotNew.Inner dni = dn.new Inner(); } }
2.在方法和作用域内的内部类
public class Percel5 { public Destination destination(String s){ class PDestination implements Destination{ private String label; private PDestination(String whereTo){ label = whereTo; } @Override public String readLabel() { return label; } } return new PDestination(s); } public static void main(String[] args) { Percel5 p = new Percel5(); Destination d = p.destination("Tasmania"); } }
3.嵌套类如果不需要内部类与其外围类对象之间有联系,那么可以将内部类声明为static,这通常成为嵌套类。嵌套类意味着:(1)要创建嵌套类的对象,并不需要其外围类的对象(2)不能从嵌套类的对象中访问非静态的外围类对象
public class Parcel11 { private static class ParcelContents implements Contents{ private int i = 11; public int value(){ return i; } } protected static class ParcelDestination implements Destination{ private String label; private ParcelDestination(String whereTo){ label = whereTo; } @Override public String readLabel() { return label; } public static void f(){} static int x = 10; static class AnotherLevel{ public static void f(){} static int x = 10; } } public static Destination destination(String s){ return new ParcelDestination(s); } public static Contents contents(){ return new ParcelContents(); } public static void main(String[] args) { Contents c = contents(); Destination d = destination("Tasmania"); } }
4.为什么需要内部类每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。接口解决了部分问题,而内部类有效地实现了”多重继承”。也就是说,内部类允许继承多个非接口类型(类或抽象类)。
class D{} abstract class E{} class Z extends D{ E makeE(){ return new E(){}; } } public class MutiImplementation { static void takesD(D d){} static void takesE(E e){} public static void main(String[] args) { Z z = new Z(); takesD(z); takesE(z.makeE()); } }
5.闭包与回调
interface Incrementable{ void increment(); } class Callee1 implements Incrementable{ private int i = 0; public void increment(){ i++; System.out.println(i); } } class MyIncrement{ public void increment(){System.out.println("Other operation");} static void f(MyIncrement mi){mi.increment();} } class Callee2 extends MyIncrement{ private int i = 0; public void increment(){ super.increment(); i++; System.out.println(i); } private class Closure implements Incrementable{ @Override public void increment() { Callee2.this.increment(); } } Incrementable getCallbackReference(){ return new Closure(); } } class Caller{ private Incrementable callbackReference; Caller(Incrementable cbh){callbackReference = cbh;} void go(){callbackReference.increment();} } public class Callbacks { public static void main(String[] args) { Callee1 c1 = new Callee1(); Callee2 c2 = new Callee2(); MyIncrement.f(c2); Caller caller1 = new Caller(c1); Caller caller2 = new Caller(c2.getCallbackReference()); caller1.go(); caller1.go(); caller2.go(); caller2.go(); } }
6.内部类的继承
class WithInner{ class Inner{} } public class InheritInner extends WithInner.Inner{ InheritInner(WithInner wi){ wi.super(); } public static void main(String[] args) { WithInner wi = new WithInner(); InheritInner ii = new InheritInner(wi); } }
类型信息
1.Class对象要理解RTTI(Run-Time Type Identification)在Java中的工作原理,首先必须知道类型信息在运行时如何表示的。这项工作是由称为Class对象的特殊对象完成的,它包含了于类有关的信息。一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。
2.创建Class对象的引用的几种方式:forName,getClass,.class当使用.class
来创建Class对象的属性时,不会自动地初始化该Class对象。为了使用泪而做的准备工作实际包含三个步骤:(1)加载:这是由类加载器执行的。该步骤将查找字节码,并从这些字节码中创建一个Class对象。(2)链接:在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必须的话,将解析这个类创建的对其他类的所有引用。(3)初始化:如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。
import java.util.Random; class Initable{ static final int staticFinal = 47; static final int staticFinal2 = ClassInitialization.rand.nextInt(1000); static { System.out.println("Initializing Initable"); } } class Initable2{ static int staticNonFinal = 147; static { System.out.println("Initializing Initable2"); } } class Initable3{ static int staticNonFinal = 74; static { System.out.println("Initializing Initable3"); } } public class ClassInitialization { public static Random rand = new Random(47); public static void main(String[] args) throws Exception{ Class initable = Initable.class; System.out.println("After creating Initable ref"); System.out.println(Initable.staticFinal); System.out.println(Initable.staticFinal2); System.out.println(Initable2.staticNonFinal); Class initable3 = Class.forName("code.chapter14.page563.Initable3"); System.out.println("After creating Initable3 ref"); System.out.println(Initable3.staticNonFinal); } }
初始化有效地实现了尽可能的”惰性”,仅使用.class语法来获得对类的引用不会引发初始化。但是,为了产生Class引用,Class.farName()立即就进行了初始化。
3.cast()
转型语法
class Building{} class House extends Building{} public class ClassCasts { public static void main(String[] args) { Building b = new House(); Class<House> houseType = House.class; House h = houseType.cast(b); h = (House)b; } }
4.反射:运行时的类信息如果不知道某个对象的确切类型,RTTI可以告诉你。但是有一个限制:这个类型在编译时必须已知,这样才能使用RTTI识别它,并利用这些信息做一些有用的事。
5.静态代理
public abstract class Subject{ public abstract void request(); } public class RealSubject extends Subject{ @Override public void request() { System.out.println("From Real Subject!"); } } /** * 代理角色 * */ public class ProxySubject extends Subject{ //代理角色对象内部含有对真实对象的引用 private RealSubject realSubject; @Override public void request(){ //在真实角色操作之前所附加的操作 preRequest(); if(null == realSubject){ realSubject = new RealSubject(); } //真实角色所完成的事情 realSubject.request(); //在真实角色操作之后所附加的操作 postRequest(); } private void preRequest(){ System.out.println("Pre Request."); } private void postRequest(){ System.out.println("Post Request"); } } /** * 客户类 * */ public class Client{ public static void main(String[] args){ Subject subject = new ProxySubject(); subject.request(); } }
如果要按照上述的方式(静态代理)使用代理模式,那么真实角色必须是实现已经存在的,并将其作为代理对象的内部属性。但是实际使用时,一个真实角色必须对应一个代理角色,但如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色,该如何使用代理呢?这个问题可以通过Java的动态代理类来解决。
6.动态代理
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; interface Interface{ void doSomething(); void somethingElse(String arg); } class RealObject implements Interface{ @Override public void doSomething() { System.out.println("doSomething"); } @Override public void somethingElse(String arg) { System.out.println("somethingElse "+arg); } } class DynamicProxyHandler implements InvocationHandler{ private Object proxied; public DynamicProxyHandler(Object proxied){ this.proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //在转调具体目标对象之前,可以执行一些功能处理 System.out.println("**** proxy:"+proxy.getClass()+", method:"+method+", args:"+args); if(args!=null) for (Object arg:args) System.out.println(" "+arg); return method.invoke(proxied,args); } } class SimpleDynamicProxy { public static void consumer(Interface iface){ iface.doSomething(); iface.somethingElse("bonobo"); } public static void main(String[] args) { RealObject real = new RealObject(); consumer(real); Interface proxy = (Interface) Proxy.newProxyInstance( Interface.class.getClassLoader(), new Class[]{Interface.class}, new DynamicProxyHandler(real)); consumer(proxy); } }
7.接口与类型信息(1)接口解耦的不足(2)反射使用setAccessible(true)
调用私有方法
泛型
1.元组类库:创建元组,实现一个函数可以返回多个值或对象。
2.泛型方法:要定义泛型方法,只需将泛型参数列表置于返回值之前。public <T> void f(T x)
3.泛型的类型擦除
4.泛型重用extends关键字
5.参数协变
持有引用(Java四种引用类型)
1.强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
2.软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
3.弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
4.虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
import java.lang.ref.*; import java.util.LinkedList; class VeryBig{ private static final int SIZE = 10000; private long[] la = new long[SIZE]; private String ident; public VeryBig(String id){ident = id;} public String toString(){return ident;} protected void finalize(){ System.out.println("Finalizing "+ident); } } public class References { private static ReferenceQueue<VeryBig> rq = new ReferenceQueue<>(); public static void checkQueue(){ Reference<? extends VeryBig> inq = rq.poll(); if(inq != null) System.out.println("In queue: "+inq.get()); } public static void main(String[] args) { int size = 10; if(args.length>0) size = new Integer(args[0]); LinkedList<SoftReference<VeryBig>> sa = new LinkedList<>(); for(int i=0;i<size;i++){ sa.add(new SoftReference<VeryBig>(new VeryBig("Soft "+i),rq)); System.out.println("Just created: "+sa.getLast()); checkQueue(); } LinkedList<WeakReference<VeryBig>> wa = new LinkedList<>(); for(int i=0;i<size;i++){ wa.add(new WeakReference<VeryBig>(new VeryBig("Weak "+i),rq)); System.out.println("Just created: "+wa.getLast()); checkQueue(); } SoftReference<VeryBig> s = new SoftReference<VeryBig>(new VeryBig("Soft")); WeakReference<VeryBig> w = new WeakReference<VeryBig>(new VeryBig("Weak")); System.gc(); LinkedList<PhantomReference<VeryBig>> pa = new LinkedList<>(); for(int i=0;i<size;i++){ pa.add(new PhantomReference<>(new VeryBig("Phantom "+i),rq)); System.out.println("Just created: "+pa.getLast()); checkQueue(); } } }
对象序列化
1.寻找类
class Alien implements Serializable{ public int id = 4; } class FreezeAlien { public static void main(String[] args) throws Exception{ ObjectOutput out = new ObjectOutputStream(new FileOutputStream("X.file")); Alien quellek = new Alien(); out.writeObject(quellek); } } public class ThawAlien { public static void main(String[] args) throws Exception{ ObjectInputStream in = new ObjectInputStream(new FileInputStream("X.file")); Object mystery = in.readObject(); System.out.println(((Alien)mystery).id); } }
2.序列化操作XML文件(需要xom.jar)
public class Person { private String first,last; public Person(String first, String last){ this.first = first; this.last = last; } public Element getXML(){ Element person = new Element("person"); Element firstName = new Element("first"); firstName.appendChild(first); Element lastName = new Element("last"); lastName.appendChild(last); person.appendChild(firstName); person.appendChild(lastName); return person; } public Person(Element person){ first = person.getFirstChildElement("first").getValue(); last = person.getFirstChildElement("last").getValue(); } public String toString(){ return first +" "+last; } public static void format(OutputStream os,Document doc) throws Exception{ Serializer serializer = new Serializer(os,"utf-8"); serializer.setIndent(4); serializer.setMaxLength(60); serializer.write(doc); serializer.flush(); } public static void main(String[] args) throws Exception{ List<Person> people = Arrays.asList(new Person("A","a"),new Person("B","b"),new Person("C","c")); System.out.println(people); Element root = new Element("people"); for (Person p:people) root.appendChild(p.getXML()); Document doc = new Document(root); format(new BufferedOutputStream(new FileOutputStream("People.xml")),doc); } }
public class People extends ArrayList<Person>{ public People(String fileName) throws Exception{ Document doc = new Builder().build(fileName); Elements elements = doc.getRootElement().getChildElements(); for (int i=0;i<elements.size();i++) add(new Person(elements.get(i))); } public static void main(String[] args) throws Exception{ People p = new People("People.xml"); System.out.println(p); } }
并发
1.定义任务
public class LiftOff implements Runnable{ protected int countDown = 10; private static int taskCount = 0; private final int id = taskCount++; public LiftOff(){} public LiftOff(int countDown){ this.countDown = countDown; } public String status(){ return "#"+id+"("+(countDown>0?countDown:"Liftoff!")+"), "; } @Override public void run() { while (countDown-->0){ System.out.println(status()); Thread.yield(); } } }
public class MainThread { public static void main(String[] args) { LiftOff launch = new LiftOff(); launch.run(); } }
在run中的静态方法Thread.yield()
的调用是对线程调度器的一种建议,它在声明:我已经执行完成声明周期中最重要的部分了,此刻正是切换其他任务执行一段时间的大好时机。
2.Thread类
当从Runnable导出一个类时,它必须具有run()方法,但是这个方法并无特殊之处,它不会产生任何内在的线程能力。要实现线程行为,你必须显示地将一个任务附着到线程上。
public class BasicThreads { public static void main(String[] args) { Thread t = new Thread(new LiftOff()); t.start(); System.out.println("Waiting for LiftOff"); } }
3.使用Executor(执行器)管理Thread对象
public class CachedThreadPool { public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); for (int i=0;i<5;i++) exec.execute(new LiftOff()); exec.shutdown(); } }
单个的Executor对象被用来创建和管理系统中的所有任务,对shutdown()
方法的调用可以防止新任务被提交给这个Executor.
4.利用Callable从任务中产生返回值
class TaskWithResult implements Callable<String>{ private int id; public TaskWithResult(int id){ this.id = id; } public String call(){ return "result of TaskWithResult "+id; } } public class CallableDemo { public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); ArrayList<Future<String>> results = new ArrayList<>(); for (int i=0;i<10;i++) results.add(exec.submit(new TaskWithResult(i))); for (Future<String> fs:results) try { System.out.println(fs.get()); }catch (Exception e){ System.out.println(e); }finally { exec.shutdown(); } } }
5.休眠
影响任务行为的一种简单方法是调用sleep()
,这将使任务中止执行给定的时间。
public class SleepingTask extends LiftOff{ public void run(){ try { while (countDown-->0){ System.out.print(status()); // Thread.sleep(100); TimeUnit.MILLISECONDS.sleep(100); } }catch (Exception e){ System.out.println("Interrupted"); } } public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); for (int i=0;i<5;i++) exec.execute(new LiftOff()); exec.shutdown(); } }
6.优先级
线程的优先级将改线程的重要性传递给了调度器。尽管CPU处理现有线程集的顺序是不确定的,但是调度器将倾向于让优先权高的线程先执行,然而,这并不意味着优先级低的线程将得不到执行,优先级低的线程仅仅是执行的频率较低。
public class SimplePriorities implements Runnable{ private int countDown = 5; private volatile double d; private int priority; public SimplePriorities(int priority){ this.priority = priority; } public String toString(){ return Thread.currentThread()+":"+countDown; } @Override public void run() { Thread.currentThread().setPriority(priority); while (true){ for (int i=1;i<100000;i++){ d+=(Math.PI+Math.E)/(double)i; if(i%1000==0) Thread.yield(); } System.out.println(this); if(--countDown == 0) return; } } public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); for (int i=0;i<5;i++) exec.execute(new SimplePriorities(Thread.MIN_PRIORITY)); exec.execute(new SimplePriorities(Thread.MAX_PRIORITY)); exec.shutdown(); } }
7.后台线程
所谓后台线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非后台线程结束时,程序也就终止了,同时会杀死进程中的所有后台进程。
public class SimpleDaemons implements Runnable{ @Override public void run() { try { while (true){ TimeUnit.MILLISECONDS.sleep(100); System.out.println(Thread.currentThread()+" "+this); } }catch (Exception e){ System.out.println("sleep() interrupted"); } } public static void main(String[] args) throws Exception{ for(int i=0;i<10;i++){ Thread daemon = new Thread(new SimpleDaemons()); daemon.setDaemon(true); daemon.start(); } System.out.println("All daemons started"); TimeUnit.MILLISECONDS.sleep(175); } }
8.加入一个线程
一个线程可以在其他线程之上调用join()
方法,其效果是等待一段时间直到第二个线程结束才继续执行。也可以在调用join()
时带上一个超时参数,这样如果目标线程在这段到期时间还没有结束的话,join()
方法总能返回。对join()
方法的调用可以被中断,做法是在调用线程上调用interrupt()
方法。
9.synchronized
解决共享受限资源
10.原子性与易变性
注意:对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的,例如
public class Counter { public volatile static int count = 0; public static void inc() { //这里延迟1毫秒,使得结果明显 try { Thread.sleep(1); } catch (InterruptedException e) { } count++; } public static void main(String[] args) { //同时启动1000个线程,去进行i++计算,看看实际结果 for (int i = 0; i < 1000; i++) { new Thread(new Runnable() { @Override public void run() { Counter.inc(); } }).start(); } System.out.println("运行结果:Counter.count=" + Counter.count); } }
这里产生的结果并不是我们期望的1000,即便你加了volatile修饰。
11.原子类AtomicInteger
,AtomicLong
,AtomicReference
12.ThreadLocal
实现线程本地存储
class Accessor implements Runnable{ private final int id; public Accessor(int idn){ id = idn; } public void run(){ while (!Thread.currentThread().isInterrupted()){ ThreadLocalVariableHolder.increment(); System.out.println(this); Thread.yield(); } } public String toString(){ return "#"+id+": "+ThreadLocalVariableHolder.get(); } } public class ThreadLocalVariableHolder { public static ThreadLocal<Integer> value = new ThreadLocal<Integer>(){ private Random rand = new Random(47); protected synchronized Integer initialValue(){ return rand.nextInt(10000); } }; public static void increment(){ value.set(value.get()+1); } public static int get(){return value.get();} public static void main(String[] args) throws Exception{ ExecutorService exec = Executors.newCachedThreadPool(); for(int i=0;i<5;i++) exec.execute(new Accessor(i)); TimeUnit.MILLISECONDS.sleep(3); exec.shutdown(); } }
13.死锁
产生死锁的四个必要条件:
(1)互斥条件:一个资源每次只能被一个进程使用。
(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3)不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
14.利用wait()
,notify()
,notifyAll()
完成线程之间的协作
class Car{ private boolean waxOn = false; public synchronized void waxed(){ waxOn = true; notifyAll(); } public synchronized void buffed(){ waxOn = false; notifyAll(); } public synchronized void waitForWaxing() throws Exception{ while (waxOn == false) wait(); } public synchronized void waitForBuffing() throws InterruptedException{ while (waxOn==true) wait(); } } class WaxOn implements Runnable{ private Car car; public WaxOn(Car c){car = c;} public void run(){ try { while (!Thread.interrupted()){ System.out.print("Wax on! "); TimeUnit.MILLISECONDS.sleep(200); car.waxed(); car.waitForBuffing(); } }catch (Exception e){ System.out.println("Exiting via interrupt"); } System.out.println("Ending Wax On task"); } } class WaxOff implements Runnable{ private Car car; public WaxOff(Car c){car = c;} public void run(){ try { while (!Thread.interrupted()){ car.waitForWaxing(); System.out.print("Wax Off! "); TimeUnit.MILLISECONDS.sleep(200); car.buffed(); } }catch (Exception e){ System.out.println("Exiting via interrupt"); } System.out.println("Ending Wax Off task"); } } public class WaxOMatic { public static void main(String[] args) throws Exception{ Car car = new Car(); ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new WaxOff(car)); exec.execute(new WaxOn(car)); TimeUnit.SECONDS.sleep(5); exec.shutdownNow(); } }