`

Java之线程同步与安全(Thread Synchronize & Safe)

阅读更多
Java之线程同步与安全(Thread Synchronize & Safe)

一、问题背景

Java语言提供了多线程的功能。
多线程创建于相同的Object,多线程间共享Object的变量或属性。
但是,当线程对共享的数据进行读写时,会导致数据的不一致(data inconsistency)。


二、线程同步情景分析

数据不一致的原因是由数据操作的非原子性引起的。
即:更新任何属性或变量,非一步完成,而是需要三部:
    1、读取现在的值。
    2、进行必要的操作以得到要更新的值。
    3、把更新的值写入到引用的变量或属性中。

   
来看一个简单的例子:
多个线程共享一个数据,并对其进行修改。   
public class ThreadSafety {

    public static void main(String[] args) throws InterruptedException {
    
        ProcessingThread pt = new ProcessingThread();
        Thread t1 = new Thread(pt, "t1");
        t1.start();
        Thread t2 = new Thread(pt, "t2");
        t2.start();
        //wait for threads to finish processing
        t1.join();
        t2.join();
        System.out.println("Processing count="+pt.getCount());
    }

}

class ProcessingThread implements Runnable{
    private int count;
    
    @Override
    public void run() {
        for(int i=1; i < 5; i++){
            processSomething(i);
        	count++;
        }
    }

    public int getCount() {
        return this.count;
    }

    private void processSomething(int i) {
        // processing some job
        try {
            Thread.sleep(i*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
}

上述循环中,使用两个线程对 count 变量的值进行增加,每个线程各增加四次。
最后 count 的值应该是 8。但是在实际的多次运行中,它的值在 6,7,8 之间。
发生这种情况,即使 count++ 看起来是单原子操作,实际不是。
从而导致数据不一致。


三、Java中保证线程同步的方法

为了在多线程运行环境中确保数据的一致,Java提供了一些方法。
下面是主要的几种:

1、使用 synchronized 关键字(最广泛使用)

2、使用 java.util.concurrent.atomic 包下的原子操作包装类。
   例如: AtomicInteger
  
3、使用 java.util.concurrent.locks 包下的锁类

4、使用 线程安全的集合类。
   例如: ConcurrentHashMap
  
5、使用 volatile 关键字确保读的一致性。(不能确保写一致)
   确保每个线程是从内存中读取值,而不是线程缓存中。


三·一 使用 synchronized 关键字

JVM 可以确保被 synchronized 修饰的代码块每次只能被一个线程访问执行。
内部通过锁住一个对象或类来实现。
   
1、synchronized 可以在被锁定的资源或未被锁定的资源情况下工作。
   但是,在任何线程执行 synchronized 代码块之前,
   它需要首先获取这个对象的锁才可以执行。
   而线程执行非 synchronized 代码块不需要获取对象的锁。
   当然,线程在代码块执行完毕后,需要释放该锁。
   这样其它处于等待状态的线程可以获取对象的锁以执行。

2、synchronized 的使用方式有两种:
    1)用在类方法的声明上。使整个方法成为 synchronized 方法。
       此时会锁定 Object (实例),如果是静态方法则锁类。
       为了提高运行性能,请尽量少使用该策略。
   
    2)单独使用。形成一个 synchronized 代码块。
       只锁定方法中需要锁定的代码块。
       此时,需要提供资源,以从该资源中取得锁。
       资源可以是 ABC.class 或 类的一个属性。
       
       synchronized(this) 会锁定当前整个实例,
       即同时获取了当前实例的锁 + 实例所有属性的锁。
       注意:为了提高执行性能,被锁的对象应该是最小范围的。
       例如在一个类中有多个 synchronized 的代码块,其中
       一块锁定了整个实例。则其它代码块则不能被其它线程执行。
      
3、synchronized 是以降低性能为代价的(本来可以并行执行,先只能串行执行)。
   所以非必需,不要用。

4、synchronized 只在同一个 JVM 中是有效的。

5、synchronized 会造成[死锁]现象。
   synchronized 代码块同时锁定多个对象。多个线程互相等待其它线程释放对象的锁。
   
6、synchronized 不能用于 构造方法 和 局部变量。

7、synchronized 锁定的实例,应该避免是 常量池 中的对象。
    这些常量池中的对象可能被其它 synchronized 代码块引用。例如:String pool
   
8、synchronized 通常锁定一个虚设的 private 的类属性,对代码块进行锁定。
    因为 private 的引用指向的实例始终是一个。不会变。


下面的改进使 count 属性线程安全的代码:
//dummy object variable for synchronization
private Object mutex=new Object();

//using synchronized block to read, increment and update count value synchronously
synchronized (mutex) {
    count++;
}


让我们看一些 synchronized 的例子,看看我们能学到什么:


例子一:
public class MyObject {
 
  // Locks on the object's monitor
  public synchronized void doSomething() { 
    // ...
  }
}

public class Hack{
    public static void main(String[] args){
        // Hackers code
        MyObject myObject = Factory.getMyObject();
        synchronized (myObject) {
          while (true) {
            // Indefinitely delay myObject
            Thread.sleep(Integer.MAX_VALUE); 
          }
        }
    }
}

如果能够拿到 MyObject 的实例,并且在锁定该实例无限长的时间。
其它代码则无法执行该实例的方法。尤其在单例模式中。


例子二:

更有甚者:
package com.gentleman.sychronized;

public class Hack {    
    public static void main(String[] args) throws Exception{
        new Thread(new R1()).start();
        Thread.sleep(1000);
        new Thread(new R2()).start();
        new Thread(new R3()).start();
    }
}

class MyObject {
    public void sayHello(){
        synchronized(MyObject.class){
            System.out.println("C: Hello, Word");
        }
    }
    public synchronized void sayHello2(){
        System.out.println("M: Hello, Word");
    }
}


class R1 implements Runnable{
    @Override
    public void run(){
        synchronized (MyObject.class) {
            System.out.println("Lock class instance!");
            while (true) {
                try {
                    Thread.sleep(Integer.MAX_VALUE);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } 
            }
        }
    }
}


class R2 implements Runnable{
    @Override
    public void run() {
        MyObject myObject = new MyObject();
        myObject.sayHello();
    }
}

class R3 implements Runnable{
    @Override
    public void run() {
        MyObject myObject = new MyObject();
        myObject.sayHello2();
    }
}

输出结果:
  Lock class instance!
  M: Hello, Word

分析:
  C: Hello, Word
  这一句没有输出,因为 MyObject.class 对象被其它 Lock 锁定


根据结果可以看出:
要锁定的对象可以是类(class),也可以是类的实例(instance)。
但是类和类的实例在锁的问题上不相关,也没有权限上你大我小的隶属关系。



锁对象和锁类的区别?

synchronized(X.class)
使用 class类 所为锁定类, 那是因为只有一个 class 类在 JVM 中被 classLoader 加载。
如果一个线程要执行该代码块,必须拥有 class 类的锁。
此时只能有一个线程在执行该代码块。

synchronized(this)
使用类的实例为锁定对象。
如果一个线程要执行该代码块,只需拥有实例的锁即可。
此时可以有多个线程在执行该代码块。


例子三:

public class MyObject {
  public Object lock = new Object();
 
  public void doSomething() {
    synchronized (lock) {
      // ...
    }
  }
}

//untrusted code

MyObject myObject = new MyObject();
//change the lock Object reference
myObject.lock = new Object();


注意:
lock 属性是 public 的。通过替换它指向引用的对象,多个线程就可以执行 synchronized 块的代码。
类似情况:private 的属性,但是有 public 的 set() 方法。


例子四:
static void myMethod() {
  synchronized(MyClass.class) {
    //code
  }
}

// 等价于:

static synchronized void myMethod() {
  //code
}

注意:没有 static 关键字,则不等价。


void myMethod() {
  synchronized(this) {
    //code
  }
}

// 等价于:

synchronized void myMethod() {
  //code
}


例子五:

import java.util.Arrays;

public class T {

    public static void main(String[] args) throws InterruptedException {
        String[] arr = {"1","2","3","4","5","6"};
        HashMapProcessor hmp = new HashMapProcessor(arr);
        Thread t1=new Thread(hmp, "t1");
        Thread t2=new Thread(hmp, "t2");
        Thread t3=new Thread(hmp, "t3");
        long start = System.currentTimeMillis();
        //start all the threads
        t1.start();t2.start();t3.start();
        //wait for threads to finish
        t1.join();t2.join();t3.join();
        System.out.println("Time taken= "+(System.currentTimeMillis()-start));
        //check the shared variable value now
        System.out.println(Arrays.asList(hmp.getMap()));
     
    }

}

class HashMapProcessor implements Runnable{
    
    private String[] strArr = null;
    
    public HashMapProcessor(String[] m){
        this.strArr=m;
    }

    @Override
    public void run() {        
        for(int i=0; i < strArr.length; i++){
            processSomething(i);            
            addThreadName( i,  Thread.currentThread().getName());
        }
    }
    
    private void addThreadName(int i, String name) {
        strArr[i] = strArr[i] +":"+name;
    }
    
    private void processSomething(int index) {
        // processing some job
        try {
            Thread.sleep(index * 100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    public String[] getMap() {
        return strArr;
    }
    
}


输出:
Time taken= 1501
[1:t1, 2:t2, 3:t2:t3, 4:t1, 5:t1:t2, 6:t3:t2]


加上同步锁:
private Object lock = new Object();
private void addThreadName2(int i, String name) {
    synchronized(lock){
    strArr[i] = strArr[i] +":"+name;
    }
}


输出:
Time taken= 1502
[1:t1:t2:t3, 2:t2:t1:t3, 3:t1:t2:t3, 4:t1:t2:t3, 5:t2:t1:t3, 6:t1:t2:t3]
   
   
继续阅读:

使用 java.util.concurrent.lock.Lock 实现线程同步



-
转载请注明,
原文出处:http://lixh1986.iteye.com/blog/2351243




-   
引用:  
   
http://www.journaldev.com/1061/thread-safety-in-java
分享到:
评论

相关推荐

    java synchronize 线程同步

    当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢作一团。

    delphi vcl线程同步synchronize

    delphi vcl线程同步synchronize

    java 线程同步 信号量控制同步

    关于线程同步 synchronize,需要牢牢记住的第一点是:线程同步就是线程排队。同步就是排队。线程同步的目的就是避免线程“同步”执行。

    Java synchronize线程安全测试

    主要介绍了Java synchronize线程安全测试,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

    Synchronize It 3.5文件夹同步器 简体中文界面

    专门用来比较和同步目录的程序,可用于源程序管理,如果比较的结果有不同的时候,可以用新的文件覆盖过旧的文件,达到同步更新的目的,也可以自己选择决定是否更新。Synchronize It!还有其他特点如下: 1.可以自订...

    单例模式,属于创建类型的一种常用的软件设计模式 通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中

    懒汉式—线程安全:加上synchronize之类保证线程安全的基础上的懒汉模式,相对性能很低,大部分时间并不需要同步 饿汉方式。指全局的单例实例在类装载时构建。 [2] 双检锁式。在懒汉式基础上利用synchronize关键字和...

    delphi多线程传递参数及同步二

    本代码介绍了如何给线程传递参数,以及用参数在主线程中显示数据

    Java线程/内存模型的缺陷和增强

    它提供了Thread/Runnable/ThreadGroup等一系列封装的类和接口,让程序员可以高效的开发Java多线程应用。为了实现同步,Java提供了synchronize关键字以及object的wait()/notify()机制,可是在简单易用的背后,应藏着...

    Java对象锁和类锁全面解析(多线程synchronize

    Java对象锁和类锁全面解析(多线程synchronized关键字)编程开发技术共14页.pdf.zip

    Java之Synchronize学习

    Synchronize的几种实现 Synchronize有三种实现。 同步方法,修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁 静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁 修饰代码块...

    JAVA面试题最全集

    多线程,用什么关键字修饰同步方法?stop()和suspend()方法为何不推荐使用? 59.使用socket建立客户端与服务器的通信的过程 60.JAVA语言国际化应用,Locale类,Unicode 61.描述反射机制的作用 62.如何读写一个...

    java程序员面试题

    最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在多个线程访问Hashtable时,不需要自己为它的方法实现同步,而HashMap 就必须为之提供外同步。 Hashtable和HashMap采用的hash/rehash算法都大概...

    Java 中synchronize函数的实例详解

    主要介绍了Java 中synchronize函数的实例详解的相关资料,希望通过本文能帮助到大家理解使用synchronize函数的使用方法,需要的朋友可以参考下

    开发利器之一键文件同步工具

    2.need_synchronize_files.txt:待同步文件名列表,直接从svn提测信息复制过来的 3.config.properites为工程运行配置文件,NEED_SYN_FILE_PATH:为待同步文件名列表的路径, SRC_PATH:源路径,DEST_PATH:目的路径...

    Delphi多线程教程

    Delphi中有一个线程类TThread是用来实现多线程编程的,这个绝大多数Delphi书藉都有说到,但基本上都是对TThread类的几个成员作一简单介绍,再说明一下Execute的实现和Synchronize的用法就完了。然而这并不是多线程编...

    bcb 简单多线程实例

    最简单的多线程实例,适合初接触多线程的人员参考使用,BCB多线程实例。

    125条常见的java面试笔试题大汇总

    HashMap和Hashtable的区别。 HashMap是Hashtable的轻量级实现(非线程安全的实现) ...现同步,而HashMap 就必须为之提供外同步。 Hashtable和HashMap采用的hash/rehash算法都大概一样,所 以性能不会有很大的差异。

    使用Win32事件对象的线程同步

    使用Win32 Event对象的线程同步。

    java 面试题 总结

    最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在多个线程访问Hashtable时,不需要自己为它的方法实现同步,而HashMap 就必须为之提供外同步。 Hashtable和HashMap采用的hash/rehash算法都大概...

Global site tag (gtag.js) - Google Analytics