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 }
浙公网安备 33010602011771号