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() + "\t"+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() + "\t"+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() + "\t"+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() + "\t"+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() + "\t"+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() + "\t"+ data + "插入队列成功");
            }else {
                System.out.println(Thread.currentThread().getName() + "\t"+ 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()+"\t超过2秒钟没有取到数据,消费退出");
                return;
            }
            System.out.println(Thread.currentThread().getName()+"\t"+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的方式,因为阻塞队列中不需要手动的去停止/唤醒线程.

原文地址:https://www.cnblogs.com/luhuajun/p/12130819.html

时间: 2024-10-05 16:19:24

synchronized关键字,Lock对象,阻塞队列问题的相关文章

获取synchronized锁中的阻塞队列中的线程是非公平的

synchronized中阻塞队列的线程是非公平的 测试demo: import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.TimeUnit; public class SleepState { public static ThreadLocal<SimpleDateFormat> threadLocal = new

synchronized与lock 对象锁、互斥锁、共享锁以及公平锁和非公平锁

synchronized与lock  都是用来实现线程同步的锁,synchronized对象锁,lock是一个接口,她的实现有reentrantlock互斥锁以及ReentrantReadWriteLock共享锁. 这里说明一下ReentrantReadWriteLock共享锁,所谓共享就是该锁提供读读锁共享,即可以多个线程共享一个读取锁,但是读写锁以及读读锁是互斥的. 看到网上有好多介绍介绍锁的种类的,有对象锁.互斥锁.共享锁以及公平锁和非公平锁,但是说明都不够详细了然,在这里用直白的说法记录

synchronized关键字以及对象锁和类锁的区别

java并发编程中最长用到的关键字就是synchronized了,这里讲解一下这个关键字的用法和容易混淆的地方.synchronized关键字涉及到锁的概念, 在java中,synchronized锁大家又通俗的称为:方法锁,对象锁 和 类锁 三种. 先上结论! 1 无论是修饰方法还是修饰代码块都是 对象锁,当一个线程访问一个带synchronized方法时,由于对象锁的存在,所有加synchronized的方法都不能被访问(前提是在多个线程调用的是同一个对象实例中的方法)2 无论是修饰静态方法

JAVA多线程之Synchronized关键字--对象锁的特点

一,介绍 本文介绍JAVA多线程中的synchronized关键字作为对象锁的特点. 二,分析 synchronized可以修饰实例方法,如下形式: 1 public class MyObject { 2 3 synchronized public void methodA() { 4 //do something.... 5 } 这里,synchronized 关键字锁住的是当前对象.这也是称为对象锁的原因. 为啥锁住当前对象?因为 methodA()是个实例方法,要想执行methodA(),

java synchronized关键字

Java中synchronized关键字和对象的内置锁结合使用,用来保护代码块在并发环境下的线程安全,可以使被保护的代码块操作原子性. synchronized关键字可以用于修饰方法来保护方法内的全部代码块,可以用synchronized(对象1) 的方式保护指定代码块.(这里说一下:很多书中都说synchronized可以给对象加锁,我实在不愿意这么说,这样让我概念混淆...因为,对象内置锁是本来就存在的,不是谁加给它的,"synchronized(对象1)"我更愿意解释成:执行到此

阻塞队列和生产者-消费者模式、DelayQueue

1.ArrayDeque, (数组双端队列) 2.PriorityQueue, (优先级队列) 3.ConcurrentLinkedQueue, (基于链表的并发队列) 4.DelayQueue,                                         (延期阻塞队列)(阻塞队列实现了BlockingQueue接口) 5.ArrayBlockingQueue,           (基于数组的并发阻塞队列) 6.LinkedBlockingQueue,        (基

Java多线程:synchronized关键字和Lock

一.synchronized synchronized关键字可以用于声明方法,也可以用来声明代码块,下面分别看一下具体的场景(摘抄自<大型网站系统与Java中间件实践>) 案例一:其中foo1和foo2是SynchronizedDemo1类的两个静态方法.在不同的线程中,这两个方法的调用是互斥的,不仅是它们之间,任何两个不同线程的调用也互斥. public class SynchronizedDemo1 { public synchronized static void foo1(){} pu

Java并发之synchronized关键字和Lock接口

欢迎点赞阅读,一同学习交流,有疑问请留言 . GitHub上也有开源 JavaHouse,欢迎star 引用 当开发过程中,我们遇到并发问题.怎么解决? 一种解决方式,简单粗暴:上锁.将千军万马都给拦下来,只允许一个人过独木桥.书面意思就是将并行的程序变成串行的程序.现实的锁有门锁.挂锁和抽屉锁等等.在Java中,我们的锁就是synchronized关键字和Lock接口. synchronized关键字 synchronized也叫同步锁,是Java里面的关键字.我们可以猜测到synchroni

CoreJava_线程并发(阻塞队列):在某个文件夹下搜索含有某关键字的文件

Java多线程编程是非常考验一个程序员水平的.传统的WEB程序中,因为框架提供了太多的健壮性.并发性.可靠性的支持,所以我们都是将所有的注意力放到了业务实现上.我们只是按照业务逻辑的要求,不停的积累自己的代码.因为知识,或者是经验的限制,经常出现了问题而不自知.例如,某些比较原始的项目中,并没有使用Spring等相对来说比较灵活健壮的框架.而是仅仅使用Servlet来作为服务端的实现方式. 举一个简单的栗子,众所周知,当请求到了容器,容器是创建并且启动了一个Servlet线程来对当前的请求作出相