c 多线程运行混乱_聊一聊多线程情况下锁的作用


什么是锁?

锁在JAVA中是一个非常重要的概念,尤其在当今互联网时代,高并发的场景下,更离不开锁.锁(lock)与互斥(mutex)是一种同步机制,用于在有许多执行线程的环境中强制对资源的访问限制.锁旨在强制实施互斥排,并发控制策略.

举一个生活中的例子:大家去超市的时候,随声携带了背包,需要将包存在柜子中.极端假设一下,如果柜子只有一个,同时来了三个人A,B,C都想存背包.这个场景就造成了多线程,多线程自然离不开锁.如下所示:

0ae077bcbbe16b894750216bbdc605c4.png

A,B,C都想往柜子里面放背包,可是柜子只有一个只能放一件东西,那要怎么办呢?这个时候就出现了锁的概念,三个人中谁抢到了锁,谁就可以使用这个柜子,其他的人只能等待.比如A抢到了锁,那么A使用柜子,B和C只能等待.

代码示例

将上面的例子放映到程序中,创建一个柜子的类:

public class Cabinet {    //柜子中存放的数字    private int number;    public int getNumber() {        return number;    }    public void setNumber(int number) {        this.number = number;    }}

创建一个用户类:

public class User {    //柜子    private Cabinet cabinet;    //存储的数字    private int number;    //构造方法    public User(Cabinet cabinet, int number) {        this.cabinet = cabinet;        this.number = number;    }    //使用柜子的方法    public void userCabinet(){        cabinet.setNumber(number);    }}

在用户的构造方法中,需要传入两个参数,一个是要使用的柜子,另一个是要存储的数字.

创建测试类,模拟三个用户使用柜子:

public class Test {    public static void main(String args[]){        Cabinet cabinet = new Cabinet();        for (int i = 0; i < 3; i++) {            final int number = i;            new Thread(() -> {                User user = new User(cabinet,number);                user.userCabinet();                System.out.println("我是用户: " + number + ",我存储的数字是: " + cabinet.getNumber());            },String.valueOf(i)).start();        }    }}

我们来分析下这个main函数的过程:

  1. 创建柜子的实例,由于假设的场景中只有一个柜子,所以我们只创建了一个柜子的实例.

  2. 新建线程池,其中有三个线程,每个线程执行一个用户的操作.

  3. 新建用户实例,传入柜子的实例,然后传入用户要存储的数字,分别是1,2,3也分别对应着A,B,C

  4. 调用使用柜子的操作,向柜子中放入要存储的数字,然后取出数字并打印.

运行结果:

我是用户: 0,我存储的数字是: 1我是用户: 2,我存储的数字是: 1我是用户: 1,我存储的数字是: 1

从结果中我们看出,三个用户在柜子中的数字都变成了1

再次运行:

我是用户: 0,我存储的数字是: 2我是用户: 2,我存储的数字是: 2我是用户: 1,我存储的数字是: 2

这次变成了1,这是为什么呢?

问题就出在user.userCabinet();这个方法上,这是因为柜子这个实例没有加锁的原因,三个用户并行的执行,向柜子中存储他们的数字,虽然同时操作,但是在具体赋值的时候,也是有顺序的,因为变量number只占有一块内存,只存储一个值,存储最后的线程锁设置的值,至于哪个线程在最后则完全不确定.赋值语句执行完成后,进入到打印语句,此时取到的是最后设置的值,就想上面的打印结果一样.

如何解决这个问题呢?我们在赋值语句上加锁,这当多个线程同时赋值时,谁抢到了这把锁,谁才能赋值.这样保证同一时刻只能有一个线程进行赋值操作.

在程序中如何加锁呢?这就要使用JAVA中的一个关键字---synchronized.synchronized分为synchronized方法和synchronized同步代码块

  1. synchronized方法,是把synchronized关键字写在方法上,表示这个方法是加了锁的,当多个线程同时调用这个方法时,只有获得锁的线程才可以执行.

    public synchronized String getTicket(){  return "hello";}

    我们可以看到getTicket()方法加了锁,当多个线程并发执行的时候,只有获得到锁的线程才可以执行

  2. synchronized快的语法:

  3. synchronized (对象锁){  ......}

我们将需要加锁的语句写在synchronized块内,在对象锁的位置需要填写加锁的对象,当多个线程并发执行的时候,只有获得这个对象的锁,才能执行后面的语句,其他的线程只能等待.

回到我们的示例当中,我们可以在设置number的方法上加锁,这样保证同时只有一个线程能调用这个方法:

public class Cabinet {    //柜子中存放的数字    private int number;    public int getNumber() {        return number;    }    public synchronized void setNumber(int number) {        this.number = number;    }}

我们在set方法上加了synchronized关键字,这样在存储数字的时候,就不会并行的去执行了

我是用户: 0,我存储的数字是: 1我是用户: 1,我存储的数字是: 1我是用户: 2,我存储的数字是: 1

结果还是错误的

for (int i = 0; i < 3; i++) {    final int number = i;    new Thread(() -> {        User user = new User(cabinet,number);        user.userCabinet();        System.out.println("我是用户: " + number + ",我存储的数字是: " + cabinet.getNumber());    },String.valueOf(i)).start();}

我们可以看到在userCabinet和打印的方法是两个语句,并没有保持原子性,虽然在set上加了锁,但是在打印时又存在一个并发,打印语句是有锁的,但是不能确定哪个线程去执行.

每个线程都初始化了user对象,总共有三个user对象,而cabinet对象只有一个,所以对cabinet使用synchronized块:

synchronized (cabinet){    user.userCabinet();    System.out.println("我是用户: " + number + ",我存储的数字是: " + cabinet.getNumber());}

运行结果

我是用户: 0,我存储的数字是: 0我是用户: 1,我存储的数字是: 1我是用户: 2,我存储的数字是: 2

由于我们加上了synchronized块,保证了存储和取出的原子性.

d65dbd61bb56681cefe4bab70ea348ed.png

总结

通过上面的示例,了解了在多线程情况下,造成的变量值前后不一致的问题,以及锁的作用.在使用了锁以后,可以避免这种混乱的现象.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值