并发三大特性解析


前言

并发编程中的三大特性:原子性、可见性和有序性。
在多线程编程中,并发性是一个重要的概念,它允许程序在多个任务之间切换执行,以提高程序的效率和响应性。然而,并发编程也带来了许多挑战,其中最主要的挑战之一是保证多个线程之间的数据一致性和正确性。为了解决这个问题,我们需要理解并发编程中的三个重要特性:原子性、可见性和有序性。


一、原子性(Atomicity)

原子性是指一个操作或者一系列操作要么全部完成,要么全部不完成,不会在中间某个环节出现中断。在并发编程中,如果一个操作是原子的,那么这个操作要么完全执行,要么完全不执行,不会被其他线程干扰。

Java的代码演示原子性的概念:

public class AtomicityExample {
    private static AtomicInteger counter = new AtomicInteger(0);
    
    public static void main(String[] args) {
        new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.incrementAndGet();
            }
        }).start();
        
        new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.incrementAndGet();
            }
        }).start();
        
        // 等待线程执行完成
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("Counter value: " + counter.get());
    }
}

在这个例子中,我们使用了AtomicInteger类来保证incrementAndGet()方法的原子性。即使有多个线程同时调用这个方法,AtomicInteger类也会保证每次只增加一个值,不会出现并发问题。最终的计数结果应该是2000,证明了原子性的正确性。


二、可见性(Visibility)

可见性是指一个线程修改了共享变量的值,其他线程能够立即看到修改后的值。在并发编程中,如果一个线程修改了共享变量的值,其他线程却看不到修改后的值,那么就可能出现数据不一致和其他并发问题。

Java的代码演示可见性的概念:

public class VisibilityExample {
    private static final Object lock = new Object();
    private static int sharedVariable = 0;
    
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lock) {
                sharedVariable = 1;
            }
        }).start();
        
        new Thread(() -> {
            while (sharedVariable != 1) {
                // do nothing, just wait for the variable to be set
            }
            System.out.println("Variable is visible");
        }).start();
    }
}

在这个例子中,我们使用了synchronized关键字来保证只有一个线程能够访问共享变量sharedVariable。当一个线程将sharedVariable的值设置为1后,其他线程能够立即看到修改后的值,并输出一条消息。这个例子证明了可见性的正确性。


三、有序性(Ordering)

有序性是指程序执行的顺序按照某种固定的规则进行,不会被操作系统或者硬件随意打乱。在并发编程中,如果一个操作先于另一个操作执行,那么它也必须在线程中先于另一个操作执行。但是,由于并发和异步的性质,线程的执行顺序可能会被操作系统调度器打乱。因此,我们需要使用同步机制来保证操作的有序性。

Java的代码演示有序性的概念:

public class OrderingExample {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();
    private static int sharedVariable1 = 0;
    private static int sharedVariable2 = 0;
    
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lock1) {
                sharedVariable1++;
            }
            synchronized (lock2) {
                sharedVariable2++;
            }
        }).start();
        
        new Thread(() -> {
            synchronized (lock2) {
                if (sharedVariable1 == 1 && sharedVariable2 == 1) {
                    System.out.println("Variables are in order");
                }
            }
        }).start();
    }
}

在这个例子中,我们使用了两个锁对象lock1和lock2来保证操作的顺序执行。第一个线程先获取lock1锁,然后将sharedVariable1的值增加1。然后,它再获取lock2锁,将sharedVariable2的值增加1。第二个线程只获取lock2锁,然后检查sharedVariable1和sharedVariable2的值是否按照预期的顺序进行了修改。这个例子证明了有序性的正确性。


四、案例分析

下面是一个使用Java并发编程的案例,通过这个案例,我们将深入理解并发编程中的原子性、可见性和有序性。

案例:

一个银行转账的例子
假设有两个账户A和B,我们想要从账户A转账100元到账户B。这个操作需要更新两个账户的状态,如果这两个操作不是原子的,那么可能会出现数据不一致的情况。
首先,我们需要定义一个Account类,这个类有两个属性:balance(余额)和 mutex(互斥锁):

public class Account {
    private int balance;
    private final Object mutex = new Object();
    
    public Account(int balance) {
        this.balance = balance;
    }
    
    public void deposit(int amount) {
        synchronized (mutex) {
            balance += amount;
        }
    }
    
    public void withdraw(int amount) {
        synchronized (mutex) {
            if (amount > balance) {
                throw new RuntimeException("Insufficient funds");
            }
            balance -= amount;
        }
    }
    
    public int getBalance() {
        synchronized (mutex) {
            return balance;
        }
    }
}

接下来,我们实现转账操作:

public class Transfer {
    public static void transfer(Account fromAccount, Account toAccount, int amount) {
        // 获取两个账户的互斥锁
        synchronized (fromAccount.mutex) {
            synchronized (toAccount.mutex) {
                // 检查账户余额是否足够
                if (fromAccount.getBalance() < amount) {
                    throw new RuntimeException("Insufficient funds in the account");
                }
                // 执行转账操作:从fromAccount中扣除amount,存入toAccount中
                fromAccount.withdraw(amount);
                toAccount.deposit(amount);
            }
        }
    }
}

在这个例子中,我们使用了两个互斥锁来保证原子性、可见性和有序性。首先,我们获取了两个账户的互斥锁,然后检查账户余额是否足够。如果余额不足,我们抛出一个异常。如果余额足够,我们执行转账操作:从fromAccount中扣除amount,存入toAccount中。这个操作是原子的,因为我们在同一时间只对一个账户进行操作。同时,由于我们使用了互斥锁,保证了可见性和有序性。


五、总结

并发编程中的原子性、可见性和有序性是保证程序正确性的重要原则。在实际应用中,我们需要根据具体的需求和场景,选择合适的并发模型和同步机制,来保证这些原则的实现。
在Java中,我们可以使用synchronized关键字、Lock接口、volatile关键字等机制来保证原子性、可见性和有序性。同时,还需要注意避免常见的并发问题,如竞态条件、死锁、活锁等。
通过深入理解并发编程中的原子性、可见性和有序性,以及掌握Java提供的并发工具,我们可以编写出高效、正确的并发程序,解决实际应用中的并发问题。

  • 43
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小阳小朋友

随便吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值