Java学习之线程同步(Synchronization)

       线程间通信主要通过对字段和引用对象共享存取实现的,而这很容易导致线程冲突和内存一致性错误。那什么是线程冲突呢?线程冲突是指多个线程对某个字段进行访问或者操作,而这些操作有可能由多个步骤组成,即使操作只是简单的语句,比如a++。由于存在多个步骤就导致了多个线程可能对数据交叉操作,这样就容易引起操作结果与期望的不一致,举个例子如下:

class Counter {
    private int c = 0;

    public void increment() {
        c++;
    }

    public void decrement() {
        c--;
    }

    public int value() {
        return c;
    }

}

       假设现在有两个线程A和B,A调用increment方法而差不多同时B调用decrement方法,那么执行交叉执行的情况可能是这样的:

  1. A:取得c的值
  2. B:取得c的值
  3. A:增加c的值,结果为1
  4. B:减少c的值,结果为-1
  5. A:存储c的值,c=1
  6. B:存储c的值,c=-1

       根据上面的执行情况,c的最终结果为-1,线程A对c的操作结果被线程B对c的操作覆盖了。而这只是可能的一种情况,或者线程A会覆盖线程B的操作,也可能根本不会出现错误。正是由于这种不可预见性,线程冲突带来的bug是很难被发现和修复的。

       当不同线程对同一数据应该得到相同的结果但得到的却是不同的结果时,内存一致性的错误便发生了。避免内存一致性错误的关键是理解happens-before 关系。该关系保证被一个特定语句写入内存的数据对另一个特定语句是可见的。考虑上面的例子,假设线程A执行了increment()方法,线程B执行输出c值的语句Sytem.out.println(c),那么存在B得到的结果为0而不是1的可能性,但如果输出c的语句在A线程中,则输出结果为1。这是因为没有保证线程A对c做的修改对线程B是可见的,除非程序员在两个语句中建立了happens-before关系。

       解决线程冲突和内存一致性错误的一种工具就是线程同步。Java提供了两种线程同步的语法,synchronized方法和synchronized语句。synchronized方法只需要在方法声明中添加synchronized关键字,如:publicsynchronized voidincrement()即可。方法同步有两个效果:

  1. 多个线程对同一个对象上的synchronized方法的调用不可能出现交叉。当一个线程执行synchronized方法时,所有调用相同对象上的synchronized方法的线程将被阻塞直到第一个线程执行完毕。
  2. 当synchronized方法退出时,自动与后续调用同一对象上的synchronized方法建立happens-before关系,这就确保了该对象状态的改变对所有线程都是可见的。

       需要注意的是构造方法是不可以被同步,在构造方法上使用synchronized关键字是语法错误。如果一个对象被多个线程共享,所有对该对象中变量的读写都要通过synchronized方法,唯一的例外是final字段,因为final字段在对象构造完成后是不可以再被修改的,因此可以安全的使用非synchronized方法读取。

       在学习synchronized语句前先要学习一下内部锁或者监控锁,线程同步就是围绕该内部实体建立的,每个对象都有与之关联的内部锁。内部锁在线程同步中起了两方面的作用:强制排它访问一个对象的状态和建立happens-before 关系。依照惯例,一个需要独占的、一致性的访问对象字段的线程必须在访问它们之前获得对象的内部锁,并在执行完毕后释放内部锁。一个线程在已经获得锁和释放锁之间的时间内拥有内部锁,只要一个线程拥有内部锁其它线程就不能获得相同的锁,其它线程将在视图获得该锁时阻塞。当一个线程释放了内部锁后,happens-before 关系在那个操作和后续对该锁的请求之间建立了。

       当一个线程调用synchronized方法时,自动获得了该方法对象的内部锁并在方法返回时释放锁,锁释放在方法因为未捕获的异常返回时也会发生。静态synchronized方法关联的是类而不是对象,因此一个线程获得的是与该类对应的Class对象的内部锁。

       synchronized语句必须显示地指明提供内部锁的对象,如:

public void addName(String name) {
      synchronized(this) {
        lastName = name;
        nameCount++;
      }
      nameList.add(name);
}
       如果没有synchronized语句则需要一个单独的,不是synchronized的方法来执行nameList.add(name)调用。synchronized语句在细粒度同步方面也有很好的用处。假设FineGrained有两个字段c1和c2,但它们从不一起使用,对这两个字段的更新都需要同步,但没有理由阻止c1的更新与c2的更新交叉进行(因为它们从不一起使用,所以交叉更新不会出现不一致的现象),使用synchronized方法因为增加了不必要的阻塞而降低了并发性,此时synchronized语句就有了用武之地了,可以提供两个对象分别为这两个字段提供内部错,这样既不会相互影响也不会降低并发性:
public class FineGrained{
    private long c1 = 0;
    private long c2 = 0;
    private Object lock1 = new Object();
    private Object lock2 = new Object();
    public void inc1() {
        synchronized(lock1) {
            c1++;
        }
    }
    public void inc2() {
        synchronized(lock2) {
            c2++;
        }
    }
}

       虽然一个线程不能获取被其它线程拥有的锁,但可以获取已经被它拥有的锁。允许一个线程多次获取同一个锁被称为可重入同步。假如某个同步代码中直接或间接地调用了其它包含同步代码的方法,而两个同步代码使用的是相同的锁,如果没有可重入同步,则会出现一个线程被它自己阻塞的情况。

       线程同步提供了解决线程冲突和内存一致性错误的方法,但在使用过程还需格外小心,严格区分哪些字段交叉访问没有问题,哪些字段相互影响。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
### 回答1: Java中的线程同步和异步指的是线程间如何协作。 同步Synchronization)是指线程之间的协调,确保每一个时刻只有一个线程执行某段代码。 异步(Asynchronization)是指线程之间不需要协调,多个线程可以同时执行某段代码。 简单来说,同步线程之间互相等待,异步是线程之间互不干扰。 ### 回答2: Java线程同步和异步是两种不同的多线程编程概念。 线程同步是指多个线程之间按照一定的顺序来共享数据和执行任务,以保证数据的一致性和正确性。在同步过程中,一个线程执行到某一点时,其他想要访问该点的线程必须等待,直到执行完毕才能继续执行。同步机制通过synchronized关键字、ReentrantLock等机制来实现,可以避免多个线程同时修改共享数据而导致的数据不一致问题。 而线程异步则是指多个线程之间独立运行,并不按照特定顺序来共享数据和执行任务。每个线程独立执行自己的任务,彼此之间无需等待。线程异步可以提高程序的并发性和性能,但同时也会增加编程复杂度和出错可能性。在异步处理中,通常使用线程池或者Future接口等机制来实现异步执行。 综上所述,线程同步和异步的区别在于线程之间的执行顺序和数据访问方式。同步机制可以保证数据的一致性和正确性,但可能会造成性能问题;而异步机制可以提高并发性和性能,但可能会引发编程复杂度和出错可能性。根据具体的应用场景和需求,选择合适的线程同步或异步方式来实现多线程编程。 ### 回答3: Java线程同步和异步是两种不同的处理机制。 线程同步是指多个线程按照一定的顺序执行,其中一个线程完成了特定的任务后,其他线程才能继续执行。在Java中,可以通过关键字synchronized和Lock来实现线程同步。使用同步机制可以有效地避免多个线程对共享资源的竞争,保证数据的一致性和正确性。然而,线程同步也有一些缺点,比如可能会引起死锁和性能下降。 线程异步是指多个线程可以独立执行,彼此之间不需要等待。每个线程可以以不同的顺序和速度执行任务,因此具有更高的并发性。在Java中,可以使用线程池和CompletableFuture等机制来实现线程的异步执行。异步编程可以提高程序的响应速度和吞吐量,并提高系统的并发性能。然而,异步编程在处理共享资源时需要额外的注意,因为多个线程可能会并发地访问和修改共享资源,可能引发数据的不一致和竞争条件。 综上所述,线程同步和异步是不同的线程处理机制。线程同步保证了多个线程按照一定的顺序执行,避免了竞争条件和数据的不一致;而线程异步则可以独立执行,提高了并发性能,但需要额外注意共享资源的并发访问和修改。在实际开发中,需要根据具体的需求和场景来选择合适的处理机制。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

skyWalker_ONLY

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值