多线程---详解各种锁和AQS原理

多线程系列(十八) -AQS原理浅析 在之前的文章中,我们介绍了 ReentrantLock、ReadWriteLock、CountDownLatch、CyclicBarrier、Semaphore、ThreadPoolExecutor 等并发工具类的使用方式,它们在请求共享资源的时候,都能实现线程同步的效果。 阅读详情

1. synchronized

详细信息参考下方文章

https://blog.youkuaiyun.com/A_Java_Dog/article/details/118679431

  • synchronized(Object)

    Object不能是String , Integer, Long , 因为字符串最终都是基于一个字符串常量池中的数据。

  • 线程同步

    synchronized

    • 锁的是对象, 是不是代码

    • synchronized void methodName 锁定的是this(当前对象)
      synchronized static void m静态方法锁定的是XXX.class

      public class T {
      
          private int count = 10;
      
          public synchronized void m() { //等同于在方法的代码执行时要synchronized(this)
              count--;
              System.out.println(Thread.currentThread().getName() + " count = " + count);
          }
      }
      
      private static int count = 10;
      
      //因为这个m是静态方法, 所以这里等同于synchronized(T.class)
      public synchronized static void m() { 
          count--;
          System.out.println(Thread.currentThread().getName() + " count = " + count);
      }
      
    • 锁定方法和非锁定方法同步执行

        public static void main(String[] args) {
            T t = new T();
            //m1是同步方法, m2是非同步方法, 如果m1和m2不能同时调用, 则肯定会在m1执行完成后,
            //也就是输出m1 end后, 才会执行m2
            //结果显示: m1开始执行后, m2立刻开始执行, 也就是两者执行不受影响
            new Thread(t::m1, "t1").start();
            new Thread(t::m2, "t2").start();
        }
      
        public synchronized void m1() {
            System.out.println(Thread.currentThread().getName() + " m1 start...");
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " m1 end");
        }
      
        public void m2() {
            System.out.println(Thread.currentThread().getName() + " m2 start...");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " m2 end");
        }
      
    • 锁升级- 偏向锁, 自旋锁, 重量级锁

    • 使用条件:1. 执行时间短(加锁代码),线程数少,用自旋

      ​ 2. 执行时间长,线程数多,用重量级锁(系统锁)(系统锁有等待队列, 不占用CPU资源)

      ​ 3. 操作消耗时间长->使用重量级锁

可重入锁, 非公平锁, 悲观锁

可重入

同一个线程

synchronized代码中 , 同一个线程可再次调用其他synchronized修饰的方法,如下例所示

public class T01_ReentrantLock1 {
   synchronized void m1() {
      for(int i=0; i<10; i++) {
         try {
            TimeUnit.SECONDS.sleep(1);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         System.out.println(i);
         if(i == 2) {
             //如果synchronized锁不可重入, 则这里将会阻塞住, 因为当前线程执行的m1方法持有该对象的锁,
            this.m2();
             //这里输出m2的结果, 证明同一个线程下synchronized锁可重入
         }
      }
      
   }
   
   synchronized void m2() {
      System.out.println("m2 ...");
   }
   
   public static void main(String[] args) {
      T01_ReentrantLock1 rl = new T01_ReentrantLock1();
      new Thread(rl::m1).start();
      try {
         TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
}

执行结果

在这里插入图片描述

不同线程

/**
 * 本例中由于m1锁定this,只有m1执行完毕的时候,m2才能执行
 * @author cyc
 */
package com.cyc.juc.c_020_lock;

import java.util.concurrent.TimeUnit;

public class T01_ReentrantLock1 {
   synchronized void m1() {
      for(int i=0; i<10; i++) {
         try {
            TimeUnit.SECONDS.sleep(1);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         System.out.println(i);
      }
      
   }
   
   synchronized void m2() {
      System.out.println("m2 ...");
   }
   
   public static void main(String[] args) {
      T01_ReentrantLock1 rl = new T01_ReentrantLock1();
      new Thread(rl::m1).start();
      try {
         TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
       //由于m1锁定this,只有m1执行完毕的时候,m2才能执行
      new Thread(rl::m2).start();
   }
}

执行结果

在这里插入图片描述

2. volatile

保证线程可见性

  • MESI
  • 缓存一致性协议

见上方 线程三大特性-可见性解析

禁止指令重排序

使用以下单例模式, 懒汉式的双重检查为例

package com.cyc.design.singleton;

/**
 * @author chenyunchang
 * @version 1.0
 * lazy loading
 * 也称懒汉式
 * 虽然达到了按需初始化的目的,但却带来线程不安全的问题
 * 可以通过synchronized解决,但也带来效率下降
 */
public class Singleton06 {

    //volatile关键字禁止指令重排
    private static volatile Singleton06 INSTANCE;


    public void c() {
        System.out.println("C");
    }

    /**
     * 构造方法为私有,只能在当前类中new,外部类无法new出来
     */
    private Singleton06() {
    }

    public static Singleton06 getInstance() {
        if (INSTANCE == null) {
            //双重检查
            synchronized (Singleton06.class) {
                if (INSTANCE == null) {
                    try {
                        //这里让进入此代码块的线程睡一毫秒
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new Singleton06();
                }
            }
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(() ->
                    System.out.println(Singleton06.getInstance().hashCode())
            ).start();
        }
    }

}

查看结果

在这里插入图片描述

为什么要加volatile?

为了防止指令重排。这里涉及到类的加载过程。

首先,第一个线程进来了, 加上锁之后,进入到INSTANCE = new Singleton06();代码,在初始化进行到一半的时候,也就是在preparation阶段,已经给Singleton06申请完内存,里面的成员变量已经赋过默认值,比如0,此时INSTANCE 已经指向这个分配的内存, 已经不再是null,此时另外一个线程进来了,由于此时INSTANCE 已经进行了半初始化状态,所以在if (INSTANCE == null)为false,此时另一个线程会拿到这个INSTANCE中的成员变量进行操作, 这样显然是不满足要求的。

想要解析这个问题, 需要查看其字节码文件

例如下面这个测试类T, 使用idea插件查看其字节码文件

在这里插入图片描述

在0 new #2 <com/cyc/jvm/c0_basic/T>之后,已经申请过内存。

4 invokespecial #3 <com/cyc/jvm/c0_basic/T.> 这个给类中的静态变量赋初始值

在调用完4之后,才会把这块内存赋值给t,但是由于指令可能会重排的原因, 如果先执行的是7 astore_1, 相当于先把这个地址扔到内存中, 然后在进行的T初始化, 这种情况下,在双重检查懒汉式单例中,就会出现有别的线程读取到半初始化的单例。

  • DCL单例
  • Double Check Lock

3. 锁优化

锁细化

 /**
 * synchronized优化
 * 同步代码块中的语句越少越好
 * 比较m1和m2
 * @author cyc
 */
package com.cyc.juc.c_016_LockOptimization;
import java.util.concurrent.TimeUnit;

public class FineCoarseLock {
   
   int count = 0;

   synchronized void m1() {
      //do sth need not sync
      try {
         TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      //业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
      count ++;
      
      //do sth need not sync
      try {
         TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
   
   void m2() {
      //do sth need not sync
      try {
         TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      //业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
      //采用细粒度的锁,可以使线程争用时间变短,从而提高效率
      synchronized(this) {
         count ++;
      }
      //do sth need not sync
      try {
         TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }

   

}

锁粗化

当一个方法中有很多代码块都加锁时 , 不如直接将锁加在方法上

锁对象

/**
 * 锁定某对象o,如果o的属性发生改变,不影响锁的使用
 * 但是如果o变成另外一个对象,则锁定的对象发生改变
 * 应该避免将锁定对象的引用变成另外的对象或者将对象设为final类型
 * @author cyc
 */
package com.cyc.juc.c_017_MoreAboutSync;

import java.util.concurrent.TimeUnit;


public class SyncSameObject {

   /*final*/ Object o = new Object();

   void m() {
      synchronized(o) {
         while(true) {
            try {
               TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName());
            
            
         }
      }
   }
   
   public static void main(String[] args) {
      SyncSameObject t = new SyncSameObject();
      //启动第一个线程
      new Thread(t::m, "t1").start();
      
      try {
         TimeUnit.SECONDS.sleep(3);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      //创建第二个线程
      Thread t2 = new Thread(t::m, "t2");
      
      t.o = new Object(); //锁对象发生改变,所以t2线程得以执行,如果注释掉这句话,线程2将永远得不到执行机会
      
      t2.start();
      
   }

   

}

4. CAS(无锁优化, 自旋)

在java.util.concurrent包下, Atomic开头的都是通过CAS来保证线程安全的类

https://blog.youkuaiyun.com/A_Java_Dog/article/details/118679431

cas(期望值,更新值)

m=0

m++

expected = read m;

cas(0,1){
for(;😉//如果当前m值==0 m=1

}

5. JUC同步锁

0. 前置知识(AQS)

AQS维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。这里volatile是核心关键词,保证线程之间的可见性和有序性。state的访问方式有三种:

  • getState()
  • setState()
  • compareAndSetState()

AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。

不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:

  • isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
  • tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
  • tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
  • tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
  • tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。

再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。

一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。

1. ReentrantLock(可重入锁, 排他锁)

源码解析

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

acquire(int)

此方法是独占模式下线程获取共享资源的顶层入口。如果获取到资源,线程直接返回,否则进入等待队列,直到获取到资源为止,且整个过程忽略中断的影响。

  1. tryAcquire()尝试直接去获取资源,如果成功则直接返回(这里体现了非公平锁,每个线程获取锁时会尝试直接抢占加塞一次,而CLH队列中可能还有别的线程在等待);
  2. addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;
  3. acquireQueued()使线程阻塞在等待队列中获取资源,一直获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
  4. 如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。

ReentrantLock属于JUC自带的锁, CAS类型,需要自己手动解锁,加锁一定要写在try finally中,保证在最终一定能解锁, 而synchronized属于自动上锁,自动释放锁。

synchronized代码块字节码展示:

在这里插入图片描述

ReentrantLock代码块字节码展示:

在这里插入图片描述

同一个线程

/**
 * reentrantlock用于替代synchronized
 * 由于m1锁定this,只有m1执行完毕的时候,m2才能执行
 * 使用reentrantlock可以完成同样的功能
 * 需要注意的是,必须要必须要必须要手动释放锁(重要的事情说三遍)
 * 使用syn锁定的话如果遇到异常,jvm会自动释放锁,但是lock必须手动释放锁,因此经常在finally中进行锁的释放
 * @author cyc
 */
public class T02_ReentrantLock2 {
    Lock lock = new ReentrantLock();

    void m1() {
        try {
            lock.lock(); //synchronized(this)
            for (int i = 0; i < 10; i++) {
                TimeUnit.SECONDS.sleep(1);

                System.out.println(i);
                if (i == 2) {
                   this.m2();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    void m2() {
        try {
            lock.lock();
            System.out.println("m2 ...");
        } finally {
            lock.unlock();
        }

    }

    public static void main(String[] args) {
        T02_ReentrantLock2 rl = new T02_ReentrantLock2();
        new Thread(rl::m1).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

不同线程

public class T02_ReentrantLock2 {
    Lock lock = new ReentrantLock();

    void m1() {
        try {
            lock.lock(); //synchronized(this)
            for (int i = 0; i < 10; i++) {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    void m2() {
        try {
            lock.lock();
            System.out.println("m2 ...");
        } finally {
            lock.unlock();
        }

    }

    public static void main(String[] args) {
        T02_ReentrantLock2 rl = new T02_ReentrantLock2();
        new Thread(rl::m1).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(rl::m2).start();
    }
}

tryLock

m1执行10秒钟, 在m1执行期间, m2去尝试在5秒钟获取锁,如果获取不到则继续执行下面的方法,输出m2 …false。

/**
 * 使用reentrantlock可以进行“尝试锁定”tryLock,这样无法锁定,或者在指定时间内无法锁定,线程可以决定是否继续等待
 * @author cyc
 */
package com.cyc.juc.c_020_lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class T03_ReentrantLock3 {
   Lock lock = new ReentrantLock();

   void m1() {
      try {
         lock.lock();
         for (int i = 0; i < 10; i++) {
            TimeUnit.SECONDS.sleep(1);

            System.out.println(i);
         }
      } catch (InterruptedException e) {
         e.printStackTrace();
      } finally {
         lock.unlock();
      }
   }

   /**
    * 使用tryLock进行尝试锁定,不管锁定与否,方法都将继续执行
    * 可以根据tryLock的返回值来判定是否锁定
    * 也可以指定tryLock的时间,由于tryLock(time)抛出异常,所以要注意unclock的处理,必须放到finally中
    */
   void m2() {
      /*
      boolean locked = lock.tryLock();
      System.out.println("m2 ..." + locked);
      if(locked) lock.unlock();
      */
      
      boolean locked = false;
      
      try {
         // 在5秒内尝试获取锁, 如果获取不到, 则放弃去获取
         locked = lock.tryLock(5, TimeUnit.SECONDS);
         System.out.println("m2 ..." + locked);
      } catch (InterruptedException e) {
         e.printStackTrace();
      } finally {
         if(locked) lock.unlock();
      }
      
   }

   public static void main(String[] args) {
      T03_ReentrantLock3 rl = new T03_ReentrantLock3();
      new Thread(rl::m1).start();
      try {
         TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      new Thread(rl::m2).start();
   }
}

执行结果

在这里插入图片描述

将m1方法改为休眠3秒钟,

   for (int i = 0; i < 3; i++) {
            TimeUnit.SECONDS.sleep(1);

            System.out.println(i);
         }

输出结果m2 …true

lockInterruptibly()

/**
 * 使用ReentrantLock还可以调用lockInterruptibly方法,可以对线程interrupt方法做出响应,
 * 在一个线程等待锁的过程中,可以被打断
 * @author cyc
 */
public class T04_ReentrantLock4 {
		
	public static void main(String[] args) {
		Lock lock = new ReentrantLock();
		
		
		Thread t1 = new Thread(()->{
			try {
				lock.lock();
				System.out.println("t1 start");
				TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
				System.out.println("t1 end");
			} catch (InterruptedException e) {
				System.out.println("interrupted!");
			} finally {
				lock.unlock();
			}
		});
		t1.start();
		
		Thread t2 = new Thread(()->{
			try {
				//lock.lock();
				lock.lockInterruptibly(); //可以对interrupt()方法做出响应
				System.out.println("t2 start");
				TimeUnit.SECONDS.sleep(5);
				System.out.println("t2 end");
			} catch (InterruptedException e) {
				System.out.println("t2 interrupted!");
			} finally {
				lock.unlock();
			}
		});
		t2.start();
		
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t2.interrupt(); //打断线程2的等待
		
	}
}

  • 输出结果

在这里插入图片描述

公平锁

new ReentrantLock(true)为新建一个公平锁,所谓公平锁, 即每个线程获取锁之前, 都要去锁的等待队列里去查看有无正在等待锁的线程, 如果有,则加入等待队列,按照先后顺序去获取锁。如果没有,则直接去拿锁。

new ReentrantLock()默认为非公平锁,忽视等待队列, 直接去争抢锁。

注: synchronized属于非公平锁

/**
 *
 * ReentrantLock还可以指定为公平锁
 *
 * @author cyc
 */
package com.cyc.juc.c_020_lock;

import java.util.concurrent.locks.ReentrantLock;

public class T05_ReentrantLock5 extends Thread {

    private static ReentrantLock lock = new ReentrantLock(true); //参数为true表示为公平锁(默认为false),请对比输出结果

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获得锁");
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        T05_ReentrantLock5 rl = new T05_ReentrantLock5();
        Thread th1 = new Thread(rl);
        Thread th2 = new Thread(rl);
        th1.start();
        th2.start();
    }
}
  • 输出结果

在这里插入图片描述

2. ReadWriteLock(读写锁)

/**
 * 读写锁(共享锁+排他锁), 读的时候是共享, 写的时候是排他
 * @author fei
 */
public class T10_TestReadWriteLock {
    static Lock lock = new ReentrantLock();
    private static int value;

    static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    static Lock readLock = readWriteLock.readLock();
    static Lock writeLock = readWriteLock.writeLock();

    public static void read(Lock lock) {
        try {
            lock.lock();
            Thread.sleep(1000);
            System.out.println("read over!");
            //模拟读取操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void write(Lock lock, int v) {
        try {
            lock.lock();
            Thread.sleep(1000);
            value = v;
            System.out.println("write over!");
            //模拟写操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }



    public static void main(String[] args) {
        //排它锁
//        Runnable readR = ()-> read(lock);
        //读写锁-读, 使用读写锁, 多个线程可同时读取同一个数据, 无需等待
        Runnable readR = ()-> read(readLock);

//        Runnable writeR = ()->write(lock, new Random().nextInt());
        //读写锁-写, 使用读写锁, 写的时候, 其他线程需要等待该线程之后完毕之后, 才可以进行读写
        Runnable writeR = ()->write(writeLock, new Random().nextInt());

        for(int i=0; i<18; i++) new Thread(readR).start();
        for(int i=0; i<2; i++) new Thread(writeR).start();


    }
}

3. LangAdder(分段锁)

/**
 * 几种锁的执行效率测试
 * count1,count2, count3分别使用不同的方式来实现递增
 */
public class T02_AtomicVsSyncVsLongAdder {
    static long count2 = 0L;
    static AtomicLong count1 = new AtomicLong(0L);
    //LongAdder内部做了一种类似分段锁(分段锁也是CAS操作)的操作, 例如本例中, 1000个线程可能会分为多个组,
    // 每组单独计算, 计算出的值最后累加一下, 得出总计值, LongAdder在线程特别多的时候, 优势比较明显
    static LongAdder count3 = new LongAdder();

    public static void main(String[] args) throws Exception {
        Thread[] threads = new Thread[1000];

        for(int i=0; i<threads.length; i++) {
            threads[i] =
                    new Thread(()-> {
                        for(int k=0; k<100000; k++) {
                            count1.incrementAndGet();
                        }
                    });
        }

        long start = System.currentTimeMillis();

        for(Thread t : threads ) {
            t.start();
        }

        for (Thread t : threads) {
            t.join();
        }

        long end = System.currentTimeMillis();

        //TimeUnit.SECONDS.sleep(10);

        System.out.println("Atomic: " + count1.get() + " time " + (end-start));
        //-----------------------------------------------------------
        Object lock = new Object();

        for(int i=0; i<threads.length; i++) {
            threads[i] =
                new Thread(() -> {
                    for (int k = 0; k < 100000; k++) {
                        synchronized (lock) {
                            count2++;
                        }
                    }
                });
        }

        start = System.currentTimeMillis();

        for(Thread t : threads ) {
            t.start();
        }

        for (Thread t : threads) {
            t.join();
        }

        end = System.currentTimeMillis();


        System.out.println("Sync: " + count2 + " time " + (end-start));


        //----------------------------------
        for(int i=0; i<threads.length; i++) {
            threads[i] =
                    new Thread(()-> {
                        for(int k=0; k<100000; k++) {
                            count3.increment();
                        }
                    });
        }

        start = System.currentTimeMillis();

        for(Thread t : threads ) {
            t.start();
        }

        for (Thread t : threads) {
            t.join();
        }

        end = System.currentTimeMillis();

        //TimeUnit.SECONDS.sleep(10);

        System.out.println("LongAdder: " + count1.longValue() + " time " + (end-start));
    }
}

4. CountDownLatch(倒数门栓)

倒数门栓

/**
 * CountDown 倒数, Latch门栓, CountDownLatch倒数几个数打开门栓
 * 常用在控制线程上, 可以控制线程执行结束后, 才开始执行下面的代码
 *
 * @author cyc
 * @date 2021-08-07 16:03:42
 */
public class T06_TestCountDownLatch {
    public static void main(String[] args) {
        usingJoin();
        //使用CountDownLatch比join更加灵活
        usingCountDownLatch();
    }

    private static void usingCountDownLatch() {
        Thread[] threads = new Thread[100];
        CountDownLatch latch = new CountDownLatch(threads.length);

        for(int i=0; i<threads.length; i++) {
            threads[i] = new Thread(()->{
                //下面的threads[i].start()执行过后才开始执行以下代码,
                //每执行一次, latch.countDown()会相应减1, 当减为0时,
                // latch.await(),才会执行
                int result = 0;
                for(int j=0; j<10000; j++) {
                    result += j;
                }
                latch.countDown();
            });
        }

        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }

        try {
            System.out.println("latch await 等待执行");
            //这里会阻塞住主线程, 只有当latch减为0的时候, 才会继续执行下面的代码
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("end latch");
    }

    private static void usingJoin() {
        Thread[] threads = new Thread[100];

        for(int i=0; i<threads.length; i++) {
            threads[i] = new Thread(()->{
                int result = 0;
                for(int j=0; j<10000; j++) {
                    result += j;
                }
            });
        }

        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }

        for (int i = 0; i < threads.length; i++) {
            try {
                //每一个线程都合并到主线程上, 主线程要等所有线程执行结束之后,
                // 才开始继续执行下面的代码(准备发车, 坐满就走)
                threads[i].join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("end join");
    }
}

  • 执行结果

在这里插入图片描述

5. CyclicBarrier(循环栅栏)

循环栅栏

package com.cyc.juc.c_020_lock;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * Cyclic循环的, Barrier栅栏, CyclicBarrier循环的栅栏
 * @author cyc
 * @date 2021-08-07 16:35:30
 */
public class T07_TestCyclicBarrier {
    public static void main(String[] args) {
        //CyclicBarrier barrier = new CyclicBarrier(20);	

        CyclicBarrier barrier = new CyclicBarrier(20, () -> System.out.println("满人"));

        for(int i=0; i<100; i++) {

                new Thread(()->{
                    try {
                        barrier.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }).start();
            
        }
    }
}
  • 执行结果

在这里插入图片描述

  • 观察源码

在这里插入图片描述
在这里插入图片描述

点击barrier.await()方法进入发现,当执行的线程数量达到new CyclicBarrier(n)中的n时, 重新生成一次新的循环栅栏, 通过观察结果可知, n值是20, 执行的线程数量为100, 所以输出了5个CyclicBarrier中run方法的执行结果

应用场景

  • 复杂操作和并发执行

    ​ 例如有三个线程A,B,C , 分别去执行访问数据库, 访问网络和读取文件, 此时 , 就可以使用CyclicBarrier, 他可以在所有线程都执行完毕之后, 才去执行主流程.

    ​

6. CountDownLatch和CyclicBarrier的区别

CountDownLatch是设定一个值, 每次countDown以下, 直到等于0, 但是并不一定要在每一个线程都countDown一下, 在一个线程里CountDown也可以

就比如这样写, 依然可以

        Thread t = new Thread(() -> {
            for (int i = 0; i < threads.length; i++) {
                latch.countDown();
            }
        });
        t.start();

而CyclicBarrier就不一样了, 他必须在每个线程里调用await()方法, 才可以

测试一下

for (int i = 0; i < 100; i++) {
    try {
        System.out.println("等待中");
        barrier.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        e.printStackTrace();
    }
}

只会输出一次 等待中, 然后就堵塞在那里了。

7. Semaphore

常用在限流上,

例如车道和收费站 , 收费站只有两个, 车道可能有多个

package com.cyc.juc.c_020_lock;

import java.util.concurrent.Semaphore;

/**
 * Semaphore 信号灯, Semaphore(n)最多允许n个线程同时运行
 * 常用在限流上
 * @author fei
 */
public class T11_TestSemaphore {
    public static void main(String[] args) {
        //允许两个线程同时执行, 没有先后顺序
        Semaphore s = new Semaphore(2);
        //允许两个线程同时执行, 公平锁,内部有等待队列,并按照先后顺序执行
//        Semaphore s = new Semaphore(2, true);
        //允许一个线程同时执行
        //Semaphore s = new Semaphore(1);

        new Thread(()->{
            try {
                //阻塞方法, 如果取不到 , 则阻塞在这里
                //这里取一下, new Semaphore(1)中的1就会变成0
                s.acquire();

                System.out.println("T1 running...");
                Thread.sleep(200);
                System.out.println("T1 running...");

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //将1加回去,即将线程加回去
                s.release();
            }
        }).start();

        new Thread(()->{
            try {
                s.acquire();

                System.out.println("T2 running...");
                Thread.sleep(200);
                System.out.println("T2 running...");

                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

8. LockSupport

/**
 * LockSupport 可以叫醒指定线程, unPark可以先与park之前调用
 */
public class T13_TestLockSupport {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(i);
                if(i == 5) {
                    //阻塞当前线程, 需要手动唤醒
                    LockSupport.park();
                }
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t.start();

        try {
            TimeUnit.SECONDS.sleep(8);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("after 8 senconds!");

        LockSupport.unpark(t);
    }
}

  • 执行结果

在这里插入图片描述

6. 测试题

线程同步

实现一个容器,提供两个方法,add,size
写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束

方法一 普通判断

线程不同步
public class T01_WithoutVolatile {

	List lists = new ArrayList();

	public void add(Object o) {
		lists.add(o);
	}

	public int size() {
		return lists.size();
	}
	
	public static void main(String[] args) {
		T01_WithoutVolatile c = new T01_WithoutVolatile();

		new Thread(() -> {
			for(int i=0; i<10; i++) {
				c.add(new Object());
				System.out.println("add " + i);
				
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "t1").start();
		
		new Thread(() -> {
			while(true) {
				if(c.size() == 5) {
					break;
				}
			}
			System.out.println("t2 结束");
		}, "t2").start();
	}
}

  • 输出结果
add 0
add 1
add 2
add 3
add 4
add 5
add 6
add 7
add 8
add 9

由于线程1给list加的值,并不同立即同步给线程2 , 所以线程2会无法结束

使用线程同步
/**
 * 给lists添加volatile之后,t2能够接到通知,但是,t2线程的死循环很浪费cpu,如果不用死循环,
 * 而且,如果在if 和 break之间被别的线程打断,得到的结果也不精确,
 * @author cyc
 */
public class T02_WithVolatile {

   //添加volatile,使t2能够得到通知
   //volatile List lists = new LinkedList();
   //同步容器
   volatile List lists = Collections.synchronizedList(new LinkedList<>());

   public void add(Object o) {
      lists.add(o);
   }

   public int size() {
      return lists.size();
   }

   public static void main(String[] args) {

      T02_WithVolatile c = new T02_WithVolatile();
      new Thread(() -> {
         for(int i=0; i<10; i++) {
            c.add(new Object());
            System.out.println("add " + i);
  
            try {
               TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
      }, "t1").start();
      
      new Thread(() -> {
         while(true) {
            if(c.size() == 5) {
               break;
            }
         }
         System.out.println("t2 结束");
      }, "t2").start();
   }
}
  • 输出结果

add 0
add 1
add 2
add 3
add 4
t2 结束
add 5
add 6
add 7
add 8
add 9

方法二 wait()和notify()

反例
/**
 * 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁
 * 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以
 * @author cyc
 */
package com.cyc.juc.c_020_01_Interview;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;


public class T03_NotifyHoldingLock { //wait notify

   //添加volatile,使t2能够得到通知
   volatile List lists = new ArrayList();

   public void add(Object o) {
      lists.add(o);
   }

   public int size() {
      return lists.size();
   }
   
   public static void main(String[] args) {
      T03_NotifyHoldingLock c = new T03_NotifyHoldingLock();
      
      final Object lock = new Object();
      
      new Thread(() -> {
         synchronized(lock) {
            System.out.println("t2启动");
            if(c.size() != 5) {
               try {
                  lock.wait();
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
            }
            System.out.println("t2 结束");
         }
         
      }, "t2").start();
      
      try {
         TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e1) {
         e1.printStackTrace();
      }

      new Thread(() -> {
         System.out.println("t1启动");
         synchronized(lock) {
            for(int i=0; i<10; i++) {
               c.add(new Object());
               System.out.println("add " + i);
               
               if(c.size() == 5) {
                  //notify()并不释放锁, 所以t1会等t2执行完之后才会执行
                  lock.notify();
               }
               
               try {
                  TimeUnit.SECONDS.sleep(1);
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
            }
         }
      }, "t1").start();
       
   }
}
优化方法
/**
 * notify之后,t1必须释放锁,t2退出后,也必须notify,通知t1继续执行
 * 整个通信过程比较繁琐
 * @author cyc
 */
package com.cyc.juc.c_020_01_Interview;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;


public class T04_NotifyFreeLock {

   //添加volatile,使t2能够得到通知
   volatile List lists = new ArrayList();

   public void add(Object o) {
      lists.add(o);
   }

   public int size() {
      return lists.size();
   }
   
   public static void main(String[] args) {
      T04_NotifyFreeLock c = new T04_NotifyFreeLock();
      
      final Object lock = new Object();
      
      new Thread(() -> {
         synchronized(lock) {
            System.out.println("t2启动");
            if(c.size() != 5) {
               try {
                  lock.wait();
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
            }
            System.out.println("t2 结束");
            //通知t1继续执行
            lock.notify();
         }
         
      }, "t2").start();
      
      try {
         TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e1) {
         e1.printStackTrace();
      }

      Thread t = new Thread(() -> {
         System.out.println("测试");
      });

      new Thread(() -> {
         System.out.println("t1启动");
         synchronized(lock) {
            for(int i=0; i<10; i++) {
               c.add(new Object());
               System.out.println("add " + i);
               
               if(c.size() == 5) {
                  lock.notify();
                  //释放锁,让t2得以执行
                  try {
                     lock.wait();
                  } catch (InterruptedException e) {
                     e.printStackTrace();
                  }
               }
               
               try {
                  TimeUnit.SECONDS.sleep(1);
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
            }
         }
      }, "t1").start();
      
      
   }
}

方法三 CountDownLatch()

一个countDownLatch
/**
 * @author cyc
 */
public class T05_CountDownLatch {

   // 添加volatile,使t2能够得到通知
   volatile List lists = new ArrayList();

   public void add(Object o) {
      lists.add(o);
   }

   public int size() {
      return lists.size();
   }

   public static void main(String[] args) {
      T05_CountDownLatch c = new T05_CountDownLatch();

      CountDownLatch latch = new CountDownLatch(1);

      new Thread(() -> {
         System.out.println("t2启动");
         if (c.size() != 5) {
            try {
               latch.await();
               
               //也可以指定等待时间
               //latch.await(5000, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
         System.out.println("t2 结束");

      }, "t2").start();

      try {
         TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e1) {
         e1.printStackTrace();
      }

      new Thread(() -> {
         System.out.println("t1启动");
         for (int i = 0; i < 10; i++) {
            c.add(new Object());
            System.out.println("add " + i);

            if (c.size() == 5) {
               // 打开门闩,让t2得以执行
               latch.countDown();
            }
         }

      }, "t1").start();

   }
}

执行结果

t2启动
t1启动
add 0
add 1
add 2
add 3
add 4
add 5
add 6
t2 结束
add 7
add 8
add 9

可以看到, 正常来说,应该在打印完add 4之后打印 t2结束,但是由于t1的latch.await();执行之后,打印t2 结束执行结束之前, t1的线程又执行了两遍,导致“t2技术”在i=6时才输出。

优化CountDownLatch

使用两个CountDownLatch

/**
 * countDownLatch优化
 * @author cyc
 */
package com.cyc.juc.c_020_01_Interview;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class T05_CountDownLatch_optimize {

	// 添加volatile,使t2能够得到通知
	volatile List lists = new ArrayList();

	public void add(Object o) {
		lists.add(o);
	}

	public int size() {
		return lists.size();
	}

	public static void main(String[] args) {
		T05_CountDownLatch_optimize c = new T05_CountDownLatch_optimize();

		CountDownLatch latch1 = new CountDownLatch(1);
		CountDownLatch latch2 = new CountDownLatch(1);

		new Thread(() -> {
			System.out.println("t2启动");
			if (c.size() != 5) {
				try {
					latch1.await();
					//也可以指定等待时间
					//latch1.await(5000, TimeUnit.MILLISECONDS);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("t2 结束");
            //在t2执行结束后, 再放开门栓
			latch2.countDown();

		}, "t2").start();

		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}

		new Thread(() -> {
			System.out.println("t1启动");
			for (int i = 0; i < 10; i++) {
				c.add(new Object());
				System.out.println("add " + i);

				if (c.size() == 5) {
					// 打开门闩,让t2得以执行
					latch1.countDown();
					try {
                        //latch1.countDown();执行后, 继续执行latch2.await();等待latch2归零
						latch2.await();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}

				/*try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}*/
			}

		}, "t1").start();

	}
}

方法四 LockSupport

使用lockSupport和上面的CountDownLatch会有同样的问题, 下面开始演示

普通方案
/**
 * @author cyc
 */
public class T06_LockSupport {

	// 添加volatile,使t2能够得到通知
	volatile List lists = new ArrayList();

	public void add(Object o) {
		lists.add(o);
	}

	public int size() {
		return lists.size();
	}

	public static void main(String[] args) {
		T06_LockSupport c = new T06_LockSupport();

		CountDownLatch latch = new CountDownLatch(1);

		Thread t2 = new Thread(() -> {
			System.out.println("t2启动");
			if (c.size() != 5) {

				LockSupport.park();

			}
			System.out.println("t2 结束");


		}, "t2");

		t2.start();

		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}

		new Thread(() -> {
			System.out.println("t1启动");
			for (int i = 0; i < 10; i++) {
				c.add(new Object());
				System.out.println("add " + i);

				if (c.size() == 5) {
					LockSupport.unpark(t2);
				}
			}

		}, "t1").start();

	}
}

同样会出现, t2结束的输出语句不一定在t1输出 add 4之后, 可能是在 add5, add6,…之后

优化方案 LockSupport
/**
 * @author cyc
 */
package com.cyc.juc.c_020_01_Interview;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.LockSupport;

public class T07_LockSupport_WithoutSleep {

	// 添加volatile,使t2能够得到通知
	volatile List lists = new ArrayList();

	public void add(Object o) {
		lists.add(o);
	}

	public int size() {
		return lists.size();
	}

	static Thread t1 = null, t2 = null;

	public static void main(String[] args) {
		T07_LockSupport_WithoutSleep c = new T07_LockSupport_WithoutSleep();

		t1 = new Thread(() -> {
			System.out.println("t1启动");
			for (int i = 0; i < 10; i++) {
				c.add(new Object());
				System.out.println("add " + i);

				if (c.size() == 5) {
					LockSupport.unpark(t2);
					LockSupport.park();
				}
			}
		}, "t1");

		t2 = new Thread(() -> {
			System.out.println("t2启动");
			LockSupport.park();
			System.out.println("t2 结束");
			LockSupport.unpark(t1);


		}, "t2");

		t2.start();
		t1.start();

	}
}

方法五 Semaphore

public class T08_Semaphore {
    // 添加volatile,使t2能够得到通知
    volatile List lists = new ArrayList();

    public void add(Object o) {
        lists.add(o);
    }

    public int size() {
        return lists.size();
    }

    static Thread t1 = null, t2 = null;

    public static void main(String[] args) {
        T08_Semaphore c = new T08_Semaphore();
        Semaphore s = new Semaphore(1);

        t1 = new Thread(() -> {
            try {
                s.acquire();
                for (int i = 0; i < 5; i++) {
                    c.add(new Object());
                    System.out.println("add " + i);
                }
                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            try {
                t2.start();
                //等t2执行完了之后, t1再继续执行
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            try {
                s.acquire();
                for (int i = 5; i < 10; i++) {
                    System.out.println("add " + i);
                }
                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }, "t1");

        t2 = new Thread(() -> {
            try {
                s.acquire();
                System.out.println("t2 结束");
                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2");
        t1.start();
    }
}

容器- 生产者消费者

* 面试题:写一个固定容量同步容器,拥有put和get方法,以及getCount方法,
* 能够支持2个生产者线程以及10个消费者线程的阻塞调用

1. 使用wait和notify/notifyAll来实现

/**
 * 写一个固定容量同步容器,拥有put和get方法,以及getCount方法,
 * 能够支持2个生产者线程以及10个消费者线程的阻塞调用
 * 
 * 使用wait和notify/notifyAll来实现
 * 
 * @author cyc
 */
public class MyContainer1<T> {
   final private LinkedList<T> lists = new LinkedList<>();
   final private int MAX = 10; //最多10个元素
   private int count = 0;


   /**
    * 生产
    * @param t
    */
   public synchronized void put(T t) {
      while(lists.size() == MAX) { //想想为什么用while而不是用if?,如果这里使用if,
         // 执行完this.wait()后, 就不会继续判断lists.size() == MAX,而是往下执行了。
         try {
            this.wait(); //effective java
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }

      lists.add(t);
      ++count;
      this.notifyAll(); //通知消费者线程进行消费
   }

   /**
    * 消费
    * @return
    */
   public synchronized T get() {
      T t = null;
      while(lists.size() == 0) {
         try {
            this.wait();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
      t = lists.removeFirst();
      count --;
      this.notifyAll(); //通知生产者进行生产
      return t;
   }

   public static void main(String[] args) {
      MyContainer1<String> c = new MyContainer1<>();
      //启动消费者线程
      for(int i=0; i<10; i++) {
         new Thread(()->{
            for(int j=0; j<5; j++) {
               System.out.println(c.get()+", count: "+ c.count);
            }
         }, "c" + i).start();
      }

      try {
         TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }

      //启动生产者线程
      for(int i=0; i<2; i++) {
         new Thread(()->{
            for(int j=0; j<25; j++) {
               c.put(Thread.currentThread().getName() + " " + j);
            }
         }, "p" + i).start();
      }
   }
}

输出结果

p1 0, count: 9
p1 5, count: 4
p1 6, count: 3
p1 7, count: 2
p1 8, count: 1
p1 4, count: 10
p1 9, count: 9
p0 0, count: 8
p0 1, count: 7

…

2. 使用lock.newCondition()

上面的代码有个问题, 那就是使用notifyAll时, 会通知所有线程, 而不是仅仅通知消费者线程, 接下来使用lock.newCondition()来进行优化,newCondition()即新建一个条件, 这个条件便是等待队列, 每次的通知也只是通知对应等待队列的线程

/**
 * 写一个固定容量同步容器,拥有put和get方法,以及getCount方法,
 * 能够支持2个生产者线程以及10个消费者线程的阻塞调用
 * 使用Lock和Condition来实现
 * 对比两种方式,Condition的方式可以更加精确的指定哪些线程被唤醒
 * 
 * @author cyc
 */
package com.cyc.juc.c_021_01_interview;

import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyContainer2<T> {
	final private LinkedList<T> lists = new LinkedList<>();
	final private int MAX = 10; //最多10个元素
	private int count = 0;
	
	private Lock lock = new ReentrantLock();
	//Condition本质上属于等待队列, new Condition()相当于新建了一个等待队列
	private Condition producer = lock.newCondition();
	private Condition consumer = lock.newCondition();
	
	public void put(T t) {
		try {
			lock.lock();
			while(lists.size() == MAX) { //想想为什么用while而不是用if?
				producer.await();
			}
			
			lists.add(t);
			++count;
			consumer.signalAll(); //通知消费者线程进行消费
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public T get() {
		T t = null;
		try {
			lock.lock();
			while(lists.size() == 0) {
				consumer.await();
			}
			t = lists.removeFirst();
			count --;
			producer.signalAll(); //通知生产者进行生产
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
		return t;
	}
	
	public static void main(String[] args) {
		MyContainer2<String> c = new MyContainer2<>();
		//启动消费者线程
		for(int i=0; i<10; i++) {
			new Thread(()->{
				for(int j=0; j<5; j++) System.out.println(c.get());
			}, "c" + i).start();
		}
		
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		//启动生产者线程
		for(int i=0; i<2; i++) {
			new Thread(()->{
				for(int j=0; j<25; j++) c.put(Thread.currentThread().getName() + " " + j);
			}, "p" + i).start();
		}
	}
}

总结

AQS原理

Abstract : 因为它并不知道到怎么上锁。参照模板方法设计模式即可,暴露出上锁逻辑

Queue:线程阻塞队列

Synchronizer: 同步

AQS便是使用CAS+state完成多线程抢锁逻辑,以及使用Queue完成抢不到锁的线程排队

AQS核心代码

获取锁的代码

	public final void acquire(int arg) {
    if (!tryAcquire(arg) && //子类判定获取锁失败返回false, 那么同理,取反,则表示为true
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //获取失败后添加到阻塞队列中
        selfInterrupt();
	}

	//模板方法, 需要子类去实现,子类去实现获取锁的逻辑, AQS并不知道你怎么使用这个statue来上锁
	//因为是子类需要去实现的, 所以这里使用protected修饰
	protected boolean tryAcquire(int arg) {
  	 throw new UnsupportedOperationException();
	}

释放锁的代码

    public final boolean release(int arg) {
        //子类判断释放锁成功后
        if (tryRelease(arg)) {
            //检查阻塞队列唤醒即可
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

	//模板方法, 需要子类去实现,子类去实现获取锁的逻辑, AQS并不知道你怎么使用这个statue来释放锁	
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

**总结: **

子类只需要实现自己的获取锁逻辑和释放锁逻辑即可, 至于排队阻塞队列等待、唤醒机制均由AQS来完成

ReentranLock原理

概念

基于AQS实现的可重入锁实现类

核心变量和构造器

public class ReentrantLock implements Lock, java.io.Serializable {
    private final Sync sync;

    
    abstract static class Sync extends AbstractQueuedSynchronizer {

        abstract void lock();

        //非公平锁获取锁方法
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //当执行到这里, 正好持有锁的线程释放了锁,那么可以尝试抢锁
            if (c == 0) {
                //继续抢锁, 不看有没有线程排队
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 当前线程就是持有锁的线程, 表明锁重入
            else if (current == getExclusiveOwnerThread()) {
                // 利用state整型变量进行次数记录
                int nextc = c + acquires;
                //如果超过了int表示范围, 表明符号溢出, 所以抛出异常(int范围 正:2的32次方-1,负:2的32次方)
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                  (nextc);
                return true;
            }
            return false;
        }

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        final boolean isLocked() {
            return getState() != 0;
        }
    }
    
    //公平锁获取锁的方法
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
		// 由ReentrantLock调用
        final void lock() {
            // 没有尝试抢锁, 直接进入AQS标准获取锁流程
            acquire(1);
        }
		// AQS调用, 自己自己实现获取锁的流程
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //注意: 这里和非公平锁的区别在于: hasQueuedPredecessors看看队列中是否有线程正在排队, 没有的话再通过CAS抢锁。
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 当前线程就是获取锁的线程, 那么这里是锁重入
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
    

    public ReentrantLock() {
        sync = new NonfairSync();
    }


    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    
    static final class NonfairSync extends Sync {
       //由ReentrantLock调用获取锁
        final void lock() {
            //非公平锁, 直接抢锁, 不管有没有线程排队
            if (compareAndSetState(0, 1))
                //上锁成功, 那么标识当前线程为获取锁的线程 
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //抢锁失败, 进入AQS的标准获取锁流程
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    
}

这里首先理解一下公平锁和非公平锁

公平锁与非公平锁

在这里插入图片描述

为什么非公平锁的性能高于公平锁?

因为公平锁需要排队, 排队需要时间, 唤醒阻塞的线程也需要时间, 就如上图公平锁图中的B加入到运行队列,期间进行线程切换需要时间。

非公平锁性能高于公平锁的原因:线程上下文切换+调度延迟时间

核心方法

获取锁操作

public void lock() {
    //直接通过sync同步器进行上锁
    sync.lock();
}

而在ReentrantLock中sync有公平锁和非公平锁, 如下所示

在这里插入图片描述

如何自己来实现一把锁?

  1. 如何表示锁状态:无锁?有锁?

    boolean status;//true 有锁 false 无锁 只能表示两种状态

    为了实现锁重入, 那么我需要记录锁重入次数: int times;

    两个变量有点冗余了, 所以我们直接用int state; 来表示锁状态和重入次数: 0无锁, 大于0重入次数 特别地: 1为重入一次, 也即只加锁一次

  2. 如何保证多线程抢锁线程安全

    CAS

  3. 如何处理获取不到锁的线程?

    自旋 阻塞 自旋+阻塞

  4. 如何释放锁?

    自旋: 自己抢锁

    阻塞: 唤醒

自旋锁

自旋锁缺点:CPU占用不干事,导致性能障碍,简称占着茅坑不拉屎。

自旋锁优点:适用于执行步骤较少且快的操作,自旋一会马上就能获取锁,这样不会消耗太多CPU资源。

注意:当CPU个数增加的情况下,优点会退化成自旋锁的缺点

使用场景:争用较少且代码量小的 临界区

AQS原理分析 AQS 通过提供 state 及 FIFO 队列的管理,为我们提供了一套通用的实现锁的底层方法,基本上定义一个锁,都是转为在其内部定义 AQS 的子类,调用 AQS 的底层方法来实现的,由于 AQS 在底层已经为了定义好了这些获取 state 及 FIFO 队列的管理工作,我们要实现一个锁就比较简单了,我们可以基于 AQS 来实现一个非可重入的互斥锁,如下所示@Override@Override@Override。 阅读详情

相关推荐

4种常用Java线程锁的特点,性能比较、使用场景.pdf

4种常用Java线程锁的特点,性能比较、使用场景 线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

多线程线程锁的概念和介绍

在多线程环境下,为了让多线程安全地访问和使用共享变量,必须引入锁机制。锁机制即当一个线程持有锁后,其他线程只能进行等待,直到持有锁的线程释放锁,再次重新竞争锁。

Jay的小提琴的博客 2110

FOC观测器之龙伯格观测器

状态观测器: 理论依据:根据控制理论,如果一个系统能够完全通过其检测到的输出值来重构其系统状态,则认为该系统是可观测的。其作用于无传感器转子的位置和速度的检测反馈,再作用到PARK逆变换和转矩磁链控制上。 Luenberger观察器原理图: Luenberger观测器的离散化: (其观测器的闭环参数为经验验证值) 引入估测电流( iα,iβ )的反馈值并离散化(T 为采样时间),可得...

ShawnChan的博客 2万+

jdk锁知识(四)—— AQS

1、AQS是什么,实现的功能是什么,其在Lock锁和整个并发包中地位 2、AQS理论介绍 3、AQS源码分析

Interest1_wyt的博客 586

基于SHIS凝视型短波红外高光谱相机(900-1700nm)区分不同宝石的差异性和可分性(宝石分选)

SHIS系列凝视型高光谱相机基于液晶可调谐滤波器(LCTF),通过电控LCTF进行连续细分窄波段分光,对每个波段进行高分辨率的二维面阵成像,进行图像处理后,获得完整的高光谱高清图像序列。具有低功耗、小型化、使用方便和易于扩展的优势。其工作波段覆盖400nm至1000nm,能够捕捉宝石在不同波长下的光谱特征,为宝石鉴定提供了新的工具。

wayho的博客 707

Clock Gating设计陷阱:为什么你的ICG cell总是导致setup violation?(附解决方案)

本文深入分析了数字IC设计中ICG(Integrated Clock Gating)单元导致setup时序违规的常见问题,包括时钟偏斜和数据路径过长两大核心原因。通过量化分析影响因素,提供了逻辑重组、寄存器重定时等优化策略,并介绍了预防性设计方法和物理实现阶段的优化技巧,帮助工程师有效解决ICG相关的setup violation问题。

weixin_29168153的博客 203

fine/coarse grain 多线程实践

#ifndef __COARSE__QUEUE__H__ #define __COARSE__QUEUE__H__ #ifdef _MSC_VER #if _MSC_VER > 1000 #pragma once #endif #endif #include #include #include #include namespace stc{ template class

何以诚的专栏 1697

AI自动打游戏全攻略:从环境搭建到Agent训练实战

本文系统介绍了AI自动打游戏的技术实现方案,重点解析了强化学习(DQN算法)在贪吃蛇游戏中的应用。文章从技术选型入手,对比了强化学习与计算机视觉两种方案,详细说明了开发环境配置步骤,并深入讲解了DQN模型构建、游戏环境封装、训练优化等核心环节。通过经验回放和目标网络等技术,有效提升了AI的学习效率和稳定性。该方案具有通用性,只需调整参数即可应用于其他简单游戏场景,为开发者实现游戏AI提供了完整的技术路径。

milk416666的博客 4114

面试必备之乐观锁与悲观锁

悲观锁总是假设最坏的情况,认为共享资源每次被访问的时候就会出现问题(比如共享数据被修改),所以每次在获取资源操作的时候都会上锁,这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放。也就是说,共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。像 Java 中和等独占锁就是悲观锁思想的实现。悲观锁通常多用于写多比较多的情况下(多写场景),避免频繁失败和重试影响性能。

JavaGuide 30万+

kmeans聚类分析结果怎么看_【Stata与机器学习】物以类聚-KMeans算法应用案例介绍...

古语有云:物以类聚,人以群分。KMeans是无监督机器学习中的聚类算法中最简单的一种——基于特征对样本进行归为不同的类,很好地阐述了‘物以类聚’这句话的精髓。机器学习又叫统计学习。既然都统计了,所以当然包括了计量经济学。所以一般来说,机器学习的算法类型远多于计量经济学。比如线性回归,这个算是机器学习算法中比较基础的部分。当然,计量经济学在回归分析、因果推断方面的造诣,也是普通机器学算法所不能比的。...

weixin_39857480的博客 1万+

普罗米修斯监控达梦数据库集群

-----------------------------------新增文件---------------------------------------注:图上即为启动成功,如果没有问题,则使用nohup后台启动程序。官网地址:https://github.com/gy297879328/dameng_exporter。注:prometheus的对数据库的指标采集如果高于60s一次,请手动修改提供表盘中的计算值。修改dameng_exporter.config配置文件,修改账号密码的部分。

weixin_49985403的博客 1679

再聊聊 synchronized 关键字

当synchronized关键字,有不少同学已经耳熟能详了,作为作为复习还是要巩固一下,因为面试常问这个东西。 第一个是多个线程去访问同一个资源的时候,对这个资源要上锁。为什么要上锁呢?访问某一段代码或者某临界资源的时候是需要有一把锁的概念的。 比如:我们对一个数字做递增,两个程序对它一块儿来做递增,递增就是把一个程序往上加1,如果两个线程共同访问的时候,第一个线程读它是0,然后把它加1,在自...

热衷技术的Java程序员 715

cartographer运行错误集合

播放bag的时候错误 Check failed: sensor_to_tracking->translation().norm() < 1e-5 The IMU frame must be colocated with the tracking frame. Transforming linear acceleration into the tracking frame will otherwise be imprecise tracking frame不是imu的link 修改lua文件中的.

Studying…… 2016

【深入AQS原理】我画了35张图就是为了让你深入 AQS

申明 本文首发自公众号:程序员cxuan,此文章为本人投稿文章。 已经和公众号作者沟通,文章投递公众号,博客平台发布可标记为原创。 此文章肝了很久,图片较多,希望大家喜欢。 另外,感兴趣的小伙伴可关注个人公众号:壹枝花算不算浪漫 公众号刚开始运营,希望与您一同成长。 前言 谈到并发,我们不得不说AQS(AbstractQueuedSynchronizer),所谓的AQS即是抽象的队列式的同步器,...

一枝花算不算浪漫的专栏 4042

《YOLO11魔术师专栏》专栏介绍 & 专栏目录

【原创自研模块】【多组合点优化】【注意力机制】【主干篇】【neck优化】【卷积魔改】 【block&多尺度融合结合】【损失&IOU优化】【上下采样优化】【小目标性能提升】【前沿论文分享】【训练实战篇】

①答疑群聊服务;②YOLO大模型知识问答系统;③计算机视觉论文生成智能体; 1万+

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

AQS原理详解

1.AQS 原理概览 AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。 CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点

a984171281的博客 2万+

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

AQS基础——多图详解CLH锁的原理与实现

源码笔记,专注于Java后端系列框架的源码分析。关注【源码笔记】公众号,第一时间接收最新源码分析文章更新。 1 什么是自旋锁和互斥锁? 由于CLH锁是一种自旋锁,那么我们先来看看自旋锁是什么? 自旋锁说白了也是一种互斥锁,只不过没有抢到锁的线程会一直自旋等待锁的释放,处于busy-waiting的状态,此时等待锁的线程不会进入休眠状态,而是一直忙等待浪费CPU周期。因此自旋锁适用于锁占用时间短的场合。 这里谈到了自旋锁,那么我们也顺便说下互斥锁。这里的互斥锁说的是传统意义的互斥锁,就是多个线程并发竞争锁.

biaolianlao0449的博客 1414

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

深入理解 AQS 底层实现原理

AQS的相关介绍: 可重入锁: 是指同一个线程在外层方法获取锁的时候,在内层仍然可以使用,并且不会发生死锁(前提: 锁对象是同一个锁) 不会因为之前已经获取锁还没有释放而阻塞 java中ReentrantLock和syncronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁 总结: 一个线程中的多个流程可以获取同一把锁,持有这把同步锁可以再次进入 自己可以获取自己的内部锁 可重入锁的种类: 隐式锁:关键字syncronized使用的锁,默认是可重入锁 (同步代码块,同步方法) 显式锁: 即(L

莫言zero的博客 9162

【多线程及高并发 五】AQS & ReentranLock 详解

这系列文章讲述多线程及高并发详解,此文主要讲述AQS及ReentranLock详解,包AQS模板方法、锁流程、资源共享方式及AQS源码解析、Lock 框架及自定义Lock、ReentranLock的加锁流程及典型案例

若明天不见 1980

Lock锁和AQS之间的关系与区别:基于第一原理的深入分析

第一原理,英文为First Principle,是指从最基本的事实或假设出发,进行逻辑推导,得出结论。它强调回归事物的本质,通过解构分析找到实现目标的最优路径。AQS是Java并发包中的一个抽象类,用于构建锁和其他同步组件的基础框架。它使用了一个整型变量state来表示同步状态,并通过内置的FIFO队列来管理获取资源的线程。管理同步状态:通过state变量表示资源是否被占用。线程排队:当线程获取资源失败时,将其加入等待队列。唤醒机制:当资源被释放时,唤醒等待队列中的线程。java复制代码。

喜欢猪猪 1808

AQS与ReentrantLock原理

AQS与ReentrantLock原理

howard1209a的博客 2170

AQS 原理详解

AQS(Abstract Queued Synchronizer),抽象队列同步器,它是用来构建锁或其他同步器的基础框架。虽然大多数程序员可能永远不会使用到它,但是知道 AQS 的原理有助于理解一些锁或同步器的是如何运行的。那么有哪些同步器是基于 AQS 实现的呢?这里仅是简单介绍,详情后续会单独总结一篇文章。同步器说明递减的计数器,直至所有线程的任务都执行完毕,才继续执行后续任务。Semaphore信号量,控制同时访问某个资源的数量。递增的计数器,所有线程达到屏障时,才会继续执行后续任务。

m0_51607909的博客 1324

Java多线程 同步队列详解(AQS)

Java多线程 同步队列详解(AQS) 文章目录Java多线程 同步队列详解(AQS)1、AQS简介1.1 什么是AQS1.2 什么是CLH锁队列2、ASQ 提供的各种锁实现流程2.1 独占锁2.2 共享锁2.3 可重入锁2.4 公平锁与非公平锁2.5 读写锁 1、AQS简介 1.1 什么是AQS 队列同步器(AQS)是用来构建锁或者其他同步组件的基础框架,使用一个int型变量代表同步状态,通过内...

烟雨gps 1869

Java多线程--AQS的原理

本文介绍Java中的AQS的原理。 Java的AQS是JDK自带的锁机制,是JUC(java.util.concurrent)的基础,很多并发类都是基于它实现的,例如:ReentrantLock、CountDownLatch、Semaphore、ReadWriteLock,CyclicBarrier。

IT利刃出鞘的博客 5162

AQS的前菜—详解CLH队列锁

https://blog.csdn.net/weixin_47184173/article/details/115340014 什么是CLH队列锁 CLH锁其实就是一种基于逻辑队列非线程饥饿的一种自旋公平锁。当多线程竞争一把锁时,获取不到锁的线程,会排队进入CLH队列的队尾,然后自旋等待,直到其前驱线程释放锁。由于是 Craig、Landin 和 Hagersten三位大佬的发明,因此命名为CLH锁。 自旋锁与互斥锁的区别 由于CLH锁是一种自旋锁,那么我们先来看看自旋锁是什么? 自旋锁说白了

yygr的博客 5708
上一篇: 多线程---多线程基础知识
下一篇: 多线程---并发容器的使用
意田天
博客等级 码龄7年 41粉丝 89原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

意田天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值