`
西口西
  • 浏览: 10002 次
社区版块
存档分类
最新评论

JAVA线程3同步Synchronization(参考官方)

    博客分类:
  • java
 
阅读更多

3.1Synchronization

线程之间的交流主要是通过共享访问域路径或引用参考段的对象。这种交流方式效率很高,但是会引发两种错误:线程干扰和内存一致性错误。防止这类错误出现就要用到线程同步这个工具。

 

不过,线程同步会导致线程contention,线程contention是在多个线程试图同时访问同一资源的时候出现的情况,它会导致线程运行变慢,甚至是被挂起。线程饥饿starvation和活锁livelock都是contention的形式。我们会在之后的Liveness中详细说明。


3.2Thread Interference  线程干扰是如何在多个线程访问共享数据时出现的。    

 先来看一个栗子:

class Counter {
    private int c = 0;

    public void increment() {
        c++;
    }

    public void decrement() {
        c--;
    }

    public int value() {
        return c;
    }

}

  

increment方法每次让c+1,,decrement方法每次让c-1。那么如果一个Counter对象同时被多个线程引用,线程之间将相互干扰,而并非按我们的本意去改变c的值。

干扰是啥时候产生的呢?——当多个线程作用于同一数据,操作交错的发生时。这些线程的执行包含多个步骤,而且这些步骤是交叠在一起的。

由于对c进行操作的两个方法都特别简单,只有一句操作,所以看起来并没有“交叠在一起的许多步骤”啊。但是在被虚拟机翻译的时候,这里的一条语句会被分解成多个步骤:(以increment方法为例)

1.取回当前c的值。

2.对取回的c值增加1

3.存储增加后的c的值

c--用同样的方法分解

假设线程A调用了increment方法,几乎同时线程B调用了decrement方法。如果c的初始值为0,两个线程交错着运行各自三个步骤的顺序有可能是这样的:

1 线程A: 取回当前c的值。

2 线程 B: 取回当前c的值。

3 线程A: 对取回的c值加1,c=1。

4 线程B: 对取回的c值减1,c=-1。

5 线程A: 存储增加后的c值,c=1。

6 线程B: 存储减少后的c值,c=-1。

完了完了,最后一句是谁干的谁就把功劳全占了,线程A白干了。这里的例子只是线程交错的一个可能的结果。在不同的环境下,可能是线程B的结果被线程A覆盖,或者没有错误。因为错误是不可预料的,检测和修正线程干扰的错误也是不同的。

 

3.3 Memory Consistency Errors  内存一致性错误。

    

    当不同的线程访问同一数据,却得到不一致的数据时,就会出现内存一致性错误。内存一致性错误特别复杂我们就不谈了哈。不过,我们不用了解其中细节就可以解决问题哦。一个对策就搞定。

    想要避免内存一致性错误的关键是要理解happens-before原则。这个原则用来确保某个语句所写的内存对另一个语句是可见的。下面是说明例子。定义和初始化一个int型的counter:

    int counter=0;

    线程A和B都可以对counter操作。假设线程A让counter增加:

    counter++;

    紧接着,线程B打印出counter:

    System.out.println(counter);

    如果以上两个语句在同一个线程中运行,那打印出的counter值就该是1。但是如果这个两个语句在不同的线程执行,打印出来的counter值可能是0。因为你不能确保线程B能看到线程A对counter的修改,除非你之前在AB之间使用了happens-before原则。

    有几种方式可以创建happens-before原则。其中一个就是同步,我们会在下面看到。

    我们已经了解的两种方式。

    1.当调用Thread.start时,和这个语句用happens-before原则关联的每一个语句,都会自动跟这个线程中的每一条语句创建happens-before原则。新线程可以看到创建自己的代码效果。

    2.当一个线程结束,导致在另一个线程中的join方法返回时,这个结束的线程执行的所有语句将和成功执行的join方法后的所有语句创建happens-before原则。这个结束的线程中的代码效果现在对包含join方法的线程来说是可见的。

3.4Synchronized Method  同步方法,一个简单的关键字可以有效防止线程干扰和内存一致性错误。

       想让一个方法同步,只需在方法声明时加上synchronized关键字。

public class SynchronizedCounter {
    private int c = 0;

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

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

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

 

 

现有SynchronizedCounter的一个实例count,加上同步关键词之后的方法有以下效果:

 

第一,对于同一个对象的两个同步方法的调用是不可能交错的。当一个线程正在执行一个对象的同步方法时,所有调用此对象的其他线程是阻塞的,直到这个线程处理完这个对象。

第二,当一个同步方法退出时,它会自动与调用同一个对象的同步方法的语句创建happens-before原则。这确保了对此对象状态的改变对所有线程都可见。

 

       构建方法不能同步,在构建方法前加上synchronized关键字是语法错误。同步构建器没有意义的,因为在创建对象的时候,只有创建对象的线程可以访问他。

 

       警告:当你在构造一个多线程共享的对象时,要注意不要使该对象过早泄露出去。例如,假设你有一个叫instance 的列表,想把某个类的所有实例都存到这个列表里。你可能会在这个类的构造函数加上这么一行:

instances.add(this);

但是其他的线程有可能在这个对象的构造方法完成之前就通过instance列表访问该对象。

 

同步方法简单的解决了线程干扰和内存一致性错误的问题:如果一个对象对多个线程可见,所有对该对象的读写操作都是通过同步方法实现的。(一个重要的特例:final关键字修饰的对象,一旦被构造之后就不能被修改,因此使用非同步方法进行读取是没有问题的)。同步方法非常有效,但是也带来了活跃性(liveness)的问题,我们下面会谈到。

 

3.5Implicit Locks and Synchronization  隐式锁和同步,一个更具一般性的同步关键字,解释同步是如何基于隐式锁工作的。

     同步是围绕隐式锁或者叫监视锁实现的。隐式锁在同步的两个方面发挥作用:强行独占访问资源,建立 happens-before 原则。

     每个对象都有一个与之相关的隐式锁。一般来说,线程想要访问某个对象的资源,就要先获取资源对象的隐式锁,访问对象,处理完之后释放隐式锁。在获取和释放资源对象隐式锁的这段时间内,线程拥有(own)该隐式锁。只要某个隐式锁被某线程所有,别的线程便不能获取该隐式锁。当别的线程尝试获取该隐式锁时,这些线程就会堵塞。

      隐式锁被线程一旦被释放,就和下一个获取该隐式锁之间建立了happens-before原则。

同步方法中的锁

      线程调用同步方法时,会自动获取方法相关对象的隐式锁,当方法return的时候释放隐式锁(即使是由异常引起的return也会导致隐式锁被释放)。

      那调用静态同步方法时会怎样呢?因为静态方法都是跟类而不是对象相关联的,线程获取的是属于这个类的隐式锁。因此类的静态域的隐式锁跟类的实例的隐式锁是分离的。

同步代码块

       使用同步代码块实现同步。使用此方法需要指明提供隐式锁的对象。

 

public void addName(String name) {
    synchronized(this) {
        lastName = name;
        nameCount++;
    }
    nameList.add(name);
}
       就上面的例子说,addName这个方法就同步修改了lastName和nameCount,但是要避免其他对象方法的同步调用。(引起的问题会在Liveness中谈到。)
       同步块也适用于非常精细的线程同步。下例中,c1和c2是两个不会同时适用的数据,它们的每次更新改动都必须同步,不要求避免两个数据的交叠操作。不用同步方法和this,我们自己搞了两个锁。
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++;
        }
    }
}
       这么用一定要非常小心,你要保证交叠不会引起问题。
重入同步
       上面我们说过,隐式锁被某线程拥有时就不能被其他线程获取,但是有一个线程可以。允许线程获取一杯其他线程获取的隐式锁,叫做重入同步。这是特殊情况下才用的:当同步代码中直接或间接地调用了同步代码,而且两个代码块用的是同一个锁。这时候,如果没有重入同步,同步代码就得采取附加措施避免引起自己的线程堵塞。

 

3.6Atomic Access 原子访问,不能被其他线程干扰的操作。

       原子操作就是要么就不运行,一运行就不能被打断知道运行结束。原子操作的效果中途是不可见的,只有操作完成之后才可见。
       我们之前见到的像c++这种简单语句不是原子操作。再简单的语句定义的操作都能被分解成一堆复杂操作。但是,有些操作就是原子性的:

       1.引用变量和原始变量读写(long和double型变量除外)。

       2.volatile关键字修饰的所有变量的读写(包括long和double型变量)。

       原子操作不能交错执行,所以他们不存在线程干扰这种麻烦。但是,这并没有消除同步原子操作的需要,因为内存一致性错误还是存在的。volatile关键字可以减少内存一致性错误的发生,因为所有对同一volatile变量的写操作与后续的读操作之间遵循happens-before原则。也就是说,volatile变量的修改对别的线程来说都是可见的。另外,当某个线程在对volatile变量进行读操作的时候,不仅能看到volatile变量的最后一步修改,还能看到引起该次修改的代码的其他效果。

       通过简单的原子变量访问比通过同步代码获取变量要方便的多,但是同时增加了编程人员避免内存一致性错误的负担。选择哪种方式,还是根据具体情况的大小和复杂程度自己权衡吧。

 

 

 参考:

 

http://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html

http://www.cnblogs.com/goodwin/archive/2010/06/11/1756017.html

http://www.infoq.com/cn/articles/atomic-operation/

分享到:
评论

相关推荐

    java经典多线程面试题

    - 线程同步(Synchronization)是为了防止多个线程同时访问共享资源而产生的数据不一致问题。在Java中通过关键字synchronized来实现同步。 - 线程互斥(Mutual Exclusion)是指多个线程在同一时刻只有一个能进入...

    Synchronization_java_synchronization_differentjka_

    标题提到的"Synchronization_java_synchronization_differentjka_"暗示我们将讨论Java中的线程同步机制,特别是通过不同的方法实现线程间的互斥。描述中提到了生产者和消费者问题,这是一个经典的多线程同步问题,...

    java多线程例子.pdf

    4. 线程同步(Synchronization): 在多线程环境中,多个线程可能会同时访问相同的资源,造成数据不一致的问题。Java提供了synchronized关键字来实现线程同步。通过同步代码块或者同步方法可以确保一次只有一个线程...

    java多线程设计模式

    6. **线程同步(Synchronization)** Java提供了多种同步机制,如`synchronized`关键字、`wait()`和`notify()`方法、`Lock`接口及其实现类,用于控制对共享资源的访问,避免数据不一致性和竞态条件。 7. **死锁...

    java同步、异步相关知识点

    在Java编程语言中,同步(Synchronization)与异步(Asynchronization)是两个非常重要的概念,它们对于多线程处理和并发控制有着不可替代的作用。本文将深入探讨这两个概念的区别及其在Java中的具体应用。 #### 一...

    Java_programming_code_for_thread_synchronization_C_java programm

    Java编程实现线程同步经典代码Java programming code for thread synchronization Classic

    Java多线程应用

    以上内容涵盖了Java多线程应用的基础和进阶知识,包括线程池的工作原理、线程的创建与控制、并发工具的使用以及同步机制和阻塞队列的概念。学习并理解这些知识点,将有助于开发者构建高效、稳定、可扩展的多线程Java...

    多线程试卷参考答案.pdf

    4. 线程同步(Synchronization): - Java中的同步机制用于控制多个线程对共享资源的访问,避免竞态条件。 - synchronized关键字可用于方法或代码块,实现同步访问。 - wait()方法使当前线程进入等待状态,直到...

    用java写的多线程下载器

    3. **同步机制(Synchronization)**:在多线程环境下,确保数据一致性是至关重要的。Java提供了`synchronized`关键字、`ReentrantLock`等工具来实现线程间的同步,防止数据竞争。 4. **进度跟踪(Progress ...

    Thread Synchronization

    ### 线程同步(Thread Synchronization) #### 一、引言 线程同步是多线程编程中的一个重要概念,主要用于解决多个线程并发访问共享资源时可能出现的数据不一致问题。在Java等支持多线程的语言中,理解并正确实现...

    Java企业系列面试题(线程篇).docx

    监视器(Monitor)是Java中实现线程同步的关键机制。每个对象都有一个与之关联的锁,线程通过`synchronized`关键字请求获取锁来进入监视器。如果锁已被其他线程持有,请求线程将被阻塞,直到锁释放。这样保证了同一...

    java多线程编程之使用Synchronized块同步变量

    在实际开发中,为了防止意外的值更改,使用`final`关键字是一个好习惯,特别是在涉及到多线程同步的情况下。 总之,`synchronized`关键字是Java多线程编程中用来保证线程安全的关键工具。它通过锁定对象或方法,...

    java-synchronization-task3

    标题“java-synchronization-task3”表明我们正在探讨一个关于Java同步的实战任务,可能是解决多线程间协作与安全问题的一个练习。在这个任务中,可能涉及到了如何在多个线程之间正确地管理和控制共享状态。 描述中...

    java oracle并发官方教程

    总结来说,Java的并发编程涉及进程和线程的概念、线程的创建和管理、线程同步机制、活跃度问题的解决方法、不可变对象的使用以及高级并发工具的应用等多个方面。掌握这些知识点对于编写高性能和高可用性的Java程序至...

    Android 线程 多线程 Multi-thread

    线程同步** - **并行(Parallel)**: 指两个或多个事件同时发生。 - **同步(Synchronization)**: 指两个或多个事件发生在同一时间段内。 在多通道处理环境中,宏观上可能有多个进程同时运行,但在单处理器系统...

    Thread Synchronization in User Mode

    线程同步在用户模式下是操作系统编程中的一个重要概念,它涉及到多线程程序设计时如何有效地协调线程间的执行顺序,以确保数据的一致性和避免竞态条件等问题。以下是对这个主题的详细解释: 线程同步是多线程环境下...

    java英文笔试面试题.pdf

    3. **多线程中的同步(Synchronization)**:在多线程环境中,同步是控制多个线程对共享资源的访问。同步防止一个线程在另一个线程更新共享变量时进行修改,避免数据不一致性的错误。 4. **创建线程的两种方式**:...

    2016-2017第2学期《Java应用程序开发》期末考试A卷及参考答案

    - 同步(Synchronization):在多线程环境中,同步用于控制对共享资源的互斥访问,以避免线程间竞争。 6. Java集合框架(Collections Framework): - Java集合框架提供了一套性能优化的数据结构和算法,包括List...

    Java并发编程(一)

    同步(Synchronization):通过synchronized关键字或更高级的互斥锁(如ReentrantLock),可以控制多个线程访问共享资源的顺序,避免数据竞争(Data Race)。 原子操作(Atomic Operations):Java提供了一些原子...

Global site tag (gtag.js) - Google Analytics