Java并发——同步(三)

1.监视器:

如何在不需要程序员考虑如何加锁的情况下,就可以保证多线程的安全性?监视器就是基于这个理念提出来的。

用Java的术语来讲,监视器具有如下特性:

1)监视器是只包含私有域的类。

2)每个监视器类的对象有一个相关的锁。

3)使用该锁对所有的方法进行加锁。

4)该锁可以有任意多个相关条件。

但是对于Java对象来说,又不同于监视器:

1)域不要求必须是private。

2)方法不要求必须是synchronized。

3)内部锁对客户是可见的。

2.Volatile域:

volatile关键字为实例域的同步访问提供了一种免锁机制。如果声明一个域为volatile,那么,编译器和虚拟机就会知道该域是可能被另一个线程并发更新的。

例如下面的例子:

private boolean done;
public synchronized boolean isDone(){return done;}
public synchronized void setDone(){done = true;}

在这种情况下很容出现阻塞。而使用Lock就会觉得很麻烦。那么,这个时候使用volatile就会很合理:

private volatile boolean;
public boolean isDone(){return done;}
public void setDone(){done = true;}

但是Volatile变量不能提供原子性。例如:

public void fileDone(){done = !done;}

不能确保翻转域中的值。不能保证读取、翻转和写入不被中断。

3.final变量:

从之前可以看出,除非使用锁或者volatile修饰符,否则无法从多个线程安全地读取一个域。

还有一种情况可以安全地访问一个共享域,即这个域声明为final。

4.原子性:

java.util.concurrent.atomic包中有很多类使用了很高效的机器级指令(而不是使用锁)来保证其他操作的原子性。例如使用AtomicInteger类中的incrementAndGet和decrementAndGet,它们分别以原子的方式将一个整数自增或自减:

public static AtomicLong nextNumber = new AtomicLong();
long id = nextNumber.incrementAndGet();

很多方法可以以原子方式设置和增减值,不过,如果希望完成更复杂的更新,就必须使用compareAndSet方法:

do{
    oldValue = largest.get();
    newValue = Math.max(oldValue, observed);
}while(!largest.compareAndSet(oldValue, newValue));

这个方法会映射到处理器操作,比使用锁速度更快。

在Java8中可以使用lambda表达式:

largest.updateAndGet(x -> Math.max(x, observed));

largest.accumulateAndGet(observed,Math::max);

accumulateAndGet方法利用一个二元操作符来合并原子值和所提供的参数。还有getAndUpdate和getAndAccumulate方法可以返回原值。

如果有大量线程要访问相同的原子值,那么性能会大幅下降,因为乐观更新需要太多次重试。Java SE 8提供了LongAdder和LongAccumulator类来解决这种问题。LongAdder包括多个变量(加数),其总和为当前值。可以有多个线程更新不同的加数,线程个数增加时会自动提供新的加数。

通常,当工作做完的时候才需要总和的值:

final LongAdder adder = new LongAdder();
for(...){
    pool.submit(()->{
        while(...){
            ...
            if(...){
                adder.increment();
            }
        }
    });
}
long total = adder.sum();

LongAccumulator面向的更像广泛,它可以在构造器中提供这个操作以及它分零元素。

LongAccumulator adder = new Accumulator(Long::sum, 0);
adder.accumulate(value);

这里的0就是零元素,用于将累加器中的每个变量初始化为0,在调用accumulate方法之后会将某个变量以原子的方式更新为value。

猜你喜欢

转载自blog.csdn.net/qq_38386085/article/details/83627878