敢问你:真的了解Java吗?(你看了颠覆你的眼界)

   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~

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
Java EE开发的颠覆者在线看是指在Java Enterprise Edition(Java EE)开发领域中一种新兴的技术或工具,能够颠覆传统的开发方式,使开发人员能够更加方便、高效地进行应用程序的开发和部署。 传统的Java EE开发方式通常需要在本地搭建开发环境、安装并配置各种开发工具和中间件,并且需要手动编译、打包和部署应用程序。这种方式费时费力,不利于快速迭代和开发效率的提升。 而Java EE开发的颠覆者在线看则提供了一种更加现代化和便捷的开发方式。开发者可以通过在线平台进行开发,无需在本地进行环境搭建和工具配置。在线平台提供了各种功能模块和工具,如代码编辑器、调试器、数据库管理器等,开发者可以直接在浏览器中进行开发和调试。 Java EE开发的颠覆者在线看还提供了自动化部署和持续集成功能。开发者可以将应用程序直接部署到云端服务器,并通过集成的CI/CD工具进行自动化构建和测试。这大大加快了开发和发布过程,提高了开发效率。 此外,Java EE开发的颠覆者在线看还支持容器化部署。开发者可以将应用程序通过容器技术(如Docker)打包,并部署到云平台上的容器集群中。这种方式可以提供更好的水平扩展性和高可用性,同时也简化了应用程序的部署和管理。 总的来说,Java EE开发的颠覆者在线看通过提供现代化的开发工具、自动化部署和持续集成、容器化部署等功能,使Java EE开发更加高效、便捷和可靠。这种技术将成为未来Java EE开发的趋势,为开发者带来更好的开发体验和效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值