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,中间并未有其他线程进入,属于独占执行。