Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
tom(一款開源的代碼編輯器)是github專門為程序員推出的一個跨平臺文本編輯器。具有簡潔和直觀的圖形用戶界面,并有很多有趣的特點:支持CSS,HTML,JavaScript等網頁編程語言。它支持宏,自動完成分屏功能,集成了文件管理器。
用過Atom的朋友都知道,它是英文版的,那怎么把它變成中文呢
很簡單,打開Atom
我們選擇【file】—>【setting】
然后選擇【Install】
然后輸入【simplified-chinese-menu】然后點擊一下【Packages】進行搜索
然后在下面就可以看到我們搜索出來的內容,看到和我們搜索的標題一模一樣,我們就是需要安裝它
最后點擊右下角的【Install】來安裝我們的漢化包,安裝完成后不用重啟自動轉換成中文了
這里看到我們已經漢化成功了
-- END --
喜歡就關注我每天get點計算機知識~
tom編輯器
最近在折騰Atom編輯器,寫Python進行交互運行還是很方便的,代碼提示什么的比Jupyter也好一些,還可以配置一堆插件,定制自己的開發環境。對于代碼整齊性來說,aligner插件是不能錯過的。
atom-aligner是干什么的呢,就是用來對齊的,比如我們寫這樣一段變量賦值的程序:
雖然也沒問題,但是如果能按=對齊就更強迫癥一些。安裝插件后,選中要對齊的行,按快捷鍵:
Mac: ctrl-cmd-/ Linux/Windows: ctrl-alt-/
就會變為這樣
需要注意是,只安裝一個atom-aligner插件是不夠的,得配合不同語言的另一些插件,比如我寫python代碼,得另外裝一個aligner-python插件才可以。
如果是JSON格式的對象,也是可以按冒號:對齊的
要注意的是,如果連同=,{}一起選中,按快捷鍵對齊是不起作用的,得寫成這樣:
對齊后是這樣的
所以:左邊是不會對齊的,我們在編輯器寫代碼的時候一般都會左對齊了,像這樣
有個問題是,官方的說明里說是可以冒號對齊的,對齊成這樣
插件設置中沒有找到相關設置,官方說明看了半天也沒找到怎么設置,最后無奈改了插件的源碼。
進入插件設置
找到插件包配置,修改默認為left
重啟編輯器就可以了。
正如我開篇所說,我們要整理一些java并發編程的學習文檔,這一篇就是第一篇:原子操作。 主要說什么是原子操作,如何實現原子操作以及java中的原子操作類。
什么是原子操作,所謂原子操作,就是一個操作是不能打斷的操作。嗯.......確切的說應該是不被其他線程或者任務影響的操作。
沒錯,原子操作就是你在家里的一次上廁所的操作 >> 進廁所,上鎖,執行操作..... 身心愉悅,開鎖,離開.....
在程序中的體現就是一個線程在執行某個任務占用某個資源在操作的時候,不會被其他的線程或者任務搶走資源,直到這個任務結束釋放資源,其他的線程或者任務才能使用這個資源。
嗯......其實就是我們說的給資源上鎖,就是同步操作。。。。。
我們都知道 i++ 是給i變量加1。那么i++是不是原子操作呢?
看看程序:
先交代一下程序: 靜態成員變量x就是要操作的資源。 靜態方法incr()就是對x進行加1操作。在main方法中定義CountDownLatch對象,用來確保所有線程結束之后再輸出x的結果。 循環創建100個線程,每個線程調用incr()方法1000次,理論上x最終的值應該是100000。
package com.qidian.atom;
import java.util.concurrent.CountDownLatch;
/**
* @author 戴著假發的程序員
*/
public class Test {
// 靜態成員變量
private static int x = 0;
public static void main(String[] args) throws InterruptedException {
// 定義CountDownLatch 確保所有線程結束之后再輸出x的值
CountDownLatch cd = new CountDownLatch(100);
// 循環創建100個線程
for(int i = 0;i<100;i++){
new Thread("線程A"){
public void run() {
// 每個線程執行1000次incr方法
for (int j = 0;j<1000;j++){
incr();
}
cd.countDown();
}
}.start();
}
// 確保所有線程結束之后再繼續執行
cd.await();
// 輸出x的值
System.out.println(x);
}
// incr方法,執行x ++
public static void incr(){
x ++;
}
}
多執行幾次,我們會發現x的值有時并不是100000.
這樣的結果就是因為x ++不是原子操作。也就是說你在上廁所的時候沒有鎖門,導致有可能上到一半就被別人打斷了,所有你上的這100000次廁所,總有那么幾次可能沒有成功,身心沒有得到放松。。。。。。。
看圖理解一下被打斷的情況:
首先要知道 x++ 實際的操作是有兩步的 就是:
第一步: int z = x + 1;
第二步: x = z;
說明一下:線程A和線程B同時給X執行++操作。
線程A先獲取x的值,并且執行 z = x +1, 這時線程A打個盹,線程B獲取x的值,并且執行 z = x + 1,然后將z的值寫入x變量。 這時x就從10被修改為11。 這時線程A醒了,直接將自己計算的z(還是11)的值寫入x變量。就會導致x的值從11修改為11。這時我們會發現,線程B的加1操作被覆蓋了。這就導致線程B的這次+1操作失敗了。
這就是上面程序產生的非100000的結果的原因。其實解決辦法非常簡單,就是給incr方法上鎖。任何一個線程操作x的時候,其他線程都必須等待。哪怕當前正在操作的線程打盹,其他線程也不能操作x。
嗯!沒錯,這就是提醒你,上廁所一定要記得鎖門......永遠都無法忘記曾經發生在大學宿舍的凌晨廁所驚悚事件.....
于是乎,程序可以是這樣:使用synchronized修飾incr方法,當然了加鎖的方法有很多種。我們后面的文章再說鎖的問題。
package com.qidian.atom;
import java.util.concurrent.CountDownLatch;
?
/**
* @author 戴著假發的程序員
* @company 江蘇極刻知學-起點編程
*/
public class Test {
// 靜態成員變量
private static int x = 0;
public static void main(String[] args) throws InterruptedException {
// 定義CountDownLatch 確保所有線程結束之后再輸出x的值
CountDownLatch cd = new CountDownLatch(100);
// 循環創建100個線程
for(int i = 0;i<100;i++){
new Thread("線程A"){
public void run() {
// 每個線程執行1000次incr方法
for (int j = 0;j<1000;j++){
incr();
}
cd.countDown();
}
}.start();
}
// 確保所有線程結束之后再繼續執行
cd.await();
// 輸出x的值
System.out.println(x);
}
// incr方法,執行x ++
// 使用synchronized修飾incr方法,使其成為同步方法,其中的操作就無法被其他線程影響了
public synchronized static void incr(){
x ++;
}
}
加鎖之后,incr方法中的操作 i++ 就可以理解為原子操作了。當然我們也要明白,任何程序只要上鎖都會有效率問題。
java中給我們提供了幾個原子操作類,這幾個類提供了好用的API。這些個API對資源的操作都是原子操作,不會有線程安全問題。我們來see see。。。。
沒錯就是java.util.concurrent.atomic包下的所有類。
我們來看幾個經典的類的使用吧!
這個類是一個int類型的原子操作類。
看看他的AIP:
構造方法:
來看看一些經典的API
來來來!先根據程序理解一下API。 后面我們來翻翻源碼,看看有沒有啥問題。。。。
package com.qidian.atom;
?
import java.util.concurrent.atomic.AtomicInteger;
?
/**
* @author 戴著假發的程序員
*/
public class Test1 {
public static void main(String[] args) {
AtomicInteger atomi = new AtomicInteger(10);
// 將給定的值原子地添加到當前值。
int i = atomi.addAndGet(10);
System.out.println(i);// 20
// 如果當前值 ==為預期值,則將該值原子設置為給定的更新值。 如果更新成功就返回true,否則返回false。
boolean b = atomi.compareAndSet(20, 30);
System.out.println(b); // true
System.out.println(atomi.get());// 30
// 原子減1當前值。并且返回更新后的值。
i = atomi.decrementAndGet();
System.out.println(i); // 29
System.out.println(atomi.get()); // 29
// 將給定的值原子地添加到當前值。 并且返回更新前的值。
i = atomi.getAndAdd(50);
System.out.println(i); // 29
System.out.println(atomi.get()); // 79
// 原子減1當前值。 并且返回更新前的值。
i = atomi.getAndDecrement();
System.out.println(i);// 79
System.out.println(atomi.get());// 78
// 原子上增加一個當前值。 并且返回更新前的值。
i = atomi.getAndIncrement();
System.out.println(i);// 78
System.out.println(atomi.get());// 79
// 將原子設置為給定值并返回舊值。
i = atomi.getAndSet(100);
System.out.println(i);// 79
System.out.println(atomi.get());// 100
// 原子上增加一個當前值。并且返回更新后的值。
i = atomi.incrementAndGet();
System.out.println(i);// 101
System.out.println(atomi.get());// 101
}
}
OK! 看看源碼:
嗯。。。。。。。。。先看看addAndGet的源碼: AtomicInteger中的源碼:
/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the updated value
*/
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
原來使用的是unsafe類中的方法,ok我們繼續翻:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
// volatile 嗯..... 歡迎持續關注,后面的文章中會有這個關鍵字的詳解
var5 = this.getIntVolatile(var1, var2);
// 這里最終是使用 unsafe的compareAndSwapInt替換數據。
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
?
return var5;
}
嗯嗯嗯嗯! 再來看看別的方法:
compareAndSet方法的源碼:
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
decrementAndGet方法源碼:
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}
getAndAdd方法源碼:
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
看到這里我們想差不多夠了,我們也明白了, AtomicInteger類中的所有的API,做到原子操作更新值的底層都是通過unsafe的CAS實現的。
那么unsafe的CAS有沒有問題呢? (關于CAS后面的文章會有詳細說明,歡迎關注,你懂的,我最喜歡你關注我了。( ^__^ ) 嘻嘻…… )
當然都看到這里了,就點個攢吧!
看看這個類的說明:
public class AtomicReference<V>
extends Object
implements Serializable
可以原子更新的對象引用。
說白了就是對任何引用類型的原子操作類。
直接上菜:
/**
* @author 戴著假發的程序員
* @company 江蘇極刻知學-起點編程
*/
public class Test3 {
public static void main(String[] args) {
AtomicReference<String> ar = new AtomicReference<>("卡卡西");
System.out.println(ar.get()); // 卡卡西
String s = ar.get();
boolean b = ar.compareAndSet(s, "旗木卡卡西");
System.out.println(b);// true
System.out.println(ar.get());// 旗木卡卡西
}
}
嗯。。。。。應該不難理解吧!
CAS有個小問題那就是ABA問題。 休息5分鐘......... 可以瀏覽一下我的其他文章......
來吧,我來稍微聊聊ABA問題。
先看看CAS: CAS的全拼是:Compare And Swap 就是比較并且替換。什么意思呢?看圖吧
稍微說明一下:一個容器里面有一只雞,小明同學左手一只雞,右手一只鴨。左手的雞就是容器里面的預期值,如果左手的小雞和容器里面的小雞一樣,就拿右手的小鴨把容器里的小雞替換了。如果左手的預期值和容器里的小雞不一樣,那就算了,就不換了。
OK,看看ABA問題:
這個圖好亂,看看我們的描述吧:
①小明小明準備把一只雞替換成一只鴨,于是就查看了容器里的內容,發現是一只雞,他就在右手抓起一只雞作為預期值。準備執行CAS。 可是就在這時,小明肚子疼,去廁所了。 如花就開始了....
②③藍色的如花使用CAS將容器里面的一只雞替換成一頭豬。
④⑤如花不知道為什么感覺雞不劃算,于是乎又使用另外一只雞把豬給換回來了。
⑥這時小明上好廁所了,于是乎拿起自己之前準備好的雞開始和容器里面的小雞進行比較,發現果然一樣,于是就使用一只鴨替換了容器里面的雞。
在這個過程中,問題就是小明一開始按照容器里面的內容,準備了一只雞,再他上完廁所再回來比較的時候,容器里面的雞已經被如花替換過了,已經不是以前的雞了。所謂此雞非彼雞。所以理論上小明的CAS應該不能成功的,但是結果缺失成功的,這就是經典的ABA問題。
看程序理解一下
package com.qidian.atom;
?
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
?
/**
* @author 戴著假發的程序員
*/
public class Test2 {
// 原始值為 3
private static AtomicInteger ai = new AtomicInteger(3);
?
public static void main(String[] args) {
// 啟動線程1
new Thread(){
public void run() {
// 取出原來的值
int i = ai.get();// 這個值作為預期值
//打個盹
try {
TimeUnit.MICROSECONDS.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 使用cas替換
boolean b = ai.compareAndSet(i, 4);
System.out.println("線程1將3替換為4:"+b);
}
}.start();
// 再來一個線程
new Thread(){
public void run() {
// 取出原來的值
int i = ai.get();
// 將3替換為4
boolean b = ai.compareAndSet(i, 4);
System.out.println("線程2將3替換為4:"+b);
// 再次將4替換為3
b = ai.compareAndSet(4, 3);
System.out.println("線程2將4替換為3:"+b);
}
}.start();
}
}
結果:
理論上線程1之前取出的3已經不是現在的3了,但是依然替換成功了。
原子操作類也有ABA問題,于是乎準備了一個新的解決方案,那就是AtomicStampedReference類。
看看這個類的說明:
public class AtomicStampedReference<V>
extends Object
一個AtomicStampedReference維護對象引用以及整數“印記”,可以原子更新。
關鍵字:“印記”。 什么意思呢?
這個類為了解決ABA問題,給每個值增了一個“印記”,就好像,小明會給自己的雞蓋上一個印章,如花也會給自己的雞蓋上一個印章,這樣的話我們就可以通過印章輕松判斷,此雞是否是彼雞。
看看構造方法:
AtomicStampedReference(V initialRef, int initialStamp) 創建一個新的 AtomicStampedReference與給定的初始值。
這個類的API有限,但是有兩個比較經典的方法:
看程序理解理解
package com.qidian.atom;
?
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
?
/**
* @author 戴著假發的程序員
*/
public class Test4 {
private static AtomicStampedReference<String> asr = new AtomicStampedReference<>("卡卡西",1);
public static void main(String[] args) {
// 線程1
new Thread(){
public void run() {
// 取值
String s = asr.getReference();
// 獲取印記
int stamp = asr.getStamp();
// 打盹
try {
TimeUnit.MICROSECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 替換值 參數說明(原始值,新值,原始印記,新的印記)
boolean b = asr.compareAndSet("卡卡西","旗木卡卡西",stamp,stamp+1);
System.out.println("線程1,替換的結果:"+b);
}
}.start();
// 線程1
new Thread(){
public void run() {
// 取值
String s = asr.getReference();
// 獲取印記
int stamp = asr.getStamp();
// 第一次替換值 參數說明(原始值,新值,原始印記,新的印記)
boolean b = asr.compareAndSet("卡卡西","旗木五五開",stamp,stamp+1);
System.out.println("線程2,第一次替換的結果:"+b);
// 第二次替換值 參數說明(原始值,新值,原始印記,新的印記)
b = asr.compareAndSet("旗木五五開","卡卡西",stamp,stamp+1);
System.out.println("線程2,第二次替換的結果:"+b);
}
}.start();
}
}
結果:
稍微說明一下:
由于每次在CAS的比較中,不光要比較預期值,還要比較”印記“。而每次替換的時候都不光要提換值,還要修改印記,所以即使值一樣,如果印記不一樣,依然會替換失敗。
所以上面的程序就是線程2量次替換,第二次把”旗木五五開“替換回”卡卡西“之后,這個"卡卡西"的印記已經不是之前的”卡卡西“的印記了,所以線程1打盹結束之后,再去CAS就失敗啦!
至于其他的原子類嗎!!!! 嗯?。?!都差不多,就是多幾個類型唄,比如AtomicLong,AtomicIntegerArray之類的。就不在贅述了。
還有一些不咋用的,我就不詳細說了,有興趣可以研究研究,我們討論討論。
這一篇就到這里了。 歡迎關注,記得點贊。。。。。。。。
作者:戴著假發的程序員
鏈接:https://juejin.cn/post/7071530851684122660
*請認真填寫需求信息,我們會在24小時內與您取得聯系。