相信大家都非常熟悉生产者和消费者了,那么在这里就简单的书写一下在并发环境下的生产者和消费者,看看他们会出现哪些问题!
并发编程步骤就是很简单的几步,总结来说就是线程操作资源类!
2. 建立资源类,这里使用Lock锁
//建立资源类以及相关属性和方法,
public class Resource { private int number =0; private Lock lock=new ReentrantLock(); private Condition condition=lock.newCondition() public void incr1() throws Exception{ lock.lock(); try { }finally { lock.unlock(); } } public void decr1()throws Exception{ lock.lock(); try {; }finally { lock.unlock(); } } };
小小的提示一下,为了安全起见我们最好是把解锁的放在finally!就算是有异常他也会去释放锁不会出现死锁的问题!
判断当前线程是否该进行下一步工作
public class Resource { private int number =0; private Lock lock=new ReentrantLock(); private Condition condition=lock.newCondition() public void incr1() throws Exception{ lock.lock(); try { if (number!=0){ condition.await(); } number++; System.out.println(Thread.currentThread().getName()+"::"+number); condition.signalAll(); }finally { lock.unlock(); } } public void decr1()throws Exception{ lock.lock(); try {; if (number!=1){ condition.await(); } number--; System.out.println(Thread.currentThread().getName()+"::"+number); condition.signalAll(); }finally { lock.unlock(); } } };
下面展示一些 内联代码片
。
测试
public class JucLock { public static void main(String[] args) { Resource resource = new Resource(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { resource.decr1(); } catch (Exception e) { e.printStackTrace(); } } },"A线程").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { resource.incr1(); } catch (Exception e) { e.printStackTrace(); } } },"B线程").start(); }
5.结果
简单解释一下:声明的加减方法 都进行了上锁操作,每个方法都是当有线程进来时就进行判断然后看是否满足条件从而进行干活!我们在上面看到两个线程去操作的时候基本上是不会出现任何问题,当竞争者多起来的时候那么就会出现一些并发问题了!
下面展示一些 内联代码片
。
并发问题的出现
// An highlighted block public class JucLock { public static void main(String[] args) { Resource resource = new Resource(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { resource.decr1(); } catch (Exception e) { e.printStackTrace(); } } },"A线程").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { resource.incr1(); } catch (Exception e) { e.printStackTrace(); } } },"B线程").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { resource.decr1(); } catch (Exception e) { e.printStackTrace(); } } },"C线程").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { resource.incr1(); } catch (Exception e) { e.printStackTrace(); } } },"D线程").start(); } } ;
看见上图就出现了相关的问题 按常理来说应该是0和1假交替执行!但这里出现负数和大于1的情况
问题就出现在if判断那个位置,试想一下 这里有abcd四个线程,ac为加操作bd为减操作,变量初始值为0的时候假如a线程抢到锁并且进入判断进行加操作,操作结束后通知其他线程。此时又被c线程抢到了执行权,那么他判断值不为0就调用await方法进行等待,等待的过程中把锁释放出来,如果这个时候被bd其中一个线程抢到执行权那么进行减一操作,然后去通知其他线程!那么重点来了,如果此时执行权被a线程抢到了,并且在通知其他线程的时候恰好被还在等待的c线程抢到也就是被唤醒了!此时c线程就会在await方法的位置直接被唤醒,他就不会进行判断就进行加一操作 ,那么此时就会变成2.
当并发量特别大的时候还可能加到更大的数字上面去,同理也可能减到很大的负数上面去!
为了解决这一问题,在官方文档中就告诉我们要使用循环判断,每一个线程抢到执行权时都会去判断是否满足进行下一步操作的条件从而避免问题的出现!!!
最终代码
public class Resource { private int number =0; private Lock lock=new ReentrantLock(); private Condition condition=lock.newCondition(); //生产者 public void incr1() throws Exception{ lock.lock(); try { while (number!=0){ condition.await(); } number++; System.out.println(Thread.currentThread().getName()+"::"+number); condition.signalAll(); }finally { lock.unlock(); } } public void decr1()throws Exception{ lock.lock(); try { while (number!=1){ condition.await(); } number--; System.out.println(Thread.currentThread().getName()+"::"+number); condition.signalAll(); }finally { lock.unlock(); } } }