1. 各種同步控制工具的使用
1.1 ReentrantLock
ReentrantLock感覺上是synchronized的增強版,synchronized的特點是使用簡單,一切交給JVM去處理,但是功能上是比較薄弱的。在JDK1.5之前,ReentrantLock的性能要好於synchronized,由於對JVM進行了優化,現在的JDK版本中,兩者性能是不相上下的。如果是簡單的實現,不要刻意去使用ReentrantLock。
相比於synchronized,ReentrantLock在功能上更加豐富,它具有可重入、可中斷、可限時、公平鎖等特點。
首先我們通過一個例子來說明ReentrantLock最初步的用法:
package test;
import java.util.concurrent.locks.ReentrantLock;public class Test implements Runnable{ public static ReentrantLock lock = new ReentrantLock(); public static int i = 0;
@Override public void run() { for (int j = 0; j < 10000000; j++)
{ lock.lock(); try
{
i++;
} finally
{ lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
Test test = new Test();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
有兩個線程都對i進行++操作,為了保證線程安全,使用了ReentrantLock,從用法上可以看出,與synchronized相比,ReentrantLock就稍微復雜一點。因為必須在finally中進行解鎖操作,如果不在finally解鎖,有可能代碼出現異常鎖沒被釋放,而synchronized是由JVM來釋放鎖。
那麼ReentrantLock到底有哪些優秀的特點呢?
1.1.1 可重入
單線程可以重復進入,但要重復退出
lock.lock();
lock.lock();try{
i++;
}
finally{
lock.unlock();
lock.unlock();
}
由於ReentrantLock是重入鎖,所以可以反復得到相同的一把鎖,它有一個與鎖相關的獲取計數器,如果擁有鎖的某個線程再次得到鎖,那麼獲取計數器就加1,然後鎖需要被釋放兩次才能獲得真正釋放(重入鎖)。這模仿了synchronized的語義;如果線程進入由線程已經擁有的監控器保護的 synchronized 塊,就允許線程繼續進行,當線程退出第二個(或者後續)synchronized塊的時候,不釋放鎖,只有線程退出它進入的監控器保護的第一個synchronized塊時,才釋放鎖。
public class Child extends Father implements Runnable{ final static Child child = new Child();//為了保證鎖唯一
public static void main(String[] args) { for (int i = 0; i < 50; i++) { new Thread(child).start();
}
}
public synchronized void doSomething() {
System.out.println("1child.doSomething()");
doAnotherThing(); // 調用自己類中其他的synchronized方法
}
private synchronized void doAnotherThing() { super.doSomething(); // 調用父類的synchronized方法
System.out.println("3child.doAnotherThing()");
}
@Override
public void run() {
child.doSomething();
}
}class Father { public synchronized void doSomething() {
System.out.println("2father.doSomething()");
}
}
我們可以看到一個線程進入不同的synchronized方法,是不會釋放之前得到的鎖的。所以輸出還是順序輸出。所以synchronized也是重入鎖
輸出:
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
...
1.1.2.可中斷
與synchronized不同的是,ReentrantLock對中斷是有響應的。中斷相關知識查看[高並發Java 二] 多線程基礎
普通的lock.lock()是不能響應中斷的,lock.lockInterruptibly()能夠響應中斷。
我們模擬出一個死鎖現場,然後用中斷來處理死鎖
package test;import java.lang.management.ManagementFactory;import java.lang.management.ThreadInfo;import java.lang.management.ThreadMXBean;import java.util.concurrent.locks.ReentrantLock;public class Test implements Runnable{ public static ReentrantLock lock1 = new ReentrantLock(); public static ReentrantLock lock2 = new ReentrantLock(); int lock; public Test(int lock)
{ this.lock = lock;
} @Override
public void run()
{ try
{ if (lock == 1)
{
lock1.lockInterruptibly(); try
{
Thread.sleep(500);
} catch (Exception e)
{ // TODO: handle exception
}
lock2.lockInterruptibly();
} else
{
lock2.lockInterruptibly(); try
{
Thread.sleep(500);
} catch (Exception e)
{ // TODO: handle exception
}
lock1.lockInterruptibly();
}
} catch (Exception e)
{ // TODO: handle exception
} finally
{ if (lock1.isHeldByCurrentThread())
{
lock1.unlock();
} if (lock2.isHeldByCurrentThread())
{
lock2.unlock();
}
System.out.println(Thread.currentThread().getId() + ":線程退出");
}
} public static void main(String[] args) throws InterruptedException {
Test t1 = new Test(1);
Test t2 = new Test(2);
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t2);
thread1.start();
thread2.start();
Thread.sleep(1000); //DeadlockChecker.check();
} static class DeadlockChecker
{ private final static ThreadMXBean mbean = ManagementFactory
.getThreadMXBean(); final static Runnable deadlockChecker = new Runnable()
{ @Override
public void run()
{ // TODO Auto-generated method stub
while (true)
{ long[] deadlockedThreadIds = mbean.findDeadlockedThreads(); if (deadlockedThreadIds != null)
{
ThreadInfo[] threadInfos = mbean.getThreadInfo(deadlockedThreadIds); for (Thread t : Thread.getAllStackTraces().keySet())
{ for (int i = 0; i < threadInfos.length; i++)
{ if(t.getId() == threadInfos[i].getThreadId())
{
t.interrupt();
}
}
}
} try
{
Thread.sleep(5000);
} catch (Exception e)
{ // TODO: handle exception
}
}
}
};
public static void check()
{
Thread t = new Thread(deadlockChecker);
t.setDaemon(true);
t.start();
}
}
}
上述代碼有可能會發生死鎖,線程1得到lock1,線程2得到lock2,然後彼此又想獲得對方的鎖。
我們用jstack查看運行上述代碼後的情況
下面舉個例子:
package test;import java.util.concurrent.CyclicBarrier;public class Test implements Runnable{ private String soldier; private final CyclicBarrier cyclic; public Test(String soldier, CyclicBarrier cyclic)
{ this.soldier = soldier; this.cyclic = cyclic;
} @Override
public void run()
{ try
{ //等待所有士兵到齊
cyclic.await();
dowork(); //等待所有士兵完成工作
cyclic.await();
} catch (Exception e)
{ // TODO Auto-generated catch block
e.printStackTrace();
}
} private void dowork()
{ // TODO Auto-generated method stub
try
{
Thread.sleep(3000);
} catch (Exception e)
{ // TODO: handle exception
}
System.out.println(soldier + ": done");
} public static class BarrierRun implements Runnable
{ boolean flag; int n; public BarrierRun(boolean flag, int n)
{ super(); this.flag = flag; this.n = n;
} @Override
public void run()
{ if (flag)
{
System.out.println(n + "個任務完成");
} else
{
System.out.println(n + "個集合完成");
flag = true;
}
}
} public static void main(String[] args)
{ final int n = 10;
Thread[] threads = new Thread[n]; boolean flag = false;
CyclicBarrier barrier = new CyclicBarrier(n, new BarrierRun(flag, n));
System.out.println("集合"); for (int i = 0; i < n; i++)
{
System.out.println(i + "報道");
threads[i] = new Thread(new Test("士兵" + i, barrier));
threads[i].start();
}
}
}
列印結果:
集合
士兵5: done士兵7: done士兵8: done士兵3: done士兵4: done士兵1: done士兵6: done士兵2: done士兵0: done士兵9: done10個任務完成
1.7 LockSupport
提供線程阻塞原語
和suspend類似
LockSupport.park();
LockSupport.unpark(t1);
與suspend相比不容易引起線程凍結
LockSupport的思想呢,和Semaphore有點相似,內部有一個許可,park的時候拿掉這個許可,unpark的時候申請這個許可。所以如果unpark在park之前,是不會發生線程凍結的。
下面的代碼是[高並發Java 二] 多線程基礎中suspend示例代碼,在使用suspend時會發生死鎖。
而使用LockSupport則不會發生死鎖。
另外
park()能夠響應中斷,但不拋出異常。中斷響應的結果是,park()函數的返回,可以從Thread.interrupted()得到中斷標志。
在JDK當中有大量地方使用到了park,當然LockSupport的實現也是使用unsafe.park()來實現的。
public static void park() { unsafe.park(false, 0L);
}
1.8 ReentrantLock 的實現
下面來介紹下ReentrantLock的實現,ReentrantLock的實現主要由3部分組成:
CAS狀態
等待隊列
park()
ReentrantLock的父類中會有一個state變數來表示同步的狀態
通過CAS操作來設置state來獲取鎖,如果設置成了1,則將鎖的持有者給當前線程
如果拿鎖不成功,則會做一個申請
首先,再去申請下試試看tryAcquire,因為此時可能另一個線程已經釋放了鎖。
如果還是沒有申請到鎖,就addWaiter,意思是把自己加到等待隊列中去
其間還會有多次嘗試去申請鎖,如果還是申請不到,就會被掛起
同理,如果在unlock操作中,就是釋放了鎖,然後unpark,這里就不具體講了。
2. 並發容器及典型源碼分析
2.1ConcurrentHashMap
我們知道HashMap不是一個線程安全的容器,最簡單的方式使HashMap變成線程安全就是使用Collections.synchronizedMap,它是對HashMap的一個包裝
同理對於List,Set也提供了相似方法。
但是這種方式只適合於並發量比較小的情況。
我們來看下synchronizedMap的實現
它會將HashMap包裝在裡面,然後將HashMap的每個操作都加上synchronized。
由於每個方法都是獲取同一把鎖(mutex),這就意味著,put和remove等操作是互斥的,大大減少了並發量。
下面來看下ConcurrentHashMap是如何實現的
在ConcurrentHashMap內部有一個Segment段,它將大的HashMap切分成若干個段(小的HashMap),然後讓數據在每一段上Hash,這樣多個線程在不同段上的Hash操作一定是線程安全的,所以只需要同步同一個段上的線程就可以了,這樣實現了鎖的分離,大大增加了並發量。
在使用ConcurrentHashMap.size時會比較麻煩,因為它要統計每個段的數據和,在這個時候,要把每一個段都加上鎖,然後再做數據統計。這個就是把鎖分離後的小小弊端,但是size方法應該是不會被高頻率調用的方法。
在實現上,不使用synchronized和lock.lock而是盡量使用trylock,同時在HashMap的實現上,也做了一點優化。這里就不提了。
2.2BlockingQueue
BlockingQueue不是一個高性能的容器。但是它是一個非常好的共享數據的容器。是典型的生產者和消費者的實現。
B. 寫命令行應用程序什麼不可或缺Go可以這樣處理命令行參數
Go語言內置的flag包實現了命令行參數的解析,flag包使得開發命令行工具更為簡單。
如果你只是簡單的想要獲取命令行參數,可以像下面的代碼示例一樣使用os.Args來獲取命令行參數。
將上面的代碼執行go build -o "args_demo"編譯之後,執行:
os.Args是一個存儲命令行參數的字元串切片,它的第一個元素是執行文件的名稱。
本文介紹了flag包的常用函數和基本用法,更詳細的內容請查看官方文檔。
flag包支持的命令行參數類型有bool、int、int64、uint、uint64、float float64、string、ration。
有以下兩種常用的定義命令行flag參數的方法。
基本格式如下:
flag.Type(flag名, 默認值, 幫助信息)*Type 例如我們要定義姓名、年齡、婚否三個命令行參數,我們可以按如下方式定義:
需要注意的是,此時name、age、married、delay均為對應類型的指針。
基本格式如下: flag.TypeVar(Type指針, flag名, 默認值, 幫助信息) 例如我們要定義姓名、年齡、婚否三個命令行參數,我們可以按如下方式定義:
通過以上兩種方法定義好命令行flag參數後,需要通過調用flag.Parse()來對命令行參數進行解析。
支持的命令行參數格式有以下幾種:
其中,布爾類型的參數必須使用等號的方式指定。
Flag解析在第一個非flag參數(單個」-「不是flag參數)之前停止,或者在終止符」–「之後停止。
定義
使用
命令行參數使用提示:
$ ./flag_demo -help
Usage of ./flag_demo:
-age int
年齡 (default 18)
-d ration
時間間隔
-married
婚否
-name string
姓名 (default "張三")
正常使用命令行flag參數:
使用非flag命令行參數:
原文鏈接:https://www.liwenzhou.com/posts/Go/go_flag/
C. 跪求JAVA五子棋源代碼
很sb的電腦五子棋:
import java.io.*;
import java.util.*;
public class Gobang {
// 定義一個二維數組來充當棋盤
private String[][] board;
// 定義棋盤的大小
private static int BOARD_SIZE = 15;
public void initBoard() {
// 初始化棋盤數組
board = new String[BOARD_SIZE][BOARD_SIZE];
// 把每個元素賦為"╋",用於在控制台畫出棋盤
for (int i = 0; i < BOARD_SIZE; i++) {
for (int j = 0; j < BOARD_SIZE; j++) {
// windows是一行一行來列印的。坐標值為(行值, 列值)
board[i][j] = "╋";
}
}
}
// 在控制台輸出棋盤的方法
public void printBoard() {
// 列印每個數組元素
for (int i = 0; i < BOARD_SIZE; i++) {
for (int j = 0; j < BOARD_SIZE; j++) {
// 列印數組元素後不換行
System.out.print(board[i][j]);
}
// 每列印完一行數組元素後輸出一個換行符
System.out.print("\n");
}
}
// 該方法處理電腦下棋:隨機生成2個整數,作為電腦下棋的坐標,賦給board數組。
private void compPlay() {
// 構造一個隨機數生成器
Random rnd = new Random();
// Random類的nextInt(int n))方法:隨機地生成並返回指定范圍中的一個 int 值,
// 即:在此隨機數生成器序列中 0(包括)和 n(不包括)之間均勻分布的一個int值。
int compXPos = rnd.nextInt(15);
int compYPos = rnd.nextInt(15);
// 保證電腦下的棋的坐標上不能已經有棋子(通過判斷對應數組元素只能是"╋"來確定)
while (board[compXPos][compYPos].equals("╋") == false) {
compXPos = rnd.nextInt(15);
compYPos = rnd.nextInt(15);
}
System.out.println(compXPos);
System.out.println(compYPos);
// 把對應的數組元素賦為"○"。
board[compXPos][compYPos] = "○";
}
// 該方法用於判斷勝負:進行四次循環掃描,判斷橫、豎、左斜、右斜是否有5個棋連在一起
private boolean judgeWin() {
// flag表示是否可以斷定贏/輸
boolean flag = false;
// joinEle:將每一個橫/豎/左斜/右斜行中的元素連接起來得到的一個字元串
String joinEle;
// 進行橫行掃描
for (int i = 0; i < BOARD_SIZE; i++) {
// 每掃描一行前,將joinEle清空
joinEle = "";
for (int j = 0; j < BOARD_SIZE; j++) {
joinEle += board[i][j];
}
// String類的contains方法:當且僅當該字元串包含指定的字元序列時,返回true。
if (joinEle.contains("●●●●●")) {
System.out.println("您贏啦!");
flag = true;
// 停止往下繼續執行,提前返回flag。
// 如果執行了這個return,就直接返回該方法的調用處;
// 不會再執行後面的任何語句,包括最後那個return語句。
// (而break僅僅是完全跳出這個for循環,還會繼續執行下面的for循環。)
return flag;
} else if (joinEle.contains("○○○○○")) {
System.out.println("您輸啦!");
flag = true;
// 提前返回flag
return flag;
}
}
// 進行豎行掃描
for (int i = 0; i < BOARD_SIZE; i++) {
joinEle = "";
for (int j = 0; j < BOARD_SIZE; j++) {
// 豎行的元素是它們的列值相同
joinEle += board[j][i];
}
if (joinEle.contains("●●●●●")) {
System.out.println("您贏啦!");
flag = true;
return flag;
} else if (joinEle.contains("○○○○○")) {
System.out.println("您輸啦!");
flag = true;
return flag;
}
}
// 進行左斜行掃描
for (int i = -(BOARD_SIZE - 2); i < BOARD_SIZE - 1; i++) {
joinEle = "";
for (int j = 0; j < BOARD_SIZE; j++) {
int line = i + j;
// 只截取坐標值沒有越界的點
if (line >= 0 && line < 15) {
joinEle += board[j][line];
}
}
if (joinEle.contains("●●●●●")) {
System.out.println("您贏啦!");
flag = true;
return flag;
} else if (joinEle.contains("○○○○○")) {
System.out.println("您輸啦!");
flag = true;
return flag;
}
}
// 進行右斜行掃描
for (int i = 1; i < 2 * (BOARD_SIZE - 1); i++) {
joinEle = "";
for (int j = 0; j < BOARD_SIZE; j++) {
int line = i - j;
if (line >= 0 && line < 15) {
joinEle += board[j][line];
}
}
if (joinEle.contains("●●●●●")) {
System.out.println("您贏啦!");
flag = true;
return flag;
} else if (joinEle.contains("○○○○○")) {
System.out.println("您輸啦!");
flag = true;
// 最後這個return可省略
}
}
// 確保該方法有返回值(如果上面條件都不滿足時)
return flag;
}
public static void main(String[] args) throws Exception, IOException {
Gobang gb = new Gobang();
gb.initBoard();
gb.printBoard();
// BufferedReader類:帶緩存的讀取器————從字元輸入流中讀取文本,並緩存字元。可用於高效讀取字元、數組和行。
// 最好用它來包裝所有其 read() 操作可能開銷很高的 Reader(如 FileReader 和 InputStreamReader)。
// 下面構造一個讀取器對象。
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// 定義輸入字元串
String inputStr = null;
// br.readLine():每當在鍵盤上輸入一行內容按回車,剛輸入的內容將被br(讀取器對象)讀取到。
// BufferedReader類的readLine方法:讀取一個文本行。
// 初始狀態由於無任何輸入,br.readLine()會拋出異常。因而main方法要捕捉異常。
while ((inputStr = br.readLine()) != null) {
// 將用戶輸入的字元串以逗號(,)作為分隔符,分隔成2個字元串。
// String類的split方法,將會返回一個拆分後的字元串數組。
String[] posStrArr = inputStr.split(",");
// 將2個字元串轉換成用戶下棋的坐標
int xPos = Integer.parseInt(posStrArr[0]);
int yPos = Integer.parseInt(posStrArr[1]);
// 校驗用戶下棋坐標的有效性,只能是數字,不能超出棋盤范圍
if (xPos > 15 || xPos < 1 || yPos > 15 || yPos < 1) {
System.out.println("您下棋的坐標值應在1到15之間,請重新輸入!");
continue;
}
// 保證用戶下的棋的坐標上不能已經有棋子(通過判斷對應數組元素只能是"╋"來確定)
// String類的equals方法:比較字元串和指定對象是否相等。結果返回true或false。
if (gb.board[xPos - 1][yPos - 1].equals("╋")) {
// 把對應的數組元素賦為"●"。
gb.board[xPos - 1][yPos - 1] = "●";
} else {
System.out.println("您下棋的點已有棋子,請重新輸入!");
continue;
}
// 電腦下棋
gb.compPlay();
gb.printBoard();
// 每次下棋後,看是否可以斷定贏/輸了
if (gb.judgeWin() == false) {
System.out.println("請輸入您下棋的坐標,應以x,y的格式:");
} else {
// 完全跳出這個while循環,結束下棋
break;
}
}
}
}
D. 怎麼卸載源碼編譯安裝的軟體
第一,看大家安裝軟體的時候有沒有使用--prefix這個命令,如果有那就把你指定的文件給刪除了就行了,
第二,如果沒有用這個信念指定就麻煩一點了,那就進入到軟體解壓後的目錄,執行sudo make uninstall看能否成功,
第三,如果不行,那就進入用 editor 查看 makefile 文件 看看裡面卸載的命令是什麼如果該源碼包沒有提供
此類方法刪除 就只能手動刪除
第四,手動刪除最無奈的辦法能不能搞干凈也只能看運氣了,使用whereis xxx 找到軟體安裝目錄,rm -rf 把
這些目錄都刪除,應該能刪除干凈,如whereis python,例子如下:
whereis python
python: /usr/bin/python2.6-config /usr/bin/python2.6 /usr/bin/python /usr/lib/python2.6 /usr/lib64/python2.6 /usr/local/bin/python3.3m-config /usr/local/bin/python3.3m /usr/local/bin/python3.3 /usr/local/bin/python3.3-config /usr/local/lib/python3.3 /usr/include/python2.6 /usr/share/man/man1/python.1.gz
rm -rf /usr/bin/python2.6-config
rm -rf /usr/bin/python2.6
rm -rf /usr/bin/python
rm -rf /usr/lib/python2.6
rm -rf /usr/lib64/python2.6
...................等等,,,
好了,文法就這些吧,如果大家有更好的方法可以評論補充,禁止吐糟,加水。