前言
著重掌握如下技術(shù)點(diǎn):
1.synchronized 對象監(jiān)視器為Object的使用暂幼;
2.synchronized 對象監(jiān)視器為Class時的使用蛛碌;
3.非線程安全時如何出現(xiàn)的炕檩;
4.關(guān)機(jī)字volatile的主要作用屎飘;
5.關(guān)鍵字volatile與synchronized的區(qū)別與使用情況凡人。
synchronized同步方法
非線程安全會在多個線程對同一個對象中的實(shí)例變量進(jìn)行并發(fā)訪問時發(fā)生措伐,產(chǎn)生的后果就是“臟讀”唠亚,也就是取到的數(shù)據(jù)是被更改過的区丑。
而線程安全就是已獲得的實(shí)例變量的值是經(jīng)過同步處理的棘利,不會出現(xiàn)臟讀的線程橱野。
2.1.1方法內(nèi)的變量為線程安全
“非線程安全”問題存在于“實(shí)例變量中”朽缴,如果是方法內(nèi)部的私有變量善玫,則不存在“非線程安全問題”,所以結(jié)果也是線程安全的密强。
下面例子實(shí)現(xiàn)方法內(nèi)部聲明一個變量茅郎,是不存在非線程安全問題的。
public class HasSelfPrivateNum {
public void addI(String userName) {
int num;
if(userName.equals("a")){
num=100;
System.out.println("a set off");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
num=200;
System.out.println("b set off");
}
System.out.println(userName+" num="+num);
}
}
class ThreadA extends Thread {
private HasSelfPrivateNum numRef;
public ThreadA(HasSelfPrivateNum numRef) {
this.numRef = numRef;
}
@Override
public void run() {
super.run();
numRef.addI("a");
}
}
class ThreadB extends Thread {
private HasSelfPrivateNum numRef;
public ThreadB(HasSelfPrivateNum numRef) {
this.numRef = numRef;
}
@Override
public void run() {
super.run();
numRef.addI("b");
}
}
class Run{
public static void main(String[] args) {
HasSelfPrivateNum numRef = new HasSelfPrivateNum();
ThreadA threadA = new ThreadA(numRef);
threadA.start();
ThreadB threadB = new ThreadB(numRef);
threadB.start();
}
}
結(jié)果為:
b set off
b num=200
a set off
a num=100
可見方法中的內(nèi)部變量num所執(zhí)行的操作 不存在非線程安全問題或渤。
2.1.2實(shí)例變量非線程安全
如果多個線程共同訪問一個對象中的實(shí)例變量就有可能出現(xiàn)“非線程安全問題”系冗。
用線程訪問的對象中如果有多個實(shí)例變量,則運(yùn)行結(jié)果有可能出現(xiàn)交叉的情況薪鹦。
如果對象中僅有一個實(shí)例變量掌敬,則有可能出現(xiàn)覆蓋。
/**
* 實(shí)例變量num被多個線程訪問
* @author Arthur
* @date 2017-12-25 14:15
*/
public class HasSelfPrivateNum {
private int num =0;
public void addI(String userName) {
if(userName.equals("a")){
num=100;
System.out.println("a set off");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
num=200;
System.out.println("b set off");
}
System.out.println(userName+" num="+num);
}
}
class ThreadA extends Thread {
private HasSelfPrivateNum numRef;
public ThreadA(HasSelfPrivateNum numRef) {
this.numRef = numRef;
}
@Override
public void run() {
super.run();
numRef.addI("a");
}
}
class ThreadB extends Thread {
private HasSelfPrivateNum numRef;
public ThreadB(HasSelfPrivateNum numRef) {
this.numRef = numRef;
}
@Override
public void run() {
super.run();
numRef.addI("b");
}
}
class Run{
public static void main(String[] args) {
HasSelfPrivateNum numRef = new HasSelfPrivateNum();
ThreadA threadA = new ThreadA(numRef);
threadA.start();
ThreadB threadB = new ThreadB(numRef);
threadB.start();
}
}
a set off
b set off
b num=200
a num=200
本例子是兩個線程同時訪問一個沒用同步的的方法池磁,如果兩個線程同時操作業(yè)務(wù)對象中的實(shí)例變量奔害,則會出現(xiàn)“非線程安全”問題。解決方案是只要在方法前加synchronized關(guān)鍵字即可地熄。在兩個線程訪問同一個對象中的同步方法時一定是線程安全的华临。
2.1.3多個對象多個鎖
public class HasSelfPrivateNum {
private int num = 0;
synchronized public void addI(String userName) {
if(userName.equals("a")){
num=100;
System.out.println("a set off");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
num=200;
System.out.println("b set off");
}
System.out.println(userName+" num="+num);
}
}
class ThreadA extends Thread {
private HasSelfPrivateNum numRef;
public ThreadA(HasSelfPrivateNum numRef) {
this.numRef = numRef;
}
@Override
public void run() {
super.run();
numRef.addI("a");
}
}
class ThreadB extends Thread {
private HasSelfPrivateNum numRef;
public ThreadB(HasSelfPrivateNum numRef) {
this.numRef = numRef;
}
@Override
public void run() {
super.run();
numRef.addI("b");
}
}
class Run{
public static void main(String[] args) {
HasSelfPrivateNum numRef = new HasSelfPrivateNum();
HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();
ThreadA threadA = new ThreadA(numRef);
threadA.start();
ThreadB threadB = new ThreadB(numRef2);
threadB.start();
}
}
a set off
b set off
b num=200
a num=100
上述代碼由于創(chuàng)建了2個HasSelfPrivateNum 實(shí)例,擁有了兩個鎖端考,雖然方法是同步的雅潭,但是造成的結(jié)果卻是異步的。效果是先打印出b的值 再打印出a的值却特。
為什么是這樣的呢扶供?synchronized關(guān)鍵字是對對象加鎖,而不是對方法或代碼塊加鎖裂明,上述例子中哪個線程先執(zhí)行帶synchronized的方法椿浓,哪個線程就先獲得該方法所屬對象的鎖Lock,其他線程等待,前提是多個線程訪問的是同一個對象轰绵。
但如果多個線程訪問多個對象粉寞,JVM就會創(chuàng)建多個鎖。上述問題就是如此左腔。
2.1.4synchronized方法與鎖對象
為了驗(yàn)證線程鎖的是對象唧垦,代碼如下:
/**
* 驗(yàn)證線程鎖的是對象
*
* @author Arthur
* @date 2017-12-26 16:08
*/
public class SynchronizedMethodLock {
public static void main(String[] args) {
MyObject myObject = new MyObject();
MyThreadA myThreadA = new MyThreadA(myObject);
MyThreadB myThreadB = new MyThreadB(myObject);
myThreadA.setName("A");
myThreadB.setName("B");
myThreadA.start();
myThreadB.start();
}
}
class MyObject{
public void method() throws InterruptedException {
System.out.println("begin threadName:"+Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("end");
}
}
class MyThreadA extends Thread{
private MyObject myObject;
public MyThreadA(MyObject myObject) {
this.myObject = myObject;
}
@Override
public void run() {
super.run();
try {
myObject.method();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyThreadB extends Thread{
private MyObject myObject;
public MyThreadB(MyObject myObject) {
this.myObject = myObject;
}
@Override
public void run() {
super.run();
try {
myObject.method();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
運(yùn)行結(jié)果為
begin threadName:A
begin threadName:B
A end
B end
當(dāng)method()方法用synchronized修飾時
public synchronized void method()
運(yùn)行結(jié)果為
begin threadName:A
A end
begin threadName:B
B end
可見調(diào)用關(guān)鍵字synchronized聲明的方法一定是排隊(duì)運(yùn)行的。另外只有共享的資源的讀寫訪問才需要同步化處理液样!
那其他方法在被調(diào)用時是什么效果呢振亮?如何查看Lock鎖對象的效果呢?更新代碼如下:
/**
* 驗(yàn)證線程鎖的是對象
*
* @author Arthur
* @date 2017-12-26 16:08
*/
public class SynchronizedMethodLock {
public static void main(String[] args) {
MyObject myObject = new MyObject();
MyThreadA myThreadA = new MyThreadA(myObject);
MyThreadB myThreadB = new MyThreadB(myObject);
myThreadA.setName("A");
myThreadB.setName("B");
myThreadA.start();
myThreadB.start();
}
}
class MyObject{
public /*synchronized*/ void method() throws InterruptedException {
System.out.println("begin threadName:"+Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName()+" end");
}
}
class MyThreadA extends Thread{
private MyObject myObject;
public MyThreadA(MyObject myObject) {
this.myObject = myObject;
}
@Override
public void run() {
super.run();
try {
myObject.method();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyThreadB extends Thread{
private MyObject myObject;
public MyThreadB(MyObject myObject) {
this.myObject = myObject;
}
@Override
public void run() {
super.run();
try {
myObject.method();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
結(jié)果如下
begin threadName:A
begin threadName:B
A end
B end
可以看到即使methodA為同步方法鞭莽,線程A持有了myObject對象的鎖坊秸,但是線程B仍能夠異步調(diào)用非synchronized方法methodB。
繼續(xù)試驗(yàn)澎怒,在methodB方法上也加上synchronized
public synchronized void methodB()
運(yùn)行結(jié)果為按順序執(zhí)行褒搔。
begin methodA threadName:A
A end
begin methodB threadName:B
B end
以此可以得出結(jié)論
1)A線程先持有object對象的鎖,B線程可以以異步的方法調(diào)用object對象中非synchronized的方法喷面。
2)A線程先持有object對象的鎖星瘾,B線程如果這時調(diào)用object對象中的synchronized的方法那么需要等待,也就是同步惧辈。
2.1.5臟讀
前面說明了在多個線程調(diào)用同一方法時琳状,為了避免數(shù)據(jù)交叉的情況,用synchroinized同步盒齿。但是有些操作雖然在賦值時進(jìn)行了同步念逞,但在取值的時候可能出現(xiàn)意外,這種情況就是臟讀边翁。發(fā)生臟讀的情況是在讀取實(shí)例變量的時候翎承,此值已經(jīng)被其他線程更改過了。
/**
* 臟讀現(xiàn)象
*
* @author Arthur
* @date 2017-12-26 17:07
*/
public class Run {
public static void main(String[] args) throws InterruptedException {
PublicVar varRef = new PublicVar();
ThreadA threadA = new ThreadA(varRef);
threadA.start();
threadA.setName("threadA");
Thread.sleep(200);//打印結(jié)果受此值大小影響
varRef.getValue();
}
}
class PublicVar{
public String username = "A";
public String password = "AA";
synchronized public void setValue(String username, String password) {
try {
this.username = username;
Thread.sleep(5000);
this.password = password;
System.out.println("setValue method threadName="+Thread.currentThread().getName()+" username="+username+" password="+password);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void getValue() {
System.out.println("getValue method threadName="+Thread.currentThread().getName()+" username="+username+" password="+password);
}
}
class ThreadA extends Thread{
private PublicVar var;
public ThreadA(PublicVar var) {
this.var = var;
}
@Override
public void run() {
super.run();
var.setValue("B","BB");
}
}
程序運(yùn)行為:
getValue method threadName=main username=B password=AA
setValue method threadName=threadA username=B password=BB
出現(xiàn)臟讀是因?yàn)閜ublic void getValue()方法不是同步的倒彰,所以在任意時刻都可以調(diào)用审洞。
解決方案是加上同步關(guān)鍵字
synchronized public void getValue() {
System.out.println("getValue method threadName="+Thread.currentThread().getName()+" username="+username+" password="+password);
}
程序運(yùn)行正常,setValue和getValue被依次執(zhí)行。
setValue method threadName=threadA username=B password=BB
getValue method threadName=main username=B password=BB
小結(jié):
1.當(dāng)A線程調(diào)用anyObject的synchronized的X方法時待讳,就取得了X方法鎖芒澜,更準(zhǔn)確的來說是anyObject的對象鎖,所以其他線程必須等A線程執(zhí)行完X方法才能調(diào)用X方法创淡,但是其他線程可以所以調(diào)用非synchronized的方法痴晦。
2.當(dāng)A線程調(diào)用anyObject的synchronized的X方法時,就取得了X方法鎖琳彩,更準(zhǔn)確的來說是anyObject的對象鎖誊酌,所以其他線程必須等A線程執(zhí)行完X方法才能調(diào)用X方法部凑,但是如果其他線程B調(diào)用聲明了synchronized的非X方法時,由于對象鎖被A線程持有碧浊,必須等A線程執(zhí)行完X方法后涂邀,才能執(zhí)行synchronized的非X方法。這種情況不會出現(xiàn)臟讀現(xiàn)象箱锐。
2.1.6鎖重入
synchronized擁有鎖重入功能比勉,通俗的講當(dāng)某個線程獲得對象鎖以后,在此請求此對象鎖可以在此得到該對象的鎖驹止。這也證明在一個synchronized方法/塊內(nèi)部調(diào)用本類的其他synchronized方法/塊是永遠(yuǎn)可以得到鎖的浩聋。
看代碼:
/**
* 鎖重入
* @author Arthur
* @date 2017-12-26 17:25
*/
public class Run {
public static void main(String[] args) {
ThreadA threadA = new ThreadA();
threadA.start();
}
}
class Service{
public synchronized void service1() {
System.out.println("service1.");
service2();
}
public synchronized void service2(){
System.out.println("service2.");
service3();
}
public synchronized void service3(){
System.out.println("service3.");
}
}
class ThreadA extends Thread{
@Override
public void run() {
super.run();
Service service = new Service();
service.service1();
}
}
結(jié)果為
service1.
service2.
service3.
可重入的概念:當(dāng)一個線程持有對象鎖時,此時這個對象鎖還未被釋放臊恋,當(dāng)它再次請求獲得鎖時還是可以獲得衣洁。如果不能重入就會造成死鎖。
重入鎖也支持在父子類中調(diào)用抖仅。
2.1.7出現(xiàn)異常坊夫,鎖自動釋放
當(dāng)一個線程執(zhí)行異常時,其所持有的鎖自動釋放岸售。
2.1.8同步不具有繼承性
/**
* 同步不具有繼承性
*
* @author Arthur
* @date 2017-12-26 17:37
*/
public class Run {
public static void main(String[] args) {
Sub subRef = new Sub();
ThreadA threadA = new ThreadA(subRef);
threadA.setName("A");
threadA.start();
ThreadB threadB = new ThreadB(subRef);
threadB.setName("B");
threadB.start();
}
}
class Main{
synchronized public void sleep(){
try {
System.out.println(" Main 下一步 sleep begin thread name="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
Thread.sleep(5000);
System.out.println(" Main 下一步 sleep end thread name="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Sub extends Main{
@Override
public void sleep(){
super.sleep();
try {
System.out.println(" Sub 下一步 sleep begin thread name="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
Thread.sleep(5000);
System.out.println(" Sub 下一步 sleep end thread name="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadA extends Thread{
private Sub sub;
public ThreadA(Sub sub) {
this.sub = sub;
}
@Override
public void run() {
super.run();
sub.sleep();
}
}
class ThreadB extends Thread{
private Sub sub;
public ThreadB(Sub sub) {
this.sub = sub;
}
@Override
public void run() {
super.run();
sub.sleep();
}
}
運(yùn)行結(jié)果:
Main 下一步 sleep begin thread name=A time=1514281689722
Main 下一步 sleep end thread name=A time=1514281694722
Main 下一步 sleep begin thread name=B time=1514281694722
Sub 下一步 sleep begin thread name=A time=1514281694722
Main 下一步 sleep end thread name=B time=1514281699723
Sub 下一步 sleep begin thread name=B time=1514281699723
Sub 下一步 sleep end thread name=A time=1514281699723
Sub 下一步 sleep end thread name=B time=1514281704723
由結(jié)果可看出同步未被繼承践樱。在子類的方法加上synchronized
public synchronized void sleep()
程序運(yùn)行正常:
Main 下一步 sleep begin thread name=B time=1514281920201
Main 下一步 sleep end thread name=B time=1514281925201
Sub 下一步 sleep begin thread name=B time=1514281925201
Sub 下一步 sleep end thread name=B time=1514281930201
Main 下一步 sleep begin thread name=A time=1514281930201
Main 下一步 sleep end thread name=A time=1514281935202
Sub 下一步 sleep begin thread name=A time=1514281935202
Sub 下一步 sleep end thread name=A time=1514281940202
下一節(jié)介紹2.2 synchronized同步語句塊