Java多线程-Synchronization

Java线程间通过共享内存来实现,当两个或多个线程访问一个变量会引起内存一致性问题,同步可以解决这个问题;同步会带来线程竞争和死锁等问题

 

Thread Interference

class Counter {
    private int c = 0;

    public void increment() {
        c++;
    }

    public void decrement() {
        c--;
    }

    public int value() {
        return c;
    }

}

当两个或多个不同线程操作Counter时会来带线程干扰问题,因为Counter里的c是共享的,每个操作会被jvm翻译成多个步骤,比如c++

1.Retrieve the current value of c.
2.Increment the retrieved value by 1.
3.Store the incremented value back in c.

Suppose Thread A invokes increment at about the same time Thread B invokes decrement. If the initial value of c is 0, their interleaved actions might follow this sequence:

1.Thread A: Retrieve c.
2.Thread B: Retrieve c.
3.Thread A: Increment retrieved value; result is 1.
4.Thread B: Decrement retrieved value; result is -1.
5.Thread A: Store result in c; c is now 1.
6.Thread B: Store result in c; c is now -1.
Thread A's result is lost, overwritten by Thread B. This particular interleaving is only one possibility. Under different circumstances it might be Thread B's result that gets lost, or there could be no error at all. Because they are unpredictable, thread interference bugs can be difficult to detect and fix.

 

Memory Consistency Errors

内存一致性错误是指不同线程对同一个数据看到了不一致的值,避免这个问题关键是理解happens—before关系,这一关系保证了一个线程的写对另外一个特定语句的可见性

举个简单的例子:counter 被定义并初始化

int counter = 0;

假设线程A和线程B共享counter,并且A执行了下面操作

counter++;

紧接着B线程执行了

System.out.println(counter);

打印结果可能不是1,因为没有任何保证线程A的改变对线程B可见,除非程序员在两个语句之间建立起了happens—before关系。

有多个动作可以建立happens—before关系,synchronization是其中一种

 

Synchronized Methods

Java语言提供了两种同步原语:同步方法和同步语句块

同步方法的例子

public class SynchronizedCounter {
    private int c = 0;

    public synchronized void increment() {
        c++;
    }

    public synchronized void decrement() {
        c--;
    }

    public synchronized int value() {
        return c;
    }
}

 

在方法上加上synchronized关键字有两个作用

1.同一个对象的同步方法不可能交替执行,当一个线程执行同步方法时,只有它运行结束第二个线程获取到对象锁才能执行

2.当退出一个同步方法后,和后继的调用自动建立起happens--before关系,以保证对象状态的改变对其它线程可见

在构造方法上不能加synchronized关键字,这样毫无意义,因为当一个对象构建完成后线程才能访问它

同步方法用一个简单的策略来防止thread interference 和 memory consistency errors:如果一个对象对多个线程可见,对这个对象变量的读写都通过同步方法来完成。这个方法是有效的,但是会引起liveness问题

 

Intrinsic Locks and Synchronization

Synchronization是建立在一个叫做intrinsic lock 或monitor lock的实体上的(Java语言规范里通常叫monitor lock)。强制排它访问和建立happens--before关系是可见性的本质,intrinsic lock在这两方面都起作用

Locks In Synchronized Methods

调用同步方法会获取方法所在对象上的intrinsic lock,调用静态同步方法会获取Class对象上的intrinsic lock

 Synchronized Statements

 Synchronized Statements 需要指定提供intrinsic lock的对象

public void addName(String name) {
    synchronized(this) {
        lastName = name;
        nameCount++;
    }
    nameList.add(name);
}

同步语句块用细粒度的同步来提高并发能力,减少不必要的同步

Synchronized statements are also useful for improving concurrency with fine-grained synchronization. Suppose, for example, class MsLunch has two instance fields, c1 and c2, that are never used together. All updates of these fields must be synchronized, but there's no reason to prevent an update of c1 from being interleaved with an update of c2 — and doing so reduces concurrency by creating unnecessary blocking. Instead of using synchronized methods or otherwise using the lock associated with this, we create two objects solely to provide locks.

public class MsLunch {
    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++;
        }
    }
}

 

Reentrant Synchronization

Recall that a thread cannot acquire a lock owned by another thread. But a thread can acquire a lock that it already owns. Allowing a thread to acquire the same lock more than once enables reentrant synchronization. This describes a situation where synchronized code, directly or indirectly, invokes a method that also contains synchronized code, and both sets of code use the same lock. Without reentrant synchronization, synchronized code would have to take many additional precautions to avoid having a thread cause itself to block.

 

Atomic Access

原子操作执行一次就结束,中间不能停顿,动作全部完成或没有发生,下面的操作是原子的:

1.读写基本数据类型(long和double除外)和引用变量是原子的

2.读写声明为volatile的变量是原子的

原子操作能解决 thread interference问题,不能解决memory consistency errors问题,volatile能减少memory consistency errors问题,因为它能建立happens-before关系,这意味着改变volatile 变量对其它线程总是可见的

 

posted on 2014-11-07 12:45  ukouryou  阅读(244)  评论(0编辑  收藏  举报

导航