老生常谈之 synchronized关键字详解

前言

对于synchronized关键字,想必是个小白都应该听说过,在日常的面试中也会经常问到对于这个关键字的理解,但是恐怕没有多少人敢说自己对synchronized很了解,当然我也是。正是因为不熟悉,所以这里和大家一起来掀开它的面纱,一睹synchronized的真容。

同步方法和同步代码块的区别

synchronized无非就是加锁,那这个锁是加在哪里呢,在类上面,方法上,还是方法中的核心逻辑代码上面?加锁的目的是为了避免多个线程对于统一资源的更改,导致资源不一致。我们看看synchronized是如何加在方法上和代码上的区别

/**
* @ClassName: CountDemo
* @Description: TODO(同步方法)
* @author 爱琴孩
*/
public class CountDemo {
    public synchronized void synchMethodTest(){
        for(int i=0;i<5;i++){
        System.out.println("当前线程是"+Thread.currentThread().getName()+",i="+i);
        }
    }
}

同步代码块

/**
* @ClassName: CountDemo
* @Description: TODO(同步代码块)
* @author 爱琴孩
*/
public class CountDemo {
    public void synchCodeBlockTest(){
        synchronized(this){
            for(int i=0;i<5;i++){
            System.out.println("当前线程是"+Thread.currentThread().getName()+",i="+i);
            }
        }   
    }
}

测试客户端如下

/**
* @ClassName: CountClient
* @Description: TODO(测试客户端)
* @author 爱琴孩
*/
public class CountClient extends Thread{
    private CountDemo countDemo;
    public CountClient(){};
    public CountClient(CountDemo countDemo){
        this.countDemo=countDemo;
    }
    public void run(){
          countDemo.synchMethodTest();
        //countDemo.synchCodeBlockTest();
    }
    public static void main(String[] args) {
        CountDemo countDemo=new CountDemo();
        CountClient thread1=new CountClient(countDemo);
        CountClient thread2=new CountClient(countDemo);
        thread1.start();
        thread2.start();
    }
}

上面的代码块和同步方法运行的结果其实是一样的,但是还有区别的,同步方法对于那些非核心逻辑代码也实行了同步,这样相对与同步代码块需要耗费更多的时间来用在同步上,也就意味着其他线程需要更长时间来等待当前线程释放对象锁,对于程序而言,多余时间的消耗是无法忍受的,所以谁更节约时间就不用讲了。

对象锁而不是代码锁

java中每一个对象都有一个锁,当对象调用加了synchronized关键字的方法或者代码块的时候,该方法或者说该代码块就会获得对象的锁,当其他线程来调用同一个对象的同步方法的时候,需要等当前线程释放这个对象的锁,其他线程才能去调用这个对象的同步方法。这里需要注意的是,同一个对象。如果不是同一个对象那么同步有没有效果呢?
依旧调用上面的synchMethodTest,在测试客户端中修改一下

/**
* @ClassName: CountClient
* @Description: TODO(测试不同对象的同步方法是否会堵塞)
* @author 爱琴孩
*/
public class CountClient extends Thread{
    private CountDemo countDemo;
    public CountClient(){};
    public CountClient(CountDemo countDemo){
        this.countDemo=countDemo;
    }
    public void run(){
          countDemo.synchMethodTest();
    }
    public static void main(String[] args) {
        CountClient thread1=new CountClient(new CountDemo ());
        CountClient thread2=new CountClient(new CountDemo ());
        thread1.start();
        thread2.start();
    }
}

测试结果如下
这里写图片描述
可以看到,上面两个线程是交替执行的,也就是说并没堵塞,这也就佐证了,锁是锁住了对象,而不是锁住代码块。

全局锁和局部锁

synchronized加在非静态方法上,那么这是获得调用该方法的对象所属的锁。这种锁被称为局部锁,如果是加在静态方法上,那么这个锁是属于类锁,也就是所谓的全局锁。

/**
* @ClassName: CountDemo
* @Description: TODO(同步静态方法)
* @author 爱琴孩
 */
public class CountDemo {
    public static synchronized void synchStaticMethodTest(){
        for(int i=0;i<5;i++){
            System.out.println("当前线程是"+Thread.currentThread().getName()+",i="+i);
        }
    }
}

上面的是采用同步静态方法,当然我们还可以采用同步静态代码块,这里需要区别的就是synchronized(this){}和synchronized(Object.class){}这两种写法,对于synchronized(this){}是局部同步代码块,而synchronized(Object.class){}是全局同步代码块。
同样用两个不同的对象来测试,静态同步方法

/**
* @ClassName: CountClient
* @Description: TODO(测试不同对象的静态同步方法是否会堵塞)
* @author 爱琴孩
*/
public class CountClient extends Thread{
    private CountDemo countDemo;
    public CountClient(){};
    public CountClient(CountDemo countDemo){
        this.countDemo=countDemo;
    }
    public void run(){
        countDemo.synchStaticMethodTest();
    }
    public static void main(String[] args) {
        CountClient thread1=new CountClient(new CountDemo ());
        CountClient thread2=new CountClient(new CountDemo ());
        thread1.start();
        thread2.start();
    }
}

测试结果如下
这里写图片描述
可以看到,这里两个对象还是堵塞了,因为静态同步方法是锁住了所有的对象。

happens-before原则

在获得一个对象锁之前,要先打开这个对象加在别的地方的锁,听起来很模糊,估计是我的表达有问题,可以结合下面的例子来看
接地气一点的说法是,一个线程调用一个对象的同步方法,然后另一个线程调用同一个对象另一同步方法,需要等待上面那个现成释放该对象的锁之后,才能调用另一个同步方法
这里一个类中有两个同步方法

/**
* @ClassName: CountDemo
* @Description: TODO(同步方法)
* @author 爱琴孩
 */
public class CountDemo {
    public synchronized void synchMethodTest(){
        for(int i=0;i<5;i++){
            System.out.println("当前线程是"+Thread.currentThread().getName()+",i="+i);
        }
    }
    public synchronized void doOtherThing(){
        System.out.println("开始测试,开始之后需要休眠!");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("休眠之后重新启动!");
    }
}

测试客户端如下

/**
* @ClassName: CountClient
* @Description: TODO(测试客户端)
* @author 爱琴孩
*/
public class CountClient extends Thread{
    private CountDemo countDemo;
    public CountClient(){};
    public CountClient(CountDemo countDemo){
        this.countDemo=countDemo;
    }
    public static void main(String[] args) {
        final CountDemo countDemo=new CountDemo();

        new Thread(new Runnable(){
            @Override
            public void run() {
                // TODO Auto-generated method stub
                countDemo.doOtherThing();
            }
        }).start();
        new Thread(new Runnable(){
            @Override
            public void run() {
                // TODO Auto-generated method stub
                countDemo.synchMethodTest();
            }
        }).start();
    }
}

测试结果如下
这里写图片描述
上述结果也佐证了上面的说法是正确。

这里对于synchronized关键字简单介绍,如有不足之处,还请不吝指正!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱琴孩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值