Java随笔(三)

与之前写的Java随笔一样,此篇博客主要是用来记录我之前存放在本地的Word文档中的一些Java的自身理解,由于水平有限所以可能只适合自己加深理解与记忆。


目录

 

Java中的类加载器

使用Callable+Future获取多线程执行结果

ConcurrentHashMap

BlockingQueue

使用LRU的LinkedHashMap

java中强软弱虚四种引用

强引用

软引用

弱引用

虚引用


Java中的类加载器

Java中,类的加载过程采用父亲委托机制,父亲委托机制,就是当一个Java程序请求加载器loader1加载Hello类时,loader1首先委托自己父亲加载器加载Hello类,如果父亲可以加载,就由父亲加载(这是一个向上递归的过程,会找父亲的父亲加载,实质就是先从最顶端看是否能加载,不能再向下看是否能加载,最后如果都不能加载抛出异常ClassNotFoundException),否则才由loader1加载。

JVM的ClassLoader采用的是树形结构,除了根类加载器以外,每个ClassLoader都会有且仅有一个父类加载器。

加载器间的父子关系实际指加载对象之间的包装关系,而不是继承关系。在子加载器对象中包装了一个父加载器对象.当生成一个自定义的类加载器实例时,如果没有指定它的父加载器,那么系统类加载器就将成为该类加载器的父加载器。如果在构造方法中指定父类加载器那么父类加载器就是指定的加载器

自定义类加载器,只需要扩展java.lang.ClassLoader,覆盖findClass(String name)方法即可,这是因为,类加载器的loadClass已经调用了findClass方法,如果父类加载器不能加载这个类(loadClass在findClass调用前,调用了父类加载器的loadClass方法),则会调用findClass方法,因此只要重写这个方法,就能实现自定义的类加载策略。

使用Callable+Future获取多线程执行结果

看如下代码:

public class Test {
	public static void main(String[] args) {
		ExecutorService executor = Executors.newCachedThreadPool();
		Task task = new Task();
		Future<Integer> result = executor.submit(task);
		executor.shutdown();
		try{
			Thread.sleep(1000);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		System.out.println("主线程在执行任务");
		try{
			System.out.println("task运行结果"+result.get());
		}catch(Exception e){
			e.printStackTrace();
		}
		System.out.println("所有任务执行完毕");
	}
}
class Task implements Callable<Integer>{
	@Override
	public Integer call() throws Exception {

		Thread.sleep(3000);
		int sum = 0;
		for(int i = 0 ; i < 100 ; i++)
			sum+=i;
		System.out.println("子线程正在计算");
		return sum;
	}
}

声明一个类Task实现Callable接口,这里面就是子线程要执行的程序。然后得到ExecutorService线程池,之后初始化Task对象,Future<Integer>  result来接收调用executor.submit(task)的返回值,使用result.get()得到子线程运行得到的结果。

注意result.get()会发生阻塞,等待 返回值之后才会进行当前线程后面的操作(不影响其他子线程的运行),因此result.get()应该是在马上需要返回值的时候调用,这样可以将执行时间缩短!

或者使用Callable+FutureTask,区别在于 executor.submit() 里面不再是传递task,而是声明FutureTask对象

FutureTask<Integer> futureTask = new FutureTask<Integer>(task);

然后 executor.submit(futureTask),futureTask.get()得到的就是子线程运行得到的结果。

ConcurrentHashMap

ConcurrentHashMap是为了解决多线程环境下,HashMap无法保证数据同步,Hashtable的效率又比较低的问题而设计的。它用分段锁的设计,只有处于同一个分段才存在竞态关系,不同的分段锁没有锁竞争。这样保证了 能够同步使用,并且由于是分段加锁,所以比直接整个map加锁 在高并发环境下的处理更轻。(Hashtable解决同步的方式是将很多方法都设置为了synchronized,而Collections.SynchronizedMap 也仅仅是粗略的在方法中自身对象使用synchronized修饰,因此一般情况效率都比不上使用分段锁的ConcurrentHashMap)ConcurrentHashMap保证了单独的方法调用是原子性的,但是如果多线程调用ConcurrentHashMap的多个方法,那么可能还是要用Synchronized来修饰ConcurrentHashMap。

BlockingQueue

放入数据的方法:

offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.(本方法不阻塞当前执行方法的线程)

offer(E o, long timeout, TimeUnit unit),可以设定等待的时间,如果在指定的时间内,还不能往队列中加入BlockingQueue,则返回失败。

put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.

获取数据的方法:

poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null

poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则知道时间超时还没有数据可取,返回失败。

take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入; 

drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数), 通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。

ArrayBlockingQueue和LinkedBlockingQueue是两个最普通也是最常用的阻塞队列

ArrayBlockingQueue中,使用了ReentrantLock,并且在take(),enqueue()方法中使用了条件变量。关于这部分,可以从我之前写的Java随笔中找到部分源码与个人理解。

使用LRU的LinkedHashMap

LRU就是最近最少使用。

在声明LinkedHashMap的时候 使用如下方式声明

LinkedHashMap<String,String>  lHashMap= new LinkedHashMap<String,String>(16,0.75,true);

也就是 创建一个初始容量16  负载因子0.75,使用最近最少算法的LinkedHashMap。使用如下代码测试:

        lHashMap.put("1111", "1111");
        lHashMap.put("2222", "2222");
        lHashMap.put("3333", "3333");
        lHashMap.put("4444", "4444");
        lHashMap.put("5555", "5555");
        Set<String> keySet = lHashMap.keySet();
        for(String key : keySet) {
        		System.out.print(key+" ");
        }
        System.out.println();
        lHashMap.get("3333");
        lHashMap.get("2222");
        keySet = lHashMap.keySet();
        for(String key : keySet) {
        		System.out.print(key+" ");
        }

我们获得输出如下:

1111 2222 3333 4444 5555

1111 4444 5555 3333 2222

也就是当我们用 使用了LRU的LinkedHashMap的时候 每次访问到的元素会放到 后面,因此当我们访问 3333 和 2222之后就会 先把3333放到最后面 然后又把2222放到最后面。

这样我们就能在做缓存的时候,为了防止内存溢出,使用LinkedHashMap,让LinkedHashMap的长度到达一定程度的时候 删除靠前面的元素(因为元素靠前面说明 最近没访问到)。但是这样要我们自己做操作,我们直接重写removeEldestEntry 方法 说明在什么情况下我们要删除元素(从前面开始删除)

LinkedHashMap<String,String> lHashMap = new LinkedHashMap(5,0.75f,true) {
        	@Override
        	protected boolean removeEldestEntry(java.util.Map.Entry eldest) {
        		// TODO Auto-generated method stub
        		return size()>4;
        	}
        };

这样如果我们一直put元素  会始终保持最多4个元素。

        lHashMap.put("1111", "1111");
        lHashMap.put("2222", "2222");
        lHashMap.put("3333", "3333");
        lHashMap.put("4444", "4444");
        lHashMap.put("5555", "5555");
        Set<String> keySet = lHashMap.keySet();
        for(String key : keySet) {
        		System.out.print(key+" ");
        }
        System.out.println();
        lHashMap.get("3333");
        lHashMap.get("2222");
        lHashMap.put("6666","6666");
        keySet = lHashMap.keySet();
        for(String key : keySet) {
        		System.out.print(key+" ");
        }

输出

2222 3333 4444 5555

5555 3333 2222 6666

上述代码 说明确实当LinkedHashMap满足删除条件 的时候,会从头开始删除元素。

java中强软弱虚四种引用

在Java语言中,除了基本数据类型外,其他的都是指向各类对象的对象引用;Java中根据其生命周期的长短,将引用分为4类。

强引用

我们平常典型编码Object obj = new Object()中的obj就是强引用。通过关键字new创建的对象所关联的引用就是强引用。 当JVM内存空间不足,JVM宁愿抛出OutOfMemoryError运行时错误(OOM),使程序异常终止,也不会靠随意回收具有强引用的“存活”对象来解决内存不足的问题。对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,就是可以被垃圾收集的了,具体回收时机还是要看垃圾收集策略。

软引用

软引用通过SoftReference类实现。 软引用的生命周期比强引用短一些。只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象:即JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。后续,我们可以调用ReferenceQueue的poll()方法来检查是否有它所关心的对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。

软引用通常用来实现内存敏感的缓存。如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。

弱引用

弱引用通过WeakReference类实现。 弱引用的生命周期比软引用短。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快回收弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。弱应用同样可用于内存敏感的缓存。

虚引用

虚引用也叫幻象引用,通过PhantomReference类来实现。无法通过虚引用访问对象的任何属性或函数。幻象引用仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

​​​ReferenceQueue queue = new ReferenceQueue ();

PhantomReference pr = new PhantomReference (object, queue);

程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取一些程序行动。可用来跟踪对象被垃圾回收器回收的活动,当一个虚引用关联的对象被垃圾收集器回收之前会收到一条系统通知。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值