掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
大家好,我是哪吒。

創(chuàng)新互聯(lián)公司專注于企業(yè)網(wǎng)絡(luò)營(yíng)銷推廣、網(wǎng)站重做改版、大安網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、H5建站、成都商城網(wǎng)站開(kāi)發(fā)、集團(tuán)公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站制作、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁(yè)設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性價(jià)比高,為大安等各大城市提供網(wǎng)站開(kāi)發(fā)制作服務(wù)。
上一章提到了一個(gè)關(guān)于 i++ 和 ++i 的面試題打趴了所有人,最終方案是在兩個(gè)方法上添加synchronized關(guān)鍵字,從而避免i++的線程安全問(wèn)題,不過(guò),這樣真的好嗎?在所有有線程安全的方法都添加synchronized?
答案是顯而易見(jiàn)的,不行。
synchronized會(huì)極大的降低程序的性能,導(dǎo)致整個(gè)程序幾乎只能支持單線程操作,性能顯著降低。
那么,如何解決呢?
鎖的粒度更小了,也解決了這個(gè)問(wèn)題,確實(shí)可以的。
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();
}
}為了更好的優(yōu)化,有的時(shí)候可以將synchronized代碼塊變?yōu)閰^(qū)分讀寫(xiě)場(chǎng)景的讀寫(xiě)鎖,也可以考慮悲觀鎖和樂(lè)觀鎖的區(qū)分。
對(duì)于讀寫(xiě)場(chǎng)景比較多的情況,可以使用ReentrantReadWriteLock區(qū)分讀寫(xiě),再次降低鎖的粒度,提高程序的性能。
ReentrantReadWriteLock 還可以選擇提供了公平鎖,在沒(méi)有明確必須使用公平鎖的情況下,盡量不要使用公平鎖,公平鎖會(huì)使程序性能降低很多很多。
簡(jiǎn)單來(lái)說(shuō),公平鎖(誰(shuí)先排隊(duì),誰(shuí)先執(zhí)行),非公平鎖(不用排隊(duì),每個(gè)人都有機(jī)會(huì))。
有一天早上,云韻、美杜莎、小醫(yī)仙結(jié)伴去買醬香拿鐵,到了咖啡店,先排隊(duì),一個(gè)一個(gè)來(lái)。不一會(huì),哪吒來(lái)了,也買醬香拿鐵,只能在末尾排隊(duì)。這個(gè)就是公平鎖。
但是呢?第二天早上,哪吒又去買醬香拿鐵,上一次去晚了沒(méi)買到(線程被餓死了),這次急了,要插隊(duì)買,不講武德。終于喝上了心心念念的醬香拿鐵,這個(gè)就是非公平鎖。
我們都知道,靜態(tài)字段屬于類,類級(jí)別的鎖才能保護(hù);非靜態(tài)字段屬于類實(shí)例,實(shí)例級(jí)別的鎖才能保護(hù)。
先看一下下面的代碼:
import lombok.Data;
import java.util.stream.IntStream;
@Data
public 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類中定義一個(gè)靜態(tài)變量time,定義一個(gè)非靜態(tài)方法increase(),實(shí)現(xiàn)time++自增。先累加10萬(wàn)次,測(cè)試一下??纯词欠裼芯€程安全的問(wèn)題。
這...不對(duì)啊,上一節(jié)在介紹高并發(fā)下i++線程安全問(wèn)題的時(shí)候,synchronized 是好使的啊。
今天這是怎么了?再運(yùn)行一次,結(jié)果依然如此,不等于100000
先來(lái)分析一下。
在非靜態(tài)的方法上加synchronized,只能確保多個(gè)線程無(wú)法執(zhí)行同一個(gè)實(shí)例的increase()方法,卻不能保證不同實(shí)例的increase()方法。靜態(tài)的變量time,在多個(gè)線程中共享,所以會(huì)出現(xiàn)線程安全的問(wèn)題,synchronized失效了。
那么,將synchronized改為靜態(tài)方法是不是就可以了,試一下。
有兩種寫(xiě)法,一種是直接將方法改為靜態(tài)方法,一種是使用synchronized代碼塊。
private static Object obj= new Object();
public void increase() {
synchronized (obj) {
time++;
}
}
很多小伙伴,可能會(huì)好奇,這個(gè)是干什么的,干了5年后端代碼開(kāi)發(fā)了,沒(méi)見(jiàn)過(guò)這玩意兒。
IntStream是一種特殊的stream,用來(lái)提供對(duì)int相關(guān)的stream操作。
IntStream.rangeClosed:生成某個(gè)數(shù)字范圍內(nèi)的數(shù)字集合的stream。
比如上面代碼中的IntStream.rangeClosed(1, 100000).parallel().forEach(i -> new LockTest().increase());。
Stream.parallel() 方法是 Java 8 中 Stream API 提供的一種并行處理方式。在處理大量數(shù)據(jù)或者耗時(shí)操作時(shí),使用 Stream.parallel() 方法可以充分利用多核 CPU 的優(yōu)勢(shì),提高程序的性能。
Stream.parallel() 方法是將串行流轉(zhuǎn)化為并行流的方法。通過(guò)該方法可以將大量數(shù)據(jù)劃分為多個(gè)子任務(wù)交由多個(gè)線程并行處理,最終將各個(gè)子任務(wù)的計(jì)算結(jié)果合并得到最終結(jié)果。使用 Stream.parallel() 可以簡(jiǎn)化多線程編程,減少開(kāi)發(fā)難度。
需要注意的是,并行處理可能會(huì)引入線程安全等問(wèn)題,需要根據(jù)具體情況進(jìn)行選擇。
定義一個(gè)list,然后通過(guò)parallel() 方法將集合轉(zhuǎn)化為并行流,對(duì)每個(gè)元素進(jìn)行i++,最后通過(guò) collect(Collectors.toList()) 方法將結(jié)果轉(zhuǎn)化為 List 集合。
使用并行處理可以充分利用多核 CPU 的優(yōu)勢(shì),加快處理速度。
public class StreamTest {
public static void main(String[] args) {
List list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(i);
}
System.out.println(list);
List result = list.stream().parallel().map(i -> i++).collect(Collectors.toList());
System.out.println(result);
}
} 我勒個(gè)去,什么情況?
這是大部分開(kāi)發(fā)人員都會(huì)犯的小錯(cuò)誤,在上篇中提到過(guò),i++ 返回原來(lái)的值,++i 返回加1后的值。這誰(shuí)都知道,可是,寫(xiě)的時(shí)候,就不一定了,因?yàn)槟懔?xí)慣了i++,寫(xiě)順手了,寫(xiě)的時(shí)候也是心不在焉,一蹴而就了。
i++改了++i即可。
優(yōu)點(diǎn):
缺點(diǎn):
在實(shí)際開(kāi)發(fā)中,應(yīng)該根據(jù)數(shù)據(jù)量、計(jì)算復(fù)雜度、硬件等因素綜合考慮。
比如:
名稱欄目:簡(jiǎn)單聊一聊公平鎖和非公平鎖,Parallel并行流
路徑分享:http://uogjgqi.cn/article/dpgscse.html

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流