Java基础也许你学得比较好了,但是,当你看了下面我的这些,我感觉你就会怀疑你的基础,不信,那么咱们继续往下看~~如果你真的都知道了,那么你算一个基本合格的程序员了!!
一:单例模式
描述:我想大部分的人看到这个的时候,都会觉得很简单,但是,我相信你了解的就只是表面而已,不信,那么你继续往下看。
单例的实现方式:恶汉式,懒汉式(二种),双重检查,静态内部类,枚举类。----如何实现就不多说,这是基础知识。
重点:我们都知道,对于开发中,静态内部类是一种最好的实现方式,因为相对枚举方式的话,更加容易理解。那么,静态内部类单例模式,你真的了解吗?
问题来了:下面的方式针对静态内部类的形式,将破坏单例模式
(1)通过反射而来实现的创建对象,那么单例模式就被破坏,如何解决?
测试代码:
package test;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import singleton.LazySingleton2;
/**
* @author zhengrongjun
*/
public class LazySingleton2Test {
public static void main(String[] args) {
//创建第一个实例
LazySingleton2 instance1 = LazySingleton2.getInstance();
//通过反射创建第二个实例
LazySingleton2 instance2 = null;
try {
Class<LazySingleton2> clazz = LazySingleton2.class;
Constructor<LazySingleton2> cons = clazz.getDeclaredConstructor();
cons.setAccessible(true);
instance2 = cons.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
//检查两个实例的hash值
System.out.println("Instance 1 hash:" + instance1.hashCode());
System.out.println("Instance 2 hash:" + instance2.hashCode());
}
}
测试结果:
Instance 1 hash:1694819250
Instance 2 hash:1365202186
总结:通过上面可以看出来,这样的实例并不是真正的单例类了,所以,需要进行下面的修改:
package singleton;
public class LazySingleton3 {
private static boolean initialized = false;
private LazySingleton3() {
synchronized (LazySingleton3.class) {
if (initialized == false) {
initialized = !initialized;
} else {
throw new RuntimeException("单例已被破坏");
}
}
}
static class SingletonHolder {
private static final LazySingleton3 instance = new LazySingleton3();
}
public static LazySingleton3 getInstance() {
return SingletonHolder.instance;
}
}
再次运行之前的测试类,就会出现下面的结果:
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at test.LazySingleton3Test.main(LazySingleton3Test.java:21)
Caused by: java.lang.RuntimeException: 单例已被破坏
at singleton.LazySingleton3.<init>(LazySingleton3.java:12)
... 5 more
Instance 1 hash:359023572
这样的话,就能够保证反射不破坏单例了。
(2)通过序列化的方式来实现创建对象,那么单例模式也被破坏,如何解决?
测试代码:(注意一点,现将之前的那个单例类,实现Serializable接口)
package test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import singleton.LazySingleton3;
public class LazySingleton3Test {
public static void main(String[] args) {
try {
LazySingleton3 instance1 = LazySingleton3.getInstance();
ObjectOutput out = null;
out = new ObjectOutputStream(new FileOutputStream("filename.ser"));
out.writeObject(instance1);
out.close();
//deserialize from file to object
ObjectInput in = new ObjectInputStream(new FileInputStream("filename.ser"));
LazySingleton3 instance2 = (LazySingleton3) in.readObject();
in.close();
System.out.println("instance1 hashCode=" + instance1.hashCode());
System.out.println("instance2 hashCode=" + instance2.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试结果:
instance1 hashCode=2051450519
instance2 hashCode=1510067370
显然,我们又看到了两个实例类。为了避免此问题,我们需要提供 readResolve() 方法的实现。readResolve()代替了从流中读取对象。这就确保了在序列化和反序列化的过程中没人可以创建新的实例。
修改后的单例类代码:
package singleton;
import java.io.Serializable;
public class LazySingleton4 implements Serializable {
private static boolean initialized = false;
private LazySingleton4() {
synchronized (LazySingleton4.class) {
if (initialized == false) {
initialized = !initialized;
} else {
throw new RuntimeException("单例已被破坏");
}
}
}
static class SingletonHolder {
private static final LazySingleton4 instance = new LazySingleton4();
}
public static LazySingleton4 getInstance() {
return SingletonHolder.instance;
}
private Object readResolve() {
return getInstance();
}
}
知识点总结:回头想一想,单例是不是也有很多的知识点呢?是不是怀疑人生,所以,针对一个问题还是要好好的思考的。
二:JVM的模型(多个不同角度的观察)
描述:对于JVM,我想,对于Java开发的人并不陌生,但是对于里面的一些内容来说,我想就不是很了解了。我们平常对于JVM我想了解得最多的就是它的内存模型了,更确切的说,我想就是JVM的运行角度的理解。那么,从其他角度来看的话,你真的了解JVM吗?下面,我从多个不同角度来对JVM进行描述。
(1)从JVM运行时视角来看,JVM内存可分为JVM栈、本地方法栈、PC计数器、方法区、堆;其中前三区是线程所私有的,后两者则是所有线程共有的;
(2)从JVM内存功能视角来看,JVM可分为堆内存、非堆内存与其他。其中堆内存对应于上述的堆区;非堆内存对应于上述的JVM栈、本地方法栈、PC计数器、方法区;其他则对应于直接内存;
(3)从线程运行视角来看,JVM可分为主内存与线程工作内存。Java内存模型规定了所有的变量都存储在主内存中;每个线程的工作内存保存了被该线程使用到的变量,这些变量是主内存的副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量;
(4)从垃圾回收视角来看,JVM中的堆区=新生代+老年代。新生代主要用于存放新创建的对象与存活时长小的对象,新生代=E+S1+S2;老年代则用于存放存活时间长的对象;
总结:其实,JVM从不同的角度来看是有不同的划分的,这样我们也能够更加的对其进行了解。并且,这里面有比较重要的知识点,比如,为什么说局部变量就是线程安全的呢?volatile关键字它的功能原理是如何呢?关于这些问题,可以浏览我的另外一篇博文:https://blog.csdn.net/cs_hnu_scw/article/details/79635874
三:代理机制
Java中,关于代理主要有:
(1)静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了
(2)动态代理:在程序运行时,运用反射机制动态创建而成。(比如JDK中的代理机制和Spring中的CGlib代理)
静态代理(详解):
定义一个接口:
/**
* 定义一个账户接口
*
* @author Administrator
*
*/
public interface Count {
// 查看账户方法
public void queryCount();
// 修改账户方法
public void updateCount();
}
接口实现类:
/**
* 委托类(包含业务逻辑)
*
* @author Administrator
*
*/
public class CountImpl implements Count {
@Override
public void queryCount() {
System.out.println("查看账户方法...");
}
@Override
public void updateCount() {
System.out.println("修改账户方法...");
}
}
代理类:
/**
* 这是一个代理类(其实就是对CountImpl接口实现类的方法的增强处理)
* 相当于对接口实现类的一个扩充
*/
public class CountProxy implements Count {
private CountImpl countImpl;
/**
* 覆盖默认构造器
*
* @param countImpl
*/
public CountProxy(CountImpl countImpl) {
this.countImpl = countImpl;
}
@Override
public void queryCount() {
System.out.println("事务处理之前");
// 调用委托类的方法;
countImpl.queryCount();
System.out.println("事务处理之后");
}
@Override
public void updateCount() {
System.out.println("事务处理之前");
// 调用委托类的方法;
countImpl.updateCount();
System.out.println("事务处理之后");
}
}
测试方法(比较简单):
public class TestCount {
public static void main(String[] args) {
CountImpl countImpl = new CountImpl();
CountProxy countProxy = new CountProxy(countImpl);
countProxy.updateCount();
countProxy.queryCount();
}
}
分析:这个代码不难,很简单,比较容易理解。。通过这个的话,我们可以看到一个静态代理很明显的缺点,就是一个代理类只能服务一个接口,那么问题就来了,如果我有几十个接口,那么这个用静态代理写就很麻烦了吧。而且,看到里面代理的方法来说,其实就是调用的方法不一样而已,所以,为了解决这两个问题,动态代理就出现了。
动态代理:(JDK的方式)
区别:与静态代理类对照的是动态代理类,动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。
定义一个接口:
public interface Count { //一个账单接口
public void addCount(); //一个增加账单的方法
}
定义接口实现类:
public class CountImpl implements Count {
@Override
public void addCount() {
System.out.println("我增加钱啦。。。");
}
}
代理类:
public class CountProxy implements InvocationHandler {
private Object target;
/**
* 绑定委托对象并返回一个代理类
* @param target 需要代理的实例
* @return 返回一个代理实例
*/
public Object bind(Object target) {
this.target = target;
//取得代理对象
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this); //一定需要绑定接口
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result=null;
System.out.println("代理开始了哦!!!");
//执行方法
result=method.invoke(target, args);
System.out.println("代理结束了哦!!!");
return result;
}
}
敲黑板:这里就详细再说一下关于这个代理类的内容:
InvocationHandler接口的分析:
public interface InvocationHandler {
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable;
}
方法参数说明:
(1)Object proxy:指被代理的对象。
(2)Method method:要调用的方法
(3)Object[] args:方法调用时所需要的参数
Proxy类的分析:
是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下的操作方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
throws IllegalArgumentException
参数说明:
(1)ClassLoader loader:类加载器
关于类加载器这又可以延伸出一个比较重要的内容了,在Java中主要有下面的三种:
1:Booststrap ClassLoader:此加载器采用C++编写,一般开发中是看不到的;
2:Extendsion ClassLoader:用来进行扩展类的加载,一般对应的是jre\lib\ext目录中的类;
3:AppClassLoader:(默认)加载classpath指定的类,是最常使用的是一种加载器。
(2)Class<?>[] interfaces:得到全部的接口
(3)InvocationHandler h:得到InvocationHandler接口的子类实例
测试类:
public class TestProxy {
public static void main(String[] args) {
BookFacadeProxy proxy = new BookFacadeProxy();
BookFacade bookProxy = (BookFacade) proxy.bind(new BookFacadeImpl());
bookProxy.addBook();
}
}
动态代理分析:看看,通过这样的方式是不是比静态的简单很多了呢?这样实现类的所有方法都可以得到代理,减少了很多冗余的代码了哦。。但是但是还有个问题,就是JDK中的Proxy对象,必须对需要代理的类实现着一个接口。。。。所以,为了解决这个问题,然后在Spring中就出现了一个CGlib动态代理哦。。。。一步步的进行更新
CGlib动态代理:
定义一个实现类:
public class CountImpl{
public void addCount() {
System.out.println("我增加钱啦。。。");
}
}
代理类:
/**
* 使用cglib动态代理
*
*/
public class BookFacadeCglib implements MethodInterceptor {
private Object target;
/**
* 创建代理对象
* @param target 需要代理的实例类
* @return 返回一个代理对象
*/
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass()); //会覆盖目标类中所有的非final修饰方法,所以说明final方法是无法进行代理的,切记
// 设置回调方法
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
@Override
// 这是一个回调方法,在接口中定义的
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("事物开始");
proxy.invokeSuper(obj, args);
System.out.println("事物结束");
return null;
}
}
测试类:
public class TestCglib {
public static void main(String[] args) {
BookFacadeCglib cglib=new BookFacadeCglib();
BookFacadeImpl1 bookCglib=(BookFacadeImpl1)cglib.getInstance(new BookFacadeImpl1());
bookCglib.addBook();
}
}
分析:通过CGlib代理就可以代理没有实现接口的类的哦。。。是不是挺好的呢。。总的来说的话,如果不需要多次代理而只是一个实例对象的话,就用CGlib代理,要不然就可以采取JDK中的Proxy对象来实现。
咳咳咳咳,,,通过看这三种形式的方法,是不是对代理机制有所了解了呢?这个还是挺好的,而且这个也是在看Spring源码中的AOP机制所了解的,真是很厉害很厉害。。。。
四:实现细粒度锁----主要用来解决高并发场景的加锁机制来保证业务的正确性
(1)分段锁
借鉴concurrentHashMap的分段思想,先生成一定数量的锁,具体使用的时候再根据key来返回对应的lock。这是几个实现里最简单,性能最高,也是最终被采用的锁策略,代码如下:
/**
* 分段锁,系统提供一定数量的原始锁,根据传入对象的哈希值获取对应的锁并加锁
* 注意:要锁的对象的哈希值如果发生改变,有可能导致锁无法成功释放!!!
*/
public class SegmentLock<T> {
private Integer segments = 16;//默认分段数量
private final HashMap<Integer, ReentrantLock> lockMap = new HashMap<>();
public SegmentLock() {
init(null, false);
}
public SegmentLock(Integer counts, boolean fair) {
init(counts, fair);
}
private void init(Integer counts, boolean fair) {
if (counts != null) {
segments = counts;
}
for (int i = 0; i < segments; i++) {
lockMap.put(i, new ReentrantLock(fair));
}
}
public void lock(T key) {
ReentrantLock lock = lockMap.get(key.hashCode() % segments);
lock.lock();
}
public void unlock(T key) {
ReentrantLock lock = lockMap.get(key.hashCode() % segments);
lock.unlock();
}
}
(2)哈希锁----------(借鉴了第一种方法,所以要看懂第一种先)
上述分段锁的基础上发展起来的第二种锁策略,目的是实现真正意义上的细粒度锁。每个哈希值不同的对象都能获得自己独立的锁。在测试中,在被锁住的代码执行速度飞快的情况下,效率比分段锁慢 30% 左右。如果有长耗时操作,感觉表现应该会更好。代码如下:
public class HashLock<T> {
private boolean isFair = false;
private final SegmentLock<T> segmentLock = new SegmentLock<>();//分段锁
private final ConcurrentHashMap<T, LockInfo> lockMap = new ConcurrentHashMap<>();
public HashLock() {
}
public HashLock(boolean fair) {
isFair = fair;
}
public void lock(T key) {
LockInfo lockInfo;
segmentLock.lock(key);
try {
lockInfo = lockMap.get(key);
if (lockInfo == null) {
lockInfo = new LockInfo(isFair);
lockMap.put(key, lockInfo);
} else {
lockInfo.count.incrementAndGet();
}
} finally {
segmentLock.unlock(key);
}
lockInfo.lock.lock();
}
public void unlock(T key) {
LockInfo lockInfo = lockMap.get(key);
if (lockInfo.count.get() == 1) {
segmentLock.lock(key);
try {
if (lockInfo.count.get() == 1) {
lockMap.remove(key);
}
} finally {
segmentLock.unlock(key);
}
}
lockInfo.count.decrementAndGet();
lockInfo.unlock();
}
private static class LockInfo {
public ReentrantLock lock;
public AtomicInteger count = new AtomicInteger(1);
private LockInfo(boolean fair) {
this.lock = new ReentrantLock(fair);
}
public void lock() {
this.lock.lock();
}
public void unlock() {
this.lock.unlock();
}
}
}
(3)弱引用锁-----------------(关于弱引用的概念,这里就不多说了,可以了解一下Java里面中的四种引用:强引用,弱引用,软引用,虚引用)
哈希锁因为引入的分段锁来保证锁创建和销毁的同步,总感觉有点瑕疵,所以写了第三个锁来寻求更好的性能和更细粒度的锁。这个锁的思想是借助java的弱引用来创建锁,把锁的销毁交给jvm的垃圾回收,来避免额外的消耗。有点遗憾的是因为使用了ConcurrentHashMap作为锁的容器,所以没能真正意义上的摆脱分段锁。这个锁的性能比 HashLock 快10% 左右。锁代码:
/**
* 弱引用锁,为每个独立的哈希值提供独立的锁功能
*/
public class WeakHashLock<T> {
private ConcurrentHashMap<T, WeakLockRef<T, ReentrantLock>> lockMap = new ConcurrentHashMap<>();
private ReferenceQueue<ReentrantLock> queue = new ReferenceQueue<>(); public ReentrantLock get(T key) {
if (lockMap.size() > 1000) {
clearEmptyRef();
}
WeakReference<ReentrantLock> lockRef = lockMap.get(key);
ReentrantLock lock = (lockRef == null ? null : lockRef.get()); while (lock == null) {
lockMap.putIfAbsent(key, new WeakLockRef<>(new ReentrantLock(), queue, key));
lockRef = lockMap.get(key);
lock = (lockRef == null ? null : lockRef.get());
if (lock != null) {
return lock;
}
clearEmptyRef();
} return lock;
}
@SuppressWarnings("unchecked")
private void clearEmptyRef() {
Reference<? extends ReentrantLock> ref;
while ((ref = queue.poll()) != null) {
WeakLockRef<T, ? extends ReentrantLock> weakLockRef = (WeakLockRef<T, ? extends ReentrantLock>) ref;
lockMap.remove(weakLockRef.key);
}
}
private static final class WeakLockRef<T, K> extends WeakReference<K> { final T key;
private WeakLockRef(K referent, ReferenceQueue<? super K> q, T key) { super(referent, q);
this.key = key;
}
}
}
其实,不管做什么方向的开发,基础真的很重要,基础不扎实,做什么都不长远,所以,好好的掌握基础知识,方能走得更远。别停下来,掌握得更多,也就会有更多的不懂,那么,只有不断跟着我继续学习。。我会持续性的更新哦。。Let's go~