本文共 4058 字,大约阅读时间需要 13 分钟。
小咸儿在上一篇多线程——线程通讯中,提到线程安全问题,今天就来说一说。
先来一张导图来看看线程安全的分布?
线程安全是什么呢?
当多个线程共享同一个全局变量,做写操作时,可能会收到其他线程的干扰,做读操作则不会发生线程安全。
既然遇到了线程安全问题,那么该如何解决这个问题呢?
这时候就会先考虑到两个关键字:volatile 和 synchronized
volatile关键字: 保证可见性的问题。保证另一个线程可见,及时将修改后的变量刷新到主内存中。虽然能够保证可见性,但是无法保证原子性。
代码展示:
package com.practice.demo.thread.lock;import java.util.HashMap;import java.util.Map;import java.util.concurrent.locks.ReentrantReadWriteLock;public class ReadWriteLock { private volatile MapcaChe = new HashMap<>(); /** * 读写锁 */ private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); /** * 写入锁 */ private ReentrantReadWriteLock.WriteLock writeLock = rwl.writeLock(); private ReentrantReadWriteLock.ReadLock readLock = rwl.readLock(); /** * 写入元素 * @param key key值 * @param value value值 */ public void put(String key, String value){ try { writeLock.lock(); System.out.println("写入put方法key:" + key + ",value:" + value + ".开始"); Thread.currentThread().sleep(100); caChe.put(key,value); System.out.println("写入put方法key:" + key + ",value:" + value + ".结束"); } catch (Exception e){ }finally { writeLock.unlock(); } } /** * 读取元素 * @param key key值 * */ public String get(String key){ try { readLock.lock(); System.out.println("读取key:" + key + ".开始"); Thread.currentThread().sleep(100); String value = caChe.get(key); System.out.println("读取key:" + key + ".结束"); return value; }catch (Exception e){ return null; }finally { readLock.unlock(); } } public static void main(String[] args){ ReadWriteLock readWriteLock = new ReadWriteLock(); // 写线程 Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++){ readWriteLock.put("i", i + ""); } } }); // 读线程 Thread t2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++){ readWriteLock.get(i + ""); } } }); t1.start(); t2.start(); }}
打印结果:
结果: 即当这个线程开始和结束都执行完之后,另一个线程才能进行,并且全局变量发生变化后对另一个线程可见。
synchronized关键字: 内置锁,保证线程原子性,当线程进入方法的时候,自动获取锁,一旦锁被某个线程获取到后,其他的线程就会等待。即可修饰代码块也可用来修饰方法。
代码展示
public void run() { // 使用account作为同步监视器,任何线程进入下面同步代码块之前 // 必须先获得对account账户的锁定——其他线程无法获得锁,也就无法修改她 // 这种做法符合:“加锁——修改——释放锁”的逻辑 synchronized (account) { // 账户余额大于取钱数目 if (account.getBalance() >= drawAmount) { // 吐出钞票 System.out.println(getName() + "取钱成功!吐出钞票:" + drawAmount); try { Thread.sleep(1); } catch (InterruptedException ex) { ex.printStackTrace(); } // 修改余额 account.setBalance(account.getBalance() - drawAmount); System.out.println("\t余额为:" + account.getBalance()); } else { System.out.println(getName() + "取钱失败,余额不足!"); } } }
volatile和synchronized的区别:
volatile不会进行加锁操作:
volatile变量是一种稍弱的同步机制在访问volatile变量时不会执行加锁操作,因此也就不会执行线程阻塞,因此volatile变量是一种比synchronized关键字更加轻量级的同步机制。volatile不如synchronized安全:
在代码中如果过度依赖volatile变量来控制状态的可见性,通常会比使用锁的代码更脆弱,也更加难以理解。仅当volatile变量能简化代码的实现以及对同步策略的验证时,才应该使用它。一般来说,使用同步机制会更安全。volatile变量作用类似于同步变量读写操作:
从内存可见性的角度看,写入volatile变量相当于退出同步代码块,而读取volatile变量相当于进入同步代码块。volatile无法同时保证内存可见性和原子性:
加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性,原因是声明为volatile的简单变量如果当前值与该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不是原子操作:“count++”,“count = count + 1”。解决线程安全的问题不仅只有这两种处理办法,还有一种锁(Lock),具体且听小咸儿下回分享。
多线程是十分实用并且常用的内容,接下来小咸儿还会继续深入学习多线程,更多的内容等待更新。
感谢您的阅读~~
转载地址:http://ajerb.baihongyu.com/