1
全限定类在同一个类加载器只能加载一次,意味着static对象及代码块只一次,为单例之依据
如果并发发生,则阻塞
故类的加载不存在多线程,因为只执行一次,其他线程等着加载线程,由jvm来保证线程安全性
public class ByLoad {
static {
try {
System.out.println(Thread.currentThread() + " start " + System.currentTimeMillis()/1000 );
Thread.sleep(20000); 【11行】
System.out.println(Thread.currentThread() + " end " + System.currentTimeMillis()/1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void func() {
System.out.println(Thread.currentThread() + " func " + System.currentTimeMillis()/1000);
}
}
public class Main {
public static void main(String []f) {
CountDownLatch countDownLatch = new CountDownLatch(1);
for(int i=0; i<3; ++i) {
int id = i+1;
Thread thread = new Thread(new MyThread(countDownLatch), "thread - " + id + " haha");
thread.start();
}
countDownLatch.countDown();
}
private static class MyThread implements Runnable {
private CountDownLatch countDownLatch;
public MyThread(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
ByLoad.func(); 【35行】
}
}
}
输出:
Thread[thread - 1 haha,5,main] start 1602256385
Thread[thread - 1 haha,5,main] end 1602256405
Thread[thread - 1 haha,5,main] func 1602256405
Thread[thread - 3 haha,5,main] func 1602256405
Thread[thread - 2 haha,5,main] func 1602256405
可以看到线程1执行到ByLoad.func()时,进行ByLoad的加载,静态代码块执行
2 3 线程的ByLoad.func()被阻塞
"thread - 3 haha" #12 prio=5 os_prio=31 tid=0x00007f820199a000 nid=0x4003 in Object.wait() [0x0000700002cf2000]
java.lang.Thread.State: RUNNABLE
at Thread.loader.Main$MyThread.run(Main.java:35)
at java.lang.Thread.run(Thread.java:745)
"thread - 2 haha" #11 prio=5 os_prio=31 tid=0x00007f820003d800 nid=0x4103 in Object.wait() [0x0000700002bef000]
java.lang.Thread.State: RUNNABLE
at Thread.loader.Main$MyThread.run(Main.java:35)
at java.lang.Thread.run(Thread.java:745)
"thread - 1 haha" #10 prio=5 os_prio=31 tid=0x00007f8201837800 nid=0x4203 waiting on condition [0x0000700002aeb000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at Thread.loader.ByLoad.<clinit>(ByLoad.java:11)
at Thread.loader.Main$MyThread.run(Main.java:35)
at java.lang.Thread.run(Thread.java:745)
3 我们来回看单例模式
饿汉式
|
1
2
3
4
5
6
7
|
public class ImageLoader{ private static ImageLoader instance = new ImageLoader; private ImageLoader(){} public static ImageLoader getInstance(){ return instance; } } |
一上来就把单例对象创建出来了,要用的时候直接返回即可
这里的“一上来”是有误区的,java的类本来就是懒加载,不存在一上来;
那么为什么还会出来后面的静态代码块式单例模式?
4
试想一下这种情况:
public class ImageLoader{ private static ImageLoader instance = new ImageLoader; private ImageLoader(){} public static ImageLoader getInstance(){ return instance; } }any() 先于getInstance执行,则不算懒加载了
故:
为了避免意外提前加载,在实际业务代码中才引入静态内部类加固懒加载,而不完全指望公开类懒加载,
单例的静态内部类模式即是如此原理
5
/
6 我们常常碰到静态基础数据加载:
6.1 双检
public class CurrencyDataProvider implements DataProvider<String> {
@Inject
private SCEFCacheUtils scefCacheUtils;
private static List<String> result;
@Override
public Collection<String> getAll() {
if (result == null) {
synchronized (CurrencyDataProvider.class) {
if (result == null) {
result = new ArrayList<>(scefCacheUtils.getCurrencyList());
Collections.sort(result);
}
}
}
return result;
}
}
比较好理解,然后sonar抵触double-check lock
6.2
public class CurrencyDataProvider implements DataProvider<String> {
@Inject
private SCEFCacheUtils scefCacheUtils;
private static List<String> result;
@Override
public Collection<String> getAll() {
if (result == null) {
result = new ArrayList<>(scefCacheUtils.getCurrencyList());
Collections.sort(result);
}
return result;
}
}
那就拆锁吧,首次访问并发时无法多查几次库,毕竟是读操作
然后被sonar指出,sort(result)有并发访问的风险-大bug
6.3
public class CurrencyDataProvider implements DataProvider<String> {
@Inject
private SCEFCacheUtils scefCacheUtils;
private static List<String> result;
@Override
public Collection<String> getAll() {
if (result == null) {
List<String> list = new ArrayList<>(scefCacheUtils.getCurrencyList());
Collections.sort(list);
result = list;
}
return result;
}
}
好我们改一下,让局部对象参与sort,搞定了再给类对象,并发时,最多是多次对result赋值
然而sonar又不干了,认为static result被并发了,非线程安全
6.4
public class CurrencyDataProvider implements DataProvider<String> {
@Override
public Collection<String> getAll() {
return InnerLoader.res;
}
private static class InnerLoader {
private static List<String> res;
static {
Injector injector = CRFGuiceContext.getInjector();
SCEFCacheUtils scefCacheUtils1 = injector.getInstance(SCEFCacheUtils.class);
List<String> list = new ArrayList<>(scefCacheUtils1.getCurrencyList());
Collections.sort(list);
res = list;
}
}
}
静态内部类-sonar推荐的方案
6.4.1 使用static代码块进行static资源加载,因为第1点指出static代码块和对象只会搞一次,这样就避免了并发重复加载,且替代了double-check
6.4.2 为啥不直接写在CurrencyDataProvider的static代码块?
因为第4点指出,如果CurrencyDataProvider被意外loadclass或forname或其他方式率先加载了,执行static代码块时,Guice环境可能还没好
6.5
public class CurrencyDataProvider implements DataProvider<String> {
@Override
public Collection<String> getAll() {
return InnerLoader.res;
}
private static class InnerLoader {
private static List<String> res;
static {
Injector injector = CRFGuiceContext.getInjector();
SCEFCacheUtils scefCacheUtils1 = injector.getInstance(SCEFCacheUtils.class);
res = new ArrayList<>(scefCacheUtils1.getCurrencyList());
Collections.sort(res);
}
}
}
有了第1点的结论:类的加载不存在多线程,因为只执行一次,其他线程等着加载线程,我们放心的对static对象res直接进行sort,而不用局部对象缓冲,因为不会有第2个线程再执行到这儿了,只此一次执行
7
mybatis guice 事务代理切面 中sqlSessionManager也可用:
// private static SqlSessionManager sqlSessionManager = null;
private SqlSessionManager getOdsSqlSessionManager() {
return SqlManagerLoader.sqlSessionManager;
}
private static class SqlManagerLoader {
private static SqlSessionManager sqlSessionManager = null;
static {
OdsTransactionMapper odsTransactionMapper = getBeanFromFactoryGuice(OdsTransactionMapper.class);
InvocationHandler lvInvHandler0 = Proxy.getInvocationHandler(odsTransactionMapper);
ManagedMapperProvider managedMapperProvider0 = (ManagedMapperProvider) lvInvHandler0;
sqlSessionManager = managedMapperProvider0.getSqlSessionManager();
}
}