synchronized是進行同步處理而保證線程安全。在一個方法中,若是是方法內的私有變量,那個這個變量是線程安全的,可是類中的實例變量是可能會出現線程安全問題的,當多個線程對這個實例變量進行修改,就可能會出現結果並非咱們指望的結果。java
下面一段代碼就出現了線程安全問題。
原本當username爲a的時候,num應該爲100,可是因爲設置讓t1休眠了2秒,致使num被刷新成了200,致使最後輸出時a和b的num都是200。安全
public class Service { private int num = 0; public void add(String username) { try { if (username.equals("a")) { num = 100; Thread.sleep(2000); } else { num = 200; } System.out.println(username + " " + num); } catch (InterruptedException e) { e.printStackTrace(); } } } public class ThreadA extends Thread { private Service service; public ThreadA(Service service) { this.service = service; } @Override public void run() { super.run(); service.add("a"); } } public class ThreadB extends Thread { private Service service; public ThreadB(Service service) { this.service = service; } @Override public void run() { super.run(); service.add("b"); } } public class Test { public static void main(String[] args) { Service service = new Service(); ThreadA t1 = new ThreadA(service); ThreadB t2 = new ThreadB(service); t1.start(); t2.start(); } }
運行結果:
異步
下面給add方法加個synchronized關鍵字。
能夠看到輸出正確。
synchronized進行了同步,使得線程按照順序進行訪問,因爲線程t1和t2的監視器都是同一個實例,至關於擁有同一個鎖對象,因此能夠進行同步訪問。ide
public class Service { private int num = 0; public synchronized void add(String username) { try { if (username.equals("a")) { num = 100; Thread.sleep(2000); } else { num = 200; } System.out.println(username + " " + num); } catch (InterruptedException e) { e.printStackTrace(); } } }
輸出結果:
性能
上面的代碼中是使用synchronized將整個方法進行上鎖,只有當一個方法執行完畢後,另外一個線程才能夠執行這個方法,這樣會致使性能損耗很大。
返回到線程安全問題,目的實際上是爲了解決對臨界資源訪問的問題,因此其實只須要將臨界資源進行上鎖就能夠了,其餘部分實際上是能夠異步進行的。this
在下面代碼中,doSomeTask方法裏前半部分沒有進行同步,後面使用了同步代碼塊進行加鎖。
從輸出結果能夠看到,前面部分A、B兩個線程是異步進行訪問的,後部分是同步進行訪問的。線程
public class Service { public void doSomeTask(String username) { for (int i = 0; i < 5; i++) { System.out.println("沒有同步 " + Thread.currentThread().getName() + " " + i); } System.out.println(); synchronized (this) { for (int i = 0; i < 5; i++) { System.out.println("同步了" + Thread.currentThread().getName() + " " + i); } } } } public class ThreadA extends Thread { private Service service; public ThreadA(Service service) { this.service = service; } @Override public void run() { super.run(); service.doSomeTask("a"); } } public class ThreadB extends Thread { private Service service; public ThreadB(Service service) { this.service = service; } @Override public void run() { super.run(); service.doSomeTask("b"); } } public class Test { public static void main(String[] args) { Service service = new Service(); ThreadA t1 = new ThreadA(service); ThreadB t2 = new ThreadB(service); t1.setName("A"); t2.setName("B"); t1.start(); t2.start(); } }
運行結果:
code
因此使用synchronized同步代碼塊能夠將須要加鎖的部分進行上鎖就好了,這樣能夠提升性能。
可使用synchronized同步代碼塊加鎖臨界資源,這樣就能夠避免出現線程安全問題。
在JDK1.8中的ConcurrentHashMap也使用synchronized鎖,synchronized鎖的性能已經有了很大的提升。對象
static方法爲類方法,那麼對static方法加上synchronized鎖後呢?加鎖的到底是什麼呢?
其實這時候監視器上鎖的對象爲這個類對象,而不是一個具體的實例對象,就是全部該類的實例訪問這個方法都會進行加鎖。blog
從下面實例能夠看出,雖然是兩個實例四個線程訪問該方法,可是仍是進行了同步,由於全部實例訪問的是同一把鎖,也就是Service類的對象鎖,只要是監視器鎖對象是同一個,那麼都是會進行上鎖同步的。
public class Service { public static synchronized void doSomeTask(String username) { for (int i = 0; i < 5; i++) { System.out.println("同步了" + Thread.currentThread().getName() + " " + i); } } } public class ThreadA extends Thread { private Service service; public ThreadA(Service service) { this.service = service; } @Override public void run() { super.run(); service.doSomeTask("a"); } } public class ThreadB extends Thread { private Service service; public ThreadB(Service service) { this.service = service; } @Override public void run() { super.run(); service.doSomeTask("b"); } } public class Test { public static void main(String[] args) { Service service = new Service(); Service service1 = new Service(); ThreadA t1 = new ThreadA(service); ThreadB t2 = new ThreadB(service); ThreadA t3 = new ThreadA(service1); ThreadB t4 = new ThreadB(service1); t1.setName("A"); t2.setName("B"); t3.setName("C"); t4.setName("D"); t1.start(); t2.start(); t3.start(); t4.start(); } }
運行結果:
使用同步代碼塊也能夠實現上述功能。
在同步代碼塊中,將監視的鎖對象設置爲Service.class,表明監視的是這個類鎖,因此也對這個類進行加鎖同步。
public class Service { public void doSomeTask(String username) { synchronized (Service.class) { for (int i = 0; i < 5; i++) { System.out.println("同步了" + Thread.currentThread().getName() + " " + i); } } } }
四個線程訪問兩個方法,線程A和C訪問task1,是一個普通的synchronized方法,線程B和D訪問task2,是一個static synchronized方法。
從輸出結果中能夠看出,A、C進行了同步,B、D也進行同步,可是B和C在一開始是交替出現輸出,表明B和C其實沒有同步,就證實它們的鎖不是同一把鎖。
A和C線程獲取的實例的對象鎖,而B和D線程獲取的是這個類的鎖。
public class Service { public synchronized void task1() { for (int i = 0; i < 5; i++) { System.out.println("task1 " + Thread.currentThread().getName() + " " + i); } } public static synchronized void task2() { for (int i = 0; i < 5; i++) { System.out.println("task2 " + Thread.currentThread().getName() + " " + i); } } } public class ThreadA extends Thread { private Service service; public ThreadA(Service service) { this.service = service; } @Override public void run() { super.run(); service.task1(); } } public class ThreadB extends Thread { private Service service; public ThreadB(Service service) { this.service = service; } @Override public void run() { super.run(); service.task2(); } } public class Test { public static void main(String[] args) { Service service = new Service(); ThreadA t1 = new ThreadA(service); ThreadB t2 = new ThreadB(service); ThreadA t3 = new ThreadA(service); ThreadB t4 = new ThreadB(service); t1.setName("A"); t2.setName("B"); t3.setName("C"); t4.setName("D"); t1.start(); t2.start(); t3.start(); t4.start(); } }
運行結果:
綜上: