
Synchronized 我們在使用中,常用的都是使用同步代碼塊,如下:
Synchronized (obj){
}
那么其實我們知道,synchronized是Java中的關(guān)鍵字,是一種同步鎖。它修飾的對象有以下幾種:
- 修飾一個代碼塊
- 修飾一個方法
- 修改一個靜態(tài)的方法
- 修改一個類,其作用的范圍是synchronized后面括號括起來的部分,作用主的對象是這個類的所有對象。
但是為什么我們在日常使用中很少用來直接修飾靜態(tài)方法、或者類呢?
那么帶著這樣的問題,我們先來看一看上面的這些用法所帶來的后果是什么!
synchronized修飾一個方法
package com.deem.thread.test;
public class Tick2 implements Runnable {
private static int count;
public Tick2() {
count = 0;
}
@Override
public synchronized void run() {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
// Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
@Test
public void test(){
Tick2 t = new Tick2();
Thread t1 = new Thread(t,"tickThread1");
Thread t2 = new Thread(t,"tickThread2");
t1.start();
t2.start();
}
結(jié)果:
tickThread1:0
tickThread1:1
tickThread1:2
tickThread1:3
tickThread1:4
tickThread2:5
tickThread2:6
tickThread2:7
tickThread2:8
tickThread2:9
在這里我們可以看到先是tickThread1執(zhí)行完后,tickThread2才開始執(zhí)行的。
所以其實在這里我們來進行分析,synchronized 關(guān)鍵字在這里的用法獲取得到一個對象的鎖, 那么當tickThread1線程在執(zhí)行時,是已經(jīng)獲取得到了t這個對象的鎖,從而使得線程tickThread2被阻塞了,當tickThread1執(zhí)行完并釋放該對象鎖時,線程tickThread2才開始執(zhí)行。
關(guān)于修飾方法的寫法一般是下面兩種
寫法一
public synchronized void method()
{
// todo
}
寫法二
public void method()
{
synchronized(this) {
// todo
}
}
對于上面的是第一種寫法,第二種寫法其實是同步代碼塊的寫法,但在這里也是相當于修飾了方法,下面是第二種寫法,得出的結(jié)果與寫法一 一樣。
@Override
public void run() {
synchronized (this) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
// Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
synchronized修飾一個靜態(tài)方法
package com.deem.thread.test;
public class TickStatic implements Runnable {
private static int count;
public TickStatic() {
count = 0;
}
public void run() {
method();
}
public synchronized static void method() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
// Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
@Test
public void test1(){
TickStatic t_0 = new TickStatic();
TickStatic t_1 = new TickStatic();
Thread t1 = new Thread(t_0,"tickThread1");
Thread t2 = new Thread(t_1,"tickThread2");
t1.start();
t2.start();
}
運行結(jié)果
tickThread1:0
tickThread1:1
tickThread1:2
tickThread1:3
tickThread1:4
tickThread2:5
tickThread2:6
tickThread2:7
tickThread2:8
tickThread2:9
其實這個時候我們會發(fā)現(xiàn)為什么明明是創(chuàng)建了兩個對象,線程還能保持同步呢?
這是因為run中調(diào)用了靜態(tài)方法method,而靜態(tài)方法是屬于類的,所以syncThread1和syncThread2相當于用了同一把鎖。這個鎖其實也可以叫做為類鎖,
在后續(xù)的章節(jié)中,將會詳細的講訴下類鎖和對象鎖之間的區(qū)別。
synchronized修飾一個類
用法如下
class ClassName {
public void method() {
synchronized(ClassName.class) {
// todo
}
}
}
package com.deem.thread.test;
public class TickClass implements Runnable {
private static int count;
public TickClass() {
count = 0;
}
public void run() {
method();
}
public synchronized void method() {
synchronized (TickClass.class) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
// Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
運行結(jié)果與TickStatic 執(zhí)行的結(jié)果是一樣的,因為synchronized作用于一個類T時,是給這個類T加鎖,T的所有對象用的是同一把鎖。
其實在上面的幾個用法當中,我們不難發(fā)現(xiàn),當兩個線程或者多個線程進行運行時,因為對象鎖或者類鎖被線程1占有而未得到釋放,使得其他的線程都被阻塞住了,而我們在實際的生產(chǎn)環(huán)境中,這樣必將導致資源耗盡,效率很低。
synchronized同步代碼塊
用法一:
synchronized(this) {
//todo
}
用法二
Object obj =new Object();
synchronized(obj) {
//todo
}
第一種用法如下:
package com.deem.thread.test;
public class TickCodeBlock implements Runnable {
private static int count;
public TickCodeBlock() {
count = 0;
}
public void run() {
method();
}
public void method() {
synchronized (this) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
// Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
@Test
public void test3(){
TickCodeBlock t = new TickCodeBlock();
Thread t1 = new Thread(t,"tickThread1");
Thread t2 = new Thread(t,"tickThread2");
t1.start();
t2.start();
}
這時候,你會發(fā)現(xiàn),還是與上面相同的結(jié)果
tickThread1:0
tickThread1:1
tickThread1:2
tickThread1:3
tickThread1:4
tickThread2:5
tickThread2:6
tickThread2:7
tickThread2:8
tickThread2:9
但是呢 如果換成這種方式去運行呢
@Test
public void test3(){
Thread t1 = new Thread(new TickCodeBlock(),"tickThread1");
Thread t2 = new Thread(new TickCodeBlock(),"tickThread2");
t1.start();
t2.start();
}
結(jié)果
tickThread2:0
tickThread1:1
tickThread2:2
tickThread1:3
tickThread2:4
tickThread1:5
tickThread2:6
tickThread1:7
tickThread2:8
tickThread1:9
其實在這里我們可以知道,synchronized 用來給對象獲得對象鎖,當不同的對象時,對象鎖也是不一樣的,所以此時能夠保證兩個線程是互斥的,不會影響,從而也不會導致堵塞的情況
第二種用法如下:
package com.deem.thread.test;
public class TickCodeBlock implements Runnable {
private static int count;
private Object object =new Object();
public TickCodeBlock() {
count = 0;
}
public void run() {
method2();
}
public void method2() {
synchronized (object) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
// Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
運行的結(jié)果是:
tickThread1:1
tickThread2:0
tickThread1:2
tickThread2:3
tickThread1:4
tickThread2:5
tickThread1:6
tickThread2:7
tickThread1:8
tickThread2:9
說明:零長度的byte數(shù)組對象創(chuàng)建起來將比任何對象都經(jīng)濟――查看編譯后的字節(jié)碼:生成零長度的byte[]對象只需3條操作碼,而Object lock = new Object()則需要7行操作碼。
總結(jié)
- 當synchronized用來修飾靜態(tài)方法或者類時,將會使得這個類的所有對象都是共享一把類鎖,導致線程阻塞,所以這種寫法一定要規(guī)避
- 無論synchronized關(guān)鍵字加在方法上還是對象上,如果它作用的對象是非靜態(tài)的,則它取得的鎖是對象;如果synchronized作用的對象是一個靜態(tài)方法或一個類,則它取得的鎖是對類,該類所有的對象同一把鎖。
- 每個對象只有一個鎖(lock)與之相關(guān)聯(lián),誰拿到這個鎖誰就可以運行它所控制的那段代碼。
- 實現(xiàn)同步是要很大的系統(tǒng)開銷作為代價的,甚至可能造成死鎖,所以盡量避免無謂的同步控制。