资讯专栏INFORMATION COLUMN

java并发编程学习7--同步--synchronized关键字

zzzmh / 471人阅读

摘要:如果两个线程存取相同的对象,并且每一个线程都调用一个修改该对象状态的方法,根据线程访问数据的顺序,可能会出现错误的数据结果,这种现象成为条件竞争。而问题往往就是有多个线程同时在执行步骤。内部锁有如下的特点不能中断正在试图获得锁的线程。

【条件竞争

在多线程的开发中,两个及其以上的线程需要共享统一数据的存取。如果两个线程存取相同的对象,并且每一个线程都调用一个修改该对象状态的方法,根据线程访问数据的顺序,可能会出现错误的数据结果,这种现象成为条件竞争。因为修改对象状态的方法并不是一个原子操作,通常步骤是:

1. 读取当前状态值到线程工作内存。
2. 在线程工作内存中修改状态值。
3. 将修改后的状态值重新写入主内存。

而问题往往就是有多个线程同时在执行步骤2。

【 有两种机制代码受并发访问的干扰

synchronized关键字。

Reentrantlock类。

【synchronized关键字

java中每个对象都有一个内部锁。如果一个方法是用synchronized关键字修声明的,那么对象的锁将保护整个方法,也就是说要调用该方法,线程必须获得对象内部锁。内部锁有如下的特点:

不能中断正在试图获得锁的线程。

试图获得锁不能设置超时时间。

只有一个条件:要么获得,要么等待,没有粒度更细的控制。

【多个线程修改同一个对象造成数据失误
public class Sync {

    private int value;

    void add(){
        this.value ++;
    }
    int getValue(){
        return this.value;
    }

    public static void main(String[] args) {
        for (int i = 0; i <30 ; i++) {
            Sync s = new Sync();
            List cfs = new ArrayList<>();
            cfs.add(CompletableFuture.runAsync(() -> s.add()));
            cfs.add(CompletableFuture.runAsync(() -> s.add()));
            cfs.add(CompletableFuture.runAsync(() -> s.add()));
            //等待子线程执行完毕
            CompletableFuture.allOf(cfs.toArray(new CompletableFuture[cfs.size()])).join();
            System.out.println(s.getValue());
        }
    }
}

现在使用synchronized关键字修饰add()看看:数据正常。

【注意

如果我们在同步的时候需要判断,切记将条件判断放在同步代码块之中,如果在外部判断,很有可能出现:

1. 第一个线程通过条件判断
2. 第二个线程获得cpu,并修改了共享对象
3. 第一个线程再次获得cpu此时已经不满足条件,但是代码在向下执行,于是出现异常的情况。

例如转账的例子:

public class Account {
    //账户金额
    private int amount;

    public Account(int amount){
        this.amount = amount;
    }

    //转账
    public synchronized void trans(Account to,int value){
        //条件判断处于同步代码中!!!!!!
        if(amount - value < 0){
            throw new RuntimeException("钱不够了!");
        }
        this.amount -= value;
        to.addAmount(value);
        System.out.println("转账成功");
    }

    public void addAmount(int amount){
        this.amount += amount;
    }
    public int getAmount(){
        return this.amount;
    }

    public static void main(String[] args) {
        Account from = new Account(100);
        Account to1 = new Account(0);
        Account to2 = new Account(0);
        List cfs = new ArrayList<>();
        cfs.add(CompletableFuture.runAsync(() -> from.trans(to1,100)));
        cfs.add(CompletableFuture.runAsync(() -> from.trans(to2,100)));
        CompletableFuture.allOf(cfs.toArray(new CompletableFuture[cfs.size()])).join();
        System.out.println(from.getAmount());
        System.out.println(to1.getAmount());
        System.out.println(to2.getAmount());
    }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/68094.html

相关文章

  • 并发编程的艺术

    摘要:假设不发生编译器重排和指令重排,线程修改了的值,但是修改以后,的值可能还没有写回到主存中,那么线程得到就是很自然的事了。同理,线程对于的赋值操作也可能没有及时刷新到主存中。线程的最后操作与线程发现线程已经结束同步。 很久没更新文章了,对隔三差五过来刷更新的读者说声抱歉。 关于 Java 并发也算是写了好几篇文章了,本文将介绍一些比较基础的内容,注意,阅读本文需要一定的并发基础。 本文的...

    curlyCheng 评论0 收藏0
  • java并发编程学习13--Atomic数据结构简介

    摘要:介绍中无锁的线程安全整数,一个提供原子操作的的类。在语言中,和操作并不是线程安全的,在使用的时候,不可避免的会用到关键字。而则通过一种线程安全的加减操作接口。就是的意思,比较并操作。有个操作数,内存值,旧的预期值,要修改的新值。 【介绍 JAVA 中无锁的线程安全整数 AtomicInteger,一个提供原子操作的Integer的类。在Java语言中,++i和i++操作并不是线程安全的...

    李增田 评论0 收藏0
  • java并发编程学习1--基础知识

    摘要:死亡状态线程退出有可能是正常执行完成也有可能遇见异常退出。类有新建与死亡状态返回其余状态返回判断线程是否存活。线程因某些原因进入阻塞状态。执行同步代码块的过程中执行了当前线程放弃开始睡眠进入就绪状态但是不会释放锁。 【java内存模型简介 JVM中存在一个主存区(Main Memory或Java Heap Memory),Java中所有变量都是存在主存中的,对于所有线程进行共享,而每个...

    huangjinnan 评论0 收藏0
  • Java多线程学习(三)volatile键字

    摘要:三关键字能保证原子性吗并发编程艺术这本书上说保证但是在自增操作非原子操作上不保证,多线程编程核心艺术这本书说不保证。多线程访问关键字不会发生阻塞,而关键字可能会发生阻塞关键字能保证数据的可见性,但不能保证数据的原子性。 系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)synchroniz...

    tain335 评论0 收藏0
  • Java多线程学习(七)并发编程中一些问题

    摘要:相比与其他操作系统包括其他类系统有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。因为多线程竞争锁时会引起上下文切换。减少线程的使用。很多编程语言中都有协程。所以如何避免死锁的产生,在我们使用并发编程时至关重要。 系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)syn...

    dingding199389 评论0 收藏0

发表评论

0条评论

zzzmh

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<