《Think in Java》20 21(并发)

时间:2021-06-09 15:00:46

chapter 20 注解

三种标准注解和四种元注解:

《Think in Java》20 21(并发)

《Think in Java》20 21(并发)

编写注解处理器

chapter 21 并发

基本的线程机制

定义任务

package cn.test;

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.print(status());
Thread.yield();//线程重新抢占
}
}
}

public class MainThread {
  public static void main(String[] args) {
    LiftOff launch=new LiftOff();
    launch.run();
  }
}

Thread类

public class BasicThreads {
public static void main(String[] args) {
for(int i=0;i<5;i++) {
new Thread(new LiftOff()).start();
}
System.out.println("Waiting for LiftOff");
}
}

使用Executor

public class CachedThreadPool {
public static void main(String[] args) {
// ExecutorService exec=Executors.newCachedThreadPool();
ExecutorService exec=Executors.newFixedThreadPool(5);
// ExecutorService exec=Executors.newSingleThreadExecutor();
for(int i=0;i<5;i++) {
exec.execute(new LiftOff());
}
exec.shutdown();
}
}

从任务中产生返回值

  Runnable 不会返回任何值。使用Callable接口能够返回一个值。

package cn.test;

import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future; class TaskWithResult implements Callable<String> {
private int id;
public TaskWithResult(int id) {
this.id=id;
}
@Override
public String call() throws Exception {
return "result of TaskWithResult "+ id;
}
}
public class CallableDemo{
public static void main(String[] args) {
ExecutorService exec=Executors.newCachedThreadPool();
ArrayList<Future<String>> results=new ArrayList<Future<String>>();
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 (InterruptedException e) {
System.out.println(e);
return;
} catch (ExecutionException e) {
System.out.println(e);
} finally {
exec.shutdown();
}
}
}

《Think in Java》20 21(并发)

休眠

  Thread.sleep();

优先级

package cn.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; 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();
} }

让步

  Thread.yield();

后台线程

  daemon线程。

package cn.test;

public class SimpleDaemons implements Runnable{
public void run() {
try {
while(true) {
Thread.sleep(100);
System.out.println(Thread.currentThread()+" "+this);
}
} catch (Exception e) {
System.out.println("sleep() interrupted");
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
for(int i=0;i<10;i++) {
Thread daemon=new Thread(new SimpleDaemons());
daemon.setDaemon(true);
daemon.start();
}
System.out.println("All daemons started");
Thread.sleep(100);
} }

使用isDaemon()方法来确定线程是否是一个后台线程。

package cn.test;

import java.util.concurrent.TimeUnit;

class Daemon implements Runnable{
private Thread[] t=new Thread[10];
public void run() {
for(int i=0;i<t.length;i++) {
t[i]=new Thread(new DaemonSpawn());
t[i].start();
System.out.println("DaemonSpawn "+i+" started,");
}
for(int i=0;i<t.length;i++)
System.out.println("t["+i+"].siDaemon()="+
t[i].isDaemon()+",");
while(true) {
Thread.yield();
}
}
class DaemonSpawn implements Runnable{
public void run() {
while(true)
Thread.yield();
}
}
}
public class Daemons {
public static void main(String[] args) throws InterruptedException {
Thread d=new Thread(new Daemon());
d.setDaemon(true);
d.start();
System.out.println("d.isDaemon()="+d.isDaemon()+",");
TimeUnit.SECONDS.sleep(1); }
}

一旦main()退出,JVM就会立即关闭所有的后台进程。

package cn.test;

import java.util.concurrent.TimeUnit;

class ADaemon implements Runnable{
public void run() {
try {
System.out.println("Starting ADaemon");
TimeUnit.SECONDS.sleep(1);
}catch(Exception e) {
System.out.println("Exiting via InterruptedException");
}finally {
System.out.println("This should always run?");
}
}
}
public class DaemonsDontRunFinally {
public static void main(String[] args) throws Exception {
Thread t= new Thread(new ADaemon());
t.setDaemon(true);
t.start();
// TimeUnit.SECONDS.sleep(1);
}
}

编码的变体

package cn.test;

import java.util.concurrent.TimeUnit;

class ADaemon implements Runnable{
public void run() {
try {
System.out.println("Starting ADaemon");
TimeUnit.SECONDS.sleep(1);
}catch(Exception e) {
System.out.println("Exiting via InterruptedException");
}finally {
System.out.println("This should always run?");
}
}
}
public class DaemonsDontRunFinally {
public static void main(String[] args) throws Exception {
Thread t= new Thread(new ADaemon());
t.setDaemon(true);
t.start();
// TimeUnit.SECONDS.sleep(1);
}
}

加入一个线程【】

package cn.test;
class Sleeper extends Thread{
private int duration;
public Sleeper(String name,int sleepTime) {
super(name);
duration=sleepTime;
start();
}
public void run() {
try {
sleep(duration);
} catch(InterruptedException e) {
System.out.println(getName()+" was interrupted ."+
"isInterrupted():"+isInterrupted());
return;
}
System.out.println(getName()+" has awakened");
}
}
class Joiner extends Thread{
private Sleeper sleeper;
public Joiner(String name,Sleeper sleeper) {
super(name);
this.sleeper=sleeper;
start();
}
public void run() {
try {
sleeper.join();
}catch (InterruptedException e) {
System.out.println("Interrupted");
}
System.out.println(getName()+" join completed");
}
}
public class Joining {
public static void main(String[] args) {
Sleeper
sleepy=new Sleeper("Sleeper",1500),
grumpy=new Sleeper("Grumpy",1500);
Joiner
dopey=new Joiner("Dopey",sleepy),
doc=new Joiner("Doc",grumpy);
grumpy.interrupt();
}
}

创建有响应的用户界面

  使用线程的动机之一就是建立有响应的用户界面。

package cn.test;

import java.io.IOException;

class UnresponsiveUI{
private volatile double d=1;
public UnresponsiveUI() throws IOException {
while(d>0)
d=d+(Math.PI+Math.E)/d;
System.in.read();
}
}
public class ResponsiveUI extends Thread{
private static volatile double d=1;
public ResponsiveUI() {
setDaemon(true);
start();
}
@Override
public void run() {
while(true)
d=d+(Math.PI+Math.E)/d;
}
public static void main(String[] args) throws IOException {
// new UnresponsiveUI();
new ResponsiveUI();
System.in.read();
System.out.println(d);
}
}

  《Think in Java》20 21(并发)

线程组

捕获异常

  《Think in Java》20 21(并发)

package cn.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; public class ExceptionThread implements Runnable{
public void run() {
throw new RuntimeException();
}
public static void main(String[] args) {
try {
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new ExceptionThread());
}catch(RuntimeException ue) {  //无法捕获异常
System.out.println("Exception has been handled!");
}
}
}
package cn.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory; class ExceptionThread2 implements Runnable{
public void run() {
Thread t=Thread.currentThread();
System.out.println("run() by "+t);
System.out.println("eh = "+t.getUncaughtExceptionHandler());
throw new RuntimeException();
}
}
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("caught "+e);
}
}
class HandlerThreadFactory implements ThreadFactory{
public Thread newThread(Runnable r) {
System.out.println(this+" creating new Thread");
Thread t=new Thread();
System.out.println("created "+t);
t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
System.out.println("eh="+t.getUncaughtExceptionHandler());
return t;
}
}
public class CaptureUncaughtException {
public static void main(String[] args) {
ExecutorService exec=Executors.newCachedThreadPool(new HandlerThreadFactory());
exec.execute(new ExceptionThread2());
}
}

《Think in Java》20 21(并发)

package cn.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; public class SettingDefaultHandler {
public static void main(String[] args) {
Thread.setDefaultUncaughtExceptionHandler(
new MyUncaughtExceptionHandler());
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new ExceptionThread());
}
}

共享受限资源

不正确地访问资源

package cn.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; abstract class IntGenerator{
private volatile boolean canceled=false;
public abstract int next();
public void cancel() {canceled=true;}
public boolean isCanceled() {return canceled;}
}
public class EvenChecker implements Runnable{
private IntGenerator generator;
private final int id;
public EvenChecker (IntGenerator g,int ident) {
generator = g;
id = ident;
}
public void run() {
while(!generator.isCanceled()) {
int val=generator.next();
if(val%2!=0) {
System.out.println(val+" not even!");
generator.cancel();
}
}
}
public static void test(IntGenerator gp,int count) {
System.out.println("Press Control-C to exit");
ExecutorService exec=Executors.newCachedThreadPool();
for(int i=0;i<count;i++)
exec.execute(new EvenChecker(gp,i));
exec.shutdown();
}
public static void test(IntGenerator gp) {
test(gp,10);
}
} package cn.test; public class EvenGenerator extends IntGenerator {
private int currentEvenValue=0;
public int next() {
++currentEvenValue;
++currentEvenValue;
return currentEvenValue;
}
public static void main(String[] args) {
EvenChecker.test(new EvenGenerator());
}
}

在java中,递增不是原子性的操作。因此,如果不保护任务,即使单一的递增也是不安全的。

解决共享资源竞争

  序列化访问共享资源方法。

  互斥量。

《Think in Java》20 21(并发)

(该类的其他对象调用synchronized任务的操作被锁定,只有已经进入被锁任务的那个对象完成任务后才可竞权)

《Think in Java》20 21(并发)

《Think in Java》20 21(并发)

package cn.test;

public class EvenGenerator extends IntGenerator {
private int currentEvenValue=0;
public synchronized int next() {
++currentEvenValue;
Thread.yield();
++currentEvenValue;
return currentEvenValue;
}
public static void main(String[] args) {
EvenChecker.test(new EvenGenerator());
}
}

使用显式的Lock对象

java.util.concurrent.locks

package cn.test;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; public class MutexEvenGenerator extends IntGenerator{
private int currentEvenValue=0;
private Lock lock=new ReentrantLock();
public int next() {
lock.lock();
try {
++currentEvenValue;
Thread.yield();
++currentEvenValue;
return currentEvenValue;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
EvenChecker.test(new MutexEvenGenerator());
} }
package cn.test;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock; public class AttemptLocking {
private ReentrantLock lock=new ReentrantLock();
public void untimed() {
boolean captured=lock.tryLock();
try {
System.out.println("untimed tryLock(): "+captured);
}finally {
if(captured)
lock.unlock();
}
}
public void timed() {
boolean captured = false;
try {
captured = lock.tryLock(2, TimeUnit.SECONDS);
}catch(InterruptedException e) {
throw new RuntimeException(e);
}
try {
System.out.println("timed tryLock(2,TimeUnit.SECONDS): "
+captured);
}finally {
if(captured)
lock.unlock();
}
}
public static void main(String[] args) {
final AttemptLocking al=new AttemptLocking();
al.untimed();
al.timed();
new Thread() {
{setDaemon(true);}
public void run() {
al.lock.lock();
System.out.println("acquired");
}
}.start();
Thread.yield();
al.untimed();
al.timed();
}
}

原子性与易变性

理解volatile

《Think in Java》20 21(并发)

《Think in Java》20 21(并发)

java中,对域中的值做赋值和返回操作是原子性的。

package cn.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; public class AtomicityTest implements Runnable{
private int i=0;
public int getValue() {return i;}
private synchronized void evenIncrement() {i++;i++;}
public void run() {
while(true)
evenIncrement();
}
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
AtomicityTest at = new AtomicityTest();
exec.execute(at);
while(true) {
int val=at.getValue();
if(val % 2 != 0) {
System.out.println(val);
System.exit(0);
}
}
}
}

原子类

package cn.test;

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger; public class AtomicIntegerTest implements Runnable{
private AtomicInteger i=new AtomicInteger(0);
public int getValue() {return i.get();}
private void evenIncrement() {i.addAndGet(2);}
public void run() {
while(true)
evenIncrement();
}
public static void main(String[] args) {
new Timer().schedule(new TimerTask() {
public void run() {
System.err.println("Aborting");
System.exit(0);
}
}, 5000);
ExecutorService exec=Executors.newCachedThreadPool();
AtomicIntegerTest ait=new AtomicIntegerTest();
exec.execute(ait);
while(true) {
int val=ait.getValue();
if(val % 2 != 0) {
System.out.println(val);
System.exit(0);
}
}
} }

临界区(同步控制块)【】

在其他对象上同步

package cn.test;
class DualSynch{
private Object syncObject = new Object();
public synchronized void f() {
for(int i=0;i<5;i++) {
System.out.println("f()");
Thread.yield();
}
}
public void g() {
synchronized(syncObject) {
for(int i=0;i<5;i++) {
System.out.println("g()");
Thread.yield();
}
}
}
}
public class SyncObject {
public static void main(String[] args) {
final DualSynch ds=new DualSynch();
new Thread() {
public void run() {
ds.f();
}
}.start();
ds.g();
}
}

线程本地存储【】

package cn.test;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; 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 {
private 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 InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool();
for(int i=0;i<5;i++)
exec.execute(new Accessor(i));
TimeUnit.SECONDS.sleep(3);
exec.shutdown();
}
}
package cn.test;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; 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 {
private 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 InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool();
for(int i=0;i<5;i++)
exec.execute(new Accessor(i));
TimeUnit.SECONDS.sleep(3);
exec.shutdown();
}
}

终结任务

装饰性花园

package cn.test;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; class Count{
private int count=0;
private Random rand=new Random(47);
public synchronized int increment() {
int temp=count;
if(rand.nextBoolean())
Thread.yield();
return (count= ++temp);
}
public synchronized int value() {return count;}
}
class Entrance implements Runnable{
private static Count count=new Count();
private static List<Entrance> entrances=
new ArrayList<Entrance>();
private int number=0;
private final int id;
private static volatile boolean canceled=false;
public static void cancel() {canceled =true;}
public Entrance(int id) {
this.id=id;
entrances.add(this);
}
public void run() {
while(!canceled) {
synchronized(this) {
++number;
}
System.out.println(this+"Total: "+count.increment());
try {
TimeUnit.MICROSECONDS.sleep(100);
}catch(InterruptedException e) {
System.out.println("sleep interrupted");
}
}
System.out.println("Stopping "+this);
}
public synchronized int getValue() {return number;}
public String toString() {
return "Entrance "+id+": "+getValue();
}
public static int getTotalCount() {
return count.value();
}
public static int sumEntrances() {
int sum=0;
for(Entrance entrance:entrances)
sum+=entrance.getValue();
return sum;
}
}
public class OrnamentalGarden {
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool();
for(int i=0;i<5;i++)
exec.execute(new Entrance(i));
TimeUnit.SECONDS.sleep(3);
Entrance.cancel();
exec.shutdown();
if(!exec.awaitTermination(250, TimeUnit.MICROSECONDS))
System.out.println("Some tasks were not terminated!");
System.out.println("Total: "+Entrance.getTotalCount());
System.out.println("Sum of Entrances: "+Entrance.sumEntrances());
}
}

在阻塞时终结

线程状态:

  1. 新建
  2. 就绪
  3. 阻塞
  4. 死亡

《Think in Java》20 21(并发)

进入阻塞状态

  调用sleep(milliseconds)会进入休眠状态,设定的时间结束时继续运行。

  调用wait()使线程挂起,直到线程被notify()或notifyAll(),线程会进入就绪状态。

  当前任务的等待输入/输出。

  当然任务被阻塞在锁外。

中断

  cancel()是一种中断由Executor启动的单个线程的方式。

package cn.test;

import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; class SleepBlocked implements Runnable{
public void run() {
try {
TimeUnit.SECONDS.sleep(100);
} catch(InterruptedException e) {
System.out.println("InterruptedException");
}
System.out.println("Exiting SleepBlocked.run() ");
}
}
class IOBlocked implements Runnable{
private InputStream in;
public IOBlocked(InputStream in) {this.in = in; }
public void run() {
try {
System.out.println("Waiting for read():");
in.read();
} catch (IOException e) {
if(Thread.currentThread().isInterrupted())
System.out.println("Interrupted from blocked I/O");
else
throw new RuntimeException();
}
System.out.println("Exiting IOBlocked.run() ");
} }
class SynchronizedBlocked implements Runnable{
public synchronized void f() {
while(true)
Thread.yield();
}
public SynchronizedBlocked() {
new Thread() {
public void run() {
f();
}
}.start();
}
public void run() {
System.out.println("Trying to call f()");
f();
System.out.println("Exiting SynchronizedBlocked.run()");
}
}
public class Interrupting {
private static ExecutorService exec=
Executors.newCachedThreadPool();
static void test(Runnable r) throws InterruptedException {
Future<?> f= exec.submit(r);
TimeUnit.MICROSECONDS.sleep(100);
System.out.println("Interrupting "+r.getClass().getName());
f.cancel(true);
System.out.println("Interrupt sent to "+r.getClass().getName());
}
public static void main(String[] args) throws Exception {
test(new SleepBlocked());
test(new IOBlocked(System.in));
test(new SynchronizedBlocked());
TimeUnit.SECONDS.sleep(3);
System.out.println("Aborting with System.exit(0)");
System.exit(0);
}
}

被互斥所阻塞【】

package cn.test;

public class MultiLock {
public synchronized void f1(int count) {
if(count-- >0) {
System.out.println("f1() calling f2() with count "+count);
f2(count);
}
}
public synchronized void f2(int count) {
if(count-- >0) {
System.out.println("f2() calling f1() with count "+count);
f1(count);
}
}
public static void main(String[] args) {
final MultiLock multiLock=new MultiLock();
new Thread() {
public void run() {
multiLock.f1(10);
}
}.start();
}
}
package cn.test;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; class BlockedMutex{
private Lock lock=new ReentrantLock();
public BlockedMutex() {
lock.lock();
}
public void f() {
try {
lock.lockInterruptibly();
System.out.println("lock acquired in f()");
} catch (InterruptedException e) {
System.out.println("Interrupted from lock acquisition in f()");
}
}
}
class Blocked2 implements Runnable {
BlockedMutex blocked=new BlockedMutex();
public void run() {
System.out.println("Waiting for f() in BlockedMutex");
blocked.f();
System.out.println("Broken out of blocked call");
}
}
public class Interrupting2 {
public static void main(String[] args) throws Exception {
Thread t= new Thread(new Blocked2());
t.start();
TimeUnit.SECONDS.sleep(1);
System.out.println("Issuing t.interrupt()");
t.interrupt();
}
}

检查中断

线程之间的协作

wait()与notifyAll()

package cn.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; 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 InterruptedException {
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.println("Wax On!");
TimeUnit.MILLISECONDS.sleep(200);
car.waxed();
car.waitForBuffing();
}
}catch(InterruptedException 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.println("Wax Off!");
TimeUnit.MILLISECONDS.sleep(200);
car.buffed();
}
}catch(InterruptedException 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();
}
}