前面講過昼弟,線程共享變量是非線程安全的属瓣,synchronized關(guān)鍵字可使方法變?yōu)榫€程安全的方法
一员舵、線程安全問題
private CountNum countNum;
public MyThread(CountNum countNum) {
this.countNum = countNum;
}
@Override
public void run() {
countNum.add("a");
}
public class MyThread2 extends Thread {
private CountNum countNum;
public MyThread2(CountNum countNum) {
this.countNum = countNum;
}
@Override
public void run() {
countNum.add("b");
}
}
public class TestMain {
public static void main(String[] args) {
CountNum countNum=new CountNum();
MyThread myThread=new MyThread(countNum);
MyThread2 myThread2=new MyThread2(countNum);
myThread.start();
myThread2.start();
}
}
輸出
a set over
b set over
name:b num:200
name:a num:200
兩個(gè)線程同時(shí)訪問一個(gè)沒有同步的方法扯躺,a修改完num的值睡眠時(shí)捉兴,b這時(shí)又修改了num的值,a打印時(shí)的num值其實(shí)已經(jīng)被b給修改了录语,這就是共享變量被多線程同事訪問時(shí)候的問題倍啥。
二、synchronized
解決上述問題很簡單澎埠,只需要在方法前加上synchronized關(guān)鍵字即可虽缕,即使a睡眠了,b也不會進(jìn)入方法執(zhí)行蒲稳,此時(shí)方法是同步順序執(zhí)行的氮趋,輸出如下
a set over
name:a num:100
b set over
name:b num:200
三、多個(gè)對象多個(gè)鎖
把TestMain代碼改成如下
public class TestMain {
public static void main(String[] args) {
CountNum countNum=new CountNum();
CountNum countNum2=new CountNum();
MyThread myThread=new MyThread(countNum);
MyThread2 myThread2=new MyThread2(countNum2);
myThread.start();
myThread2.start();
}
}
a set over
b set over
name:b num:200
name:a num:100
每個(gè)線程用不同的對象江耀,此時(shí)代碼又變?yōu)楫惒綀?zhí)行的剩胁,因?yàn)榇藭r(shí)synchronized的鎖是不同的對象。synchronized關(guān)鍵字取得的鎖都是對象鎖祥国。
注意:
A線程獲得某對象的鎖時(shí)昵观,其他線程可以以異步的方式調(diào)用該對象非synchronized類型的方法,但是其他線程調(diào)用其他synchronized的方法時(shí)需要等待
四舌稀、臟讀
public class PublicVar {
private String name="b";
private String passwd="bb";
public synchronized void setValue(String name,String passwd){
try {
this.name=name;
Thread.sleep(5000);
this.passwd=passwd;
System.out.println("setName "+Thread.currentThread().getName()+" name="+name+" passwd="+passwd);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public String getValue() {
System.out.println("getName "+Thread.currentThread().getName()+" name="+name+" passwd="+passwd);
return name;
}
}
public class MyThread extends Thread {
private PublicVar publicVar;
public MyThread(PublicVar publicVar) {
this.publicVar = publicVar;
}
@Override
public void run() {
publicVar.setValue("a", "aa");
}
}
public static void main(String[] args) throws InterruptedException {
PublicVar publicVar=new PublicVar();
MyThread myThread=new MyThread(publicVar);
myThread.start();
Thread.sleep(2000);
publicVar.getValue();
}
getName main name=a passwd=bb
setName Thread-0 name=a passwd=aa
此處出現(xiàn)臟讀的原因是getValue方法并不是同步的啊犬,主線程啟動完thread線程后休眠了2秒,thread線程在設(shè)置完name的值后休眠了5秒扩借,主線程醒來后繼續(xù)執(zhí)行g(shù)etValue方法椒惨,在休眠的兩秒時(shí)間里,name已經(jīng)被thread線程設(shè)置新值潮罪,但passwd還沒有康谆,所以出現(xiàn)了這種情況。
解決此問題的方法當(dāng)然是給getValue方法也加上同步synchronized關(guān)鍵字
總結(jié):
當(dāng)A線程調(diào)用anyObject對象加入synchronized關(guān)鍵字的X方法時(shí)嫉到,A線程就獲得了X方法鎖沃暗,更準(zhǔn)確的將,是獲得了對象的鎖何恶,所以其他線程必須等A線程執(zhí)行完畢才可以調(diào)用X方法孽锥,但其他線程可以隨意調(diào)用其他非synchronized的方法,而其他線程調(diào)用聲明synchronized關(guān)鍵字的非X方法時(shí),也必須等A線程將X方法執(zhí)行完惜辑,也就是釋放對象鎖后才可以調(diào)用唬涧。
五、synchronized鎖重入
可重入鎖:比如A線程獲得了某對象的鎖盛撑,此時(shí)這個(gè)對象鎖還沒有釋放碎节,當(dāng)其再次想獲取這個(gè)對象的鎖時(shí)還說可以獲取的,一個(gè)synchronized方法/塊的內(nèi)部調(diào)用本類或父類的其他synchronized方法/塊時(shí)抵卫,是永遠(yuǎn)可以得到鎖的狮荔。如果不可鎖重入的話,就會造成死鎖介粘。
當(dāng)存在父子類繼承關(guān)系時(shí)殖氏,子類是完全可以通過“可重入鎖”調(diào)用父類的同步方法的。
注意:
重寫父類的synchronized方法不具有同步效果姻采,如需同步仍要手動加上synchronized關(guān)鍵字
六雅采、死鎖
public class DealThread implements Runnable {
public String name;
public Object lock1 = new Object();
public Object lock2 = new Object();
public void setName(String name) {
this.name = name;
}
@Override
public void run() {
if ("a".equals(name)) {
synchronized (lock1) {
try {
System.out.println("name=" + name);
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("按lock1->lock2代碼順序執(zhí)行了");
}
}
}
if ("b".equals(name)) {
synchronized (lock2) {
try {
System.out.println("name=" + name);
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("按lock2->lock1代碼順序執(zhí)行了");
}
}
}
}
}
public static void main(String[] args) throws InterruptedException {
DealThread dealThread=new DealThread();
dealThread.setName("a");
Thread thread=new Thread(dealThread);
thread.start();
Thread.sleep(1000);
dealThread.setName("b");
Thread thread2=new Thread(dealThread);
thread2.start();
}
查看死鎖信息,進(jìn)入jdk的bin目錄執(zhí)行jps
命令偎谁,輸出如下总滩,發(fā)現(xiàn)TestMain線程的id為8196
3904 Jps
10628 Launcher
8196 TestMain
9892 RemoteMavenServer
11432
使用jstack命令查看結(jié)果jstack -l 8196
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x0000000002f1c978 (object 0x00000000d59a1d30, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x0000000002f188d8 (object 0x00000000d59a1d40, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at com.yjj.chapter02.test04_syn4.DealThread.run(DealThread.java:43)
- waiting to lock <0x00000000d59a1d30> (a java.lang.Object)
- locked <0x00000000d59a1d40> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at com.yjj.chapter02.test04_syn4.DealThread.run(DealThread.java:30)
- waiting to lock <0x00000000d59a1d40> (a java.lang.Object)
- locked <0x00000000d59a1d30> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
七、鎖對象改變
public class MyService {
private String lock="123";
public void testMethod(){
synchronized (lock){
try {
System.out.println(Thread.currentThread().getName()+" begin "+System.currentTimeMillis());
lock="456";
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+" end "+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadA extends Thread{
private MyService myService;
public ThreadA(MyService myService) {
this.myService = myService;
}
@Override
public void run() {
myService.testMethod();
}
}
ThreadA同ThreadB
public class TestMain {
public static void main(String[] args) throws InterruptedException {
MyService service=new MyService();
ThreadA a =new ThreadA(service);
a.setName("A");
ThreadB b =new ThreadB(service);
b.setName("B");
a.start();
Thread.sleep(50);
b.start();
}
}
可以看到A線程和B線程是異步執(zhí)行的巡雨,雖然加了synchronized關(guān)鍵字闰渔,但是由于A線程競爭的是鎖是“123”,得到鎖后把鎖改為了“456”铐望,而B線程競爭的是“456”冈涧,此時(shí)并沒有線程占用“456”的鎖,于是B線程也得到了鎖正蛙,就出現(xiàn)了如下的情況督弓。
A begin 1537950658147
B begin 1537950658196
A end 1537950660147
B end 1537950660196
要注意的是:只要對象不變,即使對象的屬性被改變乒验,運(yùn)行結(jié)果仍是同步的愚隧,也就是說把一個(gè)User對象當(dāng)成鎖,如果user的name屬性發(fā)生變化锻全,但是仍然是同一個(gè)user的話狂塘,仍會同步執(zhí)行
八、volatile關(guān)鍵字
關(guān)于volatile以后寫一篇專門的文章吧鳄厌,現(xiàn)在只需要知道 volatile只保證可見性荞胡、有序性,并不保證原子性了嚎,i++并不是一個(gè)原子操作
九泪漂、原子類用不好也不完全安全
public class MyService {
public static AtomicLong atomicLong=new AtomicLong();
public void addNum(){
System.out.println(Thread.currentThread().getName()+"加了100后值="+atomicLong.addAndGet(100));
atomicLong.addAndGet(1);
}
}
@Override
public void run() {
myService.addNum();
}
public static void main(String[] args) {
MyService myService=new MyService();
AddCountThread addCountThread=new AddCountThread(myService);
Thread t1=new Thread(addCountThread);
Thread t2=new Thread(addCountThread);
Thread t3=new Thread(addCountThread);
Thread t4=new Thread(addCountThread);
Thread t5=new Thread(addCountThread);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
Thread-1加了100后值=100
Thread-3加了100后值=300
Thread-2加了100后值=200
Thread-4加了100后值=403
Thread-5加了100后值=503
輸出結(jié)果如上廊营,發(fā)現(xiàn)并不是我們想要的結(jié)果,造成這個(gè)的原因是addAndGet()方法調(diào)用不是原子的萝勤,雖然這個(gè)方法是一個(gè)原子方法露筒,但是兩個(gè)addAndGet()方法之間的調(diào)用卻不是原子的,解決這一的問題必須要用同步敌卓,在addNum方法上synchronized或者用同步代碼塊包括在兩個(gè)addAndGet()方法外