Object的锁使用

来源:互联网 发布:商机是什么 知乎 编辑:程序博客网 时间:2024/06/07 10:00

上一篇中用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。

0 0