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对象,阻塞队列问题的主要内容,如果未能解决你的问题,请参考以下文章

synchronized 和 lock 的区别

java多线程知识总结

java基础: synchronized与Lock的区别

Lock与Synchronized的区别

Lock与Synchronized的区别

java面试题之synchronized和lock有什么区别