zoukankan      html  css  js  c++  java
  • 多线程的使用02

    1 synchronized

      1.1 同步方法获取的锁都是对象锁,而不是把一段代码和方法当作锁(等同于在方法中添加同步代码块synchronized(this){})

        那个线程先执行带有synchronized的方法,那个线程就拥有该方法所属对象的锁,其他线程只能等待。

        前提:多个线程访问同一个对象

    public synchronized void setAndShowAge(String username) throws 
    }
            
    public void setAndShowAge(String username) throws InterruptedException {
      synchronized (this) {   
      }         
    }

      1.2 A线程持有object对象的Lock锁,B线程可以以异步方式调用object对象中的非synchronized类型的方法,但如果要调用object对象的同步方法则需要等待。

      1.3 当一个线程持有object对象锁后,再次请求此对象锁时可以再次得到该对象锁(锁重入机制,也支持父子类继承的环境中)

      1.4 当一个线程执行出现异常时,会释放持有的锁。

      1.5 在静态方法上添加synchronized,表示对当前的.java文件对应的class类加锁

        静态同步方法和非静态同步方法持有不同的锁,前者是类锁,后者是对象锁。

    2 ReentrantLock

      2.1 ReentrantLock持有的锁是对象锁,但与Synchronized持有的对象锁不是同一个,需要在finally中进行手动unlock

      2.2 ReentrantLock与Synchronized的区别

        2.2.1 Synchronized是基于JVM层面实现的,而ReentrantLock是基于JDK层面实现的

        2.2.2 ReentrantLock在性能方面更全面,包括时间锁等待,可中断锁等待,而且一个锁可以有多个Condition,可以实现多路通知

        2.2.3 ReentrantLock性能比Synchronized好

        2.2.4 ReentrantLock提供了可轮询的锁请求,可以有效避免死锁的现象

    private static void reentrantLockDeadLock() {
            Lock lock_a=new ReentrantLock();
            Lock lock_b=new ReentrantLock();
            new Thread(()->{
                //尝试得到锁,如果尝试次数超过了指定的值还没有得到锁,就会抛出错误,该线程释放锁
           //tryLock()只探测锁是否,并没有lock()的功能,要获取锁,还得调用lock()方法
    /** * 源码中这样写道: * else if (current == getExclusiveOwnerThread()) { * int nextc = c + acquires; * if (nextc < 0) // overflow * throw new Error("Maximum lock count exceeded"); * setState(nextc); * return true; * } */ if (lock_a.tryLock()) { try {
                lock_a.lock(); System.out.println(
    "线程"+Thread.currentThread().getName()+"获取到a锁,正在等待b锁"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } if (lock_b.tryLock()) { try {
                     lock_b.lock(); System.out.println(
    "线程"+Thread.currentThread().getName()+"获取到b锁"); } finally { lock_b.unlock(); } } }finally { lock_a.unlock(); } } }).start(); new Thread(()->{ if (lock_b.tryLock()) { try {
                lock_b.lock(); System.out.println(
    "线程"+Thread.currentThread().getName()+"获取到b锁,正在等待a锁"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } if (lock_a.tryLock()) { try {
                     lock_a.lock(); System.out.println(
    "线程"+Thread.currentThread().getName()+"获取到a锁"); } finally { lock_a.unlock(); } } }finally { lock_b.unlock(); } } }).start(); }

      2.3 是否要使用ReentrantLock代替Synchronized(不可以)

        2.3.1 Synchronized代码块或者方法结束时可以自动释放锁,可以有效避免因忘记手动调用ReentrantLock的unlock()方法产生的死锁

        2.3.2 当JVM调用Synchronized管理和释放锁是会打印相应的堆栈信息,对于调试和异常处理非常有用;而Lock是普通的类,JVM不知道是那个线程调用Lock对象。

      2.4 什么时候使用ReentrantLock代替Synchronized

        只有在Synchronized没有的特性情况下才考虑使用ReentrantLock

      2.5 ReentrantLock公平锁和非公平锁

        ReentrantLock构造方法中可以传递参数,默认为false,表示非公平锁,否则为公平锁

        公平锁:哪个线程先启动,那个线程就优先获得锁

        非公平锁:采用抢占机制,各个线程随机获得锁

    3 读锁(乐观锁)和写锁(排他锁)

      多个线程可以同时进行读操作,但同一时刻只允许一个写操作;且读和写也是排斥的

    4 BlockingQueue 阻塞队列

      4.1 插入方法

        add() 如果有足够的空间返回true,否则抛出异常

        offer() 如果有足够的空间返回true,否则返回false

        put() 如果有足够的空间就插入值,没有返回值,否则将等待直到有了足够的空间

      4.2 移除方法

        take() 移除头部的元素,如果没有一直等待

        poll(long timeout, TimeUnit unit)  移除头部元素,等待指定的时间如果还没有就返回null

        remove(Object o) 移除o.equals(e)的元素,存在返回true,不存在返回false

        drainTo(Collection<? super E> c) 可以一次读取指定的个数到集合中,可以提高效率,不需要多次分批加锁和解锁

    /**
         * 
         * blockQueue
         * 
         * @Description 基于阻塞队列实现的生产者和消费者模式
         * @return void 
         * @see
         * @since
         */
        private static void blockQueue() {
            
            //阻塞队列
            BlockingDeque<Integer> blockingDeque=new LinkedBlockingDeque<>(5);
            
            //模拟三个生产者同时生产
            for(int i=0;i<3;i++){
                Thread producter=new Thread(()->{
                    while (true) {
                        int product=new Random().nextInt(10);
                        try {
                            //waiting if necessary for space to become available.
                            blockingDeque.put(product);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        System.out.println("生产者"+Thread.currentThread().getName()+"生产了"+product+",size="+blockingDeque.size()+","+blockingDeque.toString());
                        try {
                            Thread.sleep(1000);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
                producter.setName("producter"+(i+1));
                producter.start();
            }
            
            //模拟10个消费者同时消费
            for(int i=0;i<10;i++){
                Thread consumer=new Thread(()->{
                    while (true) {
                        Integer product;
                        try {
                            //waiting if necessary until an element becomes available
                            product = blockingDeque.take();
                            //List<Integer> products=new ArrayList<>();
                            //可以一次读取队列中的多个元素,但不会阻塞
                            //blockingDeque.drainTo(products,3);
                            System.out.println("消费者"+Thread.currentThread().getName()+"消费了"+product);
                            Thread.sleep(3000);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
                consumer.setName("consumer"+(i+1));
                consumer.start();
            }
            
        }

      4.3 ArrayBlockingQueue和LinkedBlockingQueue的区别

        4.3.1 ArrayBlockingQueue中的锁是没有分离的,即生产者和消费者用的是同一个锁

           LinkedBlockingQueue中的锁是分离的,即生产者用的是putLock,消费者用的是takeLock

        4.3.2 ArrayBlockingQueue基于数组,直接将枚举对象插入或移除,不产生额外的对象实例

          LinkedBlockingQueeu基于链表,需要将枚举转换为Node<E>进行插入和删除,会产生额外的Node对象,会有gc影响

        4.3.3 ArrayBlockingQueue是有界的,必须指定大小

          LinkedBlockingQueeu是无界的,可以不指定大小,默认为Integer.MAX_VALUE

      4.4 SynchronousQueue是一种没有缓冲区的阻塞队列,每一个插入操作必须等待一个线程对应的移除操作

    5 线程组

      5.1 线程组的作用:批量管理线程或线程组对象,有效地对线程或线程组进行组织

      5.2 线程必须启动后才能归属到指定的线程组中

      5.3 如果线程没有指定线程组,会自动归属到当前线程所属的线程组中

      5.4 根线程组就是系统线程组system

    6 中断机制

      6.1 中断机制是一种协作机制,只是设置中断标志,发出中断请求,并不会真正的终止线程,什么时候中断需要被中断的线程自己处理。

      6.2 interrupt() 仅仅设置中断标识,具体有JVM虚拟机实现

         interrupted() 测试当前线程是否已经中断,是否清除中断标识符由参数决定,静态方法,获取的是当前线程的中断状态

         isInterrupted() 测试当前线程是否已经中断,不清楚标识符,实例方法,测试的是调用该方法的线程锁表示的线程

    public static boolean interrupted() {
      return currentThread().isInterrupted(true);
    }
    Thread thread=new Thread();
    thread.start();
    Thread.currentThread().interrupt();
    System.out.println("interrupted:"+thread.interrupted()); //判断的是当前主线程的状态:interrupted:true
    System.out.println("isInterrupted:"+thread.isInterrupted()); //判断thread子线程的状态:isInterrupted:false

     7 Semaphore

      并发控制器,可以同时提供的信号量(同时运行的线程,如果一个线程只需要一个信号量)

      每个线程运行前需要从semaphore中请求信号量,运行结束后进行释放

      如果semaphore中暂时足够的信号量,该线程进入等待状态

    private static void semaphoreUse() {
            //定义并发管理器,同时最大运行5个信号量
            final Semaphore semaphore=new Semaphore(5);
            //同时启动了十个线程,但只能同时运行5个
            for(int i=0;i<10;i++){
                new Thread(()->{
                    try {
                        try {
                            //可以一下请求多个信号量
                            //semaphore.acquire(2);
                            semaphore.acquire();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        System.out.println("线程"+Thread.currentThread().getName()+"获取到了信号");
                        try {
                            Thread.sleep(2000);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        System.out.println("线程"+Thread.currentThread().getName()+"释放了信号");
                    } finally {
                        //可以一下释放多个信号量
                        //semaphore.release(2);
                        semaphore.release();
                    }
                }).start();
            }
        }

    8 Exchanger两个线程间的数据交换

    private static void exchangerUse() {
            
            //用于两个线程间的数据交换
            final Exchanger<String>  exchanger=new Exchanger<>();
            
            for(int i=0;i<2;i++){
                new Thread(()->{
                    String threadName=Thread.currentThread().getName();
                    System.out.println("线程"+threadName+",原数据为:"+threadName);
                    try {
                        //线程会在这里进行阻塞,直到两个线程都运行到这里,线程会被唤醒,执行执行以下代码
                        threadName=exchanger.exchange(threadName);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程"+Thread.currentThread().getName()+",交换后数据为:"+threadName);
                }).start();
            }
        }

        

        

  • 相关阅读:
    输入和输出

    4. 深入 Python 流程控制
    js下拉框选择图片
    按下enter触发事件
    js多种方法取数组的最后一个元素
    apply,call,bind函数作用与用法
    vue中的js绑定样式
    js添加删除class
    rem等比例自适应手机尺寸
  • 原文地址:https://www.cnblogs.com/lifeone/p/7878455.html
Copyright © 2011-2022 走看看