11/18整理

来源:互联网 发布:性别角色知乎 编辑:程序博客网 时间:2024/06/10 16:34
OR会引起索引失效的说法是这样来的:
如果是这样一个查询
SELECT * FROM TB WHERE A=1 AND B>2 AND C<3 AND D IN (4,5,6)
并且在TB表上有这样一个索引:CREATE INDEX INX_TB_ABCD ON TB (A,B,C,D)
那么这个查询可以用到这个索引
如果同样是这个索引,查询换成
SELECT * FROM TB_ WHERE A=1 OR B>2 OR C<3 OR D IN (4,5,6)
那么这个查询就用不到上面那个索引,因为结果集是几个条件的并集,最多只能在查找A=1的数据时用索引,其它几个条件都需要表扫描,那优化器就会选择直接走一遍表扫描,所以索引就失效了。

那么像第二个查询这样的应该怎么建索引呢,答案就是四个列上各建一个索引,或者只在选择性最高的列上建索引,比如A=1的数据量很少,就在A上建,如果D是4,5,6的数据很少,就在D上建,这样优化器就会选择先走索引查找,再对找出的结果集进行筛选,扫描数就会大幅减少。

回过头来看你上面那个查询,两个条件都在TID上,所以只有TID上有一个索引就OK了,不存在索引失效的问题。

看书不要全信,多想想书上为什么这么说,自己多试试。


一个实例中,多个synchronized方法的调用

原创 2016年11月21日 17:52:43
[java] view plain copy
  1. public class ThreadA extends Thread {  
  2.     private MyObject object;  
  3.   
  4.     public ThreadA(MyObject object){  
  5.         this.object = object;  
  6.     }  
  7.   
  8.     public void run(){  
  9.         super.run();  
  10.         object.menthodA();  
  11.     }  
  12. }  


[java] view plain copy
  1. public class ThreadB extends Thread {  
  2.     private MyObject object;  
  3.   
  4.     public ThreadB(MyObject object){  
  5.         this.object = object;  
  6.     }  
  7.   
  8.     public void run(){  
  9.         super.run();  
  10.         object.methodB();  
  11.     }  
  12. }  

[java] view plain copy
  1. public class Run {  
  2.     public static void main(String args[]){  
  3.         MyObject myObject = new MyObject();  
  4.         ThreadA threadA = new ThreadA(myObject);  
  5.         threadA.setName("A");  
  6.         threadA.start();  
  7.         ThreadB threadB = new ThreadB(myObject);  
  8.         threadB.setName("B");  
  9.         threadB.start();  
  10.     }  
  11. }  

代码如上所示,MyObject类有两个方法,分别创建两个线程调用方法A和方法B:

1、方法A和方法B都没有加synchronized关键字时,调用方法A的时候可进入方法B;

2、方法A加synchronized关键字而方法B没有加时,调用方法A的时候可以进入方法B;

3、方法A和方法B都加了synchronized关键字时,调用方法A之后,必须等A执行完成才能进入方法B;

4、方法A和方法B都加了synchronized关键字时,且方法A加了wait()方法时,调用方法A的时候可以进入方法B;

5、方法A加了synchronized关键字,而方法B为static静态方法时,调用方法A的时候可进入方法B;

6、方法A和方法B都是static静态方法,且都加了synchronized关键字,则调用方法A之后,需要等A执行完成才能进入方法B;

7、方法A和方法B都是static静态方法,且都加了synchronized关键字,创建不同的线程分别调用A和B,需要等A执行完成才能执行B(因为static方法是单实例的,A持有的是Class锁,Class锁可以对类的所有对象实例起作用)

总结:

同一个object中多个方法都加了synchronized关键字的时候,其中调用任意方法之后需等该方法执行完成才能调用其他方法,即同步的,阻塞的

此结论同样适用于对于object中使用synchronized(this)同步代码块的场景;

synchronized锁定的都是当前对象!


---------------------------------------------------------------------------

问题: 
两个线程同时对i=0的数据分别进行i++一百次,结果出来并不是200。理论上来讲,结果最小值为2,最大值为200。 
首先解释一下为什么会这样。 
i++并非原子操作。执行过程中JVM从内存中把变量i的值取出来放到寄存器中,在寄存器中做加1操作,之后把寄存器中的值在写入到内存中。 
值为2的情况: 
线程A:从内存中读到i值为0,在寄存器中加1,还没有写入内存时线程B开始执行。 
线程B:从内存中读到i值依然为0,线程B执行99次i++,这时候写入内存,此时内存中i=99,还没开始第100次读取内存中i的值,线程A开始执行。 
线程A:把寄存器中的1写入到内存,此时内存中i=1,还没有开始第二次读取内存中i的值时,线程B开始执行。 
线程B:把寄存器中的i=1读入到寄存器,进行加1操作,此时寄存器中值为2,还没有写入到内存中时,线程A开始执行。 
线程A:把内存中的i=1读入到寄存器中加1然后写入内存,执行99次后,内存中的i=100,此时线程A执行完毕。线程B开始执行。 
线程B:把寄存器中的i=2写入到内存中,线程B结束。 
此时内存中的i=2。 
值为200的情况: 
线程完成从内存中读取到寄存器运算后写入内存这一个完整步骤。 
其余情况类似。

解决这个问题的方法是加入同步块。 
一开始我很愚蠢的这样写了

Count.java

package com.hnzhrh.Java7MultipleThread;public class Count {    public int i=0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

CountRunnable.java

package com.hnzhrh.Java7MultipleThread;public class CountRunnable implements Runnable{    Count count=null;    public CountRunnable(Count count) {        // TODO Auto-generated constructor stub        this.count=count;    }    public synchronized void add(){        for(int j=0;j<100;j++){            count.i++;        }    }    @Override    public void run() {        // TODO Auto-generated method stub        add();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

Main.java

package com.hnzhrh.Java7MultipleThread;public class Main {    public static void main(String[] args) throws InterruptedException {        Count count=new Count();        Thread a =new Thread(new CountRunnable(count));        Thread b =new Thread(new CountRunnable(count));        a.start();        b.start();        a.join();        b.join();        System.out.println(count.i);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

这个输出是错误的!完全错误的!一开始还怀疑这同步块并没有同步啊,后来发现是我傻逼了。

解释这个问题就要看一下JVM的内存模型: 
这里写图片描述 
JVM把内存分为两块,一块为线程栈,一块为堆。不同线程栈之间是不能相互访问的。比如线程栈A中有个本地变量,那么线程栈B是不能访问到该变量的。当然需要注意引用的情况,如果线程栈A中有个类的变量指向堆中的一个对象,线程栈B中也有个类的变量指向堆中的同一个对象,那么写操作是非线程安全的。 
有了这点基础,我们可以分析一下我上面代码错到哪了。 
我把add方法用了同步块,但是,我的add方法写在了实现了Runnable接口的CountRunnable类中,那么我创建了两个线程A和B,线程栈A中有这个方法,线程栈B中有这个方法,两个方法互不相干的对堆中的Count对象中的成员变量i进行操作,当然没有达到同步的作用。 
只要做以下修改就可以了: 
Count.java

package com.hnzhrh.Java7MultipleThread;public class Count {    public int i=0;    public synchronized void add(){        for(int j=0;j<100;j++){            i++;        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

CountRunnable.java

package com.hnzhrh.Java7MultipleThread;public class CountRunnable implements Runnable{    Count count=null;    public CountRunnable(Count count) {        // TODO Auto-generated constructor stub        this.count=count;    }    @Override    public void run() {        // TODO Auto-generated method stub        count.add();    }}
------------------------------------------------------------------

这个类真的非常实用,更重要的是 它确实非常简单:

附上自己的代码,可以自己试试:

AtomicInteger,一个提供原子操作的Integer的类。在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口。

 

代码:

package test;

import java.util.concurrent.atomic.AtomicInteger;
/**
 * 来看AtomicInteger提供的接口。

 //获取当前的值
 
 public final int get()
 
 //取当前的值,并设置新的值
 
  public final int getAndSet(int newValue)
 
 //获取当前的值,并自增
 
  public final int getAndIncrement() 
 
 //获取当前的值,并自减
 
 public final int getAndDecrement()
 
 //获取当前的值,并加上预期的值
 
 public final int getAndAdd(int delta)


 * @author YangBaoBao
 *
 */
public class AtomicIntegerDemo {
 public static void main(String[] args) {
  AtomicInteger ai=new AtomicInteger(0);
  int i1=ai.get();
  v(i1);
  int i2=ai.getAndSet(5);
  v(i2);
  int i3=ai.get();
  v(i3);
  int i4=ai.getAndIncrement();
  v(i4);
  v(ai.get());
  
 }
 static void v(int i)
 {
  System.out.println("i : "+i);
 }
}


那么为什么不使用记数器自加呢,例如count++这样的,因为这种计数是线程不安全的,高并发访问时统计会有误,而AtomicInteger为什么能够达到多而不乱,处理高并发应付自如呢,我们才看看AtomicInteger的源代码:

 

 

Java代码  收藏代码
  1. private volatile int value;  
 

 

大家可以看到有这个变量,value就是你设置的自加起始值。注意看它的访问控制符,是volatile,这个就是保证AtomicInteger线程安全的根源,熟悉并发的同学一定知道在java中处理并发主要有两种方式:

1,synchronized关键字,这个大家应当都各种面试和笔试中经常遇到。

2,volatile修饰符的使用,相信这个修饰符大家平时在项目中使用的也不是很多。

 

这里重点说一下volatile:

Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存重新读取该成员的值,而且,当成员变量值发生变化时,强迫将变化的值重新写入共享内存,这样两个不同的线程在访问同一个共享变量的值时,始终看到的是同一个值。

 

java语言规范指出:为了获取最佳的运行速度,允许线程保留共享变量的副本,当这个线程进入或者离开同步代码块时,才与共享成员变量进行比对,如果有变化再更新共享成员变量。这样当多个线程同时访问一个共享变量时,可能会存在值不同步的现象。

 

而volatile这个值的作用就是告诉VM:对于这个成员变量不能保存它的副本,要直接与共享成员变量交互。

 

建议:当多个线程同时访问一个共享变量时,可以使用volatile,而当访问的变量已在synchronized代码块中时,不必使用。

缺点:使用volatile将使得VM优化失去作用,导致效率较低,所以要在必要的时候使用。

--------------------------------------------------------------------------------

 

java中的原子操作类AtomicInteger及其实现原理


        /**
         * 一,AtomicInteger 是如何实现原子操作的呢?
         * 
         * 我们先来看一下getAndIncrement的源代码:
         *    public final int getAndIncrement() {
         *        for (;;) {
         *              int current = get();  // 取得AtomicInteger里存储的数值
         *            int next = current + 1;  // 加1
         *            if (compareAndSet(current, next))   // 调用compareAndSet执行原子更新操作
         *                return current;
         *        }
         *    }
         * 
         * 这段代码写的很巧妙:
         * 1,compareAndSet方法首先判断当前值是否等于current;
         * 2,如果当前值 = current ,说明AtomicInteger的值没有被其他线程修改;
         * 3,如果当前值 != current,说明AtomicInteger的值被其他线程修改了,这时会再次进入循环重新比较;
         *    
         * 注意这里的compareAndSet方法,源代码如下:
         * public final boolean compareAndSet(int expect, int update) {
         *     return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
         * }
         * 
         * 调用Unsafe来实现
         * private static final Unsafe unsafe = Unsafe.getUnsafe();
         * 
         * 二,java提供的原子操作可以原子更新的基本类型有以下三个:
         * 
         * 1,AtomicBoolean
         * 2,AtomicInteger
         * 3,AtomicLong
         * 
         * 三,java提供的原子操作,还可以原子更新以下类型的值:
         * 
         * 1,原子更新数组,Atomic包提供了以下几个类:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
         * 2,原子更新引用类型,也就是更新实体类的值,比如AtomicReference<User>
         * AtomicReference:原子更新引用类型的值
         * AtomicReferenceFieldUpdater:原子更新引用类型里的字段
         * AtomicMarkableReference:原子更新带有标记位的引用类型
         * 3,原子更新字段值
         * AtomicIntegerFieldUpdater:原子更新整形的字段的更新器
         * AtomicLongFieldUpdater:原子更新长整形的字段的更新器
         * AtomicStampedReference:原子更新带有版本号的引用类型的更新器
         * 
         * 

         */


示例代码如下:

[java] view plain copy
  1. import java.util.concurrent.atomic.AtomicInteger;  
  2. import sun.misc.Unsafe;  
  3.   
  4. public class TestAtomic {  
  5.   
  6.     /** 
  7.      * @param java中的原子操作类AtomicInteger 
  8.      * @author yangcq 
  9.      *  
  10.      * 关于AtomicInteger的说明(来自官方文档注解) 
  11.      * /** 
  12.      * An {@code int} value that may be updated atomically.  See the 
  13.      * {@link java.util.concurrent.atomic} package specification for 
  14.      * description of the properties of atomic variables. An 
  15.      * {@code AtomicInteger} is used in applications such as atomically 
  16.      * incremented counters, and cannot be used as a replacement for an 
  17.      * {@link java.lang.Integer}. However, this class does extend 
  18.      * {@code Number} to allow uniform access by tools and utilities that 
  19.      * deal with numerically-based classes. 
  20.      * 
  21.      * @since 1.5 
  22.      * @author Doug Lea 
  23.      */  
  24.     public static void main(String[] args) {  
  25.         // 初始值为1  
  26.         AtomicInteger atomicInteger = new AtomicInteger(1);  
  27.         System.out.println("--初始值atomicInteger = " + atomicInteger);  
  28.           
  29.         // 以原子方式将当前值加1,注意这里返回的是自增前的值   
  30.         System.out.println("atomicInteger.getAndIncrement() = " + atomicInteger.getAndIncrement());  
  31.         System.out.println("--自增后的 atomicInteger = " + atomicInteger);  
  32.           
  33.         // 以原子方式将当前值减1,注意这里返回的是自减前的值   
  34.         System.out.println("atomicInteger.getAndIncrement() = " + atomicInteger.decrementAndGet());  
  35.         System.out.println("--自减后的 atomicInteger = " + atomicInteger);  
  36.           
  37.         // 以原子方式将当前值与括号中的值相加,并返回结果  
  38.         System.out.println("atomicInteger.getAndIncrement() = " + atomicInteger.addAndGet(10));  
  39.         System.out.println("--自减后的 atomicInteger = " + atomicInteger);  
  40.           
  41.         // 如果输入的值等于预期的值,则以原子方式将该值设置成括号中的值  
  42.         System.out.println("atomicInteger.getAndIncrement() = " + atomicInteger.compareAndSet(12));  
  43.         System.out.println("--自减后的 atomicInteger = " + atomicInteger);  
  44.         System.out.println("atomicInteger.getAndIncrement() = " + atomicInteger.compareAndSet(119999));  
  45.         System.out.println("--自减后的 atomicInteger = " + atomicInteger);  
  46.           
  47.         /** 
  48.          * 一,AtomicInteger 是如何实现原子操作的呢? 
  49.          *  
  50.          * 我们先来看一下getAndIncrement的源代码: 
  51.          *    public final int getAndIncrement() { 
  52.          *        for (;;) { 
  53.          *            int current = get();  // 取得AtomicInteger里存储的数值 
  54.          *            int next = current + 1;  // 加1 
  55.          *            if (compareAndSet(current, next))   // 调用compareAndSet执行原子更新操作 
  56.          *                return current; 
  57.          *        } 
  58.          *    } 
  59.          *  
  60.          * 这段代码写的很巧妙: 
  61.          * 1,compareAndSet方法首先判断当前值是否等于current; 
  62.          * 2,如果当前值 = current ,说明AtomicInteger的值没有被其他线程修改; 
  63.          * 3,如果当前值 != current,说明AtomicInteger的值被其他线程修改了,这时会再次进入循环重新比较; 
  64.          *     
  65.          * 注意这里的compareAndSet方法,源代码如下: 
  66.          * public final boolean compareAndSet(int expect, int update) { 
  67.          *     return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
  68.          * } 
  69.          *  
  70.          * 调用Unsafe来实现 
  71.          * private static final Unsafe unsafe = Unsafe.getUnsafe(); 
  72.          *  
  73.          * 二,java提供的原子操作可以原子更新的基本类型有以下三个: 
  74.          *  
  75.          * 1,AtomicBoolean 
  76.          * 2,AtomicInteger 
  77.          * 3,AtomicLong 
  78.          *  
  79.          * 三,java提供的原子操作,还可以原子更新以下类型的值: 
  80.          *  
  81.          * 1,原子更新数组,Atomic包提供了以下几个类:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray 
  82.          * 2,原子更新引用类型,也就是更新实体类的值,比如AtomicReference<User> 
  83.          * AtomicReference:原子更新引用类型的值 
  84.          * AtomicReferenceFieldUpdater:原子更新引用类型里的字段 
  85.          * AtomicMarkableReference:原子更新带有标记位的引用类型 
  86.          * 3,原子更新字段值 
  87.          * AtomicIntegerFieldUpdater:原子更新整形的字段的更新器 
  88.          * AtomicLongFieldUpdater:原子更新长整形的字段的更新器 
  89.          * AtomicStampedReference:原子更新带有版本号的引用类型的更新器 
  90.          *  
  91.          *  
  92.          */  
  93.     }  
  94.   
  95. }  

四,AtomicIntegerFieldUpdater:原子更新整形的字段的更新器

[java] view plain copy
  1. import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;  
  2.   
  3. public class TestAtomicIntegerFieldUpdater {  
  4.   
  5.     /** 
  6.      * @param AtomicIntegerFieldUpdater:原子更新整形的字段的更新器 
  7.      * @author yangcq 
  8.      */  
  9.       
  10.     // 创建原子更新器,并设置需要更新的对象类和对象的属性  
  11.     private static AtomicIntegerFieldUpdater<User> atomicIntegerFieldUpdater   
  12.         = AtomicIntegerFieldUpdater.newUpdater(User.class"age");  
  13.       
  14.     public static void main(String[] args) {  
  15.           
  16.         // 设置age的初始值为1000  
  17.         User user = new User();  
  18.         user.setUserName("yangcq");  
  19.         user.setAge(1000);  
  20.           
  21.         // 原子更新引用数据类型的字段值  
  22.         System.out.println(atomicIntegerFieldUpdater.getAndIncrement(user));  
  23.         // 更新以后的值  
  24.         System.out.println(atomicIntegerFieldUpdater.get(user));  
  25.     }  
  26.       
  27.     //实体类User  
  28.     public static class User{  
  29.         private String userName;  
  30.         public volatile int age;  
  31.   
  32.         // setter、getter方法  
  33.         public String getUserName() {  
  34.             return userName;  
  35.         }  
  36.         public void setUserName(String userName) {  
  37.             this.userName = userName;  
  38.         }  
  39.         public int getAge() {  
  40.             return age;  
  41.         }  
  42.         public void setAge(int age) {  
  43.             this.age = age;  
  44.         }  
  45.     }  
  46.   
  47. }  

五,java原子操作类在实际项目中的应用(java原子操作类的应用场景)

java原子操作类 AtomicInteger 在实际项目中的应用。HttpClientFacotryBean工厂会工作在多线程环境中,生成Httpclient,
就相当于建立HttpClient连接,通过工厂模式控制HttpClient连接,能够更好的管理HttpClient的生命周期。而我们使用java原子
操作类AtomicInteger来控制计数器,就是为了保证,在多线程的环境下,建立HttpClient连接不会出错,不会出现2个线程竞争一个
HttpClient连接的情况。


[html] view plain copy
  1. bean配置如下:  
  2.     <bean id="Httpclient" name="httpclient" class="com.yangcq.initBean.HttpClientFacotryBean">  
  3.         <property name="connectionManager" ref="connectionManagers" ></property>  
  4.         <property name="map">  
  5.             <map>  
  6.                 <entry key="http.socket.timeout" value="30000" />  
  7.                 <entry key="http.connection.timeout" value="30000" />  
  8.                 <entry key="http.conn-manager.timeout"  value="6000" />  
  9.             </map>  
  10.         </property>  
  11.     </bean>  

[java] view plain copy
  1. java实现类:  
  2. import java.io.IOException;  
  3. import java.util.Map;  
  4. import java.util.concurrent.atomic.AtomicInteger;  
  5. import org.apache.http.HttpException;  
  6. import org.apache.http.HttpRequest;  
  7. import org.apache.http.HttpRequestInterceptor;  
  8. import org.apache.http.client.HttpClient;  
  9. import org.apache.http.conn.ClientConnectionManager;  
  10. import org.apache.http.conn.params.ConnManagerPNames;  
  11. import org.apache.http.conn.params.ConnManagerParamBean;  
  12. import org.apache.http.impl.client.DefaultHttpClient;  
  13. import org.apache.http.params.BasicHttpParams;  
  14. import org.apache.http.params.CoreConnectionPNames;  
  15. import org.apache.http.params.HttpConnectionParamBean;  
  16. import org.apache.http.params.HttpParams;  
  17. import org.apache.http.protocol.HttpContext;  
  18. import org.apache.log4j.Logger;  
  19. import org.springframework.beans.factory.BeanInitializationException;  
  20. import org.springframework.beans.factory.DisposableBean;  
  21. import org.springframework.beans.factory.FactoryBean;  
  22. import org.springframework.beans.factory.InitializingBean;  
  23. /** 
  24.  * 在容器启动时注入connectionManager,然后初始化httpClient 
  25.  * 主要参数: 
  26.  * CONNECTION_TIMEOUT : 连接主机超时时间设置 
  27.  * SO_TIMEOUT :         读取主机数据超时时间设置 
  28.  * TIMEOUT :            获取连接超时时间 
  29.  */  
  30. public class HttpClientFacotryBean implements FactoryBean,InitializingBean,DisposableBean {  
  31.     private static final Logger logger = Logger.getLogger(HttpClientFacotryBean.class);  
  32.     private DefaultHttpClient httpClient;  
  33.     private ClientConnectionManager clientConnectionManager = null;  
  34.     private Map map = null;   
  35.     //设置httpClient超时参数  
  36.     public void afterPropertiesSet() throws Exception {  
  37.         if (null == clientConnectionManager) {  
  38.             throw new BeanInitializationException("The connection manager must be set in " + this.getClass().getName() + "...");  
  39.         }  
  40.         HttpParams httpParams = new BasicHttpParams();  
  41.         if (null != map) {  
  42.             HttpConnectionParamBean httpConnectionParamBean = new HttpConnectionParamBean(httpParams);  
  43.             String connectionTimeout = (String) map.get(CoreConnectionPNames.CONNECTION_TIMEOUT);  
  44.             if (null != connectionTimeout)  
  45.                 httpConnectionParamBean.setConnectionTimeout(Integer.parseInt(connectionTimeout));  
  46.             String soTimeout = (String) map.get(CoreConnectionPNames.SO_TIMEOUT);  
  47.             if (null != connectionTimeout)  
  48.                 httpConnectionParamBean.setSoTimeout(Integer.parseInt(soTimeout));  
  49.             ConnManagerParamBean connManagerParamBean = new ConnManagerParamBean(httpParams);  
  50.             String timeout = (String) map.get(ConnManagerPNames.TIMEOUT);  
  51.             if (null != timeout)  
  52.                 connManagerParamBean.setTimeout(Long.parseLong(timeout));  
  53.         }  
  54.         this.httpClient = new DefaultHttpClient(clientConnectionManager, httpParams);  
  55.         this.httpClient.addRequestInterceptor(new HttpRequestInterceptor() {  
  56.             public void process(final HttpRequest request,final HttpContext context) throws HttpException,IOException {  
  57.                 AtomicInteger count = (AtomicInteger) context.getAttribute("count"); // 从HttpContext中获取计数器count  
  58.                 if (null == count) {  
  59.                     count = new AtomicInteger(1); // 如果计数器为空,则初始化值为1  
  60.                     context.setAttribute("count", count); // 放到context中  
  61.                 }  
  62.                 request.addHeader("Count", Integer.toString(count.getAndIncrement())); // 把计数器放到request请求中  
  63.                 if (logger.isDebugEnabled()) {  
  64.                     logger.debug("\n=====这是第 " + count + " 次连接=====\n");  
  65.                 }  
  66.             }  
  67.         });  
  68.     }  
  69.     public void destroy() throws Exception {  
  70.         if (null != params)  
  71.             map.clear();  
  72.         if (null != clientConnectionManager)  
  73.             clientConnectionManager.closeExpiredConnections();  
  74.     }  
  75.     public ClientConnectionManager getConnectionManager() {  
  76.         return clientConnectionManager;  
  77.     }  
  78.     public Map getParams() {  
  79.         return map;  
  80.     }  
  81.     public void setConnectionManager(ClientConnectionManager clientConnectionManager) {  
  82.         this.clientConnectionManager = clientConnectionManager;  
  83.     }  
  84.     public void setParams(Map map) {  
  85.         this.map = map;  
  86.     }  
  87.     public Object getObject() throws Exception {  
  88.         return this.httpClient;  
  89.     }     
  90.     public Class getObjectType() {  
  91.         return HttpClient.class;  
  92.     }  
  93.     public boolean isSingleton() {  
  94.         return false;  
  95.     }  
  96. }  
--------------------------------------------------------------

双检锁写法: 

Java代码  收藏代码
  1. public class Singleton{  
  2.   private static Singleton single;    //声明静态的单例对象的变量  
  3.   private Singleton(){}    //私有构造方法   
  4.     
  5.   public static Singleton getSingle(){    //外部通过此方法可以获取对象    
  6.     if(single == null){     
  7.         synchronized (Singleton.class) {   //保证了同一时间只能只能有一个对象访问此同步块        
  8.             if(single == null){      
  9.                 single = new Singleton();          
  10.         }     
  11.       }  
  12.     }    
  13.     return single;   //返回创建好的对象   
  14.   }  
  15. }  

 


“双重检验锁失效”的问题说明


原文地址:The “Double-Checked Locking is Broken” Declaration

译文:

“双重检验锁失效”的问题说明

作者:David Bacon (IBM Research) Joshua Bloch (Javasoft), Jeff Bogda, Cliff Click (Hotspot JVM project), Paul Haahr, Doug Lea, Tom May, Jan-Willem Maessen, Jeremy Manson, John D. Mitchell (jGuru) Kelvin Nilsen, Bill Pugh, Emin Gun Sirer 
双重检验锁

双重检验锁是一个高效的懒加载方法,被广泛引用或应用于多线程环境中。

但是,在Java中,如果没有额外的同步,它并不能以独立的方式可靠地工作。当在其它语言上时,比如C++,它依赖于处理器的内存模型,编译器重排序以及编译器和同步库的交互。由于C++并没有对以上内容作出具体说明,所以很难说明在什么情况下双重检验锁会正常运行。在C++中可以显式地使用内存屏障来保证双重检验锁正常运行,但是在Java中没有这样的屏障可用。

为了说明我们想要的结果,首先考虑下面的代码:

// 单线程版本class Foo {  private Helper helper = null;  public Helper getHelper() {    if (helper == null)        helper = new Helper();    return helper;    }  // other functions and members...  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

如果上述代码被用在多线程中,将会出现诸多错误。最明显的便是,可能会创建出两个或者更多的Helper对象(将在下文提出更多错误)。最简单的修复这个问题的方式便是使用同步方法。

// 正确的多线程版本class Foo {  private Helper helper = null;  public synchronized Helper getHelper() {    if (helper == null)        helper = new Helper();    return helper;    }  // other functions and members...  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

上述代码将在每一次执行getHelper()方法时使用同步锁。而双重检验锁的方式可以避免在创建helper对象后依然使用同步方法:

// 错误的多线程版本// “双重检验锁”方式class Foo {  private Helper helper = null;  public Helper getHelper() {    if (helper == null)      synchronized(this) {        if (helper == null)          helper = new Helper();      }    return helper;    }  // other functions and members...  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

可惜,这一段代码并不能在使用优化过的编译器或者共享内存的多处理器的情况下正确执行。(译者注:我们使用的hotspot就有指令重排序等优化,指令重排序会影响双重检验锁正常运行)

它不能运行

有许多原因可以导致它不能运行。我们将描述几个比较显而易见的原因。理解了这些之后,你可能会想要尝试设计出一个方式去“修复”双重检验锁形式。但是你的修复并不会起作用:因为这里有很多细微的因素会影响它。理解了这些原因后,你可能会进一步修复,但却是重蹈覆辙,因为还有更加细致的因素。

大多数非常聪明的人曾花费大量时间在这里。但是除了在每一个线程取得helper对象时运行同步锁以外,没有别的方法。

第一个不能运行的原因

最明显的一个原因是初始化Helper对象和把对象写入到helper变量中这两步操作的并不是按顺序的。因此,一个线程调用getHelper()方法时,可能看到一个“非空”引用,但是却得到不同的helper对象,而不是在构造函数中设置的值。

如果编译器使用内联方式调用构造函数,那么只要编译器能够证明构造函数不会抛出异常或者调用同步锁,初始化对象和写入对象到helper变量中就可能进行自由的重排序。

即使编译器没有进行指令重排序,在一个多核心处理器中,处理器或者内存系统也可能会重排序这两步写入操作,运行在其它处理器上的线程就会看到重排序后的结果。

Doug Lea 曾经写过一篇文章对编译重排序进行了更加详细的叙述

一个展示它不能正常工作的测试方法 
Paul Jakubik 发现一个使用了双重检验锁但是不能正常运行的例子。在这里有一个稍微清晰的代码。 
译者:这里我直接贴出代码,有兴趣可以去研究一下

public class DoubleCheckTest  {  // static data to aid in creating N singletons  static final Object dummyObject = new Object(); // for reference init  static final int A_VALUE = 256; // value to initialize 'a' to  static final int B_VALUE = 512; // value to initialize 'b' to  static final int C_VALUE = 1024;  static ObjectHolder[] singletons;  // array of static references  static Thread[] threads; // array of racing threads  static int threadCount; // number of threads to create  static int singletonCount; // number of singletons to create  static volatile int recentSingleton;  // I am going to set a couple of threads racing,  // trying to create N singletons. Basically the  // race is to initialize a single array of  // singleton references. The threads will use  // double checked locking to control who  // initializes what. Any thread that does not  // initialize a particular singleton will check  // to see if it sees a partially initialized view.  // To keep from getting accidental synchronization,  // each singleton is stored in an ObjectHolder  // and the ObjectHolder is used for  // synchronization. In the end the structure  // is not exactly a singleton, but should be a  // close enough approximation.  //  // This class contains data and simulates a  // singleton. The static reference is stored in  // a static array in DoubleCheckFail.  static class Singleton    {    public int a;    public int b;    public int c;    public Object dummy;    public Singleton()      {      a = A_VALUE;      b = B_VALUE;      c = C_VALUE;      dummy = dummyObject;      }    }  static void checkSingleton(Singleton s, int index)    {    int s_a = s.a;    int s_b = s.b;    int s_c = s.c;    Object s_d = s.dummy;    if(s_a != A_VALUE)      System.out.println("[" + index + "] Singleton.a not initialized " +s_a);    if(s_b != B_VALUE)      System.out.println("[" + index                         + "] Singleton.b not intialized " + s_b);    if(s_c != C_VALUE)      System.out.println("[" + index                         + "] Singleton.c not intialized " + s_c);    if(s_d != dummyObject)      if(s_d == null)        System.out.println("[" + index                           + "] Singleton.dummy not initialized,"                           + " value is null");      else        System.out.println("[" + index                           + "] Singleton.dummy not initialized,"                           + " value is garbage");    }  // Holder used for synchronization of  // singleton initialization.  static class ObjectHolder    {    public Singleton reference;    }  static class TestThread implements Runnable    {    public void run()      {      for(int i = 0; i < singletonCount; ++i)        {    ObjectHolder o = singletons[i];        if(o.reference == null)          {          synchronized(o)            {            if (o.reference == null) {              o.reference = new Singleton();          recentSingleton = i;          }            // shouldn't have to check singelton here            // mutex should provide consistent view            }          }        else {          checkSingleton(o.reference, i);      int j = recentSingleton-1;      if (j > i) i = j;      }        }      }    }  public static void main(String[] args)    {    if( args.length != 2 )      {      System.err.println("usage: java DoubleCheckFail" +                         " <numThreads> <numSingletons>");      }    // read values from args    threadCount = Integer.parseInt(args[0]);    singletonCount = Integer.parseInt(args[1]);    // create arrays    threads = new Thread[threadCount];    singletons = new ObjectHolder[singletonCount];    // fill singleton array    for(int i = 0; i < singletonCount; ++i)      singletons[i] = new ObjectHolder();    // fill thread array    for(int i = 0; i < threadCount; ++i)      threads[i] = new Thread( new TestThread() );    // start threads    for(int i = 0; i < threadCount; ++i)      threads[i].start();    // wait for threads to finish    for(int i = 0; i < threadCount; ++i)      {      try        {        System.out.println("waiting to join " + i);        threads[i].join();        }      catch(InterruptedException ex)        {        System.out.println("interrupted");        }      }    System.out.println("done");    }  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160

上述代码使用在Symantec JIT系统之上时不能正常工作。尤其是,Symantec JIT 编译singletons[i].reference = new Singleton();成下面的样子(注:Symantec JIT是一个使用基于句柄的对象分配系统) 
译者注:Symantec JIT是一个Java编译器

0206106A   mov         eax,0F97E78h0206106F   call        01F6B210                  ; 为单实例分配空间                                                 ; 返回结果到寄存器eax中02061074   mov         dword ptr [ebp],eax       ; EBP 中一个对象指针引用                                                 ; 存储了一个没有初始化的对象02061077   mov         ecx,dword ptr [eax]       ; 间接引用句柄得到原始指针02061079   mov         dword ptr [ecx],100h      ; 下面4行代码是单实例对象的0206107F   mov         dword ptr [ecx+4],200h    ; 内联构造方法02061086   mov         dword ptr [ecx+8],400h0206108D   mov         dword ptr [ecx+0Ch],0F84030h
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

正如所见,声名对象引用在对象构造方法执行之前就已经被调用了。这在现有的Java模型上是完全合法的(译者:即被允许),在C/C++中也是如此(即使在C/C++中没有内存模型。) 
译者注:C++11已经存在内存模型,简单来说,内存模型是为了解决在多线程中的编译器优化程度,保证多线程程序正常运行的一种可靠的方式。

一种修复但是并没有效果

给出了上面的解释,有很多人便想出了下面的代码:

// 多线程下依然不能正常工作// “双重检验锁”方式class Foo {  private Helper helper = null;  public Helper getHelper() {    if (helper == null) {      Helper h;      synchronized(this) {        h = helper;        if (h == null)            synchronized (this) {              h = new Helper();            } // release inner synchronization lock        helper = h;        }      }    return helper;    }  // other functions and members...  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

上述代码把构造函数一个Helper对象放在了内部同步块里。直觉上来说,在释放同步块之前会有一个内存屏障,这也将阻止初始化对象和设置变量值的指令重排序。

然而,这种直觉是错误的。同步锁规则并不是这样使用的。释放同步块的规则(monitorexit:释放同步块)是释放同步块前的代码必需在释放动作执行前执行。然而,并没有规则规定在释放同步块后的代码不能在释放锁之前执行。这才是编译器把helper=h移到同步块里的真正合理的原因——这也就回到了之前的问题上。许多处理器提供了单向内存屏障处理指令。改变锁的语义会导致需要释放一个完整的内存屏障,这也将导致性能损失。

更多不生效的修复方法

这里有一些方法使用了完整双向的内存屏障去强制写入操作执行。这种方式太过于简单暴力了,而且是非常低效的,并且几乎可以肯定一旦Java的内存模型修改了便不能继续正确运行。请不要这样使用。如果有兴趣,我写了一篇更加详细的描述了这一技术在另一个页面。再次强调,请不要这样使用。

纵然是使用了一个完整的双向内存屏障在线程中去创建一个helper对象,它也不能正常工作。

问题在于一些操作系统,那些从变量helper中获取到一个非空的对象也需要执行内存屏障。

为什么?因为处理器对内存有自己的本地缓存。在一些处理器上,除非处理器执行了缓存连贯性指令(一个内存屏障),否则读操作可能会读到一个过期的缓存,即使其它处理器在全局内存中强制使用了内存屏障。

值得这么麻烦吗?

对于大多数应用程序,直接把getHelper()方法标记为同步方法的代价并不高。除非你知道这样的方法会会对应用程序带来巨大的性能开支时,才应该考虑这些细节上的优化操作。

一般情况下,更高级的见解是,在使用内置的归并排序而不是交换排序(参见 the SPECJVM DB benchmark)时会有更大的影响。

让静态单例生效

如果你创建了一个静态的单例(即只有一个Helper对象被创建),和一个对象拥有一个属性相反(即,所有的Foo对象都只是拥有唯一的一个Helper对象),这里有一个简单优雅的方法(译者注:这就是饿汉方式,不使用懒加载)。

class HelperSingleton {  static Helper singleton = new Helper();  }
  • 1
  • 2
  • 3

在32位变量上可正常运行

虽然双重检验锁会在使用引用对象时失效,但是它可以在32位变量上生效(如,int、float)。注意它不能运行在long或者double上,因为非同步的读/写操作在64位变量上不保证是原子的。

// 原生32位变量上正确的双重检验锁class Foo {  private int cachedHashCode = 0;  public int hashCode() {    int h = cachedHashCode;    if (h == 0)    synchronized(this) {      if (cachedHashCode != 0) return cachedHashCode;      h = computeHashCode();      cachedHashCode = h;      }    return h;    }  // other functions and members...  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

事实上,假如上述代码中的computeHashCode()方法总是能返回一样的值并且没有其它副作用(即,它是幂等的),甚至可以不使用任何同步方法即可实现单例:

// 原生32位变量懒加载// 如果computeHashCode()方法幂等的,则是线程安全的class Foo {  private int cachedHashCode = 0;  public int hashCode() {    int h = cachedHashCode;    if (h == 0) {      h = computeHashCode();      cachedHashCode = h;      }    return h;    }  // other functions and members...  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

译者注:这里也就是说,computeHashCode()返回一个int类型,如果每一次都是返回 100,不管运行多少次,它都是100,而在Java中,int类型(原生32位类型)无论怎样比较都是一样的,不会不同,所以它是幂等的,所以它也是线程安全的。这种类型不包含包装类型如Integer。

在明确的内存屏障下正常运行

如果你使用了明确的内存屏障,也是可以让双重检验锁按照规则正常运行的。例如,如果你用C++编程,你可以使用Doug Schmidt 等人所著的书中的代码:

// 明确使用内存屏障的 C++ 实现// 可以在任何平台下正确运行, 包括 DEC Alphas(译者:早就死掉了,可见这篇文章写的时间之早)// 来自书籍 "Patterns for Concurrent and Distributed Objects",// 作者 Doug Schmidttemplate <class TYPE, class LOCK> TYPE *Singleton<TYPE, LOCK>::instance (void) {    // First check    TYPE* tmp = instance_;    // Insert the CPU-specific memory barrier instruction    // to synchronize the cache lines on multi-processor.    asm ("memoryBarrier");    if (tmp == 0) {        // Ensure serialization (guard        // constructor acquires lock_).        Guard<LOCK> guard (lock_);        // Double check.        tmp = instance_;        if (tmp == 0) {                tmp = new TYPE;                // Insert the CPU-specific memory barrier instruction                // to synchronize the cache lines on multi-processor.                asm ("memoryBarrier");                instance_ = tmp;        }    return tmp;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

使用本地线程存储修复双重检验锁

Alexander Terekhov (TEREKHOV@de.ibm.com) 提出了利用线程本地存储实现双重检查锁定的巧妙建议。每一个线程保持一个线程本地标记来决定这个线程是否已经执行了必要的同步。

  class Foo {     /** 如果 perThreadInstance.get() 返回一个非空值, 这个线程已经执行过了同步块并          且可以获取到已经初始化了的helper对象 */         private final ThreadLocal perThreadInstance = new ThreadLocal();         private Helper helper = null;         public Helper getHelper() {             if (perThreadInstance.get() == null) createHelper();             return helper;         }         private final void createHelper() {             synchronized(this) {                 if (helper == null)                     helper = new Helper();             }         // 任何非空值均可以做为参数             perThreadInstance.set(perThreadInstance);         }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

这个实现技巧的的性能决定于你使用的JDK实现版本。在Sun的1.2实现版本里,ThreadLocal是非常低效的。在1.3版本里有了显著的提高,并且预计在1.4版本里会更快。Doug Lea 分析了使用这种个技巧去实现懒加载方式的性能。

使用新的Java内存模型

在JDK5(译者:即JDK1.5,后来就都不用1.x了直接使用版本号)中,有一个新的Java内存模型和线程标准。

用Volatic修复双重检验锁 
JDK5及以后版本扩展了volatile的语义,因此系统将不再允许对一个volatile变量的写操作与它之前的读写操作进行重排序,并且一个volatile的读操作也不能与它之后的读写操作进行重排序。详细信息参见 Jeremy Manson 的博客。

有了这个改变,只要在声名变量helper时添加关键字volatile,则双重检验锁的方式可以正常工作。这在JDK4及以前版本是不可行的。

// 正常工作在 获取/释放锁 volatile 语义 之下// 在当前主义下不能正常工作(译者:作者的意思应该是现在的JDK4不能正常运行,可在JDK5运行,可能当时JDK5并没有正式发布)  class Foo {        private volatile Helper helper = null;        public Helper getHelper() {            if (helper == null) {                synchronized(this) {                    if (helper == null)                        helper = new Helper();                }            }            return helper;        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

双重检验锁定不可改变对象

如果Helper对象是一个不一变对象,即Helper对象的所有域(变量)都是final类型,那么即使没有使用 volatile ,双重检验锁也可以正常运行。这个思想主要是引用一个和int或者float一样的不可变对象(像String 或者 Integer),读写不可变对象的引用时都是原子的。

其相关双重检验锁:

Reality Check, Douglas C. Schmidt, C++ Report, SIGS, Vol. 8, No. 3, March 1996. 
Double-Checked Locking: An Optimization Pattern for Efficiently Initializing and Accessing Thread-safe Objects, Douglas Schmidt and Tim Harrison. 3rd annual Pattern Languages of Program Design conference, 1996 
Lazy instantiation, Philip Bishop and Nigel Warren, JavaWorld Magazine 
Programming Java threads in the real world, Part 7, Allen Holub, Javaworld Magazine, April 1999. 
Java 2 Performance and Idiom Guide, Craig Larman and Rhett Guthrie, p100. 
Java in Practice: Design Styles and Idioms for Effective Java, Nigel Warren and Philip Bishop, p142. 
Rule 99, The Elements of Java Style, Allan Vermeulen, Scott Ambler, Greg Bumgardner, Eldon Metz, Trvor Misfeldt, Jim Shur, Patrick Thompson, SIGS Reference library 
Global Variables in Java with the Singleton Pattern, Wiebe de Jong, Gamelan