Java并发编程1--synchronized关键字用法详解

1.synchronized的作用

首先synchronized可以修饰方法或代码块,可以保证同一时刻只有一个线程可以执行这个方法或代码块,从而达到同步的效果,同时可以保证共享变量的内存可见性

2.synchronized的实现原理

首先,每个java对象都可以作为一个对象锁,对象锁分为三种情况对应三种对象锁,分别是方法锁、对象锁和类锁,实际都是对象锁,只是针对不同用法方便区分所以分别定义了一种锁。这个是理解synchronized实现同步的基础

3.synchronized用法详解

synchronized主要有三种用法
修饰方法:方法锁,锁的对象是当前的对象
修饰静态方法:类锁,锁的对象是当前的类,实际是这个类的.class对象
修饰代码块:对象锁,锁的对象是synchronized修饰的对象

4.synchronized用法案例

synchroinzed主要的用法如下案例

public  class  SynchronizedDemo {

    /**
     * synchronized只可修饰方法或代码块,不可修饰变量
     * */
    
    public static Integer age = 10;
    
    /**
     * synchronized修饰非静态方法
     * */
    public synchronized void test1() {
        System.out.println("这个是非静态方法,需要获得当前实例的对象锁才可以访问");
    }

    /**
     * synchronized修饰静态方法
     * */
    public synchronized static void test2() {
        System.out.println("这个是静态方法,需要获得当前类的锁才可以访问");
    }

    public void test3() {
        /**
         * synchronized修饰代码块:锁的对象是当前对象
         * */
        synchronized (this) {
            System.out.println("锁代码块,需要获取当前对象的锁");
        }
    }
    
    public static void test4(){
        /**
         * synchronized修饰代码块:锁的对象是Integer类型对象age
         * */
        synchronized (age) {
            System.out.println("锁代码块,需要获取age对象的锁");
        }
    }
    
    public void test5(){
        /**
         * synchronized修饰代码块:锁的对象是User.class这个对象,由于这个对象只有一个,所以效果相当于类锁
         * */
        synchronized(User.class){
            System.out.println("锁代码块,需要获取User类.class对象的锁,相当于类锁");
        }
    }
}

5.synchronized关键字不同情况下同步情况(*面试必备*)

5.1、同步非静态方法test1(),不同线程调用同一个对象的该方法则同步,调用不同对象的该方法则不同步,即线程threadA和线程theadB两个线程,两个对象demo1,demo2
则threadA和threadB都调用demo1.test1()或demo2.test1()则同步,分别调用demo1.test1()和demo2.test1()方法则不同步,完整Demo如下:

/**
     * 非静态方法test1方法测试(不同线程调用不同对象的非静态同步方法)
     * */
    public static void test1_test(){
        SynchronizedDemo demoA = new SynchronizedDemo();
        SynchronizedDemo demoB = new SynchronizedDemo();
        
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("----->开始执行线程=>"+Thread.currentThread().getName()+":需要锁的对象是:"+demoA.toString());
                demoA.test1();
            }
        });
        
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("----->开始执行线程=>"+Thread.currentThread().getName()+":需要锁的对象是:"+demoA.toString());
                demoA.test1();
            }
        });
        Thread t3 = new Thread(new Runnable() {
            
            @Override
            public void run() {
                System.out.println("----->开始执行线程=>"+Thread.currentThread().getName()+":需要锁的对象是:"+demoB.toString());
                demoB.test1();
            }
        });
        //线程t1执行变量A的test1方法
        t1.start();
        //线程t2执行变量A的test1方法
        t2.start();
        //线程t3执行变量B的test1方法
        t3.start();
    }

执行结果如下:

----->开始执行线程=>Thread-2:需要锁的对象是:com.test.lucky.concurrent.SynchronizedDemo@46cf8b7b
----->开始执行线程=>Thread-0:需要锁的对象是:com.test.lucky.concurrent.SynchronizedDemo@6426ac69
----->开始执行线程=>Thread-1:需要锁的对象是:com.test.lucky.concurrent.SynchronizedDemo@6426ac69
这个是非静态方法,需要获得当前实例的对象com.test.lucky.concurrent.SynchronizedDemo@6426ac69锁才可以访问
这个是非静态方法,需要获得当前实例的对象com.test.lucky.concurrent.SynchronizedDemo@46cf8b7b锁才可以访问
Thread-2休眠第1秒
Thread-0休眠第1秒
Thread-2休眠第2秒
Thread-0休眠第2秒
Thread-2休眠第3秒
Thread-0休眠第3秒
Thread-0休眠第4秒
Thread-2休眠第4秒
Thread-2休眠第5秒
Thread-0休眠第5秒
这个是非静态方法,需要获得当前实例的对象com.test.lucky.concurrent.SynchronizedDemo@6426ac69锁才可以访问
Thread-1休眠第1秒
Thread-1休眠第2秒
Thread-1休眠第3秒
Thread-1休眠第4秒
Thread-1休眠第5秒

结论:

可以看出t1和t2执行同一个变量A的test1方法,t3执行另一个变量B的test1方法,则t1和t2同步,和t3则不同步。
因为t1和t2都需要获取对象demoA的对象锁,同一时间只能有一个线程能获取到,而t3是获取对象demoB的对象锁

5.2、同步静态方法test2(),不同线程不管是调用同一个对象还是不同对象的该方法,都是调用该类的静态方法,都需要同步
即线程threadA和线程theadB两个线程,两个对象demo1,demo2
则threadA和threadB都调用demo1.test2()则同步,分别调用demo1.test2()和demo2.test2()方法则也同步,直接调用该类的test2()方法也同步,完整Demo如下:

/**
     * 静态方法test2方法测试(不同线程调用不同或相同对象的静态方法)
     * */
    public static void test2_test(){
        final SynchronizedDemo demoA = new SynchronizedDemo();
        final SynchronizedDemo demoB = new SynchronizedDemo();

        Thread t1 = new Thread(new Runnable() {

            public void run() {
                System.out.println("----->开始执行线程=>"+Thread.currentThread().getName()+":需要锁的对象是:"+SynchronizedDemo.class.toString());
                demoA.test2();
            }
        });

        Thread t2 = new Thread(new Runnable() {

            public void run() {
                System.out.println("----->开始执行线程=>"+Thread.currentThread().getName()+":需要锁的对象是:"+SynchronizedDemo.class.toString());
                demoB.test2();
            }
        });

        Thread t3 = new Thread(new Runnable() {
            public void run() {
                System.out.println("----->开始执行线程=>"+Thread.currentThread().getName()+":需要锁的对象是:"+SynchronizedDemo.class.toString());
                SynchronizedDemo.test2();
            }
        });
        //线程t1执行变量A的test1方法
        t1.start();
        //线程t2执行变量A的test1方法
        t2.start();
        //线程t3执行变量B的test1方法
        t3.start();
    }

执行结果如下:

执行结果如下:
----->开始执行线程=>Thread-0:需要锁的对象是:class com.codehelp.luck.concurrent.SynchronizedDemo
这个是静态方法,需要获得当前类的锁class com.codehelp.luck.concurrent.SynchronizedDemo才可以访问
----->开始执行线程=>Thread-1:需要锁的对象是:class com.codehelp.luck.concurrent.SynchronizedDemo
----->开始执行线程=>Thread-2:需要锁的对象是:class com.codehelp.luck.concurrent.SynchronizedDemo
Thread-0休眠第1秒
Thread-0休眠第2秒
Thread-0休眠第3秒
Thread-0休眠第4秒
Thread-0休眠第5秒
这个是静态方法,需要获得当前类的锁class com.codehelp.luck.concurrent.SynchronizedDemo才可以访问
Thread-2休眠第1秒
Thread-2休眠第2秒
Thread-2休眠第3秒
Thread-2休眠第4秒
Thread-2休眠第5秒
这个是静态方法,需要获得当前类的锁class com.codehelp.luck.concurrent.SynchronizedDemo才可以访问
Thread-1休眠第1秒
Thread-1休眠第2秒
Thread-1休眠第3秒
Thread-1休眠第4秒
Thread-1休眠第5秒

结论:

线程1通过变量demoA调用静态方法test2(),线程2通过变量demoB调用静态方法test2(),线程3通过直接调用类SynchronizedDemo.test2(),不同线程不同调用方式,都是需要锁SynchronizedDemo.class这点对象,也就是都需要获取类锁,

所以静态同步方法,无论什么场景都会达到同步的效果。

5.3、同步代码块,锁的是当前对象,则不同线程调用该对象的所有同步方法都会同步,效果和5.1一样,案例代码如下:

首先定义两个逻辑完全一样的方法test3(),test33()

public void test3() {
        /**
         * synchronized修饰代码块:锁的对象是当前对象
         * */
        synchronized (this) {
            System.out.println("锁代码块方法test3,需要获取当前对象的锁"+this.toString());
            try {
                for (int i = 1; i <= 5; i++) {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName()+"休眠第"+i+"秒");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void test33() {
        /**
         * synchronized修饰代码块:锁的对象是当前对象
         * */
        synchronized (this) {
            System.out.println("锁代码块test33,需要获取当前对象的锁"+this.toString());
            try {
                for (int i = 1; i <= 5; i++) {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName()+"休眠第"+i+"秒");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

测试代码如下:

    /**
     * test3方法测试
     * */
    public  static  void test3_test(){
        final SynchronizedDemo demoA = new SynchronizedDemo();

        Thread t1 = new Thread(new Runnable() {

            public void run() {
                System.out.println("----->开始执行线程=>"+Thread.currentThread().getName()+":需要锁的对象是:"+demoA.toString());
                demoA.test3();
            }
        });

        Thread t2 = new Thread(new Runnable() {

            public void run() {
                System.out.println("----->开始执行线程=>"+Thread.currentThread().getName()+":需要锁的对象是:"+demoA.toString());
                demoA.test3();
            }
        });

        Thread t3 = new Thread(new Runnable() {
            public void run() {
                System.out.println("----->开始执行线程=>"+Thread.currentThread().getName()+":需要锁的对象是:"+demoA.toString());
                demoA.test33();
            }
        });
        //线程t1执行变量A的test1方法
        t1.start();
        //线程t2执行变量A的test1方法
        t2.start();
        //线程t3执行变量A的test1方法
        t3.start();
    }

执行结果如下:

----->开始执行线程=>Thread-1:需要锁的对象是:com.codehelp.luck.concurrent.SynchronizedDemo@568a65a0
----->开始执行线程=>Thread-0:需要锁的对象是:com.codehelp.luck.concurrent.SynchronizedDemo@568a65a0
锁代码块方法test3,需要获取当前对象的锁com.codehelp.luck.concurrent.SynchronizedDemo@568a65a0
----->开始执行线程=>Thread-2:需要锁的对象是:com.codehelp.luck.concurrent.SynchronizedDemo@568a65a0
Thread-1休眠第1秒
Thread-1休眠第2秒
Thread-1休眠第3秒
Thread-1休眠第4秒
Thread-1休眠第5秒
锁代码块test33,需要获取当前对象的锁com.codehelp.luck.concurrent.SynchronizedDemo@568a65a0
Thread-2休眠第1秒
Thread-2休眠第2秒
Thread-2休眠第3秒
Thread-2休眠第4秒
Thread-2休眠第5秒
锁代码块方法test3,需要获取当前对象的锁com.codehelp.luck.concurrent.SynchronizedDemo@568a65a0
Thread-0休眠第1秒
Thread-0休眠第2秒
Thread-0休眠第3秒
Thread-0休眠第4秒
Thread-0休眠第5秒

结论:

不管是调用对象demoA的test3方法还是test33方法,都是需要获取demoA的对象锁,所以不同线程只要是执行demeA的同步方法或是同步代码块,都需要获取demoA对象锁,所以都会同步

5.4、同步代码块,锁的对象是非静态变量则不同线程调用不同变量的该方法不同步,调用相同实例的该方法则同步;锁的对象是静态变量,则不管调用是哪个实例的该方法都需要同步

private User user1 = new User();//定义一个非静态变量

private static User user2 = new User();//定义一个静态变量
   
public void test4(){
        /**
         * synchronized修饰代码块:锁的对象是User类型对象user1
         * */
        synchronized (user1) {
            System.out.println("锁代码块,需要获取对象"+user1.toString()+"的锁");
            try {
                for (int i = 1; i <= 5; i++) {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName()+"休眠第"+i+"秒");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void test44(){
        /**
         * synchronized修饰代码块:锁的对象是静态变量User类型对象user2
         * */
        synchronized (user2) {
            System.out.println("锁代码块,需要获取对象"+user2.toString()+"的锁");
            try {
                for (int i = 1; i <= 5; i++) {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName()+"休眠第"+i+"秒");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

SynchronizedDemo有两个属性,一个是变量user1,一个是静态变量user2,测试代码如下:

/**
     * test4方法测试
     * */
    public  static  void test4_test(){
        final SynchronizedDemo demoA = new SynchronizedDemo();
        final SynchronizedDemo demoB = new SynchronizedDemo();

        Thread t1 = new Thread(new Runnable() {

            public void run() {
                System.out.println("----->开始执行线程=>"+Thread.currentThread().getName()+":需要锁的对象是:"+demoA.user1.toString());
                demoA.test4();
            }
        });

        Thread t2 = new Thread(new Runnable() {

            public void run() {
                System.out.println("----->开始执行线程=>"+Thread.currentThread().getName()+":需要锁的对象是:"+demoB.user1.toString());
                demoB.test4();
            }
        });

        Thread t3 = new Thread(new Runnable() {
            public void run() {
                System.out.println("----->开始执行线程=>"+Thread.currentThread().getName()+":需要锁的对象是:"+user2.toString());
                demoA.test44();
            }
        });

        Thread t4 = new Thread(new Runnable() {
            public void run() {
                System.out.println("----->开始执行线程=>"+Thread.currentThread().getName()+":需要锁的对象是:"+user2.toString());
                demoB.test44();
            }
        });
        //线程t1执行变量A的test4方法
        t1.start();
        //线程t2执行变量B的test4方法
        t2.start();
        //线程t3执行变量A的test44方法
        t3.start();
        //线程t4执行变量B的test44方法
        t4.start();
    }

线程t1和t2分别调用实例demoA和demoB的test4方法,线程t3和t4分别调用实例demoA和demoB的test44方法,执行结果如下:

----->开始执行线程=>Thread-0:需要锁的对象是:com.codehelp.lucky.model.User@68ee5860
锁代码块方法test4,需要获取对象com.codehelp.lucky.model.User@68ee5860的锁
----->开始执行线程=>Thread-1:需要锁的对象是:com.codehelp.lucky.model.User@7d9f22fa
锁代码块方法test4,需要获取对象com.codehelp.lucky.model.User@7d9f22fa的锁
----->开始执行线程=>Thread-3:需要锁的对象是:com.codehelp.lucky.model.User@491c6a05
----->开始执行线程=>Thread-2:需要锁的对象是:com.codehelp.lucky.model.User@491c6a05
锁代码块方法test44,需要获取对象com.codehelp.lucky.model.User@491c6a05的锁
Thread-0休眠第1秒
Thread-1休眠第1秒
Thread-3休眠第1秒
Thread-0休眠第2秒
Thread-1休眠第2秒
Thread-3休眠第2秒
Thread-0休眠第3秒
Thread-1休眠第3秒
Thread-3休眠第3秒
Thread-0休眠第4秒
Thread-3休眠第4秒
Thread-1休眠第4秒
Thread-0休眠第5秒
Thread-3休眠第5秒
Thread-1休眠第5秒
锁代码块方法test44,需要获取对象com.codehelp.lucky.model.User@491c6a05的锁
Thread-2休眠第1秒
Thread-2休眠第2秒
Thread-2休眠第3秒
Thread-2休眠第4秒
Thread-2休眠第5秒

结论:

可以看出线程t1和t2获取的是不同对象的属性user1对象锁,不会同步,而t3和t4则获取的都是静态变量user2的锁,会同步

5.5、同步方法块,锁的对象是类,即User.class对象,则同于5.4案例的t3和t4线程结果一样,由于是类锁,所以肯定会同步(有兴趣同学可自行测试)

总结:
1.判断两个线程调用同步方法或同步块会不会同步的前提是需要理解线程需要获取的锁的对象是什么,获取的对象锁相同则会同步,不同对象锁则不会同步;
2.同步方法:锁的对象是当前对象,如SynchroizedDmoe demoA,则锁的是demoA这个对象
3.同步静态方法:锁的对象是当前类,即锁的是SynchronizedDemo.class这个对象
3.同步方法块,锁的对象是啥就是获取啥的对象锁,
   synchronized(this){},则锁的是当前对象,和同步方法一样
   synchronized(非静态变量),则锁的是这个变量的所属对象
   synchronized(类),则锁的是这个类的.class对象
4.方法锁、对象锁、类锁实际都是某个对象的锁,只需找到锁的对象是什么,即可判定会不会达到同步效果。

ps:本篇主要分析了Synchronized关键字的基本作用及基本用法,下一篇主要分析Synchronized的底层实现原理及Synchronized的优化

原文地址:https://www.cnblogs.com/jackion5/p/9789828.html