Object的锁使用

上一篇中用Condition实现了生产者和消费者,其中用到了Condition的await和singal,这个实现和Object的锁特别像,如果用Object的锁机制实现生产者和消费者,如下:

import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by lizhiqiang on 2016/12/23.
 */
public class testObjectLock2 {
    public static void main(String[] args){
        final BoundedBuffer buffer = new BoundedBuffer();
        ExecutorService service = Executors.newFixedThreadPool(2);

        service.execute(new Runnable() {
            @Override
            public void run() {
                while(true){
                    try {
                        Thread.currentThread().sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    buffer.put(1);
                }
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println(new Date() + ":" + buffer.take());
                }
            }
        });
        service.shutdown();
    }
    public static class BoundedBuffer{
        Object notEmpty = new Object();
        Object notFull = new Object();
        final Object[] items = new Object[3];
        int count;
        public BoundedBuffer(){
            count=0;
        }
        public void put(Object x){
            synchronized (notFull){
                if(count==2){
                    try {
                        notFull.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                synchronized (notEmpty){
                    ++count;
                    items[count] = x;
                    notEmpty.notify();
                }
            }
        }
        public Object take(){
            Object result;
            synchronized (notEmpty){
                if(count==0) {
                    try {
                        notEmpty.wait();

                    } catch (InterruptedException e) {
                    }
                }
                synchronized (notFull){
                    result = items[count--];
                    notFull.notify();
                }
                return result;
            }
        }
    }
}
执行结果:

Fri Dec 23 11:07:20 CST 2016:1
Fri Dec 23 11:07:21 CST 2016:1
Fri Dec 23 11:07:22 CST 2016:1
Fri Dec 23 11:07:23 CST 2016:1
Fri Dec 23 11:07:24 CST 2016:1
Fri Dec 23 11:07:25 CST 2016:1
Fri Dec 23 11:07:26 CST 2016:1

发现效果和上一篇一样。


下面开始讲两个问题:

1.用Condition或者Object怎么实现多消费者、多生产者?

2.Condition和Object到底有什么不同?


1.用Condition或者Object怎么实现多消费者、多生产者?

Condition实现:

import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by lizhiqiang on 2016/12/23.
 */
public class testCondition3 {
    public static void main(String[] args){
        final BoundedBuffer buffer = new BoundedBuffer();
        ExecutorService service = Executors.newFixedThreadPool(4);
        service.execute(new Runnable() {
            @Override
            public void run() {
                while(true){
                    buffer.put(1);
                }
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("消费者"+Thread.currentThread().getName()+":"+new Date() + ":" + buffer.take());
                }
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("消费者"+Thread.currentThread().getName()+":"+new Date() + ":" + buffer.take());
                }
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("消费者"+Thread.currentThread().getName()+":"+new Date() + ":" + buffer.take());
                }
            }
        });
        service.shutdown();
    }
    public static class BoundedBuffer{
        final Lock lock = new ReentrantLock();

        Condition notEmpty = lock.newCondition();
        Condition notFull = lock.newCondition();
        final Object[] items = new Object[3];
        int count;
        public BoundedBuffer(){
            count=0;
        }
        public void put(Object x){
            lock.lock();
            if(count==2){
                try {
                    notFull.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            ++count;
            items[count] = x;
            notEmpty.signal();
            lock.unlock();
        }
        public Object take(){
            lock.lock();
            Object result;
            if(count==0) {
                try {
                    notEmpty.await();
                } catch (InterruptedException e) {
                }
            }
            result = items[count--];
            notFull.signal();
            lock.unlock();
            return result;
        }
    }
}
Object实现:

import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by lizhiqiang on 2016/12/23.
 */
public class testObjectLock3 {
    public static void main(String[] args){
        final BoundedBuffer buffer = new BoundedBuffer();
        ExecutorService service = Executors.newFixedThreadPool(4);

        service.execute(new Runnable() {
            @Override
            public void run() {
                while(true){
                    buffer.put(1);
                }
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("消费者"+Thread.currentThread().getName()+":"+new Date() + ":" + buffer.take());
                }
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("消费者"+Thread.currentThread().getName()+":"+new Date() + ":" + buffer.take());
                }
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("消费者"+Thread.currentThread().getName()+":"+new Date() + ":" + buffer.take());
                }
            }
        });
        service.shutdown();
    }
    public static class BoundedBuffer{
        Object notEmpty = new Object();
        Object notFull = new Object();
        final Object[] items = new Object[3];
        int count;
        public BoundedBuffer(){
            count=0;
        }
        public void put(Object x){
            synchronized (notFull){
                if(count==2){
                    try {
                        notFull.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                synchronized (notEmpty){
                    ++count;
                    items[count] = x;
                    notEmpty.notify();
                }
            }
        }
        public Object take(){
            Object result;
            synchronized (notEmpty){
                if(count==0) {
                    try {
                        notEmpty.wait();

                    } catch (InterruptedException e) {
                    }
                }
                synchronized (notFull){
                    result = items[count--];
                    notFull.notify();
                }
                return result;
            }
        }
    }
}

以上两个实现方式,均实现了一个生产者多个消费者的问题,但是执行会报错:数组越界

为什么?

原因其实出在singal和notify后,对应的线程不是立即执行,而是cpu从等待池中拿到对应的线程,然后再进行上下文切换,才开始执行对应的线程。比如生产者生产一个产品,通知消费者A,此时,生产者开始生产第二个产品,通知消费者B,消费者A和B同时执行,而A执行比较快,一下消费了两个产品,导致B一个产品也拿不到,造成数组越界。

解决方法:

让生产者等一会再通知另一个消费者,让消费者有充足的的时间执行,并再次进入等待。但是这种思路导致生产者效率低下,因为同一时间它只是让一个消费者在执行。

在生产者代码中假如等待时间:

service.execute(new Runnable() {
    @Override
    public void run() {
        while(true){
            try {
                Thread.currentThread().sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            buffer.put(1);
        }
    }
});
另一个思路:

让消费者take一次之后休眠一会,这个比较符合实际的场景。


多生产者多消费者的场景呢?
加等待时间已经不能解决这类数组越界的问题了,只能用try catch将数组越界给吃掉了。

下面一起讨论下第二个内容:

Synchronize和lock的区别是什么呢,有了Synchronize为什么还需要有lock?

首先,synchronize无法解决多个对象锁出现的死锁问题。

比如我们上面的事例中,

public void put(Object x){
    synchronized (notFull){
        if(count==2){
            try {
                System.out.println("产品库满了");
                notFull.wait();
                System.out.println("可以添加产品了,当前有"+count+"个");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("生产者"+Thread.currentThread().getName());
        synchronized (notEmpty){
            System.out.println("要添加产品,当前有"+count+"个");
            ++count;
            items[count] = x;
            System.out.println("添加完产品,当前有"+count+"个");
            notEmpty.notify();
        }
    }
}
public Object take(){
    Object result;
    synchronized (notEmpty){
        if(count==0) {
            try {
                System.out.println("没有产品了");
                notEmpty.wait();
                System.out.println("有产品了");
            } catch (InterruptedException e) {
            }
        }
        System.out.println("消费者"+Thread.currentThread().getName());
        synchronized (notFull){
            System.out.println("要获取产品,当前有"+count+"个");
            result = items[count--];
            System.out.println("取走产品,当前有"+count+"个");
            notFull.notify();
        }
        return result;
    }
}
以上两个方法,一个是生产者调用的,一个是消费者调用的,如果生产者和消费者是一对多的关系,那么极为可能出现死锁。

检查死锁的方法就是将所有的步骤的执行时间都可以看成无限长(主要是和cpu切换上下文以及时间片分配有关)。因为notify方法实现的是将锁交给任意一个,比如:消费者A拿到notEmpty,它在等待notFull锁,而生产者拿到notFull,这时候生产者释放notEmpty锁,将锁交给消费者B,生产者自身开始等消费者A释放notEmpty锁,这样就进入了死锁状态了。(一个生产者和一个消费者一样会出现死锁:只是可能性不是很高)

但是如果改为Lock实现:

public static class BoundedBuffer{
    final Lock lock = new ReentrantLock();

    Condition notEmpty = lock.newCondition();
    Condition notFull = lock.newCondition();
    final Object[] items = new Object[3];
    int count;
    public BoundedBuffer(){
        count=0;
    }
    public void put(Object x){
        lock.lock();
        if(count==2){
            try {
                System.out.println("产品库满了");
                notFull.await();
                System.out.println("可以添加产品了,当前有" + count + "个");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("要添加产品,当前有"+count+"个");
        ++count;
        items[count] = x;
        notEmpty.signal();
        System.out.println("添加完产品,当前有" + count + "个");
        lock.unlock();
    }
    public Object take(){
        lock.lock();
        Object result;
        if(count==0) {
            try {
                System.out.println("没有产品了");
                notEmpty.await();
                System.out.println("有产品了");
            } catch (InterruptedException e) {
            }
        }
        System.out.println("要获取产品,当前有"+count+"个");
        result = items[count--];
        notFull.signal();
        System.out.println("取走产品,当前有" + count + "个");
        lock.unlock();
        return result;
    }
}
就不会出现死锁的问题,因为Lock锁可以有多个condition,两个线程不同方法进入lock代码块是同步的。

这也就是为什么又出现Lock,或者Lock为什么更高级的地方!Lock可以实现不同代码块的同步执行!(而Synchronize做不到,它只是可以做到对象的使用同步)

如果只是一个对象进行加锁,那么无所谓用Lock还是Synchronize,但是如果是多个对象,那么只能用Lock。

你可能感兴趣的:(Object的锁使用)