在Java的集合類中,有些是同步不安全的,如HashMap,有些是同步安全的,如HashTable,Vector。
查看源碼會(huì)發(fā)現(xiàn),這些所謂同步安全的集合,其實(shí)現(xiàn)方式 就是 通過在某些方法上,如add,增加synchronized修飾。然后同步就實(shí)現(xiàn)了。
同步是什么,同步就是排隊(duì),比如公共電話亭打電話,一個(gè)電話,現(xiàn)在來了3個(gè)人,同步就是說第一個(gè)人打完了,第二個(gè)人再上,之后第三個(gè)人再上。
需要同步的原因是因?yàn)橛胁l(fā),有多個(gè)線程,單線程永遠(yuǎn)沒有同步的問題,那本身就是同步的。
多線程也未必就有同步的需要,當(dāng)眾多線程 對(duì)于 觀察對(duì)象,不進(jìn)行干擾的話,并發(fā)也不需要同步,比如看直播,1個(gè)人看直播,和10萬人看直播,你們只是看的話,是沒有并發(fā)問題的,不需要同步,只需要把直播畫面,拷貝10萬份給每個(gè)人就可以了。
但下面這種情況就有問題了,10個(gè)人在看一個(gè)主播直播,其中3個(gè)人希望主播換一首歌,換成自己喜歡的,如果這三個(gè)請(qǐng)求同時(shí)提出,那么主播該怎么辦。
如果三首同時(shí)放,那不可能,也沒法聽。
如果隨便忽略掉請(qǐng)求,也不算完美的解決辦法。
最后的辦法只能是 把并行的需求,串行化,一個(gè)一個(gè)來。
所以同步說白一點(diǎn),就是排隊(duì),一個(gè)一個(gè)來。
那一個(gè)一個(gè)來有什么問題呢? 這不又成單線程了嗎
所以這其中的問題是
1,單線程,正確,但一個(gè)一個(gè)來,處理的太慢。
2,多線程,并發(fā),同時(shí)做,肯定速度快,但某些點(diǎn)上,有可能出現(xiàn)錯(cuò)誤。
同步就要求,即快,又正確。這就是難點(diǎn),難在,需要分析出,
在這件事情中,哪些區(qū)域是可以并發(fā),也不會(huì)出問題的,哪些區(qū)域是不能并發(fā),還是要排隊(duì)的。
所以同步就是 多線程+單線程的組合。
用關(guān)鍵字synchronized,首先要分析出,要對(duì)誰進(jìn)行同步,先看個(gè)錯(cuò)誤例子,這里面用了synchronized,還是沒有達(dá)到同步的效果。
class Task implements Runnable{
synchronized void add(){
int tmp = TestMain.i;
tmp = tmp + 1;
TestMain.i = tmp;
}
public void run() {
//每個(gè)線程會(huì)增加50次
for(int i=0;i<50;i++){
add();
}
}
}
public class TestMain {
static int i=0;
static Object o = new Object();
public static void main(String[] args) throws InterruptedException {
// 創(chuàng)建100個(gè)線程
for(int i=0;i<100;i++){
Thread t = new Thread(new Task());
t.start();
}
Thread.sleep(1000);
System.out.println(i);
}
}
某一次輸出為
4969
這個(gè)程序會(huì)創(chuàng)建100個(gè)線程,每個(gè)線程執(zhí)行的任務(wù)是 對(duì) 變量i增加50次, 所以我期望的結(jié)果,i最后變成5000了,但事與愿違
這個(gè)程序基本每次輸出都不一樣,但都小于5000.
原因就處在add()方法上, 但我已經(jīng)給這個(gè)方法 加上synchronized關(guān)鍵字了,為什么還是同步不了呢?
這個(gè)問題困擾了我很久,直到---
因?yàn)槲覜]搞明白一個(gè)問題,synchronized是給誰加鎖的。
鎖在哪兒, Java中,每個(gè)對(duì)象都是鎖,包括實(shí)例出來的對(duì)象,類本身。
現(xiàn)在回頭看這個(gè)程序, 我把synchronized加再一個(gè) 普通方法上, 這時(shí) 鎖就是 實(shí)例化的Task對(duì)象, 它能實(shí)現(xiàn)的效果是, 多個(gè)線程,在操作這個(gè)實(shí)例的 add方法時(shí),保證它們是排隊(duì)進(jìn)行的。
可我的程序中
Thread t = new Thread(new Task()); 這段代碼, 在創(chuàng)建每個(gè)線程時(shí),都新創(chuàng)建 了一個(gè) 新的Task實(shí)例, 等于是100個(gè)線程,創(chuàng)建了100把鎖,足球場(chǎng)100個(gè)人,發(fā)了100個(gè)球,各玩各的,根本不同競(jìng)爭(zhēng)。 那么怎么修改呢
簡(jiǎn)單,只創(chuàng)建一個(gè) Task實(shí)例,給100個(gè)線程去玩,到時(shí)候,自然會(huì)競(jìng)爭(zhēng)add方法的, 代碼修改如下
class Task implements Runnable{
synchronized void add(){
int tmp = TestMain.i;
tmp = tmp + 1;
TestMain.i = tmp;
}
public void run() {
//每個(gè)線程會(huì)增加50次
for(int i=0;i<50;i++){
add();
}
}
}
public class TestMain {
static int i=0;
static Object o = new Object();
public static void main(String[] args) throws InterruptedException {
//只創(chuàng)建一個(gè)Task ,然后傳給100個(gè)線程。
Task task = new Task();
// 創(chuàng)建100個(gè)線程
for(int i=0;i<100;i++){
Thread t = new Thread(task);
t.start();
}
Thread.sleep(1000);
System.out.println(i);
}
}
輸出
5000
輸出5000, 符合預(yù)期。 這是synchronized的一種情況,對(duì)普通方法加鎖,鎖是該類實(shí)例后的 對(duì)象。
synchronized還有兩種用法, 修飾靜態(tài)方法, 和 修飾代碼塊。
下面的代碼是 同步靜態(tài)方法 的 例子,將原來的add方法改成static的了
class Task implements Runnable{
synchronized static void add(){
int tmp = TestMain.i;
tmp = tmp + 1;
TestMain.i = tmp;
}
public void run() {
//每個(gè)線程會(huì)增加50次
for(int i=0;i<50;i++){
add();
}
}
}
public class TestMain {
static int i=0;
static Object o = new Object();
public static void main(String[] args) throws InterruptedException {
//只創(chuàng)建一個(gè)Task ,然后傳給100個(gè)線程。
// 創(chuàng)建100個(gè)線程
for(int i=0;i<100;i++){
Thread t = new Thread(new Task());
t.start();
}
Thread.sleep(1000);
System.out.println(i);
}
}
輸出
5000 符合預(yù)期。
在這個(gè)例子中, 100個(gè)線程 創(chuàng)建了100個(gè)Task實(shí)例, 但并沒有出現(xiàn) 并發(fā)的錯(cuò)誤, 原因是 add是靜態(tài)方法, 和具體的實(shí)例無關(guān), 即使new100個(gè)實(shí)例,也是用的一個(gè)方法, synchronized修飾靜態(tài)方法, 對(duì)應(yīng)的鎖是這個(gè)靜態(tài)方法的類,所以 同步?jīng)]有問題。
第三種情況, synchronized代碼塊
class Task implements Runnable{
void add(){
synchronized (Task.class) {
int tmp = TestMain.i;
tmp = tmp + 1;
TestMain.i = tmp;
}
}
public void run() {
//每個(gè)線程會(huì)增加50次
for(int i=0;i<50;i++){
add();
}
}
}
public class TestMain {
static int i=0;
static Object o = new Object();
public static void main(String[] args) throws InterruptedException {
// 創(chuàng)建100個(gè)線程
for(int i=0;i<100;i++){
Thread t = new Thread(new Task());
t.start();
}
Thread.sleep(1000);
System.out.println(i);
}
}
這次add還是普通方法,而add的函數(shù)體 都包含在 synchronized中,
這樣寫的時(shí)候, synchronized后面有個(gè)括號(hào), 其中需要一個(gè)對(duì)象,也就是鎖, 這樣的好處是 這個(gè)鎖可以根據(jù)需要, 傳不同的對(duì)象,前面兩種方式 一個(gè)是 所在實(shí)例的作為鎖,一個(gè)是所在類作為鎖,有些不靈活, 第三種方法,你可以將任何對(duì)象傳進(jìn)來,這其實(shí)是為了 縮小鎖定的范圍。
畢竟鎖定和多線程是矛盾的。一定要選擇合適的 同步范圍 。
正確性是第一優(yōu)先的, 如果程序無法保證正確,那就無法使用,
在保證正確性的前提下, 縮小鎖定的范圍, 提高程序的整體效率,是追求的目標(biāo)。