多线程的使用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();
        }
    }

    

    

原文地址:https://www.cnblogs.com/lifeone/p/7878455.html