java多线程 (6) --原子性和synchronized对象锁

原子性和synchronized对象锁

原子性

原子指化学反应不可再分的基本微粒,在互联网指的是一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行,其实也可以看作"一个"操作,这个操作只能成功或者失败。

比如我们在银行转钱,B要转钱1000块钱到A的账户,第一步获取B账户的钱是否大于1000块钱 ,如果大于1000块钱那么就扣减1000,然后A账户加钱1000元,按照常理来说是不会有问题的,但是如果不是原子性,那么就会被其他干扰,或者在你做操作的时候其他人同时也来做操作,比如现在B的老婆同时也要转钱给C,B来查询钱是1000元,B老婆来查询也是1000元,这个时候同时来进行扣减就会B转的1000元,B老婆转的1000元,转给A和C,虽然B的账户只有1000但是转出了2000块钱 ,这是不合理的

public class Test1 {
    public static void main(String[] args) throws InterruptedException {
        Account b = new Account(); // b账户
        Account a = new Account(); // a账户
        Account c = new Account(); // c账户
        Thread thread = new Thread(() -> {  // b转钱给a
            if (b.getMoney() >= 1000) { // 如果账户余额大于 1000
                // 扣减 b账户1000元
                log.debug("B操作");
                b.deduct(1000);
                // a账户添加1000元
                a.add(1000);
            }
        }, "B操作");
        Thread thread2 = new Thread(() -> {  // b老婆转钱给c
            if (b.getMoney() >= 1000) { // 如果账户余额大于 1000
                // 扣减 b账户1000元
                log.debug("B老婆操作");
                b.deduct(1000);
                // c账户添加1000元
                c.add(1000);
            }
        }, "B老婆操作");
        // 启动线程
        thread.start();  
        thread2.start();
        // 等待两个线程结束
        thread.join();
        thread2.join();

        log.debug("b账户-->{}", b.getMoney() + "");
        log.debug("a账户-->{}", a.getMoney() + "");
        log.debug("c账户-->{}", c.getMoney() + "");
    }
}

@Data
class Account { // 账户对象 有一余额,加钱,扣钱
    private int money = 1000;

    void deduct(int num) {
        this.money -= num;
    }

    void add(int num) {
        this.money += num;
    }
}

在这里插入图片描述
最终我们发现b的账户已经是负数了,a和c账户也加上钱

Synchronized

synchronized对象锁能够使多个操作变成原子性操作,解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。

通俗的说就是给把代码放到一个有锁的房间里运行,每次只能有一个人拿到钥匙进去,他把他要干的事情做完出来 把钥匙放出来然后下一个人拿到钥匙,有进去操作出来。 但是会有很多这样的房间,你需要拿对应的钥匙进对应的房间。
Synchronized用法

Synchronized代码块

class TestSynchronized {
    public void test1() {
        synchronized (this) {
            int i = 5;
            while (i-- > 0) {
                log.debug(Thread.currentThread().getName() + " : " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ie) {
                }
            }
        }
    }

    public void test2() {
        synchronized (this) {
            int i = 5;
            while (i-- > 0) {
                log.debug(Thread.currentThread().getName() + " : " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ie) {
                }
            }
        }
    }

    public static void main(String[] args) {
        final TestSynchronized myt2 = new TestSynchronized();
        Thread t1 = new Thread(myt2::test1, "t1");
        Thread t2 = new Thread(myt2::test2, "test2");
        t1.start();
        t2.start();
    }
}

在这里插入图片描述
上面代码我们发现我们只new了一个TestSynchronized对象,也就是说我们用的一个对象调用的test1方法和test2方法,而里面的 synchronized (this) {}
锁对象this都是一个对象TestSynchronized对象,两个方法谁去先拿到对象锁谁就先执行,synchronized 代码块里面包裹的代码是原子性的可以看做一个操作,所以说没有发生交替执行的情况

public static void main(String[] args) {
        // 创建对象
        final TestSynchronized myt2 = new TestSynchronized();
        final TestSynchronized myt1 = new TestSynchronized();
        // 创建线程
        Thread t2 = new Thread(myt2::test2, "t1");
        Thread t1 = new Thread(myt1::test1, "test2");
        t1.start();
        t2.start();
    }

这里我们改变mian方法 new 了2个TestSynchronized 对象 myt1和调用test1方法,myt2调用test2方法, 而test1方法的synchronized (this) {}的this拿到的就是myt1对象,而test2方法的this拿到的就是myt2对象,就相当于大家都拿到了锁进入了各自的房间执行
在这里插入图片描述
如何解决这个问题啊,那么就去找他们两都能拿到的对象当锁 比如TestSynchronized .class对象他们都能拿到,或者Object对象
在这里插入图片描述
在这里插入图片描述
当我们换成.class对象后两个方法都去拿同一个锁 拿到锁的就执行 没拿到的就等待

Synchronized修饰方法

这里有两种一个是修饰静态方法,一个是修饰类方法

如果是修饰修饰静态方法其实锁是该对象的class对象

    public static synchronized void test3() {
        
    }
    // 两种写法意思差不多
 	public static void test4() {
        synchronized (TestSynchronized.class) {
        }
    }


如果是修饰修饰类方法其实锁是该类自己也就是this

 	public synchronized void test3() {

    }
    // 两种写法意思差不多
    public void test4() {
        synchronized (this) {

        }
    }

改造原子性案例

我们知道了Synchronized的用法那么来改造一下刚才原子性用的案例
我们只要保证读取-扣减-添加是原子性的就能保证b账户钱的正确性

public class Test1 {
    public static void main(String[] args) throws InterruptedException {
        Account b = new Account(); // b账户
        Account a = new Account(); // a账户
        Account c = new Account(); // c账户
        Thread thread = new Thread(() -> {  // b转钱给a
            synchronized (b) { 
                if (b.getMoney() >= 1000) { // 如果账户余额大于 1000
                    // 扣减 b账户1000元
                    log.debug("B操作");
                    b.deduct(1000);
                    // a账户添加1000元
                    a.add(1000);
                }
            }
        }, "B操作");
        Thread thread2 = new Thread(() -> {  // b老婆转钱给c
            synchronized (b) {
                if (b.getMoney() >= 1000) { // 如果账户余额大于 1000
                    // 扣减 b账户1000元
                    log.debug("B老婆操作");
                    b.deduct(1000);
                    // c账户添加1000元
                    c.add(1000);
                }
            }
        }, "B老婆操作");
        thread.start();
        thread2.start();
        thread.join();
        thread2.join();

        log.debug("b账户-->{}", b.getMoney() + "");
        log.debug("a账户-->{}", a.getMoney() + "");
        log.debug("c账户-->{}", c.getMoney() + "");
    }
}

@Data
class Account { // 账户对象 有一余额,加钱,扣钱
    private int money = 1000;

    void deduct(int num) {
        this.money -= num;
    }

    void add(int num) {
        this.money += num;
    }
}



在这里插入图片描述
我们把b账户对象当锁就解决了这个问题,当然也可以用Account.class,a对象,c对象也是一样的

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值