第一题:volatile关键字有什么作用?
由于CPU为了提高对变量的读写效率会将变量存入自己的缓存中,在多个线程访问相同变量时,如果多个线程位于不同的CPU(多核CPU)里面,那么每个线程访问变量(以k=0
为例)时会从不同的CPU缓存里面读取,若一个线程将k的值改为了2,但还未将k的值写入了内存中,此时另一个线程所读取到的k是错误的值,这种问题称为可见性问题,即一个线程对变量的修改无法被其他线程“看到”。
volatile
关键字的作用就是禁用CPU缓存,即不将变量k存入CPU缓存中,而是直接在内存中进行读取,这样多个线程访问变量k时将会访问到相同的值。
volatile
的英文意思是不稳定的,反复无常的,用其修饰变量表明这个变量是不稳定的,每次读写都需要在内存中进行。
第二题:编写Java程序模拟烧水泡茶最优工序。
烧水泡茶最优工序图示如下:
先分析一下,从图中可以看出,需要两个线程,线程t1执行洗水壶、烧水和泡茶,线程t2执行洗茶壶、洗茶杯、拿茶叶,这些事务的关系是:线程t1执行完洗水壶之后去烧水,同时给线程t2发消息告诉它可以洗茶壶了,当线程t2拿完茶叶之后则通知线程t1表示泡茶工具已经准备好了,等t1烧完水后就可以泡茶了。
代码如下:
package thread;
class Worker{
volatile private boolean isKettleWashed = false;
synchronized public void washKettle(){ // 洗水壶
System.out.println("I am washing kettle, please wait 2s......");
try{
Thread.sleep(2*1000); // 2s
} catch(InterruptedException ie){}
System.out.println("Kettle is clean!");
isKettleWashed = true;
notifyAll();
}
public void heatWater(){ // 烧水
System.out.println("I am heating up water, please wait 10s......");
try{
Thread.sleep(10*1000); // 30s
} catch(InterruptedException ie){}
System.out.println("Water is boiling!");
}
synchronized public void washTeapot(){ // 洗茶壶
while(! isKettleWashed){
System.out.println(isKettleWashed);
try{ wait(); }
catch(InterruptedException ie) {}
}
System.out.println("I am washing teapot, please wait 2s......");
try{
Thread.sleep(2*1000); // 2s
} catch(InterruptedException ie){}
System.out.println("Teapot is clean!");
}
public void washTeacup(){ // 洗茶杯
System.out.println("I am washing teacup, please wait 4s......");
try{
Thread.sleep(4*1000); // 4s
} catch(InterruptedException ie){}
System.out.println("Teacup is clean!");
}
public void takeTea(){ // 拿茶叶
System.out.println("I am taking tea, please wait 2s......");
try{
Thread.sleep(2*1000); // 2s
} catch(InterruptedException ie){}
System.out.println("Tea is prepared!");
}
public void makeTea(){ // 泡茶
System.out.println("I am making tea......");
}
}
class Thread1 implements Runnable{
Worker w;
public Thread1(Worker _w){ this.w = _w; }
public void run(){
w.washKettle();
w.heatWater();
w.makeTea();
}
}
class Thread2 implements Runnable{
Worker w;
public Thread2(Worker _w){ this.w = _w; }
public void run(){
w.washTeapot();
w.washTeacup();
w.takeTea();
}
}
public class MakeTea {
public static void main(String[] args){
Worker w = new Worker();
Thread thr1 = new Thread(new Thread1(w), "thread1");
Thread thr2 = new Thread(new Thread2(w), "thread2");
long startTime = System.currentTimeMillis();
thr1.start();
thr2.start();
try{
thr1.join();
thr2.join();
}
catch(InterruptedException it){}
long endTime = System.currentTimeMillis();
long usedTime = (endTime-startTime)/1000;
System.out.print("Total time is "+usedTime+"s");
}
}
输出如下:
I am washing kettle, please wait 2s......
Kettle is clean!
I am heating up water, please wait 10s......
I am washing teapot, please wait 2s......
Teapot is clean!
I am washing teacup, please wait 4s......
Teacup is clean!
I am taking tea, please wait 2s......
Tea is prepared!
Water is boiling!
I am making tea......
Total time is 12s
上述代码中需要注意的两个方法如下:
synchronized public void washKettle()
synchronized public void washTeapot()
之所以需要在这三个方法前面加上synchronized
,是因为必须在洗完水壶(washKettle
方法)之后才可以洗茶壶(washTeapot
方法)。
补充知识点:创建线程的两种方式
方式1:通过继承Thread
类来创建线程
/** MyThread即是新的线程 */
class MyThread extends Thread{
/** 必须覆盖Thread类的run方法 */
public void run(){
System.out.println("Subclass of Thread");
}
}
public class CreateThread {
public static void main(String[] args) {
// 1. 继承Thread类
Thread t1 = new MyThread(); // 创建线程对象
t1.start();
}
}
方式2:实现Runnable
接口,并通过Thread(Runnable target)
来创建线程对象
这种方式可以让多个线程访问相同的资源(变量),该资源在实现Runnable
接口的类中定义,如下例子中线程t2和t3都可以访问MyRunThread
的name
属性。
class MyRunThread implements Runnable{
String name = "heihei";
/** 必须实现Runnable接口的run方法 */
public void run(){
System.out.println("Runnable thread, name is "+name);
}
}
public class CreateThread {
public static void main(String[] args) {
// 2. 实现Runnable接口
MyRunThread myrun = new MyRunThread();
Thread t2 = new Thread(myrun);
t2.start();
Thread t3 = new Thread(myrun);
t3.start();
}
}