【并发编程】java并发编程之synchronized

锁是什么?什么要使用它?

      相信接触过并发的人都知道synchronized是一种锁,那什么是锁呢?显示生活中的解释:一把锁管控这某一个空间,只有人们拿钥匙打开锁才能进入。编程世界中的锁,和现实世界很相似,它的目的很明确:加锁的方法或者代码块,只有拿到钥匙才能访问。

     那什么时候需要用到它呢?这个就要看业务场景,如果程序没有牵扯到并发或者共享变量不存在竞争,这种情况下一般不需要使用到锁,但是现在的程序基本上是不可能不存在资源竞争的,这时候就要在高性能和高一致性作取舍,选择高可用,那么一致性就会下降,如果保证强一致性,那么性能就会收到瓶颈,所以,锁是一把双刃剑,用好他可以让程序快乐的飞起,用不好,可能造成不可估量的后果。

synchronized是什么?

synchronized其实就是一把锁,它能保证数据的一致性,但是会牺牲一部分的性能。

优点:
1.保证线程互斥
2.保证数据一致性

缺点:
1.效率较低
2.使用不当容易造成死锁(程序崩溃)

所以锁是一把双刃剑,用好它,才是一个合格的程序猿。

synchronized的四种用法
1.锁定普通的方法
2.锁定静态方法
3.锁定当前类
4.锁定代码块

介绍这4中用法之前我们先看一下这个程序:

package com.ymy.utils;

/**
 * synchronized锁
 */
public class MySynchronized {
    public static   long sum  = 0;
    public static  void add(){
            for(long i = 0 ;i <10000 ; i++){
                // count.incrementAndGet();
                sum += 1;
            }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            add();
        });
        t1.start();
        Thread t2 = new  Thread(() ->{
            add();
        });
        t2.start();
        t1.join();
        t2.join();
        System.out.println("累加总和:"+sum);

    }
}

程序很简单,两个线程都让sum累加1w,我们预期的结果应该是2w,结果是多少呢?程序不能凭我们想象,因该要去实践,运行结果如下:

第一次:
累加总和:17854

第二次:
累加总和:16268

第三次:
累加总和:20000

为什么会出现这种结果呢?那这个就要说到cpu和内存了,想必大家都知道计算机cpu处理速度远远大于内存,由于内存的速度相对于cpu来说较慢,就会导致cpu在很长的一段时间都处于空闲状态,这样大大浪费了cpu的性能,后来就有大牛研在cpu上做了一个缓存配置,也叫cpu缓存,那现在程序是如何工作的呢?

      这中操作为什么为造成上面的输出结果呢?如果对多线程有点了解的话,应该知道,线程之间的执行是无序的,cpu会根据线程的优先级来处理线程,如过线程a得到了执行权,但是cpu不会一直执行线程,cpu会在多个线程之间来回切换,处理不同的事情,这就是为什么你在使用qq向A发送视频的时候还能和B聊天,如果没有这种线程切换,那么你只能等视频发送完成,你才能和B交流,线程(上下文)切换就是导致上面输出结果不等于2w的原因。

    为什么呢?如果线程1先执行第一次读取内存数据sum的时候等于0,将值读到cpu缓存之后还没来得及做自加操作,此时切换到了线程2,他往内存读数据的时候也是0,因为线程1还没有进行计算,他们计算完的结果都是1,然后在分别写回内存,这就导致了原本结果应该是2却得到了1,为了解决这个并发问题,锁就出现了。

下面我们来介绍一下synchronized的四种用法。
锁定普通的方法
什么叫锁定普通方法呢?

就是在add()前面加上synchronized关键字,锁住的区域也仅仅是当前实例,代码如下:

package com.ymy.utils;

/**
 * synchronized锁
 */
public class MySynchronized {
    public static   long sum  = 0;

    public synchronized void add(){
            for(long i = 0 ;i <10000 ; i++){
                sum += 1;
            }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            new MySynchronized().add();
        });
        t1.start();
        Thread t2 = new  Thread(() ->{
            new MySynchronized().add();
        });
        t2.start();
        t1.join();
        t2.join();
        System.out.println("累加总和:"+sum);

    }
}

需要注意的一点是这样加锁是不能保证线程安全的,因为普通方法加锁只能锁住当前实例,而我在main方法中实例化了2个MySynchronized对象,所以这里有两把锁,分别锁着两个不同的实例,这两个线程之间还是存在问题,如果保证线程安全,只需要做一下改动,让两个线程共用一个实例即可,代码如下:

package com.ymy.utils;

/**
 * synchronized锁
 */
public class MySynchronized {
    public static   long sum  = 0;

    public synchronized void add(){
            for(long i = 0 ;i <10000 ; i++){
                sum += 1;
            }
    }

    public static void main(String[] args) throws InterruptedException {

        MySynchronized mySynchronized = new MySynchronized();
        Thread t1 = new Thread(() -> {
            mySynchronized.add();
        });
        t1.start();
        Thread t2 = new  Thread(() ->{
            mySynchronized.add();
        });
        t2.start();
        t1.join();
        t2.join();
        System.out.println("累加总和:"+sum);

    }
}

修改前结果:

修改后结果:

锁定静态方法

什么叫锁定静态方法呢?其实就是在方法前加一个static关键字,这种方式锁定的是当前类,不管你创建多少实例,它都能保证前程的互斥性。

 

package com.ymy.utils;

/**
 * synchronized锁
 */
public class MySynchronized {
    public static   long sum  = 0;

    public static  synchronized void add(){
            for(long i = 0 ;i <10000 ; i++){
                sum += 1;
            }
    }

    public static void main(String[] args) throws InterruptedException {

        MySynchronized mySynchronized = new MySynchronized();
        Thread t1 = new Thread(() -> {
            new MySynchronized().add();
        });
        t1.start();
        Thread t2 = new  Thread(() ->{
            new MySynchronized().add();
        });
        t2.start();
        t1.join();
        t2.join();
        System.out.println("累加总和:"+sum);

    }
}

由于这种加锁方式是锁定当前类,虽然保证了所有线程的互斥性,但是性能极低,使用的还需慎重。

代码块锁定(锁定当前类)
刚刚介绍加锁带static关键字的方法就是锁定当前类,为什么这里还要介绍一下锁定当前类呢?那是因为通过锁定static方法只是其中的一种方式,还可以通过代码块来锁定当前类,请看代码:

package com.ymy.utils;

/**
 * synchronized锁
 */
public class MySynchronized {
    public static long sum = 0;

    public void add() {
        synchronized (MySynchronized.class) {
            for (long i = 0; i < 10000; i++) {
                sum += 1;
            }
        }

    }

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            new MySynchronized().add();
        });
        t1.start();
        Thread t2 = new Thread(() -> {
            new MySynchronized().add();
        });
        t2.start();
        t1.join();
        t2.join();
        System.out.println("累加总和:" + sum);

    }
}

这种方式就是通过代码块锁定当前类,效果和锁定static方法是一样的,这样做的效果会比第一种好一点,为什么这么说呢?因为static锁定的是整个方法,而代码块锁定的是你需要锁定的某一行或者某几行代码,效果比较好。

代码块锁定(对象)

锁定一个你指定的对象,所有引用此对象的线程保持同步(互斥),不同对象互不影响。

package com.ymy.utils;

/**
 * synchronized锁
 */
public class MySynchronized {
    public static long sum = 0;


    private static  String lock1 = "lock1";

    private static  String lock2 = "lock2";

    public  void add() throws InterruptedException {
        synchronized (lock1) {
            System.out.println("这里锁住的对象是:lock1,线程名:"+Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("lock1休眠结束");
        }

        synchronized (lock2) {
            System.out.println("这里锁住的对象是:lock2,线程名:"+Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("lock2休眠结束");
        }

    }

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            try {
                new MySynchronized().add();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();
        Thread t2 = new Thread(() -> {
            try {
                new MySynchronized().add();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t2.start();

    }
}

输出结果:

lock1和lock2处于并行状态,为什么第一次进来锁定lock1的时候lock2处于阻塞状态呢?那是因为add()是一个同步方法,代码忧伤到下依次执行。

 

在这里还要注意一点:synchronized的可重入行以及不可中断性

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值