Java多线程07——JUC并发包03

1 线程的锁的synchronized、Lock、volatile区别

1.1 synchronized 和 volatile 区别

用法:

  • volatile 关键字解决的是变量在多个线程之间的可见性;
  • synchronized 关键字解决的是多个线程之间访问共享资源的同步性;
  • 多线程访问 volatile 时,程序不会发生阻塞;在访问 synchronized 修饰的方法或代码块时,会出现阻塞;
  • volatile 能保证变量在多个线程之间的可见性,但无法保证原子性;synchronized 可以保证数据操作的原子性,也可以间接保证数据的可见性,会将线程中私有内存和公有内存的数据进行同步。

比如我们使用信用卡消费,在消费中如果银行对卡片进行了冻结,那么扣款就应该会被拒绝。

此时就需要所有线程都能看到这个卡片状态的变化才行,否则就会造成用户损失。

要让这个状态被有线程看到,就需要使用 ​​volatile​​ 来修饰该变量。

扣款就更好理解了,如果不对账户的扣款动作进行加锁,账户相同的一笔钱可以被重复消费,将会造成银行的损失。加上锁之后,所有扣款行为将在这里串行进行,消费一笔扣减一笔,避免账户透支或者重复支付。

使用场景:

  • volatile关键字只能用于修饰变量;
  • synchronized 关键字可以修饰方法,代码块。

1.2 synchronized 和 Lock 区别

实现

Lock 是一个接口,而 ​​synchronized​​ 是 Java 中的关键字,由内置语言实现。

异常处理机制

synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁发生;

Lock 在发生异常时,如果没有主动通过 unlock() 方法去释放锁,则很可能造成死锁,因此使用 Lock 时需要在 finally 块中增加手动释放锁的语句。

synchronized{
     语句块;
}

Lock lock = new ReentrantLock()
lock.lock();
lock.unLock();

lock() 和 unlock() 必须成对存在。

效率

Lock 可以提高多个线程进行读操作的效率(读写锁)。

2 线程的读写分离机制

​ReadWriteLock​​ 是读写锁:

  • 维护了一对相关的锁“读取锁”和“写入锁”,一个用于读取操作,另一个用于写入操作。
  • 读取锁,用于只读操作,它是共享锁,能同时被多个线程获取。
  • 写入锁,用于写入操作,它是独占锁,写入锁只能被一个线程锁获取。
  • 不能同时存在读取锁和写入锁,可以同时进行读/读操作,但不能同时读/写、写/写操作。

2.1 创建账号类

public class MyCount {
    private String id;//账号
    private int cash;//账户余额

    public MyCount(String id, int cash) {
        this.id = id;
        this.cash = cash;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    //读取操作
    public int getCash() {
        System.out.println(Thread.currentThread().getName() + " getcash, cash=" + cash);
        return cash;
    }

    //写入操作
    public void setCash(int cash) {
        System.out.println(Thread.currentThread().getName() + " setcash, cash=" + cash);
        this.cash = cash;
    }
}

2.2 创建用户信息类

在用户信息类中声明了读写锁,

并在读取方法中创建读取锁,

在写入方法中创建写入锁,

所有锁在使用完后,均需要手动关闭锁。

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class User {
    private String name;
    private MyCount myCount;
    //声明读写锁
    private ReadWriteLock readWriteLock;

    public User(String name, MyCount myCount) {
        this.name = name;
        this.myCount = myCount;
        this.readWriteLock = new ReentrantReadWriteLock();
    }

    //查询余额
    public void getCash(){
        new Thread(){
            @Override
            public void run() {
                //创建读取锁
                readWriteLock.readLock().lock();
                try {
                    System.out.println(Thread.currentThread().getName() + " getCash start");
                    myCount.getCash();
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + " getCash end");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //手动关闭锁
                    readWriteLock.readLock().unlock();
                }
            }
        }.start();
    }

    //设置余额
    public void setCash(final int cash){
        new Thread(){
            @Override
            public void run() {
                //创建写入锁
                readWriteLock.writeLock().lock();
                try {
                    System.out.println(Thread.currentThread().getName() + " setCash start");
                    myCount.setCash(cash);
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + " setCash end");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //手动关闭锁
                    readWriteLock.writeLock().unlock();
                }
            }
        }.start();
    }
}

2.3 创建测试类

在测试类中创建用户及账户,并对账户信息使用多线程进行读取和写入操作。

public class Test {

    public static void main(String[] args) {
        //创建账户
        MyCount myCount = new MyCount("abcd12", 5000);
        //创建用户,并指定账户
        User user = new User("小张", myCount);

        for (int i = 0; i < 3; i++) {
            user.getCash();
            user.setCash((i + 1) * 1000);
        }
    }
}

2.4 输出结果

Thread-0 getCash start

Thread-0 getcash, cash=5000

Thread-2 getCash start

Thread-2 getcash, cash=5000

Thread-2 getCash end

Thread-0 getCash end

Thread-1 setCash start

Thread-1 setcash, cash=1000

Thread-1 setCash end

Thread-4 getCash start

Thread-4 getcash, cash=1000

Thread-4 getCash end

Thread-5 setCash start

Thread-5 setcash, cash=3000

Thread-5 setCash end

Thread-3 setCash start

Thread-3 setcash, cash=2000

Thread-3 setCash end

在输出的结果中,可以看到:

读取操作 getCash ,在 Thread-0 执行时,但未执行完,Thread-2 也同时进入到了读取操作,它们正在并行执行

写入操作 setCash ,所有线程均从 start 到 end,中间并未有其他线程进入,属于独占执行

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

我有健康

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

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

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

打赏作者

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

抵扣说明:

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

余额充值