需求
我们的程序想要实现这样的一个功能,两个线程,一个不断往一个容器加数据,一个不断从这个容器取数据。
设计的问题
我们的第一个问题是,如果容器满了怎么办,空了又怎么。解决的办法是使用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方法要加锁,作为原子操作。