这篇文章主要讲解了“java中线程安全问题举例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“java中线程安全问题举例分析”吧!
一、什么时候数据在多线程并发的环境下会存在安全问题?
三个条件:
条件1:多线程并发。
条件2:有共享数据。
条件3:共享数据有修改的行为。
满足以上3个条件之后,就会存在线程安全问题。
二、怎么解决线程安全问题?
线程排队执行。(不能并发)。用排队执行解决线程安全问题。这种机制被称为:线程同步机制。
三、银行 取钱/存钱 案例
Account 类
package ThreadSafa; public class Account { // 账号 private String actno; // 余额 private double balance; public Account() { } public Account(String actno, double balance) { this.actno = actno; this.balance = balance; } public String getActno() { return actno; } public void setActno(String actno) { this.actno = actno; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } //取款方法 public void withdraw(double money) { // 取款之前的余额 double before = this.getBalance(); // 取款之后的余额 double after = before - money; // 更新余额 try { //模拟网络延时 更新余额不及时 百分百会出问题 Thread.sleep(1 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } this.setBalance(after); }}
AccountThread 类
package ThreadSafa; public class AccountThread extends Thread { // 两个线程必须共享同一个账户对象。 private Account act; //通过构造方法传递过来账户对象 public AccountThread(Account act) { this.act = act; } @Override public void run() { double money = 5000; //取款 act.withdraw(5000); System.out.println(Thread.currentThread().getName() + "账户" + act.getActno() + "取款成功,余额" + act.getBalance()); }}
Test 类
package ThreadSafa; public class Test { public static void main(String[] args) { // 创建账户对象 Account act = new Account("act-001", 10000); //创建两个线程 Thread t1 = new AccountThread(act); Thread t2 = new AccountThread(act); //设置name t1.setName("t1"); t2.setName("t2"); //启动线程 t1.start(); t2.start(); }}
运行问题
解决方法 修改 Account 类 中的 withdraw 方法
package ThreadSafa; public class Account { // 账号 private String actno; // 余额 private double balance; public Account() { } public Account(String actno, double balance) { this.actno = actno; this.balance = balance; } public String getActno() { return actno; } public void setActno(String actno) { this.actno = actno; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } //取款方法 public void withdraw(double money) { // 以下这几行代码必须是线程排队的,不能并发 // 一个线程把这里的代码全部执行结束之后,另外一个线程才能进来 synchronized (this) { // 取款之前的余额 double before = this.getBalance(); // 取款之后的余额 double after = before - money; // 更新余额 try { //模拟网络延时 更新余额不及时 百分百会出问题 Thread.sleep(1 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } this.setBalance(after); } }}
为什么会出现线程安全问题
计算机系统资源分配的单位为进程,同一个进程中允许多个线程并发执行,并且多个线程会共享进程范围内的资源:例如内存地址。当多个线程并发访问同一个内存地址并且内存地址保存的值是可变的时候可能会发生线程安全问题,因此需要内存数据共享机制来保证线程安全问题。
对应到java服务来说,在虚拟中的共享内存地址是java的堆内存,比如以下程序中线程安全问题:
public class ThreadUnsafeDemo { private static final ExecutorService EXECUTOR_SERVICE; static { EXECUTOR_SERVICE = new ThreadPoolExecutor(100, 100, 1000 * 10, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(100), new ThreadFactory() { private AtomicLong atomicLong = new AtomicLong(1); @Override public Thread newThread(Runnable r) { return new Thread(r, "Thread-Safe-Thread-" + atomicLong.getAndIncrement()); } }); } public static void main(String[] args) throws Exception { Map<String, Integer> params = new HashMap<>(); List<Future> futureList = new ArrayList<>(100); for (int i = 0; i < 100; i++) { futureList.add(EXECUTOR_SERVICE.submit(new CacheOpTask(params))); } for (Future future : futureList) { System.out.println("Future result:" + future.get()); } System.out.println(params); } private static class CacheOpTask implements Callable<Integer> { private Map<String, Integer> params; CacheOpTask(Map<String, Integer> params) { this.params = params; } @Override public Integer call() { for (int i = 0; i < 100; i++) { int count = params.getOrDefault("count", 0); params.put("count", ++count); } return params.get("count"); } }}
创建100个task,每个task对map中的元素累加100此,程序执行结果为:
{count=9846}
而预期的正确结果为:
{count=10000}
至于出现这种问题的原因,下面会具体分析。
判断是否有线程安全性的一个原则是:
是否有多线程访问可变的共享变量
感谢各位的阅读,以上就是“java中线程安全问题举例分析”的内容了,经过本文的学习后,相信大家对java中线程安全问题举例分析这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!