需求
我們的程序想要實現這樣的一個功能,兩個線程,一個不斷往一個容器加數據,一個不斷從這個容器取數據。
設計的問題
我們的第一個問題是,如果容器滿了怎麼辦,空了又怎麼。解決的辦法是使用wait()和notify()。思路是當容器滿或空時,對應的線程就應該停下了,等到不空或者不滿的時候再繼續。顯然wait()和notify()可以很好的實現。當空時,暫停取,使用wait(),添加線程添加了後就不空了,就可以使用notify()喚醒取的線程了。滿的時候也是一樣的。
sleep()和wait()的區別
暫停也可用sleep(),爲什麼不用sleep()呢?首先是我們不知道停多久,有notify()配合使用才方便。sleep()和wait()有一個重要的區別是,sleep()不會讓出對象鎖。我們寫個例子來證明下:
class Lock{
public synchronized void printlnTest(){
for(int i=0;i<100;i++)
System.out.println(Thread.currentThread().getName());
}
public synchronized void sleepTest(){
System.out.println("sleeped 2 second");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class Print implements Runnable{
private Lock lock;
public Print(Lock lock){
this.lock = lock;
}
@Override
public void run(){
lock.printlnTest();
}
}
class SleepTest implements Runnable{
private Lock lock;
public SleepTest(Lock lock){
this.lock = lock;
}
@Override
public void run(){
lock.sleepTest();
}
}
public class SleepAndWait {
public static void main(String[] args) {
Lock lock = new Lock();
Print print = new Print(lock);
SleepTest sleep = new SleepTest(lock);
new Thread(sleep).start();
new Thread(print).start();
}
}
輸出的結果是,輸出了sleeped 2 second,等待兩秒後,纔可以看到輸出的100個Thread-1。如果把synchronized去掉,也就是不加鎖,Thread-1會立即輸出不會等兩秒。這裏證明sleep是不會讓出對象鎖的。
非常有用的線程圖
消費者實例
這是容器,使用一個類似棧的容器
public class Stack {
private char[] container = new char[6];
private int top;//指向棧頂元素的上一個
public synchronized void push(char a){
if(top==container.length){//if 改爲while更合適,思考下爲什麼
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.notify();
container[top]=a;
top++;
System.out.println(Thread.currentThread().getName()+"入棧的元素"+a);
}
public synchronized char pop(){
if(top==0){//if 改爲while更合適,思考下爲什麼
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.notify();
top--;
System.out.println(Thread.currentThread().getName()+"出棧的元素:"+container[top]);
return container[top];
}
}
生產者
public class Producer implements Runnable{
private Stack stack;
public Producer(Stack stack){
this.stack = stack;
}
@Override
public void run(){
for(int i=0;i<10;i++){
stack.push((char)('a'+i));
}
}
}
消費者
public class Consumer implements Runnable{
private Stack stack;
public Consumer(Stack stack){
this.stack = stack;
}
@Override
public void run(){
try {
for(int i=0;i<10;i++)
stack.pop();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
主線程
public class ThreadTest {
public static void main(String[] args) {
Stack stack = new Stack();
Producer p = new Producer(stack);
Consumer c = new Consumer(stack);
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
同步的問題
當添加線程的push方法和取線程的pop方法,不是原子操作時意思就是push方法執行一半,cpu切換給pop方法的線程執行,就會出現問題。比如說,當執行了container[top]=a;但是還沒執行top++;切換到取線程時,把原來棧頂的元素取出來了並且指針向下減,如果再返回添加線程,繼續執行top++;結果就是新添加的元素丟失了,原來棧頂的元素取了一次還在。所以pop方法和push方法要加鎖,作爲原子操作。