前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java多线程07——JUC并发包03

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

作者头像
头发还在
发布2023-10-16 10:59:00
1620
发布2023-10-16 10:59:00
举报
文章被收录于专栏:桃花源桃花源

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 块中增加手动释放锁的语句。

代码语言:javascript
复制
synchronized{
     语句块;
}

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

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

效率

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

2 线程的读写分离机制

​ReadWriteLock​​ 是读写锁:

  • 维护了一对相关的锁“读取锁”和“写入锁”,一个用于读取操作,另一个用于写入操作。
  • 读取锁,用于只读操作,它是共享锁,能同时被多个线程获取。
  • 写入锁,用于写入操作,它是独占锁,写入锁只能被一个线程锁获取。
  • 不能同时存在读取锁和写入锁,可以同时进行读/读操作,但不能同时读/写、写/写操作。
2.1 创建账号类
代码语言:javascript
复制
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 创建用户信息类

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

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

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

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

代码语言:javascript
复制
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 创建测试类

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

代码语言:javascript
复制
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,中间并未有其他线程进入,属于独占执行

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-02-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 线程的锁的synchronized、Lock、volatile区别
    • 1.1 synchronized 和 volatile 区别
      • 1.2 synchronized 和 Lock 区别
      • 2 线程的读写分离机制
        • 2.1 创建账号类
          • 2.2 创建用户信息类
            • 2.3 创建测试类
              • 2.4 输出结果
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档