多個線程調用同一個對象中的不同名稱的synchronized同步方法或synchronized(this)同步代碼塊時,調用的效果就是按順序執行,也就是同步的,阻塞的。
這說明synchronized同步方法或synchronized(this)同步代碼塊分別有兩種作用。
synchronized同步方法
synchronized同步方法或synchronized(this)同步代碼塊調用呈阻塞狀態。
同一時間只有一個線程可以執行synchronized同步方法中的代碼synchronized(this)同步代碼塊
對其他synchronized同步方法或synchronized(this)同步代碼塊調用呈阻塞狀態
同一時間只有一個線程可以執行synchronized(this)同步代碼塊中的代碼
在前面的學習中,使用synchronized(this)格式同步代碼塊,其實java還支持對“任意對象”作為“對象監聽器”來實現同步的功能。這個“任意對象”大多數是實例變量及方法的參數,使用格式為synchronized(非this對象)
根據前面對synchronized(this)同步代碼塊的作用總結可知,synchronized(非this對象)格式的作用只有1種:synchronized(非this對象x)同步代碼塊中的代碼
當持有“對象監視器”為同一個對象的前提下,同一時間只有一個線程可以執行synchronized(非this對象x)同步代碼塊中的代碼。
/**
* @author wuyoushan
* @date 2017/4/10.
*/
public class Service {
private String usernameParam;
private String passwordParam;
private String anyString=new String();
public void setUsernamePassword(String username,String password) {
try{
synchronized (anyString){
System.out.println("線程名稱為:"+Thread.currentThread().getName()
+"在"+System.currentTimeMillis()+"進入同步塊");
usernameParam=username;
Thread.sleep(3000);
passwordParam=password;
System.out.println("線程名稱為:"+Thread.currentThread().getName()
+"在"+System.currentTimeMillis()+"離開同步塊");
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
/**
* @author wuyoushan
* @date 2017/4/4.
*/
public class ThreadA extends Thread{
private Service service;
public ThreadA(Service service){
super();
this.service=service;
}
@Override
public void run() {
service.setUsernamePassword("a","aa");
}
}
/**
* @author wuyoushan
* @date 2017/4/4.
*/
public class ThreadB extends Thread{
private Service service;
public ThreadB(Service service){
super();
this.service=service;
}
@Override
public void run() {
service.setUsernamePassword("b","bb");
}
}
/**
* @author wuyoushan
* @date 2017/3/20.
*/
public class Run {
public static void main(String[] args){
Service service=new Service();
ThreadA a=new ThreadA(service);
a.setName("A");
a.start();
ThreadB b=new ThreadB(service);
b.setName("B");
b.start();
}
}
程序的運行結果為:
線程名稱為:A在1492391031020進入同步塊
線程名稱為:A在1492391034020離開同步塊
username:a password:aa
線程名稱為:B在1492391034020進入同步塊
線程名稱為:B在1492391037020離開同步塊
username:b password:bb
鎖非this對象具有一定的優點:如果在一個類中有很多個synchronized方法,這是雖然能實現同步,但會受到阻塞,所以影響運行效率;但如果使用同步代碼塊鎖非this對象,則synchronized(非this)代碼塊中的程序與同步方法是異步的,不與其他鎖this同步方法爭搶this鎖,則可大大提升運行效率。
將Service.java文件代碼更改如下:
/**
* @author wuyoushan
* @date 2017/4/10.
*/
public class Service {
private String usernameParam;
private String passwordParam;
public void setUsernamePassword(String username,String password) {
try{
String anyString=new String();
synchronized (anyString){
System.out.println("線程名稱為:"+Thread.currentThread().getName()
+"在"+System.currentTimeMillis()+"進入同步塊");
usernameParam=username;
Thread.sleep(3000);
passwordParam=password;
System.out.println("線程名稱為:"+Thread.currentThread().getName()
+"在"+System.currentTimeMillis()+"離開同步塊");
System.out.println("username:"+usernameParam+" password:"+passwordParam);
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
程序的運行結果如下所示:
線程名稱為:A在1492474468701進入同步塊
線程名稱為:B在1492474468702進入同步塊
線程名稱為:A在1492474471702離開同步塊
username:b password:aa
線程名稱為:B在1492474471702離開同步塊
username:b password:bb
可見,使用“synchronized(非this對象x)同步代碼塊”格式進行同步操作時,對象監視器必須是同一個對象。如果不是同一個對象監視器,運行的結果就是異步調用了,就會交叉運行。
下面我們再用另外一個項目來驗證一下使用“synchronized(非this對象x)同步代碼塊”格式時,持有不同的對象監聽器時異步的效果。
/**
* @author wuyoushan
* @date 2017/4/10.
*/
public class Service {
private String anyString=new String();
public void a() {
try{
synchronized (anyString){
System.out.println("a begin");
Thread.sleep(3000);
System.out.println("a end");
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
synchronized public void b() {
System.out.println("b begin");
System.out.println("b end");
}
}
/**
* @author wuyoushan
* @date 2017/4/4.
*/
public class ThreadA extends Thread{
private Service service;
public ThreadA(Service service){
super();
this.service=service;
}
@Override
public void run() {
service.a();
}
}
/**
* @author wuyoushan
* @date 2017/4/4.
*/
public class ThreadB extends Thread{
private Service service;
public ThreadB(Service service){
super();
this.service=service;
}
@Override
public void run() {
service.b();
}
}
/**
* @author wuyoushan
* @date 2017/3/20.
*/
public class Run {
public static void main(String[] args){
Service service=new Service();
ThreadA a=new ThreadA(service);
a.setName("A");
a.start();
ThreadB b=new ThreadB(service);
b.setName("B");
b.start();
}
}
程序的運行結果為:
a begin
b begin
b end
a end
由于對象監視器的不同,所以運行結果就是異步的。
同步代碼塊放在非同步synchronized方法中進行聲明,并不能保證調用方法的線程執行同步/順序性,也就是線程調用方法的順序是無序的,雖然在同步代碼塊中執行的順序是同步的,這樣極易出現“臟讀”問題。
使用“synchronized(非this對象x)同步代碼塊”格式也可以解決“臟讀”問題。但在解決臟讀問題之前,先做一個實驗,實驗的目標是驗證多個線程調用同一個方法是隨機的。
/**
* @author wuyoushan
* @date 2017/2/5.
*/
public class MyList{
private List list=new ArrayList();
synchronized public void add(String username){
System.out.println("ThreadName="+Thread.currentThread().getName()
+"執行了add方法");
list.add(username);
System.out.println("ThreadName="+Thread.currentThread().getName()
+"退出了add方法");
}
synchronized public int getSize(){
System.out.println("ThreadName="+Thread.currentThread().getName()
+"執行了getSize方法");
int sizeValue=list.size();
System.out.println("ThreadName="+Thread.currentThread().getName()
+"退出了getSize方法");
return sizeValue;
}
}
/**
* @author wuyoushan
* @date 2017/2/5.
*/
public class ThreadA extends Thread{
private MyList list;
public ThreadA( MyList list) {
super();
this.list = list;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
list.add("threadA"+(i+1));
}
}
}
/**
* @author wuyoushan
* @date 2017/2/5.
*/
public class ThreadB extends Thread {
private MyList list;
public ThreadB( MyList list) {
super();
this.list = list;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
list.add("threadA"+(i+1));
}
}
}
/**
* @author wuyoushan
* @date 2017/2/5.
*/
public class Run {
public static void main(String[] args) {
MyList myList=new MyList();
ThreadA a=new ThreadA(myList);
a.setName("A");
a.start();
ThreadB b=new ThreadB(myList);
b.setName("B");
b.start();
}
}
程序運行后的結果如下:
ThreadName=B執行了add方法
ThreadName=B退出了add方法
ThreadName=A執行了add方法
ThreadName=A退出了add方法
ThreadName=B執行了add方法
ThreadName=B退出了add方法
從運行的結果來看,同步代碼塊中的代碼是同步打印的,當前線程的“執行”與“退出”是成對出現的。但線程A和線程B的執行卻是異步的,這就有可能出現臟讀的環境。由于線程執行方法的順序不確定,所以當A和B兩個線程執行帶有分支判斷的方法時,就會出現邏輯上的錯誤,有可能出現臟讀。
/**
* @author wuyoushan
* @date 2017/4/19.
*/
public class MyOneList {
private List<String> list=new ArrayList<>();
synchronized public void add(String data){
list.add(data);
}
synchronized public int getSzie(){
return list.size();
}
}
/**
* @author wuyoushan
* @date 2017/4/19.
*/
public class MyService {
public MyOneList addServiceMethod(MyOneList list,String data){
try{
if(list.getSzie()<1){
Thread.sleep(2000); //模擬從遠程花費2秒取回數據
list.add(data);
}
}catch (InterruptedException e){
e.printStackTrace();
}
return list;
}
}
/**
* @author wuyoushan
* @date 2017/4/4.
*/
public class ThreadA extends Thread{
private MyOneList list;
public ThreadA(MyOneList list){
super();
this.list=list;
}
@Override
public void run() {
MyService service=new MyService();
service.addServiceMethod(list,"A");
}
}
/**
* @author wuyoushan
* @date 2017/4/4.
*/
public class ThreadB extends Thread{
private MyOneList list;
public ThreadB(MyOneList list){
super();
this.list=list;
}
@Override
public void run() {
MyService service=new MyService();
service.addServiceMethod(list,"B");
}
}
/**
* @author wuyoushan
* @date 2017/3/20.
*/
public class Run {
public static void main(String[] args) throws InterruptedException {
MyOneList list=new MyOneList();
ThreadA a=new ThreadA(list);
a.setName("A");
a.start();
ThreadB b=new ThreadB(list);
b.setName("B");
b.start();
Thread.sleep(6000);
System.out.println("listSize="+list.getSzie());
}
}
程序運行后的結果為:
listSize=2
臟讀出現了。出現的原因是兩個線程以異步的方式返回list參數的size()大小。解決辦法就是“同步化”
更改MyService.java類文件代碼:
/**
* @author wuyoushan
* @date 2017/4/19.
*/
public class MyService {
public MyOneList addServiceMethod(MyOneList list,String data){
try{
synchronized (list) {
if (list.getSzie() < 1) {
Thread.sleep(2000); //模擬從遠程花費2秒取回數據
list.add(data);
}
}
}catch (InterruptedException e){
e.printStackTrace();
}
return list;
}
}
由于list參數對象在項目中是一份實例,是單例的,而且也正需要對list參數的getSize()方法做同步的調用,所以就對list參數進行同步處理。
程序的運行結果為:
listSize=1
摘選自 java多線程核心編程技術-2.2.7