1. 程式人生 > >Java中synchronized關鍵字使用實踐

Java中synchronized關鍵字使用實踐

1、synchronized修飾類的普通方法

package main.thread;

/**
 * Created by leboop on 2018/11/18.
 * 測試synchronized關鍵字使用
 */
public class SynchronizedClass {

    public void run(){
        for(int i=0;i<5;i++){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("執行緒"+Thread.currentThread().getName()+"正在列印i="+i);
        }
    }
    
    //被synchronized修飾的普通方法
    public synchronized void print() {
        System.out.println(Thread.currentThread().getName()+"執行緒開始執行......");
        run();
        System.out.println(Thread.currentThread().getName()+"執行緒執行結束......");

    }
}

測試類如下:

package main.thread;

/**
 * Created by leboop on 2018/11/18.
 */
public class SychronizedTest {
    public static void main(String[] args) {
        SynchronizedClass p=new SynchronizedClass();
        //建立t1執行緒
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                p.print();
            }
        });
        t1.setName("t1");

        //建立t2執行緒
        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                p.print();
            }
        });
        t2.setName("t2");
        
        //啟動執行緒
        t1.start();
        t2.start();

    }
}

測試類執行結果:

t1執行緒開始執行......
執行緒t1正在列印i=0
執行緒t1正在列印i=1
執行緒t1正在列印i=2
執行緒t1正在列印i=3
執行緒t1正在列印i=4
t1執行緒執行結束......
t2執行緒開始執行......
執行緒t2正在列印i=0
執行緒t2正在列印i=1
執行緒t2正在列印i=2
執行緒t2正在列印i=3
執行緒t2正在列印i=4
t2執行緒執行結束......

從執行結果來看,執行緒t1和執行緒t2是同步執行print方法的,這是因為兩個執行緒中都是通過同一個物件p來呼叫print方法的,當t1開始執行p.print()時,會取得物件p的鎖,此時執行緒t2拿不到物件p的鎖無法操作p的print方法,等執行緒t1執行完畢釋放物件p的鎖,此時執行緒t2才可以拿到p的鎖來執行print方法。如果建立兩個物件p1和p2,執行緒t1和t2分別使用兩個物件來呼叫print方法,此時測試類修改如下:

package main.thread;

/**
 * Created by leboop on 2018/11/18.
 */
public class SychronizedTest {
    public static void main(String[] args) {
        SynchronizedClass p1=new SynchronizedClass();
        SynchronizedClass p2=new SynchronizedClass();
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                p1.print();
            }
        });
        t1.setName("t1");


        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                p2.print();
            }
        });
        t2.setName("t2");
        t1.start();
        t2.start();

    }
}

程式執行結果如下:

t1執行緒開始執行......
t2執行緒開始執行......
執行緒t2正在列印i=0
執行緒t1正在列印i=0
執行緒t2正在列印i=1
執行緒t1正在列印i=1
執行緒t1正在列印i=2
執行緒t2正在列印i=2
執行緒t1正在列印i=3
執行緒t2正在列印i=3
執行緒t1正在列印i=4
t1執行緒執行結束......
執行緒t2正在列印i=4
t2執行緒執行結束......

此時,執行緒t1和t2是非同步執行的,因為此時執行緒t1拿到的是p1物件的鎖,和物件p2無關,執行緒t2執行p2.print時會取得p2的鎖,互不影響。

 

2、synchronized修飾普通方法中的程式碼塊

package main.thread;

/**
 * Created by leboop on 2018/11/18.
 * 測試synchronized關鍵字使用
 */
public class SynchronizedClass {

    public void run(){
        for(int i=0;i<5;i++){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("執行緒"+Thread.currentThread().getName()+"正在列印i="+i);
        }
    }

    public  void print() {
        synchronized (this){
            System.out.println(Thread.currentThread().getName()+"執行緒開始執行......");
            run();
            System.out.println(Thread.currentThread().getName()+"執行緒執行結束......");
        }
    }
}

這種方式和1相同。

 

3、synchronized修飾類的靜態方法

package main.thread;

/**
 * Created by leboop on 2018/11/18.
 * 測試synchronized關鍵字使用
 */
public class SynchronizedClass {

    public static void run() {
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("執行緒" + Thread.currentThread().getName() + "正在列印i=" + i);
        }
    }

    public synchronized static void print() {
        System.out.println(Thread.currentThread().getName() + "執行緒開始執行......");
        run();
        System.out.println(Thread.currentThread().getName() + "執行緒執行結束......");
    }
}
package main.thread;

/**
 * Created by leboop on 2018/11/18.
 */
public class SychronizedTest {
    public static void main(String[] args) {
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                SynchronizedClass.print();
            }
        });
        t1.setName("t1");


        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                SynchronizedClass.print();
            }
        });
        t2.setName("t2");
        t1.start();
        t2.start();

    }
}

程式執行結果:

t1執行緒開始執行......
執行緒t1正在列印i=0
執行緒t1正在列印i=1
執行緒t1正在列印i=2
執行緒t1正在列印i=3
執行緒t1正在列印i=4
t1執行緒執行結束......
t2執行緒開始執行......
執行緒t2正在列印i=0
執行緒t2正在列印i=1
執行緒t2正在列印i=2
執行緒t2正在列印i=3
執行緒t2正在列印i=4
t2執行緒執行結束......

從結果來看,我們發現執行緒t1和執行緒t2是同步執行的。SynchronizedClass.print()改成物件 new SynchronizedClass().print()呼叫也是同步執行的。這是因為執行緒t1執行SynchronizedClass.print()時,取得類的鎖(因為靜態方法是屬於類的),在未執行完前,執行緒t2無法取得該類的鎖,當執行緒t1執行完後,釋放類的鎖,此時執行緒t2可以拿到類的鎖去執行SynchronizedClass.print()。

 

4、synchronized修飾靜態方法中的程式碼塊

package main.thread;

/**
 * Created by leboop on 2018/11/18.
 * 測試synchronized關鍵字使用
 */
public class SynchronizedClass {

    public static void run() {
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("執行緒" + Thread.currentThread().getName() + "正在列印i=" + i);
        }
    }

    public  static void print() {
        synchronized(SynchronizedClass.class){
            System.out.println(Thread.currentThread().getName() + "執行緒開始執行......");
            run();
            System.out.println(Thread.currentThread().getName() + "執行緒執行結束......");
        }

    }
}

這種方式與第3種方式相同。

 

5、如果synchronized修飾了某個靜態方法,該類是否還可以呼叫其他無synchronized關鍵字修飾的靜態方法?

答案是可以的。如下:

package main.thread;

/**
 * Created by leboop on 2018/11/18.
 * 測試synchronized關鍵字使用
 */
public class SynchronizedClass {
    //無synchronized修飾的靜態方法
    public static void otherPrint() {
        System.out.println(Thread.currentThread().getName() + "執行緒開始執行......");
        run();
        System.out.println(Thread.currentThread().getName() + "執行緒執行結束......");
    }

    //synchronized修飾的靜態方法
    public synchronized static   void print() {
            System.out.println(Thread.currentThread().getName() + "執行緒開始執行......");
            run();
            System.out.println(Thread.currentThread().getName() + "執行緒執行結束......");
    }

    public static void run() {
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("執行緒" + Thread.currentThread().getName() + "正在列印i=" + i);
        }
    }

}

測試類如下:

package main.thread;

/**
 * Created by leboop on 2018/11/18.
 */
public class SychronizedTest {
    public static void main(String[] args) {
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                //呼叫synchronized修飾的靜態方法
                SynchronizedClass.print();
            }
        });
        t1.setName("t1");


        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                //呼叫無synchronized修飾的靜態方法
                SynchronizedClass.otherPrint();
            }
        });
        t2.setName("t2");
        t1.start();
        t2.start();

    }
}

程式執行結果:

t1執行緒開始執行......
t2執行緒開始執行......
執行緒t1正在列印i=0
執行緒t2正在列印i=0
執行緒t2正在列印i=1
執行緒t1正在列印i=1
執行緒t2正在列印i=2
執行緒t1正在列印i=2
執行緒t2正在列印i=3
執行緒t1正在列印i=3
執行緒t2正在列印i=4
執行緒t1正在列印i=4
t1執行緒執行結束......
t2執行緒執行結束......

從程式執行結果來看,互不影響。這是因為執行緒t1執行SynchronizedClass.print();時取得類鎖,但是執行緒t2執行SynchronizedClass.otherPrint();不需要鎖,可以直接執行。

6、如果synchronized修飾了某個普通方法,當某個執行緒通過物件呼叫該普通方法時,該物件是否還可以呼叫其他無synchronized關鍵字修飾的普通方法?

答案是可以的。自行測試。

 

7、如果類中有兩個synchronized修飾的普通方法,那麼兩個執行緒是否可以通過同一個物件併發呼叫這兩個方法?

答案是不可以的。原因是執行synchronized修飾的方法必須取得物件鎖。如下:

package main.thread;

/**
 * Created by leboop on 2018/11/18.
 * 測試synchronized關鍵字使用
 */
public class SynchronizedClass {
    //synchronized修飾的普通方法
    public synchronized void otherPrint() {
        System.out.println(Thread.currentThread().getName() + "執行緒開始執行......");
        run();
        System.out.println(Thread.currentThread().getName() + "執行緒執行結束......");
    }

    //synchronized修飾的普通方法
    public synchronized void print() {
        System.out.println(Thread.currentThread().getName() + "執行緒開始執行......");
        run();
        System.out.println(Thread.currentThread().getName() + "執行緒執行結束......");
    }

    public void run() {
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("執行緒" + Thread.currentThread().getName() + "正在列印i=" + i);
        }
    }

}

測試類如下:

package main.thread;

/**
 * Created by leboop on 2018/11/18.
 */
public class SychronizedTest {
    public static void main(String[] args) {
        SynchronizedClass p=new SynchronizedClass();
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                //呼叫synchronized修飾的靜態方法
                p.print();
            }
        });
        t1.setName("t1");


        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                //呼叫無synchronized修飾的靜態方法
                p.otherPrint();
            }
        });
        t2.setName("t2");
        t1.start();
        t2.start();
    }
}

測試類執行結果如下:

t1執行緒開始執行......
執行緒t1正在列印i=0
執行緒t1正在列印i=1
執行緒t1正在列印i=2
執行緒t1正在列印i=3
執行緒t1正在列印i=4
t1執行緒執行結束......
t2執行緒開始執行......
執行緒t2正在列印i=0
執行緒t2正在列印i=1
執行緒t2正在列印i=2
執行緒t2正在列印i=3
執行緒t2正在列印i=4
t2執行緒執行結束......

8、如果類中有兩個synchronized修飾的靜態方法,那麼兩個執行緒是否可以併發呼叫這兩個方法?

答案是不可以的。這是因為執行synchronized修飾的靜態方法必須取得類鎖。如下:

package main.thread;

/**
 * Created by leboop on 2018/11/18.
 * 測試synchronized關鍵字使用
 */
public class SynchronizedClass {
    //synchronized修飾的靜態方法
    public synchronized  static void otherPrint() {
        System.out.println(Thread.currentThread().getName() + "執行緒開始執行......");
        run();
        System.out.println(Thread.currentThread().getName() + "執行緒執行結束......");
    }

    //synchronized修飾的靜態方法
    public synchronized static void print() {
        System.out.println(Thread.currentThread().getName() + "執行緒開始執行......");
        run();
        System.out.println(Thread.currentThread().getName() + "執行緒執行結束......");
    }

    public static void run() {
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("執行緒" + Thread.currentThread().getName() + "正在列印i=" + i);
        }
    }

}

測試類如下:

package main.thread;

/**
 * Created by leboop on 2018/11/18.
 */
public class SychronizedTest {
    public static void main(String[] args) {
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                //呼叫synchronized修飾的靜態方法
                SynchronizedClass.print();
            }
        });
        t1.setName("t1");


        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                //呼叫無synchronized修飾的靜態方法
                SynchronizedClass.otherPrint();
            }
        });
        t2.setName("t2");
        t1.start();
        t2.start();
    }
}

測試類執行結果:

t2執行緒開始執行......
執行緒t2正在列印i=0
執行緒t2正在列印i=1
執行緒t2正在列印i=2
執行緒t2正在列印i=3
執行緒t2正在列印i=4
t2執行緒執行結束......
t1執行緒開始執行......
執行緒t1正在列印i=0
執行緒t1正在列印i=1
執行緒t1正在列印i=2
執行緒t1正在列印i=3
執行緒t1正在列印i=4
t1執行緒執行結束......

9、如果類中一個是synchronized修飾的靜態方法,另一個是synchronized修飾的普通方法,那麼兩個執行緒是否可以併發呼叫這兩個方法?

答案是可以的。這是因為一個是物件鎖,一個是類鎖,不是同一把鎖。如下:

package main.thread;

/**
 * Created by leboop on 2018/11/18.
 * 測試synchronized關鍵字使用
 */
public class SynchronizedClass {
    //synchronized修飾的普通方法
    public synchronized  void otherPrint() {
        System.out.println(Thread.currentThread().getName() + "執行緒開始執行......");
        run();
        System.out.println(Thread.currentThread().getName() + "執行緒執行結束......");
    }

    //synchronized修飾的靜態方法
    public synchronized static void print() {
        System.out.println(Thread.currentThread().getName() + "執行緒開始執行......");
        run();
        System.out.println(Thread.currentThread().getName() + "執行緒執行結束......");
    }

    public static void run() {
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("執行緒" + Thread.currentThread().getName() + "正在列印i=" + i);
        }
    }

}

測試類如下:

package main.thread;

/**
 * Created by leboop on 2018/11/18.
 */
public class SychronizedTest {
    public static void main(String[] args) {
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                //呼叫synchronized修飾的靜態方法
                SynchronizedClass.print();
            }
        });
        t1.setName("t1");


        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                //呼叫無synchronized修飾的靜態方法
                new SynchronizedClass().otherPrint();
            }
        });
        t2.setName("t2");
        t1.start();
        t2.start();
    }
}

執行結果如下:

t1執行緒開始執行......
t2執行緒開始執行......
執行緒t1正在列印i=0
執行緒t2正在列印i=0
執行緒t1正在列印i=1
執行緒t2正在列印i=1
執行緒t2正在列印i=2
執行緒t1正在列印i=2
執行緒t1正在列印i=3
執行緒t2正在列印i=3
執行緒t1正在列印i=4
執行緒t2正在列印i=4
t2執行緒執行結束......
t1執行緒執行結束......

 

注意:類鎖和物件鎖不是一把鎖。每個物件都有一把鎖與之相關。