(一)
public class Demo01 {
private int count = 10;
private Object object = new Object();
@Test
public void test(){
synchronized (object) { //任何線程要執行下面的代碼,必須先拿到object對象的鎖
count --;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
public static void main(String[] args) {
Demo01 demo01=new Demo01();
new Thread(()->demo01.test()).start();
new Thread(()->demo01.test()).start();
}
1.synchronized關鍵字鎖定的是對象不是代碼塊,demo中鎖的是object對象的實例(堆內存中)
2.鎖定的對象有兩種情況:①類的實例 ②類的字節碼(.class)
3.關於線程安全:加synchronized關鍵字之后不一定能實現線程安全,具體還要看
鎖定的對象是否唯一。
(二)
public class Demo02 {
private int count = 10;
@Test
public void test(){
synchronized (this) { //任何線程要執行下面的代碼,必須先拿到Demo02對象實例的鎖
count --;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
1.synchronized(this)鎖定的是當前類的實例,demo中鎖定的是Demo02類的實例
2.此demo中如果Demo02類是單例的話可以保證在多線程訪問時是線程安全的,
如果存在有多個Demo02的實例的話在多線程中不能保證線程安全,因為方法中的鎖不唯一了。(堆內存中的地址不一樣)
(三)
public class Demo03 {
private int count = 10;
public synchronized void test(){//等同於synchronized(this),鎖定的是Demo03對象的實例
count --;
System.out.println(Thread.currentThread().getName() + " count =" + count);
}
}
1.synchronized關鍵字修飾普通方法等同於synchronized(this)
(四)
public class Demo04 {
private static int count = 10;
public synchronized static void test1(){ //這里等同於synchronized(Demo04.class)
count --;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void test2(){ //考慮一下這里寫synchronize(this)是否可以
synchronized (Demo04.class) {
count --;
}
}
}
1.synchronize關鍵字修飾靜態方法鎖定的是類的.class文件
2.靜態方法中synchronize鎖定代碼塊,鎖定的對象不能是類的實例,只能是類的.class文件。
原理如同在靜態方法中不能直接調用非靜態方法
3.類的.class文件是唯一的,所以說synchronize修飾靜態方法或者鎖定的對象是類的.class文件的時候
在多線程中是可以實現線程安全的
(五)
public class Demo05 implements Runnable{
private int count = 10;
@Override
public /* synchronized*/ void run(){
count --;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void main(String[] args) {
Demo05 demo05 = new Demo05();
for (int i = 0; i < 5; i++) {
new Thread(demo05,"THREAD" + i).start();
}
}
}
1.run()方法沒加synchronized關鍵字時,多個線程同時訪問count,線程是不安全的
2.run()方法加上synchronized關鍵字后,鎖定的是Demo05對象的實例,因為只創建了
一個Demo05的實例,多個線程訪問時都要拿到Demo05的鎖標記才能執行,在多個線程同時訪問時也是線程安全的
(六)
public class Demo06 implements Runnable{
private int count = 10;
@Override
public synchronized void run() {
count --;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
Demo06 demo06 = new Demo06();//注意這里
new Thread(demo06,"THREAD" + i).start();
}
}
}
1.執行可以知道,demo中雖然加上了synchronized關鍵字來修飾方法,但是線程是不安全的。為什么呢??
分析一下:synchronized修飾的是普通方法,鎖定的是Demo06實例,從Main方法中可以看到,在for循環中
創建了多個Demo06的實例,也就是說每個線程對應都拿到各自的鎖標記,可以同時執行。
例子:
多人同時上廁所,廁所門只有一把鎖的時候是一個人上完之后把鑰匙(鎖標記)給到下一個人才可以開門上廁所
如果廁所門的鎖有多個鑰匙的情況下,就是每個人都有鎖的鑰匙了,大家可以一起去打開門來上廁所。(歸根結底還是堆內存上的地址)
demo中就如同廁所門的鎖有多把鑰匙(鎖標記),不能實現線程安全
(七)
public class Demo07 {
public synchronized void test1(){
System.out.println(Thread.currentThread().getName() + " test1 start..........");
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " test1 end........");
}
public void test2(){
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "test2 execute......");
}
public static void main(String[] args) {
Demo07 demo07 = new Demo07();
new Thread(demo07 :: test1,"t1").start(); //JDK1.8新特性
new Thread(demo07 :: test2,"t2").start(); //JDK1.8新特性
}
}
運行結果:
t1 test1 start..........
t2test2 execute......
t1 test1 end........
1.同步方法和非同步方法是可以同時調用的
(八)
package thread.demo_008;
import java.util.concurrent.TimeUnit;
/**
* 對業務寫方法加鎖
* 對業務讀方法不加鎖
* 容易產生臟讀問題
* @author Jcon
*
*/
public class Demo08 {
String name;
double balance;
public synchronized void set(String name, double balance){
this.name = name;
try {
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = balance;
}
public synchronized double getBalance(String name){
return this.balance;
}
public static void main(String[] args) {
Demo08 demo08 = new Demo08();
new Thread(()->demo08.set("zhangsan",100.0)).start(); //JDK1.8新特性
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(demo08.getBalance("zhangsan"));
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(demo08.getBalance("zhangsan"));
}
}
2.對業務寫方法加鎖,同時也要對業務讀方法加鎖,否則容易產生臟讀問題
(九)
/**
* 一個同步方法可以調用另一個同步方法,一個線程已經擁有某個對象的鎖,再次申請的時候
* 仍然會得到該對象的鎖
* 也就是說synchronized獲得的鎖是可重入的
* @author Jcon
*
*/
public class Demo09 {
synchronized void test1(){
System.out.println("test1 start.........");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
test2();
}
synchronized void test2(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test2 start.......");
}
public static void main(String[] args) {
Demo09 demo09 = new Demo09();
demo09.test1();
}
}
1.一個同步方法可以調用另一個同步方法,一個線程已經擁有某個對象的鎖,
再次申請的時候仍然會得到該對象的鎖
也就是說synchronized獲得的鎖是可重入的
(十)
package thread.demo_010;
import java.util.concurrent.TimeUnit;
/**
* 一個同步方法可以調用另外一個同步方法,一個線程已經擁有某個對象的鎖,
* 再次申請的時候仍然會得到該對象的鎖,也就是說synchronize獲得的鎖是可重入的
* 這里是繼承中有可能發生的情形,子類調用父類的同步方法
* @author Jcon
*
*/
public class Demo10 {
synchronized void test(){
System.out.println("test start........");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test end........");
}
public static void main(String[] args) {
new Demo100().test();
}
}
class Demo100 extends Demo10{
@Override
synchronized void test() {
System.out.println("child test start.......");
super.test();
System.out.println("child test end.......");
}
}
2.一個同步方法可以調用另一個同步方法,一個線程已經擁有某個對象的鎖,
再次申請的時候仍然會得到該對象的鎖
也就是說synchronized獲得的鎖是可重入的(
這里是繼承中有可能發生的情形,子類調用父類的同步方法
)
(十一)
/**
* 程序在執行過程中,如果出現異常,默認情況鎖會被釋放
* 所以,在並發處理的過程中,有異常要多加小心,不然可能會發生不一致的情況
* 比如,在一個web app處理過程中,多個servlet線程共同訪問通一個資源,這是如果異常處理不合適
* 在第一個線程中拋出異常,其他線程就會進入同步代碼去,有可能訪問到異常產生是的數據
* 因此要非常小心的處理同步業務邏輯中的異常
* @author Jcon
*
*/
public class Demo11 {
int count = 0;
synchronized void test(){
System.out.println(Thread.currentThread().getName() + " start......");
while (true) {
count ++;
System.out.println(Thread.currentThread().getName() + " count = " + count);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) {
int i = 1/0; //此處拋出異常,鎖將被釋放,要想不被釋放,可以在這里進行catch處理,然后讓循環繼續
}
}
}
public static void main(String[] args) {
Demo11 demo11 = new Demo11();
Runnable r = new Runnable() {
@Override
public void run() {
demo11.test();
}
};
new Thread(r, "t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r, "t2").start();
}
}
1.程序在執行過程中,如果出現異常,默認情況鎖會被釋放
(十二)
public class Demo12 {
volatile boolean running = true;
public void test(){
System.out.println("test start.......");
while (running) {
/*try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
System.out.println("test end........");
}
public static void main(String[] args) {
Demo12 demo12 = new Demo12();
new Thread(demo12 :: test, "t1").start(); //JDK1.8新特性
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
demo12.running = false;
}
}
* volatile 關鍵字,使一個變量在多個線程間可見
* A B線程都用到一個變量,java默認是A線程中保留一份copy,這樣如果B線程修改了該變量,則A線程未必知道
* 使用volatile關鍵字,會讓所有線程都會讀到變量的修改值
*
* 在下面的代碼中,running是存在於堆內存的t對象中
* 當線程t1開始運行的時候,會把running值從內存中讀到t1線程的工作區,在運行過程中直接使用這個copy,並不會每次都去
* 讀取堆內存,這樣,當主線程修改running的值之后,t1線程感知不到,所以不會停止運行
*
* 使用volatile,將會強制所有線程都去堆內存中讀取running的值
*
* 可以閱讀這篇文章進行更深入的理解
*
(十三)
**
* volatile並不能保證多個線程共同修改running變量時所帶來的不一致問題,
* 也就是說volatile不能替代synchronize
* 運行下面的程序,並分析結果
* @author Jcon
*
*/
public class Demo13 {
volatile int count = 0;
public void test(){
for (int i = 0; i < 10000; i++) {
count ++;
}
}
public static void main(String[] args) {
Demo13 demo13 = new Demo13();
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(demo13::test, "thread-" + i));
}
threads.forEach((o)->o.start()); //JDK1.8新特性
threads.forEach((o)->{ //JDK1.8新特性
try {
o.join(); //等線程執行完畢之后才執行主線程main
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.println(demo13.count);
}
}
* volatile並不能保證多個線程共同修改running變量時所帶來的不一致問題,也就是說volatile不能替代synchronized
(十四)
public class Demo14 {
int count = 0;
public synchronized void test(){
for (int i = 0; i < 10000; i++) {
count ++;
}
}
public static void main(String[] args) {
Demo14 demo14 = new Demo14();
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(demo14::test, "thread-" + i));
}
threads.forEach((o)->o.start()); //JDK1.8新特性
threads.forEach((o)->{ //JDK1.8新特性
try {
o.join(); //等線程執行完畢之后才執行主線程main
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.println(demo14.count);
}
}
* 對比上一個程序,可以用synchronized解決,synchronize可以保證可見性和原子性,volatile只能保證可見性
(十五)
public class Demo15 {
//int count = 0;
AtomicInteger count = new AtomicInteger(0);
public /*synchronized*/ void test(){
for (int i = 0; i < 10000; i++) {
//count ++;
count.incrementAndGet(); //count++
// 注意下面則不構成原子性,因為在get時,線程a進行判斷后,但是不執行下面代碼
// 線程b進行判斷,執行完代碼,此時代碼是1000,然后線程a執行,此時結果是1001
// if (count.get() > 1000) {
// count.incrementAndGet();
// }
}
}
public static void main(String[] args) {
Demo15 demo15 = new Demo15();
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(demo15::test, "thread-" + i));
}
threads.forEach((o)->o.start()); //JDK1.8新特性
threads.forEach((o)->{ //JDK1.8新特性
try {
o.join(); //等線程執行完畢之后才執行主線程main
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.println(demo15.count);
}
}
* 解決同樣的問題的更高效的方法,使用AtomXXX類
* AtomXXX類本身方法都是原子性的,但不能保證多個方法連續調用是原子性
(十六)
/**
* synchronize優化
* 同步代碼快中的語句越少越好
* 比較test1和test2
* @author Jcon
*
*/
public class Demo16 {
int count = 0;
public synchronized void test1(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//業務邏輯中只有下面這句需要sync,這時不應該給整個方法上鎖
count ++;
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void test2(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//業務邏輯中只有下面這句需要sync,這時不應該給整個方法上鎖
//采用細粒度的鎖,可以是線程爭用時間變短,從而提高效率
synchronized (this) {
count ++;
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
1.業務邏輯中只有下面這句需要sync,這時不應該給整個方法上鎖
2.采用細粒度的鎖,可以是線程爭用時間變短,從而提高效率
(十七)
/**
* 鎖定某對象o,如果o的屬性發生改變,不影響鎖的使用
* 但是如果o變成另外一個對象,則鎖定的對象發生改變
* 應該避免將鎖定對象的引用變成另外一個對象
* @author Jcon
*
*/
public class Demo17 {
Object o = new Object();
public void test(){
synchronized (o) {
while (true) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
public static void main(String[] args) {
Demo17 demo17 = new Demo17();
//啟動第一個線程
new Thread(demo17 :: test, "t1").start(); //JDK1.8新特性
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
//啟動第二個線程
Thread t2 = new Thread(demo17 :: test, "t2");
demo17.o = new Object(); //鎖對象發生改變,所以t2線程得以執行,如果注釋掉這句話,線程t2將永遠得不到執行機會
t2.start();
}
}
* 鎖定某對象o,如果o的屬性發生改變,不影響鎖的使用
* 但是如果o變成另外一個對象,則鎖定的對象發生改變
* 應該避免將鎖定對象的引用變成另外一個對象
(十八)
/**
* 不要以字符串常量作為鎖定的對象
* 在下面的例子中,test1和test2其實鎖定的是同一個對象
* 這種情況還會發生比較詭異的現象,比如你用到了一個類庫,在該類庫中代碼鎖定了字符串"hello",
* 但是你讀不到源碼,所以你在自己的代碼中也鎖定了"hello",這時候就有可能發生非常詭異的死鎖阻塞,
* 因為你的程序和你用的的類庫不經意間使用了同一把鎖
* @author Jcon
*
*/
public class Demo18 {
String s1 = "hello";
String s2 = "hello";
public void test1(){
synchronized (s1) {
}
}
public void test2(){
synchronized (s2) {
}
}
}
1.不要以字符串常量作為鎖定的對象