南通java培训:理解并发概念
1、1.概念
2、2.1竞争条件多个线程对共享资源执行一系列操作,根据每个线程的操作顺序可能存在几种结果,这时出现竞争条件。下面的代码不是线程安全的,而且可以不止一次地初始化 value,因为 check-then-act(检查 null,然后初始化),所以延迟初始化的字段不具备原子性:
3、2.2 数据竞争两个或多个线程试图访问同一个非 final 变量并且不加上同步机制,这时会发生数据竞争。没有同步机制可能导致这样的情况,线程执行过程中做出其他线程绣诅收蟮无法看到的更改,因而导致读到修改前的数据。这样反过来可能又会导致无限循环、破坏数据结构或得到错误的计算结果。下面这段代码可能会无限循环,因为读线程可能永远不知道写线程所做的更改:
4、3.Ja箪滹埘麽va 内存模型:happens-before 关系Java 内存模型定义基于一些操作,比如读写字段、 Monitor 同步等。这些操作可以按照 happens-before 关系进行排序。这种关系可用来推断一个线程何时看到另一个线程的操作结果,以及构成一个程序同步后的所有信息。happens-before 关系具备以下特性:在线程开始所有操作前调用 Thread#start在获取 Monitor 前,释放该 Monitor在读取 volatile 变量前,对该变量执行一次写操作在写入 final 变量前,确保在对象引用已存在线程中的所有操作应在 Thread#join 返回之前完成
5、4.标准同步特性4.1 synchronized 关键字使用 synchronized 关键字可以防止不同线程同时执行相同代码块。由于进入同步执行的代码块之前加锁,受该锁保护的数据可以在排他模式下操作,从而让操作具备原子性。此外,其他线程在获得相同的锁后也能看到操作结果。
6、也可以在方法上加 synchronized 关键字
7、表2 当整个方法都标记 synchronized 时使用的 Monitor锁是可重入的。如果线程已经持有锁,它可以再次成功地获得该锁。
8、竞争的程度对获取 Monitor 的方式有影响
9、表3: Monitor 状态
10、4.2 wait/notifywait/notify/notifyAll 方法在 Object 类中驾搭鼯携声明。如果之前设置了超时,线程进入 WAITING 或 TIMED_WAITING 状态前保持 wait状态。要唤醒一个线程,可以执行下列任何操作:另一个线程调用 notify 将唤醒任意一个在 Monitor 上等待的线程。另一个线程调用 notifyAll 将唤醒所有在等待 Monitor 上等待的线程。调用 Thread#interrupt 后会抛出 InterruptedException 异常。最常见的模式是条件循环:
11、请记住,在对象上调用 wait/notify/notifyAll,需要首先获得该对象的锁在检查等待条件的循环中保持等待:这解决了薷蒴塾寒另一个线程在等待开始之前即满足条件时的计时问题。 此外,这样做还可以让你的代码免受可能(也的确会)发生的虚假唤醒在调用 notify/notifyAll 前,要确保满足等待条件。如果不这样做会引发通知,然而没有线程能够避免等待循环
12、4.3 volatile 关键字volatile 解决了可见性问题,让修改成为原子操作。由于存在 happens-before 关系,在接下来读取 volatile 变量前,先对 volatile 变量进行写操作。 从而保证了对该字段的任何读操作都能督读到最近一次修改后的值。
13、4.4 Atomicjava.util.concurrent.atomic package 包含了一组类,它们用类似 volatile 的无锁方式支持单个值的原子复合操作。使用 AtomicXXX 类,可以实现 check-then-act 原子操作:
14、AtomicInteger 和 AtomicLong 都提供原子 increment/decrement 操作:
15、如果你希望有这样一个计数器,不需要在获取计数的时候具备原子性,可以考虑用 LongAdder 取代 AtomicLong/AtomicInteger。 LongAdder 能在多个单元中存值并在需要时增加计数,因此在竞争激烈的情况下表现更好。
16、4.5 ThreadLocal一种在线程中包含数据但不用锁的方法是使用 ThreadLocal 存储。从概念上讲,ThreadLocal 可以看做每个 Thread 存有一份自己的变量。Threadlocal 通常用于保存每个线程的值,比如“当前事务”或其他资源。 此外,还可以用于维护每个线程的计数器、统计信息或 ID 生成器。