PC问题

title:PC问题

date:2017年11月12日00:17:02


​ 在大学学习OS的时候,我们学习进程管理的时候,讲进程同步与互斥的时候,有用几个例子来进行说明:生产者消费者问题,读者写者问题,哲学家问题,三人吸烟问题等。有空还是需要好好去回顾下四大基础课的。今天我们就先来讲讲消费者生产者问题。

​ 在学习OS的时候我们解决生产者消费者问题是通过PV原语来控制的。而在我们的Java语言这门高级语言中,我们通常是通过锁来解决生产者消费者问题的。今天我们就来看看Java中几种解决生产者消费者问题的方法。

使用synchronized关键字加wait() notifyAll()

话不多说,先上代码

package com.wangcc.MyJavaSE.thread.pc;

class Storage {
    // 实际大小 ,就类似list嘛,其实更好使用list来做,不过这里就先用这种方法了
    private int size;
    // 初始化大小
    private int capacity;

    public Storage(int size, int capacity) {
        this.size = size;
        this.capacity = capacity;
    }

    public synchronized void produce(int val) {
        try {
            int prod = val;
            while (prod > 0) {
                // 库存满了,要等待consumer消费
                while (size >= capacity) {
                    wait();
                }
                int inc = (size + prod) > capacity ? (capacity - size) : prod;
                size = size + inc;
                // 无法生产的个数
                int left = prod - inc;
                System.out.printf("%s produce(%3d) --> left=%3d, inc=%3d, size=%3d\n", Thread.currentThread().getName(),
                        val, left, inc, size);
                notifyAll();

            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public synchronized void consume(int val) {
        try {
            int cons = val;
            while (cons > 0) {
                while (size <= 0) {
                    wait();
                }
                int des = (cons < size) ? cons : size;
                size = size - des;
                // 无法消费的的个数
                int left = cons - des;
                System.out.printf("%s consume(%3d) <-- left=%3d, dec=%3d, size=%3d\n", Thread.currentThread().getName(),
                        val, left, des, size);
                notifyAll();
            }
        } catch (Exception e) {
            // TODO: handle exception
        }
    }

    public String toString() {
        return "capacity:" + capacity + ", actual size:" + size;
    }
}

class Producer implements Runnable {
    private Storage storage;
    private int val;

    public Producer(Storage storage, int val) {
        this.storage = storage;
        this.val = val;
    }

    public void run() {
        // TODO Auto-generated method stub
        storage.produce(val);
    }

}

class Consumer implements Runnable {
    private Storage storage;
    private int val;

    public Consumer(Storage storage, int val) {
        this.storage = storage;
        this.val = val;
    }

    public void run() {
        // TODO Auto-generated method stub
        storage.consume(val);
    }
}

// 不能同时消费和生产
public class Version0 {
    public static void main(String[] args) {
        Storage storage = new Storage(0, 100);
        Thread t1 = new Thread(new Consumer(storage, 12), "t1");

        Thread t2 = new Thread(new Consumer(storage, 34), "t2");
        Thread t3 = new Thread(new Producer(storage, 56), "t3");
        Thread t4 = new Thread(new Producer(storage, 3), "t4");
        Thread t5 = new Thread(new Consumer(storage, 12), "t5");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();

    }
}

结合之前学习的线程相关知识,我们可以使用synchronized关键字加wait()notify()来实现PC。这是最简单,但同时也是效率最低的方法。我们知道synchronized是悲观锁,对于每一个对象,有且仅有一个同步锁;不同的线程能共同访问该同步锁。但是,在同一个时间点,该同步锁能且只能被一个线程获取到。这样,获取到同步锁的线程就能进行CPU调度,从而在CPU上执行;而没有获取到同步锁的线程,必须进行等待,直到获取到同步锁之后才能继续运行。

我们要注意这个程序是会一直运行下去,至于为什么呢,要注意while循坏的有效条件。

使用Lock+Condition

在JDK5之后,Java增加了JUC包,即并发库包,这对于Java并发编程的实践起着非常重要的作用,个人认为JDK5版本对于Java来说是划时代的版本,增加了并发库包,增加了泛型,增加了枚举,修复了volatile关键字中Java指令重排。这些改动都为我们使用Java编程带来了极大的便利。而我们最近的学习重点就是学习JUC包,今天就算学习JUC的开端了。

我们看看代码的实现:

package com.wangcc.MyJavaSE.thread.pc;

import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyStorage{
    private LinkedList<Object> list = new LinkedList<Object>();  
    private final static int MAX_VALUE=100;
    private final Lock lock=new ReentrantLock();
    //仓库满了
    private final Condition full=lock.newCondition();
    //仓库空了
    private final Condition empty=lock.newCondition();

    public void produce(int val) {
        lock.lock();
        try {
            while(list.size()+val>MAX_VALUE) {
                full.await();
            }
              for (int i = 1; i <= val; ++i)  
                {  
                    list.add(new Object());  
                }  

                System.out.println("【已经生产产品数】:" + val + "/t【现仓储量为】:" + list.size());  
                empty.signalAll();
        } catch (Exception e) {
            // TODO: handle exception
        }
        finally {
            lock.unlock();
        }
    }
    public void consume(int val) {
        lock.lock();
        try {
            while(list.size()<val) {
                empty.await();
            }
            // 消费条件满足情况下,消费val个产品  
            for (int i = 1; i <= val; ++i)  
            {  
                list.remove();  
            }  

            System.out.println("【已经消费产品数】:" + val + "/t【现仓储量为】:" + list.size());  
            full.signalAll();
        } catch (Exception e) {
            // TODO: handle exception
        }
        finally {
            lock.unlock();
        }
    }
    // set/get方法  
    public  int getMAX_SIZE()  
    {  
        return MAX_VALUE;  
    }  

    public LinkedList<Object> getList()  
    {  
        return list;  
    }  

    public void setList(LinkedList<Object> list)  
    {  
        this.list = list;  
    }  
}
class MyProducer implements Runnable{
    private MyStorage storage;
    private int val;
    public MyProducer(MyStorage storage,int val) {
        this.storage=storage;
        this.val=val;
    }
    public void run() {
        // TODO Auto-generated method stub
        storage.produce(val);
    }

}
class MyConsumer implements Runnable{
    private MyStorage storage;
    private int val;
    public MyConsumer(MyStorage storage,int val) {
        this.storage=storage;
        this.val=val;
    }
    public void run() {
        // TODO Auto-generated method stub
        storage.consume(val);
    }
}
public class Version1 {
    public static void main(String []args) {
        MyStorage storage=new MyStorage();
        Thread t1=new Thread(new MyConsumer(storage, 12),"t1");


        Thread t2=new Thread(new MyConsumer(storage, 34),"t2");
        Thread t3=new Thread(new MyProducer(storage, 26),"t3");
        Thread t4=new Thread(new MyProducer(storage, 56),"t4");
        Thread t5=new Thread(new MyProducer(storage, 12),"t5");
        Thread t6=new Thread(new MyConsumer(storage, 34),"t6");
        Thread t7=new Thread(new MyConsumer(storage, 32),"t7");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        t6.start();
        t7.start();
    }
}

上述就是使用Lock完成同步的代码。

synchronized和Lock

那么这两种方式到底有上面区别,到底哪个更好呢?

性能比较

    在JDK1.5中,synchronized是性能低效的。因为这是一个重量级操作,它对性能最大的影响是阻塞的是实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性带来了很大的压力。相比之下使用Java提供的Lock对象,性能更高一些。Brian Goetz对这两种锁在JDK1.5、单核处理器及双Xeon处理器环境下做了一组吞吐量对比的实验,发现多线程环境下,synchronized的吞吐量下降的非常严重,而ReentrankLock则能基本保持在同一个比较稳定的水平上。但与其说ReetrantLock性能好,倒不如说synchronized还有非常大的优化余地,于是到了JDK1.6,发生了变化,对synchronize加入了很多优化措施,有自适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在JDK1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地,所以还是提倡在synchronized能实现需求的情况下,优先考虑使用synchronized来进行同步。

    下面浅析以下两种锁机制的底层的实现策略。

    互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题,因而这种同步又称为阻塞同步,它属于一种悲观的并发策略,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。synchronized采用的便是这种并发策略。

    随着指令集的发展,我们有了另一种选择:基于冲突检测的乐观并发策略,通俗地讲就是先进性操作,如果没有其他线程争用共享数据,那操作就成功了,如果共享数据被争用,产生了冲突,那就再进行其他的补偿措施(最常见的补偿措施就是不断地重拾,直到试成功为止),这种乐观的并发策略的许多实现都不需要把线程挂起,因此这种同步被称为非阻塞同步。ReetrantLock采用的便是这种并发策略。

    在乐观的并发策略中,需要操作和冲突检测这两个步骤具备原子性,它靠硬件指令来保证,这里用的是CAS操作(Compare and Swap)。JDK1.5之后,Java程序才可以使用CAS操作。我们可以进一步研究ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法是compareAndSetState,这里其实就是调用的CPU提供的特殊指令。现代的CPU提供了指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而compareAndSet() 就用这些代替了锁定。这个算法称作非阻塞算法,意思是一个线程的失败或者挂起不应该影响其他线程的失败或挂起。

    Java 5中引入了注入AutomicInteger、AutomicLong、AutomicReference等特殊的原子性变量类,它们提供的如:compareAndSet()、incrementAndSet()和getAndIncrement()等方法都使用了CAS操作。因此,它们都是由硬件指令来保证的原子方法。

   用途比较

    基本语法上,ReentrantLock与synchronized很相似,它们都具备一样的线程重入特性,只是代码写法上有点区别而已,一个表现为API层面的互斥锁(Lock),一个表现为原生语法层面的互斥锁(synchronized)。ReentrantLock相对synchronized而言还是增加了一些高级功能,主要有以下三项:

    1、等待可中断:当持有锁的线程长期不释放锁时,正在等待的线程可以选择放弃等待,改为处理其他事情,它对处理执行时间非常上的同步块很有帮助。而在等待由synchronized产生的互斥锁时,会一直阻塞,是不能被中断的。

    2、可实现公平锁:多个线程在等待同一个锁时,必须按照申请锁的时间顺序排队等待,而非公平锁则不保证这点,在锁释放时,任何一个等待锁的线程都有机会获得锁。synchronized中的锁时非公平锁,ReentrantLock默认情况下也是非公平锁,但可以通过构造方法ReentrantLock(ture)来要求使用公平锁。

    3、锁可以绑定多个条件:ReentrantLock对象可以同时绑定多个Condition对象(名曰:条件变量或条件队列),而在synchronized中,锁对象的wait()和notify()或notifyAll()方法可以实现一个隐含条件,但如果要和多于一个的条件关联的时候,就不得不额外地添加一个锁,而ReentrantLock则无需这么做,只需要多次调用newCondition()方法即可。而且我们还可以通过绑定Condition对象来判断当前线程通知的是哪些线程(即与Condition对象绑定在一起的其他线程)。

可中断锁

    ReetrantLock有两种锁:忽略中断锁和响应中断锁。忽略中断锁与synchronized实现的互斥锁一样,不能响应中断,而响应中断锁可以响应中断。

    如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,如果此时ReetrantLock提供的是忽略中断锁,则它不会去理会该中断,而是让线程B继续等待,而如果此时ReetrantLock提供的是响应中断锁,那么它便会处理中断,让线程B放弃等待,转而去处理其他事情。

我们来看一个例子:

转载:http://blog.csdn.net/ns_code/article/details/17487337

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值