大家好,我是哪吒。
上一章提到了一個關于 i++ 和 ++i 的面試題打趴了所有人,最終方案是在兩個方法上添加synchronized關鍵字,從而避免i++的線程安全問題,不過,這樣真的好嗎?在所有有線程安全的方法都添加synchronized?
答案是顯而易見的,不行。
synchronized會極大的降低程序的性能,導致整個程序幾乎只能支持單線程操作,性能顯著降低。
那么,如何解決呢?
鎖的粒度更小了,也解決了這個問題,確實可以的。
package com.guor.thread;public class SynchronizedTest2 { int a = 1; int b = 1; public void add() { System.out.println("add start"); synchronized (this) { for (int i = 0; i < 10000; i++) { a++; b++; } } System.out.println("add end"); } public synchronized void compare() { System.out.println("compare start"); synchronized (this) { for (int i = 0; i < 10000; i++) { boolean flag = a < b; if (flag) { System.out.println("a=" + a + ",b=" + b + "flag=" + flag + ",a < b = " + (a < b)); } } } System.out.println("compare end"); } public static void main(String[] args) { SynchronizedTest2 synchronizedTest = new SynchronizedTest2(); new Thread(() -> synchronizedTest.add()).start(); new Thread(() -> synchronizedTest.compare()).start(); }}
為了更好的優化,有的時候可以將synchronized代碼塊變為區分讀寫場景的讀寫鎖,也可以考慮悲觀鎖和樂觀鎖的區分。
對于讀寫場景比較多的情況,可以使用ReentrantReadWriteLock區分讀寫,再次降低鎖的粒度,提高程序的性能。
ReentrantReadWriteLock 還可以選擇提供了公平鎖,在沒有明確必須使用公平鎖的情況下,盡量不要使用公平鎖,公平鎖會使程序性能降低很多很多。
簡單來說,公平鎖(誰先排隊,誰先執行),非公平鎖(不用排隊,每個人都有機會)。
有一天早上,云韻、美杜莎、小醫仙結伴去買醬香拿鐵,到了咖啡店,先排隊,一個一個來。不一會,哪吒來了,也買醬香拿鐵,只能在末尾排隊。這個就是公平鎖。
但是呢?第二天早上,哪吒又去買醬香拿鐵,上一次去晚了沒買到(線程被餓死了),這次急了,要插隊買,不講武德。終于喝上了心心念念的醬香拿鐵,這個就是非公平鎖。
我們都知道,靜態字段屬于類,類級別的鎖才能保護;非靜態字段屬于類實例,實例級別的鎖才能保護。
先看一下下面的代碼:
import lombok.Data;import java.util.stream.IntStream;@Datapublic class LockTest { public static void main(String[] args) { IntStream.rangeClosed(1, 100000).parallel().forEach(i -> new LockTest().increase()); System.out.println(time); } private static int time = 0; public synchronized void increase() { time++; }}
在LockTest類中定義一個靜態變量time,定義一個非靜態方法increase(),實現time++自增。先累加10萬次,測試一下。看看是否有線程安全的問題。
這...不對啊,上一節在介紹高并發下i++線程安全問題的時候,synchronized 是好使的啊。
今天這是怎么了?再運行一次,結果依然如此,不等于100000
先來分析一下。
在非靜態的方法上加synchronized,只能確保多個線程無法執行同一個實例的increase()方法,卻不能保證不同實例的increase()方法。靜態的變量time,在多個線程中共享,所以會出現線程安全的問題,synchronized失效了。
那么,將synchronized改為靜態方法是不是就可以了,試一下。
有兩種寫法,一種是直接將方法改為靜態方法,一種是使用synchronized代碼塊。
private static Object obj= new Object();public void increase() { synchronized (obj) { time++; }}
很多小伙伴,可能會好奇,這個是干什么的,干了5年后端代碼開發了,沒見過這玩意兒。
IntStream是一種特殊的stream,用來提供對int相關的stream操作。
IntStream.rangeClosed:生成某個數字范圍內的數字集合的stream。
比如上面代碼中的IntStream.rangeClosed(1, 100000).parallel().forEach(i -> new LockTest().increase());。
Stream.parallel() 方法是 Java 8 中 Stream API 提供的一種并行處理方式。在處理大量數據或者耗時操作時,使用 Stream.parallel() 方法可以充分利用多核 CPU 的優勢,提高程序的性能。
Stream.parallel() 方法是將串行流轉化為并行流的方法。通過該方法可以將大量數據劃分為多個子任務交由多個線程并行處理,最終將各個子任務的計算結果合并得到最終結果。使用 Stream.parallel() 可以簡化多線程編程,減少開發難度。
需要注意的是,并行處理可能會引入線程安全等問題,需要根據具體情況進行選擇。
定義一個list,然后通過parallel() 方法將集合轉化為并行流,對每個元素進行i++,最后通過 collect(Collectors.toList()) 方法將結果轉化為 List 集合。
使用并行處理可以充分利用多核 CPU 的優勢,加快處理速度。
public class StreamTest { public static void main(String[] args) { List<Integer> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { list.add(i); } System.out.println(list); List<Integer> result = list.stream().parallel().map(i -> i++).collect(Collectors.toList()); System.out.println(result); }}
我勒個去,什么情況?
這是大部分開發人員都會犯的小錯誤,在上篇中提到過,i++ 返回原來的值,++i 返回加1后的值。這誰都知道,可是,寫的時候,就不一定了,因為你習慣了i++,寫順手了,寫的時候也是心不在焉,一蹴而就了。
i++改了++i即可。
優點:
缺點:
在實際開發中,應該根據數據量、計算復雜度、硬件等因素綜合考慮。
比如:
本文鏈接:http://www.tebozhan.com/showinfo-26-12358-0.html簡單聊一聊公平鎖和非公平鎖,Parallel并行流
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 你真的理解Python Qt6基礎知識中的信號和槽機制嗎?
下一篇: 有了這個代碼模板,合并排序手到擒來