JAVA高并发编程 梦里梦外; 2022-12-11 05:22 193阅读 0赞 转载:[JAVA高并发编程][JAVA] * * synchronized 关键字 * 同步方法 * 同步代码块 * 锁的底层实现 * 锁的种类 * volatile 关键字 * wait¬ify * AtomicXxx 类型组 * CountDownLatch 门闩 * 锁的重入 * ReentrantLock * 同步容器 * Map/Set * List * Queue * ThreadPool&Executor * Executor * ExecutorService * Future * Callable * Executors * FixedThreadPool * CachedThreadPool * ScheduledThreadPool * SingleThreadExceutor * ForkJoinPool * WorkStealingPool * ThreadPoolExecutor ## synchronized 关键字 ## 可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。可能锁对象包括: this, 临界资源对象,Class 类对象 ### 同步方法 ### 同步方法锁定的是当前对象。当多线程通过同一个对象引用多次调用当前同步方法时, 需同步执行。 public synchronized void test(){ System.out.println("测试一下"); } 1 2 3 ### 同步代码块 ### 同步代码块的同步粒度更加细致,是商业开发中推荐的编程方式。可以定位到具体的同步位置,而不是简单的将方法整体实现同步逻辑。在效率上,相对更高。 **锁定临界对象** 同步代码块在执行时,是锁定 object 对象。当多个线程调用同一个方法时,锁定对象不变的情况下,需同步执行。 public void test(){ synchronized(o){ System.out.println("测试一下"); } } 1 2 3 4 5 **锁定当前对象** public void test(){ synchronized(this){ System.out.println("测试一下"); } } 1 2 3 4 5 ### 锁的底层实现 ### Java 虚拟机中的同步(Synchronization)基于进入和退出管程(Monitor)对象实现。同步方法 并不是由 monitor enter 和 monitor exit 指令来实现同步的,而是由方法调用指令读取运行时常量池中方法的 ACC\_SYNCHRONIZED 标志来隐式实现的。 **![对象内存简图][70]** **对象头**:存储对象的 hashCode、锁信息或分代年龄或 GC 标志,类型指针指向对象的类元数据,JVM 通过这个指针确定该对象是哪个类的实例等信息。 **实例变量**:存放类的属性数据信息,包括父类的属性信息 **填充数据**:由于虚拟机要求对象起始地址必须是 8 字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐 *当在对象上加锁时,数据是记录在对象头中。当执行 synchronized 同步方法或同步代码块时,会在对象头中记录锁标记,锁标记指向的是 monitor 对象(也称为管程或监视器锁) 的起始地址。每个对象都存在着一个 monitor 与之关联,对象与其 monitor 之间的关系有存在多种实现方式,如 monitor 可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,但当一个 monitor 被某个线程持有后,它便处于锁定状态。* 在 Java 虚拟机(HotSpot)中,monitor 是由 ObjectMonitor 实现的。 ObjectMonitor 中有两个队列,\_WaitSet 和 \_EntryList,以及\_Owner 标记。其中\_WaitSet 是用于管理等待队列(wait)线程的,\_EntryList 是用于管理锁池阻塞线程的,\_Owner 标记用于记录当前执行线程。线程状态图如下: ![这里写图片描述][70 1] 当多线程并发访问同一个同步代码时,首先会进入\_EntryList,当线程获取锁标记后, monitor 中的\_Owner 记录此线程,并在 monitor 中的计数器执行递增计算(+1),代表锁定,其他线程在\_EntryList 中继续阻塞。若执行线程调用 wait 方法,则 monitor 中的计数器执行赋值为 0 计算,并将\_Owner 标记赋值为 null,代表放弃锁,执行线程进如\_WaitSet 中阻塞。若执行线程调用 notify/notifyAll 方法,\_WaitSet 中的线程被唤醒,进入\_EntryList 中阻塞,等待获取锁标记。若执行线程的同步代码执行结束,同样会释放锁标记,monitor 中的\_Owner 标记赋值为 null,且计数器赋值为 0 计算。 ### 锁的种类 ### Java 中锁的种类大致分为偏向锁,自旋锁,轻量级锁,重量级锁。 锁的使用方式为:先提供偏向锁,如果不满足的时候,升级为轻量级锁,再不满足,升级为重量级锁。自旋锁是一个过渡的锁状态,不是一种实际的锁类型。 锁只能升级,不能降级。 **重量级锁** 在锁的底层实现中解释的就是重量级锁。 **偏向锁** 是一种编译解释锁。如果代码中不可能出现多线程并发争抢同一个锁的时候,JVM 编译代码,解释执行的时候,会自动的放弃同步信息。消除 synchronized 的同步代码结果。使用锁标记的形式记录锁状态。在 Monitor 中有变量 ACC\_SYNCHRONIZED。当变量值使用的时候, 代表偏向锁锁定。可以避免锁的争抢和锁池状态的维护。提高效率。 **轻量级锁** 过渡锁。当偏向锁不满足,也就是有多线程并发访问,锁定同一个对象的时候,先提升为轻量级锁。也是使用标记 ACC\_SYNCHRONIZED 标记记录的。ACC\_UNSYNCHRONIZED 标记记录未获取到锁信息的线程。就是只有两个线程争抢锁标记的时候,优先使用轻量级锁。 两个线程也可能出现重量级锁。 **自旋锁** 是一个过渡锁,是偏向锁和轻量级锁的过渡。 当获取锁的过程中,未获取到。为了提高效率,JVM 自动执行若干次空循环,再次申请锁,而不是进入阻塞状态的情况。称为自旋锁。自旋锁提高效率就是避免线程状态的变更。 ### volatile 关键字 ### 变量的线程可见性。在 CPU 计算过程中,会将计算过程需要的数据加载到 CPU 计算缓存中,当 CPU 计算中断时,有可能刷新缓存,重新读取内存中的数据。在线程运行的过程中,如果某变量被其他线程修改,可能造成数据不一致的情况,从而导致结果错误。而 volatile 修饰的变量是线程可见的,当 JVM 解释 volatile 修饰的变量时,会通知 CPU,在计算过程中, 每次使用变量参与计算时,都会检查内存中的数据是否发生变化,而不是一直使用 CPU 缓存中的数据,可以保证计算结果的正确。 volatile 只是通知底层计算时,CPU 检查内存数据,而不是让一个变量在多个线程中同步。 volatile int count = 0; 1 ### wait¬ify ### ### AtomicXxx 类型组 ### 原子类型。 在 concurrent.atomic 包中定义了若干原子类型,这些类型中的每个方法都是保证了原子操作的。多线程并发访问原子类型对象中的方法,不会出现数据错误。在多线程开发中,如果某数据需要多个线程同时操作,且要求计算原子性,可以考虑使用原子类型对象。 AtomicInteger count = new AtomicInteger(0); void m(){ count.incrementAndGet(); } 1 2 3 4 注意:原子类型中的方法是保证了原子操作,但多个方法之间是没有原子性的。如: AtomicInteger i = new AtomicInteger(0); if(i.get() != 5){ i.incrementAndGet(); } 1 2 3 4 *在上述代码中,get 方法和 incrementAndGet 方法都是原子操作,但复合使用时,无法保证原子性,仍旧可能出现数据错误。* ### CountDownLatch 门闩 ### 门闩是 concurrent 包中定义的一个类型,是用于多线程通讯的一个辅助类型。 门闩相当于在一个门上加多个锁,当线程调用 await 方法时,会检查门闩数量,如果门 闩数量大于 0,线程会阻塞等待。当线程调用 countDown 时,会递减门闩的数量,当门闩数量为 0 时,await 阻塞线程可执行。 CountDownLatch latch = new CountDownLatch(5); <span class="hljs-keyword">void</span> m1(){ <span class="hljs-keyword">try</span> { latch.<span class="hljs-keyword">await</span>();<span class="hljs-comment">// 等待门闩开放。</span> } <span class="hljs-keyword">catch</span> (InterruptedException e) { e.printStackTrace(); } System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"m1() method"</span>); } <span class="hljs-keyword">void</span> m2(){ <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">10</span>; i++){ <span class="hljs-keyword">if</span>(latch.getCount() != <span class="hljs-number">0</span>){ System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"latch count : "</span> + latch.getCount()); latch.countDown(); <span class="hljs-comment">// 减门闩上的锁。</span> } <span class="hljs-keyword">try</span> { TimeUnit.MILLISECONDS.sleep(<span class="hljs-number">500</span>); } <span class="hljs-keyword">catch</span> (InterruptedException e) { <span class="hljs-comment">// TODO Auto-generated catch block</span> e.printStackTrace(); } System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"m2() method : "</span> + i); }<div class="hljs-button {2}" data-title="复制" data-report-click="{"spm":"1001.2101.3001.4259"}"></div></code><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li><li style="color: rgb(153, 153, 153);">7</li><li style="color: rgb(153, 153, 153);">8</li><li style="color: rgb(153, 153, 153);">9</li><li style="color: rgb(153, 153, 153);">10</li><li style="color: rgb(153, 153, 153);">11</li><li style="color: rgb(153, 153, 153);">12</li><li style="color: rgb(153, 153, 153);">13</li><li style="color: rgb(153, 153, 153);">14</li><li style="color: rgb(153, 153, 153);">15</li><li style="color: rgb(153, 153, 153);">16</li><li style="color: rgb(153, 153, 153);">17</li><li style="color: rgb(153, 153, 153);">18</li><li style="color: rgb(153, 153, 153);">19</li><li style="color: rgb(153, 153, 153);">20</li><li style="color: rgb(153, 153, 153);">21</li><li style="color: rgb(153, 153, 153);">22</li><li style="color: rgb(153, 153, 153);">23</li><li style="color: rgb(153, 153, 153);">24</li><li style="color: rgb(153, 153, 153);">25</li></ul></pre> ### 锁的重入 ### 在 Java 中,同步锁是可以重入的。只有同一线程调用同步方法或执行同步代码块,对同一个对象加锁时才可重入。 当线程持有锁时,会在 monitor 的计数器中执行递增计算,若当前线程调用其他同步代码,且同步代码的锁对象相同时,monitor 中的计数器继续递增。每个同步代码执行结束, monitor 中的计数器都会递减,直至所有同步代码执行结束,monitor 中的计数器为 0 时,释放锁标记,\_Owner 标记赋值为 null。 ### ReentrantLock ### 重入锁,建议应用的同步方式。相对效率比 synchronized 高。量级较轻。 synchronized 在 JDK1.5 版本开始,尝试优化。到 JDK1.7 版本后,优化效率已经非常好了。在绝对效率上,不比 reentrantLock 差多少。 使用重入锁,必须必须必须手工释放锁标记。一般都是在 finally 代码块中定义释放锁标记的 unlock 方法。 **公平锁** ![这里写图片描述][70 2] private static ReentrantLock lock = new ReentrantLock(true); public void run(){ for(int i = 0; i < 5; i++){ lock.lock(); try{ System.out.println(Thread.currentThread().getName() + " get lock"); }finally{ lock.unlock(); } } } 1 2 3 4 5 6 7 8 9 10 11 8ThreadLocal remove 问题 ![这里写图片描述][70 3] ## 同步容器 ## 解决并发情况下的容器线程安全问题的。给多线程环境准备一个线程安全的容器对象。线程安全的容器对象: Vector, Hashtable。线程安全容器对象,都是使用 synchronized 方法实现的。 concurrent 包中的同步容器,大多数是使用系统底层技术实现的线程安全。类似 native。 Java8 中使用 CAS。 ### Map/Set ### **ConcurrentHashMap/ConcurrentHashSet** 底层哈希实现的同步 Map(Set)。效率高,线程安全。使用系统底层技术实现线程安全。量级较 synchronized 低。key 和 value 不能为 null。 **ConcurrentSkipListMap/ConcurrentSkipListSet** 底层跳表(SkipList)实现的同步 Map(Set)。有序,效率比 ConcurrentHashMap 稍低。 ![这里写图片描述][70 4] ### List ### **CopyOnWriteArrayList** 写时复制集合。写入效率低,读取效率高。每次写入数据,都会创建一个新的底层数组。 ### Queue ### **ConcurrentLinkedQueue** 基础链表同步队列。 **LinkedBlockingQueue** 阻塞队列,队列容量不足自动阻塞,队列容量为 0 自动阻塞。 **ArrayBlockingQueue** 底层数组实现的有界队列。自动阻塞。根据调用 API(add/put/offer)不同,有不同特性。 当容量不足的时候,有阻塞能力。 add 方法在容量不足的时候,抛出异常。 put 方法在容量不足的时候,阻塞等待。 offer 方法, 单参数 offer 方法,不阻塞。容量不足的时候,返回 false。当前新增数据操作放弃。三参数 offer 方法(offer(value,times,timeunit)),容量不足的时候,阻塞 times 时长(单 位为 timeunit),如果在阻塞时长内,有容量空闲,新增数据返回 true。如果阻塞时长范围 内,无容量空闲,放弃新增数据,返回 false。 **DelayQueue** 延时队列。根据比较机制,实现自定义处理顺序的队列。常用于定时任务。如:定时关机。 **LinkedTransferQueue** 转移队列,使用 transfer 方法,实现数据的即时处理。没有消费者,就阻塞。 **SynchronusQueue** 同步队列,是一个容量为 0 的队列。是一个特殊的 TransferQueue。必须现有消费线程等待,才能使用的队列。 add 方法,无阻塞。若没有消费线程阻塞等待数据,则抛出异常。 put 方法,有阻塞。若没有消费线程阻塞等待数据,则阻塞。 ## ThreadPool&Executor ## ### Executor ### 线程池顶级接口。 常用方法 - void execute(Runnable) 作用是: 启动线程任务的。 ### ExecutorService ### Executor 接口的子接口。 常见方法 - Future submit(Callable), Future submit(Runnable) ### Future ### 未来结果,代表线程任务执行结束后的结果。 ### Callable ### 可执行接口。 接口方法 : Object call();相当于 Runnable 接口中的 run 方法。区别为此方法有返回值。不能抛出已检查异常。 和 Runnable 接口的选择 - 需要返回值或需要抛出异常时,使用 Callable,其他情况可任意选择。 ### Executors ### 工具类型。为 Executor 线程池提供工具方法。类似 Arrays,Collections 等工具类型的功用。 ### FixedThreadPool ### 容量固定的线程池 queued tasks - 任务队列 completed tasks - 结束任务队列 ### CachedThreadPool ### 缓存的线程池。容量不限(Integer.MAX\_VALUE)。自动扩容。默认线程空闲 60 秒,自动销毁。 ### ScheduledThreadPool ### 计划任务线程池。可以根据计划自动执行任务的线程池。 ### SingleThreadExceutor ### 单一容量的线程池。 ### ForkJoinPool ### 分支合并线程池(mapduce 类似的设计思想)。适合用于处理复杂任务。初始化线程容量与 CPU 核心数相关。 线程池中运行的内容必须是 ForkJoinTask 的子类型(RecursiveTask,RecursiveAction)。 ### WorkStealingPool ### JDK1.8 新增的线程池。工作窃取线程池。当线程池中有空闲连接时,自动到等待队列中窃取未完成任务,自动执行。 初始化线程容量与 CPU 核心数相关。此线程池中维护的是精灵线程。 ExecutorService.newWorkStealingPool(); ### ThreadPoolExecutor ### 线程池底层实现。除 ForkJoinPool 外,其他常用线程池底层都是使用 ThreadPoolExecutor 实现的。 public ThreadPoolExecutor (int corePoolSize, // 核心容量 int maximumPoolSize, // 最大容量 long keepAliveTime, // 生命周期,0 为永久 TimeUnit unit, // 生命周期单位 BlockingQueue workQueue // 任务队列,阻塞队列。 ); [JAVA]: https://blog.csdn.net/cx105200/article/details/80220937?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522160128318119725255503146%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=160128318119725255503146&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_click~default-1-80220937.pc_first_rank_v2_rank_v28&utm_term=java%20%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B&spm=1018.2118.3001.4187#synchronized-%E5%85%B3%E9%94%AE%E5%AD%97 [70]: /images/20221123/be6d3aee4ae54750ab930e92e77a9134.png [70 1]: /images/20221123/50405207a70d481a93597e4344bcab7e.png [70 2]: /images/20221123/a6622159827f402c9ffc17aeced33acb.png [70 3]: /images/20221123/8df2bef9326d442e907280c786996db5.png [70 4]: /images/20221123/7255f5e9206b4b1eb61ffe689c3ddd2e.png
还没有评论,来说两句吧...