文章目錄
- 1. 概述
- 2. synchronized關鍵字鎖的是什么?
- 3.鎖物件的屬性改變和鎖物件改變對于鎖的使用的影響
- 4.同步方法和非同步方法是否可以同時呼叫?
- 5.讀方法需不需要加synchronized?
- 6. synchronized是否支持可重入
- 7.synchronized可重入的另一種情況,繼承
- 8.synchronized同步方法內發生例外,是否會釋放鎖?
1. 概述
本篇博客記錄synchronized的使用,注意的事項,
2. synchronized關鍵字鎖的是什么?
synchronized關鍵字鎖定的是物件不是代碼塊,Demo1中鎖的是Object物件的實體,
鎖定的物件有兩種:1.類的實體 2.類物件(類鎖),
加synchronized關鍵字之后不一定能實作執行緒安全,具體還要看鎖定的物件是否唯一,
下面舉幾個例子來說明
@Slf4j(topic = "s")
public class Demo1 {
private int count = 10;
private Object object = new Object();
public void test(){
synchronized (object){
//臨界區
count--;
log.debug("count = " + count);
}
}
}
@Slf4j(topic = "s")
public class Demo2 {
private int count = 10;
public void test(){
/**
* synchronized(this)鎖定的是當前類的實體,這里鎖定的是Demo2類的實體
*/
synchronized (this){
count--;
log.debug("count = " + count);
}
}
}
demo1和demo2 鎖的都是實體物件,區別在于demo1 鎖的是呼叫test方法的物件內的一個屬性object物件,
而demo2鎖的是呼叫test方法的物件本身,注意只有非靜態方法才可以用demo2的寫法,
@Slf4j(topic = "s")
public class Demo3 {
private int count = 10;
//直接加在方法宣告上,相當于是synchronized(this)
public synchronized void test(){
count--;
log.debug("count = " + count);
}
}
Demo3 直接將 synchronized作為方法的修飾符,效果相當于demo2,
但是區別是直接加在方法上,該方法可以是靜態方法,若是靜態方法的話,那么它鎖的其實就是類物件,也就是類鎖,例如下面的Demo4
@Slf4j(topic = "s")
public class Demo4 {
private static int count = 10;
//synchronized 關鍵字修飾靜態方法鎖定的是類的物件
public synchronized static void test(){
count--;
log.debug("count =" + count);
}
//與上面的效果相同
public static void test2(){
synchronized (Demo4.class){//這里不能替換成this
count--;
}
}
}
3.鎖物件的屬性改變和鎖物件改變對于鎖的使用的影響
鎖定某物件o,如果o的屬性發生改變,不影響鎖的使用,但是如果o變成另外一個物件,則鎖定的物件發生改變,此時就會影響鎖的使用,應該避免將鎖定物件的參考變成另外一個物件,
下面舉個例子
@Slf4j(topic = "s")
public class Demo1 {
O o = new O();
public void test(){
synchronized (o) {
//這里無限執行
while (true) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("xxxxxxx");
}
}
}
public static void main(String[] args) {
Demo1 demo = new Demo1();
new Thread(demo :: test, "t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t2 = new Thread(demo :: test, "t2");
//鎖物件改變
// demo.o = new O();
//鎖物件的屬性改變
demo.o.num = 1;
//t2能否執行?
t2.start();
}
public static class O{
int num = 0;
}
}
程式比較簡單,執行緒t1 先啟動,獲得到了鎖執行列印,執行緒t2 啟動后,無法獲取鎖,盡管此時修改了鎖物件的屬性num,對于鎖的使用也是沒有影響的,控制臺輸出結果如下:

只有執行緒t1 在列印東西,
若此時將代碼改成如下
@Slf4j(topic = "s")
public class Demo1 {
O o = new O();
public void test(){
synchronized (o) {
//這里無限執行
while (true) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("xxxxxxx");
}
}
}
public static void main(String[] args) {
Demo1 demo = new Demo1();
new Thread(demo :: test, "t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t2 = new Thread(demo :: test, "t2");
//鎖物件改變
demo.o = new O();
//鎖物件的屬性改變
// demo.o.num = 1;
//t2能否執行?
t2.start();
}
public static class O{
int num = 0;
}
}
此時t1執行緒和t2執行緒都在執行列印,因為他們現在鎖的物件不是同一個,所以不存在競爭,都可以執行,

4.同步方法和非同步方法是否可以同時呼叫?
答:可以
例如下面的例子,同步方法獲取到鎖,并不會影響其他非同步方法的使用,
@Slf4j(topic = "s")
public class Demo {
public synchronized void test1(){
log.debug(" test1 start...");
try {
//睡眠5s 由于還要t2要執行 cpu回去執行t2
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug(" test1 end...");
}
public void test2(){
log.debug(" test2 start...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug(" test2 end...");
}
public static void main(String[] args) {
Demo demo = new Demo();
//正在執行一個同步方法 沒有釋放鎖
new Thread(demo :: test1,"t1").start();
//不影響其他執行緒執行非同步方法(就算他是一個同步方法,如果鎖的不是同一個物件也不影響)
new Thread(demo :: test2,"t2").start();
}
}
控制臺輸出結果如下:

5.讀方法需不需要加synchronized?
在set方法的時候,涉及到了共享資源的修改,所以需要加上synchronized,那讀方法需要嗎?
讀方法是否需要加synchronized 首先取決于,這個方法內 存不存在執行緒安全的問題,若有執行緒安全的問題那么就要加鎖,
然后再看具體的業務是否允許臟讀,若不允許臟讀,那么就需要加鎖,
例如下面的代碼
@Slf4j(topic = "s")
public class Demo {
//卡的持有人 senlin
String name;
//卡上的余額 0
double balance;
public synchronized void set(String name,double balance){
this.name = name;
try {
log.debug("set");
//模擬存錢耗時 銀行系統處理
Thread.sleep(2000);
log.debug("set end");
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = balance;
}
public double getBalance(String name){
return this.balance;
}
// public synchronized double getBalance(String name){
// return this.balance;
// }
@SneakyThrows
public static void main(String[] args) {
Demo demo = new Demo();
//沒有啟動
Thread zl = new Thread(() -> {
log.debug("余額-{}", demo.getBalance("senlin"));
try {
TimeUnit.SECONDS.sleep(2);
}catch (Exception e){
e.printStackTrace();
}
log.debug("----余額-{}", demo.getBalance("senlin"));
}, "senlin");
//2s
new Thread(() -> demo.set("senlin", 100.0), "yiyi").start();
TimeUnit.SECONDS.sleep(1);
zl.start();
}
}
代碼很簡單,yiyi向senlin轉了100塊錢,與此同時senlin查賬戶,查了兩次,出現了臟讀,控制臺結果如下:

若當前業務不允許臟讀的出現,那么對讀方法也需要加鎖,
代碼改成如下:
@Slf4j(topic = "s")
public class Demo {
//卡的持有人 senlin
String name;
//卡上的余額 0
double balance;
public synchronized void set(String name,double balance){
this.name = name;
try {
log.debug("set");
//模擬存錢耗時 銀行系統處理
Thread.sleep(2000);
log.debug("set end");
} catch (InterruptedException e) {
e.printStackTrace();
}
/**
*
*/
this.balance = balance;
}
// public double getBalance(String name){
// return this.balance;
// }
public synchronized double getBalance(String name){
return this.balance;
}
@SneakyThrows
public static void main(String[] args) {
Demo demo = new Demo();
//沒有啟動
Thread zl = new Thread(() -> {
log.debug("余額-{}", demo.getBalance("senlin"));
try {
TimeUnit.SECONDS.sleep(2);
}catch (Exception e){
e.printStackTrace();
}
log.debug("----余額-{}", demo.getBalance("senlin"));
}, "senlin");
//2s
new Thread(() -> demo.set("senlin", 100.0), "yiyi").start();
TimeUnit.SECONDS.sleep(1);
zl.start();
}
}
此時的就解決了臟讀的問題,控制臺輸出結果如下:

6. synchronized是否支持可重入
答:支持可重入,
在一個同步方法里面呼叫另一個同步方法,可以正常的執行,
@Slf4j(topic = "s")
public class Demo {
synchronized void test1() throws InterruptedException {
log.debug("test1 start.........");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
test2();
log.debug("test1 end.........");
}
synchronized void test2() throws InterruptedException {
log.debug("test2 start.......");
TimeUnit.SECONDS.sleep(1);
log.debug("test2 end.......");
}
public static void main(String[] args) throws InterruptedException {
Demo demo= new Demo();
demo.test1();
}
}
首先呼叫test1方法獲取到了鎖,test1方法內部呼叫了同步方法test2,此時可以正常執行,說明synchronized支持可重入,
控制臺的結果如下:

7.synchronized可重入的另一種情況,繼承
子類呼叫同步方法,同步方法內部呼叫父類的同步方法,可正常執行,
代碼如下:
@Slf4j(topic = "s")
public class Demo {
synchronized void test(){
log.debug("demo test start........");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("demo test end........");
}
public static void main(String[] args) {
new Demo2().test();
}
}
@Slf4j(topic = "s")
class Demo2 extends Demo {
@Override
synchronized void test(){
log.debug("demo2 test start........");
super.test();
log.debug("demo2 test end........");
}
}
Demo2是子類,呼叫了同步方法test,此時鎖定的物件就是Demo2實體,內部呼叫父類的同步方法test(),可正常執行,這是可重入的另一種形式,
控制臺輸出結果如下:
8.synchronized同步方法內發生例外,是否會釋放鎖?
這要分兩種情況,
- 若對例外進行了處理,則不會釋放鎖
- 若不處理例外,則會釋放鎖,
代碼如下:
@Slf4j(topic = "s")
public class Demo {
Object o = new Object();
int count = 0;
void test(){
synchronized(o) {
//t1進入并且啟動
log.debug("start......");
//t1 會死回圈 t1 講道理不會釋放鎖
while (true) {
count++;
log.debug(" count = {}", count);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//加5次之后 發生例外
if (count == 5) {
try {
int i = 1 / 0;
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Demo demo = new Demo();
new Thread(()->{
demo.test();
},"t1").start();
TimeUnit.MILLISECONDS.sleep(1);
new Thread(()->{
demo.test();
}, "t2").start();
}
}
上面的代碼對例外進行了處理,此時不會釋放鎖,t2執行緒拿不到鎖,控制臺輸出結果如下:

只有t1執行緒在執行,
若將代碼改成如下,不處理例外
@Slf4j(topic = "s")
public class Demo {
Object o = new Object();
int count = 0;
void test() {
//
synchronized (o) {
//t1進入并且啟動
log.debug("start......");
//t1 會死回圈 t1 講道理不會釋放鎖
while (true) {
count++;
log.debug(" count = {}", count);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//加5次之后 發生例外
if (count == 5) {
int i = 1 / 0;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Demo demo = new Demo();
new Thread(() -> {
demo.test();
}, "t1").start();
TimeUnit.MILLISECONDS.sleep(1);
new Thread(() -> {
demo.test();
}, "t2").start();
}
}
此時例外不處理,鎖會釋放掉,t1執行緒結束,t2獲得到鎖執行代碼,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/239628.html
標籤:其他
下一篇:代理ARP實驗
