class JamesLazy {
private volatile T value;
T get() {
if (value == null)//这里做了null判断, 延迟初始化的字段它不是原子的
value = initialize();
return value;
}
}
数据竞争
当两个或多个线程在没有同步的情况下尝试访问相同的非final变量时,就会发生数据竞争。不使用同步可能导致你的所有操作对而其它线程是不可见的,因此可以读取过时数据,但若反过来可能会产生无限循环,损坏的数据结构或不准确的计算等后果。此代码可能会导致无限循环,因为读者线程可能永远不会观察到写入器线程所做的更改:
class JamesWaiter implements Runnable {
private boolean shouldFinish;
void finish() {
shouldFinish = true;
}
public void run() {
long iteration = 0;
while (!shouldFinish) {
iteration++;
}
System.out.println("完成后的结果: " + iteration);
}
}
class JamesDataRace {
public static void main(String[] args) throws InterruptedException {
JamesWaiter waiter = new JamesWaiter();
Thread waiterThread = new Thread(waiter);
waiterThread.start();
waiter.finish();
waiterThread.join();
}
}
运行结果为:
第3节 Java内存模型:Happens-before 关系
Java内存模型是根据读取和写入字段以及在监听器上同步等操作定义的。可以通过Happens-before关系对操作进行排序,一般用于推断线程何时看到另一个线程的操作结果,以及用来分析同步的程序构成状况。
Happens-before关系具有以下特性: |
---|
在Thread1线程执行任何业务操作之前调用Thread1.start()。也就是说Thread1对象的start()方法先行发生于此线程的任何一个操作 |
在监视器锁上的解锁操作必须在同一个监视器锁上的加锁操作之前执行。 |
对volatile变量的写入在任何后续读取volatile变量之前发生。 |
在发布对象的引用之前发生对最终变量的写入。 |
线程中的所有操作都是在从Thread#join 该线程返回之前发生的。 |
在下图中,Thread 1的Action X
操作在Action Y
操作之前就调用了,因此在Thread 2
中所有操作Action Y
的右侧业务操作时, 将会看到Thread 1中Action X前的所有操作。
第4节 标准同步功能
synchronized
关键字
synchronized
关键字用于防止不同的线程同时执行相同的代码块, 其实就是指这个代码块只被一个线程执行。当线程A获得synchronized锁后,只有线程A访问synchronized代码块,是一种独占式模式操作, 例如: 13号技师被王根基同学带进屋后,王根基同学在门上挂了把锁, 其它线程得等待, 保证了13号技师只能与王根基(线程)进行业务操作 ,不难看出这个操作原子操作(只有王根基线程操作13号技师)。此外,它保证其它线程在获取相同的锁之后将观察正在操作线程的结果,何时释放锁。
class JamesAtomicOperation {
private int counter0 ;
private int counter1 ;
void increment(){
synchronized(this){
counter0 ++ ;
counter1 ++ ;
}
}
}
synchronized
关键字可以在方法级来指定。
方法类型 | 描述 |
---|---|
静态 | 对该类对象加锁,俗称“类锁” |
非静态 | 参考以上synchronized(this) 代码块,对调用该方法的对象加锁,俗称“对象锁”** |
锁是可重入的,因此如果线程已经拥有锁,可以再次成功获取它。
class JamesReentrantcy {
synchronized void doAll(){
doFirst();
doSecond();
}
synchronized void doFirst(){
System.out.println(“第一次操作成功。”);
}
synchronized void doSecond(){
System.out.println(“第二次操作成功。”);
}
}
等待/通知
wait/notify/notifyAll
方法在Object
类中声明。wait
的作用可以使线程状态变成WAITING
或TIMED_WAITING
(如果已等待超时)状态。为了唤醒一个线程,可以执行以下任何操作:
- 另一个线程调用
notify
,唤醒在监视器上等待的任意线程。 - 另一个线程调用
notifyAll
,唤醒监视器上等待的所有线程。 - 如果调用
Thread#interrupt
。在这种情况下,会抛出InterruptedException
。
最常见的模式是条件循环:
class JamesConditionLoop {
private boolean condition;
synchronized void waitForCondition()throws InterruptedException {
while(!condition){
wait();
}
}
synchronized void satisfCondition(){
condition = true ;
notifyAll();
}
}
- 同志们请记住,如果
wait/notify/notifyAll
要在你的对象上使用,需要首先获取此对象锁。 - 总是在一个循环中等待检查正在等待的条件 - 如果另一个线程在等待开始之前满足条件,这就解决了时间问题。此外,它还可以保护您的代码免受可能(并且确实发生)的虚假唤醒。
- 在打电话之前,请务必确保您满足等待条件
notify/notifyAll
。如果不这样做将导致通知,但没有线程能够逃脱其等待循环。
volatile
关键字
volatile
解决了多线程之间的资源可见性问题,有这么一层关系大家需要知道:对某个volatile字段的写操作happens-before后续对同一个volatile字段的读操作,比如线程1写入了volatile变量v(这里和后续的“变量”都指的是对象的字段、类字段和数组元素),接着线程2读取了v,那么,线程1写入v及之前的写操作都对线程2可见(线程1和线程2可以是同一个线程)。因此,它保证字段的任何后续读取都将看到由最近写入设置的值。
class JamesVolatileFlag implements Runnable {
private volatile boolean shouldStop;
public void run() {
while (!shouldStop) {
// TODO业务代码
}
System.out.println(“停止.”);
}
void stop() {
shouldStop = true;
}
public static void main(String[] args) throws InterruptedException {
JamesVolatileFlag flag = new JamesVolatileFlag();
Thread thread = new Thread(flag);
thread.start();
flag.stop();
thread.join();
}
}
原子操作
在java.util.concurrent.atomic
包路径下的一些类以无锁方式支持单个值上的原子复合操作,类似于volatile。
使用AtomicXXX类,可以实现原子check-then-act
操作:
class JamesCheckThenAct {
private final AtomicReference value = new AtomicReference<>();
void initialize() {
if (value.compareAndSet(null, “value”)) {
System.out.println(“仅初始化一次.”);
}
}
}
jdk8中新增的AtomicInteger
和AtomicLong
具有increment/decrement
(自增自减)的原子操作:
class JamesIncrement {
private final AtomicInteger state = new AtomicInteger();
void advance() {
int oldState = state.getAndIncrement();
System.out.println(“Advanced: '” + oldState + “’ -> '” + (oldState + 1) + “'.”);
}
}
若计数器不需要原子操作来获取其值,请考虑使用LongAdder
而不是AtomicLong
/ AtomicInteger
类。LongAdder
其实就是把一个变量分解为多个变量,让同样多的线程去竞争多个资源,性能问题就可以解决了,因此它在高争用下表现更好。
ThreadLocal
从概念上讲,ThreadLocal就好像在每个Thread中都有一个具有自己版本的变量。ThreadLocal通常用于存储每个线程的值,如“当前事务”或其他资源。此外,它们还用于维护每线程计数器,统计信息或ID生成器。
class JamesTransactionManager {
private final ThreadLocal currentTransaction = ThreadLocal.withInitial(NullTransaction::new);
Transaction currentTransaction() {
Transaction current = currentTransaction.get();
if (current.isNull()) {
current = new TransactionImpl();
currentTransaction.set(current);
}
return current;
}
}
好了, 第1篇就到这里,希望大家持续关注更新…………
现在都说互联网寒冬,其实只要自身技术能力够强,咱们就不怕!我这边专门针对Android开发工程师整理了一套【Android进阶学习视频】、【全套Android面试秘籍】、【Android知识点PDF】。如有需要获取资料文档的朋友,可以点击我的GitHub免费获取!
,可以点击我的GitHub免费获取!