synchronized关键字,Lock对象,阻塞队列问题
Posted luhuajun
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了synchronized关键字,Lock对象,阻塞队列问题相关的知识,希望对你有一定的参考价值。
一. 请你谈一谈synchronized和lock有什么区别?
1.synchronized是java的关键字,属于jvm层面,底层是通过moninter对象实现的.Lock是具体的接口,属于api层面.
2.synchronized不需要用户去手动释放锁,当synchronized的代码执行完成后,系统会自动释放线程对锁的占用,Lock
则需要用户去手动释放锁,如果没有主动去释放锁,就会导致死锁的发生.
3.synchronized不可被中断,除非程序执行完毕或抛出异常.Lock可以被中断(1.设置超时方法 tryLock() 2.lockInterruptibly()
放代码块中,调用interrupt()可中断)
4.synchronized是非公平锁,Lock默认也是非公平锁,但可以通过构造方法,传入布尔值来确定锁的类型.(true 公平锁,false 非公平锁)
5.synchronized没有绑定多个条件,Lock可以分组唤醒需要唤醒的线程,实现精确唤醒,而不像synchronized那样随机唤醒一个线程
要么全部唤醒.
案例:实现多线程之间的精确调用(实现A->B->C三个线程之间的启动),
lock优于synchronized的举例
要求:A线程打印5次,B线程打印10次,C线程打印15次,紧接着A在打印5次
B打印10次,C打印15次.循环执行10次
class ShareResource{ private int num = 1; // 标志位 1表示A线程执行,2表示B线程执行,3表示C线程执行 private Lock lock = new ReentrantLock(); private Condition c1 = lock.newCondition(); // A线程执行条件 private Condition c2 = lock.newCondition(); // B线程执行条件 private Condition c3 = lock.newCondition(); // C线程执行条件 public void print5(){ lock.lock(); try { //如果不是A线程执行 while (num != 1){ //A线程等待 c1.await(); } // 切换到B线程 num = 2; // 唤醒B线程 c2.signal(); for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + " "+i); } }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.unlock(); } } public void print10(){ lock.lock(); try { while (num != 2){ c2.await(); } num = 3; c3.signal(); for (int i = 1; i <= 10; i++) { System.out.println(Thread.currentThread().getName() + " "+i); } }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.unlock(); } } public void print15(){ lock.lock(); try { while (num != 3){ c3.await(); } num = 1; c1.signal(); for (int i = 1; i <= 15; i++) { System.out.println(Thread.currentThread().getName() + " "+i); } }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.unlock(); } }
public static void main(String[] args) { ShareResource sr = new ShareResource(); //获取对象 // 执行10次A线程 new Thread(()->{ for (int i = 1; i <= 10; i++) { sr.print5(); } },"A").start(); // 执行10次B线程 new Thread(()->{ for (int i = 1; i <= 10; i++) { sr.print10(); } },"B").start(); // 执行10次C线程 new Thread(()->{ for (int i = 1; i <= 10; i++) { sr.print15(); } },"C").start(); }
执行结果:
A,B,C三个线程依次执行,没有插队的情况,结果就是正确的
二 . 请你谈一谈生产者/消费者模型,并结合业务,写一个案例
举例:多个线程同时操作一份数据,生产者+1,消费者-1
1.使用Lock实现
class ShareData{ private int number = 0; // 资源数据 private Lock lock = new ReentrantLock(); // 锁对象 private Condition condition = lock.newCondition(); // 条件 /*生产者 +1操作*/ public void increment(){ lock.lock(); try { // 如果生产者已经满了,就进入等待状态 // 要使用while防止虚假唤醒(jdk1.8文档源码,不能使用if !!!) while (number != 0){ // 等待 condition.await(); } // 业务逻辑 +1 number++; System.out.println(Thread.currentThread().getName() + " "+number); //唤醒消费者线程进行-1 condition.signalAll(); }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.unlock(); } } /*消费者线程 -1操作*/ public void decrement(){ lock.lock(); try { // 如果已经没有资源了,number = 0 ,等待生产者线程生产 while (number == 0){ condition.await(); } // 业务逻辑 -1 number--; System.out.println(Thread.currentThread().getName() + " "+number); // 唤醒生产者线程 condition.signalAll(); }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.unlock(); } } }
public static void main(String[] args) { ShareData shareData = new ShareData(); // 生产者 new Thread(()->{ for (int i = 1; i <= 5; i++) { shareData.increment(); } },"T1").start(); //消费者 new Thread(()->{ for (int i = 1; i <= 5; i++) { shareData.decrement(); } },"T2").start(); }
执行结果:
生产一个,消费一个,生成一个,消费一个 .......
2. 使用BlockingQueue实现
class ShareMessage{ // 开关,true表示执行,false表示关闭 private volatile boolean FLAG = true; // 原子整型,操作不能被分割 private AtomicInteger atomicInteger = new AtomicInteger(); // 阻塞队列,用于存放数据 private BlockingQueue<String> blockingQueue = null; // 初始化BlockingQueue 对象的具体的实现类 public ShareMessage(BlockingQueue<String> blockingQueue) { this.blockingQueue = blockingQueue; System.out.println(blockingQueue.getClass().getName()); } /*生产者模型,*/ public void produce() throws InterruptedException{ String data = null; boolean retValue ; while (FLAG){ // 如果开关打开 // 等同于 i++ + "" data = atomicInteger.incrementAndGet() + ""; // 把数据放到阻塞队列中,并设置超时的时间 retValue = blockingQueue.offer(data,2L, TimeUnit.SECONDS); // 如果放置成功 if(retValue){ System.out.println(Thread.currentThread().getName() + " "+ data + "插入队列成功"); }else { System.out.println(Thread.currentThread().getName() + " "+ data + "插入队列失败"); } // 让消费者线程取,方便显示 TimeUnit.SECONDS.sleep(1); } System.out.println(Thread.currentThread().getName() + "生产结束,FLAG设置为false"); } /*消费者线程*/ public void consume() throws InterruptedException{ String res = null; while (FLAG){ // 从阻塞队列中获取数据 res = blockingQueue.poll(2L,TimeUnit.SECONDS); // 如果没有获取到数据, if(null == res || res.equalsIgnoreCase("")){ // 停止生产者线程,并退出当前的消费者线程 FLAG = false; System.out.println(Thread.currentThread().getName()+" 超过2秒钟没有取到数据,消费退出"); return; } System.out.println(Thread.currentThread().getName()+" "+res+"消费队列成功"); } } /*关闭生产/消费模型*/ public void stop(){ this.FLAG = false; } }
public static void main(String[] args) { ShareMessage sm = new ShareMessage(new ArrayBlockingQueue<>(10)); // 启动生产者 new Thread(()->{ System.out.println(Thread.currentThread().getName()+"生产线程启动"); try { sm.produce(); } catch (InterruptedException e) { e.printStackTrace(); } },"produce").start(); // 启动消费者 new Thread(()->{ System.out.println(Thread.currentThread().getName()+"消费线程启动"); try { sm.consume(); } catch (InterruptedException e) { e.printStackTrace(); } },"consume").start(); // 执行5秒钟后,停止运行 try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } sm.stop(); }
执行结果:
j结论:
使用阻塞队列实现生产者/消费者模型更优于使用Lock的方式,因为阻塞队列中不需要手动的去停止/唤醒线程.
以上是关于synchronized关键字,Lock对象,阻塞队列问题的主要内容,如果未能解决你的问题,请参考以下文章