java中的线程安全

    java中的许多类都非常相似,他们之间的区别就是在多线程时使用是否线程安全比如StringBuffer 和 StringBuilder。

    这个问题一直没有深入探索,所以经常搞不清他们之间的关系。要想深入的理解他们,首先要搞明白什么是线程安全。

1.多线程的内存分配

    在jvm中,有一个主内存,主内存是多线程共享的。

    每个线程有一个自己的工作线程,多个线程之间是不能互相传递数据通信的,它们之间的沟通只能通过共享变量来进行。

    当new一个对象的时候,其被分配在主内存中,某个线程想要操作它,不能获取,而是从主内存获取该对象的副本

    下面是某个线程操作对象时的执行流程:

1. 从主存复制变量到当前工作内存
2. 执行代码,改变共享变量值
3. 用工作内存数据刷新主存相关内容

2.引起线程不安全的原因

下面举个简单的例子:

for(int i=0;i<10;i++)    x++; 

当单个线程a执行以上代码,流程如下:

1.从主内存中读取x的副本到工作内存

2.x+1

3.将x+1后的值写回主存 

若另一个线程b同时执行另一段代码:

for(int i=0;i<10;i++)    x--; 

假设x现在为10,可能会有以下情况发生:

1:线程a从主存读取x副本到工作内存,工作内存中x值为10
2:线程b从主存读取x副本到工作内存,工作内存中x值为10
3:线程a将工作内存中x加1,工作内存中x值为11
4:线程a将x提交主存中,主存中x为11
5:线程b将工作内存中x值减1,工作内存中x值为9
6:线程b将x提交到中主存中,主存中x为9 

如果x是银行账户,线程a是存款操作,线程b是扣款操作,显然会出现严重问题,看下面的代码:

public class TestThread {

    @Test
    public void testThread() throws InterruptedException {
        Account account = new Account(10000);
        Thread a = new Thread(){
            public void run(){
                for (int i = 0; i < 10000; i++){
                    account.add(1);
                }
            }
        };
        Thread b = new Thread(){
            public void run(){
                for (int i = 0; i < 10000; i++){
                   account.withdraw(1);
                }
            }
        };
        a.start();
        b.start();
        a.join();
        b.join();
        System.out.println(account.getBalance());
    }
}

class Account{
    private int balance;
    public Account(int balance) {
        this.balance = balance;
    }
    public int getBalance() {
        return balance;
    }
    public  void add(int num) {
        balance = balance + num;
    }
    public  void withdraw(int num) {
        balance = balance - num;
    }
}

注:join()方法保证了 main函数输出 balance 在 a,b两线程执行完成后执行

上面代码执行多次的结果都不一样。这样的代码如果放在实际项目中,就会出现很大的问题。

3.如何保证线程安全

java用synchronized关键字做为多线程并发环境的执行有序性的保证手段之一。

当synchronized标记一个方法时,方法中调用的共享对象相当于加了一把线程锁。(synchronized只对共享对象操作才会显得有意义)

一个线程执行加了synchronized标记的代码过程如下:

1 获得同步锁
2 清空工作内存
3 从主存拷贝变量副本到工作内存
4 对这些变量计算
5 将变量从工作内存写回到主存
6 释放锁

synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。

当上面代码中add方法withdraw方法加了synchronized标记后,最后执行结果每次都是准确的10000。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值