1.volatile关键字用于声明变量.

Java有个思想叫“主”内存区域,这里存放了变量目前的“准确值”。每个线程可以有它自己的变量拷贝,而这个变量拷贝值可以和“主”内存区域里存放的不同。因此实际上存在一种可能:“主”内存区域里的i1值是1,线程1里的i1值是2,线程2里的i1值是3——这在线程1和线程2都改变了它们各自的i1值,某个线程对i1变量的改变也没有传递给其他线程和主内存区域。 而 geti2()得到的是“主”内存区域的i2数值。用volatile修饰后的变量不允许有不同于“主”内存区域的变量拷贝(换句话说,一个变量经 volatile修饰后在所有线程中必须是同步的:任何线程中改变了它的值,所有其他线程立即获取到了相同的值)(理所当然的,volatile修饰的变量存取时比一般变量消耗的资源要多一点,因为线程有它自己的变量拷贝更为高效)

1 int i1;
2 int geti1() {return i1;}
3 volatile int i2;
4 int geti2(){return i2;}
5 int i3;
6 synchronized int geti3() {return i3;}

 

2.单例模式:

  1 /**
  2  * 案例分析:对象设计(设计一个单例对象)
  3  * 单例对象设计?类的实例对象在一个JVM内存中只有一份.
  4  * 1)对类自身进行设计?(构造方法私有化,外界不能直接构建对象)
  5  * a)构造方法私有化
  6  * b)提供公有方法,获取单例对象
  7  * 2)基于外部环境进行设计?(对象创建以后存储到池中,需要时从池中获取)
  8  * a)提供池对象
  9  * b)将创建的对象放入池中
 10  * c)需要时从池中取.
 11  */
 12 /**
 13  * 线程不安全的单例设计
 14  * 问题?
 15  * 1)如何理解线程不安全?多线程并发执行时导致了数据的不正确.
 16  * 2)导致线程不安全的因素?
 17  * a)存在多个线程并发执行
 18  * b)多个线程存在共享数据
 19  * c)多个线程在共享数据上的操作不是原子操作.
 20  * 3)如何保证线程安全?(取消共享,保证原子操作)
 21  */
 22 /**
 23  * 线程不安全的懒汉单例
 24  * 懒汉特点:对象是何时需要何时创建
 25  * 应用场景:单线程,大对象
 26  */
 27 class Singleton01{
 28     private Singleton01() {
 29         System.out.println("Singleton01()");
 30     }
 31     private static Singleton01 instance;
 32     public static Singleton01 getInstance() {
 33         if(instance==null) {
 34             instance=new Singleton01();
 35         }
 36         return instance;
 37     }
 38 }
 39 /**
 40  * 线程安全懒汉单例设计:思路保证方法操作的原子性,
 41  * 借助synchronized进行实现
 42  * 应用场景:多线程(并发量相对较小),大对象,稀少用
 43  */
 44 class Singleton02{
 45     private Singleton02() {
 46         System.out.println("Singleton02()");
 47     }
 48     private static Singleton02 instance;
 49     /**
 50      * 同步方法:线程安全,但性能会下降.
 51      */
 52     public static synchronized Singleton02 getInstance() {
 53         if(instance==null) {
 54             instance=new Singleton02();
 55         }
 56         return instance;
 57     }//静态方法使用的锁对象为静态方法所在类的字节码对象
 58 }
 59 /***
 60  * 双重验证懒汉单例设计,实现对Singleton02的优化
 61  * 应用场景:大对象,稀少用,并发量不能太大
 62  */
 63 class Singleton03{
 64     private Singleton03() {
 65         System.out.println("Singleton03()");
 66     }
 67     //public static void show() {}
 68     /**
 69      * volatile 用于修饰属性:
 70      * 1)可以保证对象修改时的可见性
 71      * 2)可以禁止指令重排序
 72      * 3)不能保证原子性(还是需要synchronized保证其原子性)
 73      */
 74     private static volatile Singleton03 instance;
 75     /**
 76      * 同步方法:通过双重验证,减少阻塞次数,提高性能.
 77      */
 78     public static Singleton03 getInstance() {
 79         if(instance==null) {
 80          synchronized(Singleton03.class) {
 81            if(instance==null) {
 82               instance=new Singleton03();
 83               //1)分配内存
 84               //2)初始化属性,执行构造方法
 85               //3)将对象赋值给instance
 86            }
 87          }
 88         }
 89         return instance;
 90     }
 91 }
 92 /**
 93  * 饿汉单例:类加载时则创建对象实例.
 94  * 饿汉特点:类加载时则创建对象
 95  * 应用场景:小对象,频繁用,高并发
 96  */
 97 class Singleton04{
 98     //int[] array=new int[1024*1024];
 99     private Singleton04() {}
100     /**对象在类加载时初始化*/
101     private static final Singleton04 instance=
102             new Singleton04();
103     public static Singleton04 getInstance() {
104         return instance;
105     }
106     //public static void show() {}
107 }
108 
109 
110 /**
111  * 基于内部类实现饿汉单例
112  * 特点:延迟对象创建,减少资源占用,提高系统性能
113  * 应用场景:大对象,频繁用,支持高并发  (1)此类对外只有一个实例, 2)此类对象可以在高并发环境下频繁使用,3)此类的对象在本类加载时不会创建实例)
114  */
115 class Singleton05{
116     private Singleton05() {}
117     static class Inner{//外部类加载时不会加载内部类
118         private static final Singleton05 instance=
119                 new Singleton05();
120     }
121     public static Singleton05 getInstance() {
122         return Inner.instance;
123     }
124     //public static void show() {}
125     //public void display() {}
126 }
127 
128 
129 /**
130  * 基于枚举实现的饿汉单例
131  */
132 enum Singleton06{//一个特殊的类,Singleton06.class
133     instance;//instance表示它是Singleton06的一个实例(对象)(类加载时创建),
134 }
135 
136 public class TestObjectInstance04 {
137     public static void main(String[] args) {
138         Thread t1=new Thread() {
139             public void run() {
140                 Singleton02.getInstance();
141             };
142         };
143         Thread t2=new Thread() {
144             public void run() {
145                 Singleton02.getInstance();
146             };
147         };
148         t1.start();
149         t2.start();
150     }
151 }

3.用ThreadLocal实现:一个线程一个对象 

 1 package com.zyq.testMyBatis;
 2 
 3 /**
 4  * 如何保证Looper对象,在被多个线程访问时,每个线程一份. 说明:此设计为一种线程内部单例设计( 可以借助ThreadLocal)
 5  * ThreadLocal应用:它提供了一种线程绑定机制,可以将 某个对象绑定到当前线程,也可以从当前线程获取某个 对象.
 6  */
 7 class Instance1 {// 不用关心类的名字
 8     protected Instance1() {
 9         System.out.println("Looper()");
10     }
11 }
12 
13 class InstanceFactory{
14     private static ThreadLocal<Instance1> tLocal = new ThreadLocal<Instance1>();
15     /** 获取Looper对象 */
16     public static Instance1 getInstance() {
17         // 1.从当前线程获取Instance1对象
18         Instance1 ins = tLocal.get();
19         // 2.当前线程没有则创建Looper并绑定到当前线程
20         if (ins == null) {
21             ins = new Instance1();
22             tLocal.set(ins);// 绑定到线程(存储到了哪里)
23         }
24         // 3.返回looper对象
25         return ins;
26     }
27 }
28 
29 public class TestObjectInstance06 {
30     public static void main(String[] args) {
31         new Thread() {//当前匿名内部类有一个ThreadLocal对象,ThreadLocal上绑定了一个Instance1
32             @Override
33             public void run() {
34                 Instance1 i1=InstanceFactory.getInstance();//两次得到的是同一个Instance1对象
35                 Instance1 i2=InstanceFactory.getInstance();
36                 System.out.println(i1==i2);
37             }
38         }.start();
39     }
40 }

 案例: 用ThreadLocal实现一个线程绑定一个Connection对象

 1 class ConnectionUtil{
 2     private static ThreadLocal<Connection> tLocal=new ThreadLocal<Connection>() {
 3         //此方法何时调用?
 4         //当从当前线程获取绑定对象时,假如当前
 5         //线程没有绑定的对象,则调用此方法创建对象并绑定到当前线程上
 6         protected Connection initialValue() { 7             Connection conn=null;
 8             try {
 9                 Class.forName("com.mysql.jdbc.Driver");
10                 conn=DriverManager.getConnection(
11                         "jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&useSSL=false",
12                         "root","123456");
13                 
14             } catch (Exception e) {
15                 e.printStackTrace();
16             }
17             return conn;
18         };
19     };
20     public static Connection getConnection() {
21         return tLocal.get();
22     }
23 }
 1 public class Test {
 2     
 3     @org.junit.Test
 4     public void txxxx() throws Exception{
 5         new Thread() {
 6             public void run() {
 7                 System.out.println(ConnectionUtil.getConnection()==ConnectionUtil.getConnection());
 8             };
 9         }.start();
10     }
11 }    //不过不知道这个案例代码为什么运行没有结果

 

案例: 用ThreadLocal实现一个线程绑定一个SimpleDateFormat对象

 1 package com.zyq.testMyBatis;
 2 
 3 import org.junit.Test;
 4 import java.text.SimpleDateFormat;
 5 import java.util.Date;
 6 
 7 class DateUtils{
 8     //private static SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd");
 9     private static ThreadLocal<SimpleDateFormat> tLocal=new ThreadLocal<>();
10     private static SimpleDateFormat getInstance() {
11         //1.从当前线程获取SimpleDateFormat对象的get()方法底层实现思路?
12         //  1)获取当前线程对象Thread.currentThread();
13         //  2)获取当前线程中的Map对象:ThreadLocalMap
14         //  3)检查map对象是否存在,不存在则创建并用initialValue()方法创建一个SimpleDateFormat数据放入map.
15         //  4)存在则从map中以ThreadLocal作为key取元素SimpleDateFormat,
16         SimpleDateFormat sdf=tLocal.get();
17         //2.假如没有,则创建,并绑定到当前线程
18         if(sdf==null) {
19             //创建对象
20             sdf=new SimpleDateFormat("yyyy/MM/dd");
21             //绑定到当前线程
22             tLocal.set(sdf);
23             //分析:set方法底层做了什么事情?
24             //1)获取当前线程对象
25             //2)获取当前线程中的Map对象,ThreadLocalMap
26             //3)如果map不为空则将ThreadLocal对象作为key,SimpleDateFormat对象作为value存储map
27             //  否则就先创建map然后把key-value存入map
28         }
29         System.out.println(sdf);
30         return sdf;
31     }
32     public static  Date parse(String source)
33     throws Exception{
34         //SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd");
35         //return sdf.parse(source);
36         return getInstance().parse(source);
37     }
38     public static  String format(Date date) {
39         //SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd");
40         //return sdf.format(date);
41         return getInstance().format(date);
42     }
43 }
44 
45 public class TestT {
46     static class DateTask implements Runnable{
47         public void run() {
48             DateUtils.format(new Date());
49             try {
50             DateUtils.parse("2019/09/25");
51             }catch(Exception e) {e.printStackTrace();}
52         };
53     }
54     @Test
55     public void test1x(){
56         DateTask task=new DateTask();
57         for(int i=0;i<1000;i++) {
58             new Thread(task).start();
59         }
60     }
61 }

注: 本案例中每个线程拥有一个独立的SimpleDateFormat对象, (注意: 每个日期格式相同的SimpleDateFormat对象的hashCode值是相同的)

所以不能通过每个线程的SimpleDateFormat对象的hashCode值判断所有的SimpleDateFormat对象是不同的(因为它们的hashCode值全部是相同的)

如果把上边案例改成下边形式,则可以发现每个SimpleDateFormat对象都是不同的(执行了1000次SimpleDateFormat的构造函数, 所以创建了1000个SimpleDateFormat对象)

 1 package com.zyq.testMyBatis;
 2 
 3 import org.junit.Test;
 4 import java.util.Date;
 5 class SimpleDateFormat{SimpleDateFormat(String s){
 6     System.out.println(s);
 7 }}
 8 class DateUtils{
 9     //private static SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd");
10     private static ThreadLocal<SimpleDateFormat> tLocal=new ThreadLocal<>();
11     private static SimpleDateFormat getInstance() {
12         //1.从当前线程获取SimpleDateFormat对象的get()方法底层实现思路?
13         //  1)获取当前线程对象Thread.currentThread();
14         //  2)获取当前线程中的Map对象:ThreadLocalMap
15         //  3)检查map对象是否存在,不存在则创建并用initialValue()方法创建一个SimpleDateFormat数据放入map.
16         //  4)存在则从map中以ThreadLocal作为key取元素SimpleDateFormat,
17         SimpleDateFormat sdf=tLocal.get();
18         //2.假如没有,则创建,并绑定到当前线程
19         if(sdf==null) {
20             //创建对象
21             //sdf=new SimpleDateFormat("yyyy/MM/dd");
22             sdf=new SimpleDateFormat(Math.random()+10+"");
23             //绑定到当前线程
24             tLocal.set(sdf);
25             //分析:set方法底层做了什么事情?
26             //1)获取当前线程对象
27             //2)获取当前线程中的Map对象,ThreadLocalMap
28             //3)如果map不为空则将ThreadLocal对象作为key,SimpleDateFormat对象作为value存储map
29             //  否则就先创建map然后把key-value存入map
30         }
31         return sdf;
32     }
33     public static  Date parse(String source)
34     throws Exception{
35         //SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd");
36         //return sdf.parse(source);
37         //return getInstance().parse(source);////////////
38         
39         java.text.SimpleDateFormat ss=new java.text.SimpleDateFormat("yyyy/MM/dd");
40         return ss.parse("2020/07/23");
41         //return  getInstance();//////////////////////////
42     }
43     public static  String format(Date date) {
44         //SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd");
45         //return sdf.format(date);
46        // return getInstance().format(date);///////////////
47         return getInstance().toString();
48     }
49 }
50 
51 public class TestT {
52     static class DateTask implements Runnable{
53         public void run() {
54             DateUtils.format(new Date());
55             try {
56             DateUtils.parse("2019/09/25");
57             }catch(Exception e) {e.printStackTrace();}
58         };
59     }
60     @Test
61     public void test1x(){
62         DateTask task=new DateTask();
63         for(int i=0;i<1000;i++) {
64             new Thread(task).start();
65         }
66     }
67 }

 

注意: 两个对象的hashCode值相同, 两个对象可能不同(可能是不同的两个对象)

例如下边的两个SimpleDateFormat是不同的,但是它们俩的hashCode值是相同的, 

1 SimpleDateFormat sdf01=new SimpleDateFormat("yyyy/MM/dd");
2         System.out.println(sdf01.toString());
3         SimpleDateFormat sdf02=new SimpleDateFormat("yyyy/MM/dd");
4         System.out.println(sdf02.toString());//toString()
5         System.out.println(sdf01==sdf02);

 

 

下边是双重验证机制在springBoot整合MyBatis中的应用案例

 1 class HikariPoolT{}
 2 //参考HikariDataSource中的getConnection方法中看到的一种双重校验机制的单例模式
 3 //两次if==null的判断是单例模式中的典型双重校验机制(如果第一次校验对象存在就不用进入同步代码块了<效率更快的同时还能保证对象只有一份儿>)
 4 public class SingleTonDemo {
 5     HikariPoolT pool=new HikariPoolT();
 6     public HikariPoolT getInstance(){
 7         HikariPoolT result=pool;
 8         if(result==null) {//1.先检查result变量是否指向某个对象, 如果指向了某个对象就不进入synchronized,这样效率更快
 9             synchronized(this) {
10                 result=pool; //没有就让result指向一个HikariPoolT对象
11                 if(result==null) {
12                     pool=result=new HikariPoolT();
13                 }
14             }
15         }
16         return result;
17     }
18     
19 }