用關鍵字synchronized聲明方法是有弊端的。好比線程A調用同步方法執行一個長時間任務,那麼線程B就要等較長時間才能調用。java
下面看一個例子:git
public class Task {
private String getData1;
private String getData2;
public synchronized void longTimeTask(){
try {
System.out.println("begin task");
Thread.sleep(3000);
getData1 = "長時間處理任務後從遠程返回的值1 threadName=" + Thread.currentThread().getName();
getData2 = "長時間處理任務後從遠程返回的值2 threadName=" + Thread.currentThread().getName();
System.out.println(getData1);
System.out.println(getData2);
System.out.println("end task");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
複製代碼
public class Utils {
public static long begainTime1;
public static long endTime1;
public static long begainTime2;
public static long endTime2;
}
複製代碼
public class MyThread extends Thread{
private Task task;
private String name;
public MyThread(Task task, String name){
super();
this.task=task;
this.name=name;
super.setName(name);
}
@Override
public void run() {
super.run();
if ("A".equals(name)){
Utils.begainTime1 = System.currentTimeMillis();
task.longTimeTask();
Utils.endTime1 = System.currentTimeMillis();
}else {
Utils.begainTime2 = System.currentTimeMillis();
task.longTimeTask();
Utils.endTime2 = System.currentTimeMillis();
}
}
}
複製代碼
public class Main {
public static void main(String[] args){
Task task = new Task();
MyThread myThread = new MyThread(task, "A");
MyThread myThread1 = new MyThread(task, "B");
myThread.start();
myThread1.start();
try {
Thread.sleep(10000);
}catch (InterruptedException e){
e.printStackTrace();
}
long beginTime = Utils.begainTime1;
if (Utils.begainTime2<Utils.begainTime1){
beginTime = Utils.begainTime2;
}
long endTime = Utils.endTime1;
if (Utils.endTime2>Utils.endTime1){
endTime = Utils.endTime2;
}
System.out.println("耗時:" + (endTime-beginTime)/1000 + "s");
}
}
複製代碼
輸出內容:github
begin task
長時間處理任務後從遠程返回的值1 threadName=A
長時間處理任務後從遠程返回的值2 threadName=A
end task
begin task
長時間處理任務後從遠程返回的值1 threadName=B
長時間處理任務後從遠程返回的值2 threadName=B
end task
耗時:6s
複製代碼
從運行時間上來看,synchronized方法的問題很明顯。可使用synchronized同步塊來解決這個問題。可是要注意synchronized同步塊的使用方式,若是synchronized同步塊使用很差的話並不會帶來效率的提高。緩存
將上文的Task.class文件修改以下:bash
public void longTimeTask(){
try {
System.out.println("begin task");
Thread.sleep(3000);
String data1 = "長時間處理任務後從遠程返回的值1 threadName=" + Thread.currentThread().getName();
String data2 = "長時間處理任務後從遠程返回的值2 threadName=" + Thread.currentThread().getName();
synchronized (this){
getData1 = data1;
getData2 = data2;
}
System.out.println(getData1);
System.out.println(getData2);
System.out.println("end task");
}catch (InterruptedException e){
e.printStackTrace();
}
}
複製代碼
輸出以下:微信
begin task
begin task
長時間處理任務後從遠程返回的值1 threadName=B
長時間處理任務後從遠程返回的值2 threadName=A
end task
長時間處理任務後從遠程返回的值1 threadName=A
長時間處理任務後從遠程返回的值2 threadName=A
end task
耗時:3s
複製代碼
從上面代碼能夠看出當一個線程訪問一個對象的synchronized同步代碼塊時,另外一個線程任然能夠訪問該對象非synchronized同步代碼塊。不在synchronized塊中的就是異步執行,在synchronized塊中就是同步執行。多線程
當一個線程訪問一個對象的synchronized(this)同步代碼塊時,其餘線程對同一個object中的其餘synchronized(this)同步代碼塊訪問將被阻塞。異步
若是在一個類中有不少個synchronized方法,這是雖然能實現同步,但會受到阻塞。若是使用同步代碼塊鎖非this對象,則synchronized(非this)代碼塊中的程序與同步方法是異步的,不與其餘this同步方法爭搶this鎖。ide
關鍵字synchronized還能夠在static方法是使用,是對當前的*.java文件的Class類進行加鎖。非靜態的synchronized關鍵字是給對象加鎖。ui
public static void printA() {
synchronized (Service.class) {
try {
System.out.println(
"線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printA");
Thread.sleep(3000);
System.out.println(
"線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printA");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
synchronized public static void printB() {
System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printB");
System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printB");
}
synchronized public void printC() {
System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printC");
System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printC");
}
複製代碼
public class Main {
public static void main(String[] args){
Service service = new Service();
new Thread(Service::printA, "A").start();
new Thread(Service::printB, "B").start();
new Thread(() -> service.printC(), "C").start();
}
}
複製代碼
輸出內容:
線程名稱爲:A在1552262297299進入printA
線程名稱爲:C在1552262297300進入printC
線程名稱爲:C在1552262297300離開printC
線程名稱爲:A在1552262300301離開printA
線程名稱爲:B在1552262300301進入printB
線程名稱爲:B在1552262300301離開printB
複製代碼
從運行結果能夠看出:靜態同步synchronized方法與synchronized(class)代碼塊持有的鎖同樣,都是Class鎖,Class鎖對對象的全部實例起做用。synchronized關鍵字加到非static靜態方法上持有的是對象鎖。線程A,B和線程C持有的鎖不同,因此A和B運行同步,可是和C運行不一樣步。
JVM具備String常量池緩存的功能,將synchronized(string)與String聯合使用時會出現一些問題。
String s1 = "a";
String s2="a";
System.out.println(s1==s2);//true
複製代碼
好比兩個同步方法都是synchronized("abc"){}那麼多線程會持有相同的鎖,因此大多數同步代碼塊不用String做爲鎖。
本文代碼:GitHub
歡迎關注公衆號: