标签 Java 下的文章 - 第 35 页 - 酷游博客
首页
关于
友链
Search
1
阿里的简历多久可以投递一次?次数多了有没有影响?可以同时进行吗?
45 阅读
2
Java中泛型的理解
40 阅读
3
Java 14 发布了,再也不怕 NullPointerException 了!
38 阅读
4
Java中的可变参数
37 阅读
5
该如何创建字符串,使用" "还是构造函数?
30 阅读
技术
登录
/
注册
找到
191
篇与
Java
相关的结果
- 第 35 页
2025-01-22
Java中常见亲属比较
写在前面:常见亲属指的是java中几组有密切关系的几个类,比如linkedlist和arraylist,其实很多区别我以前都知道,当然有些是为了面试特意背下来的,但是最近在做代码review的时候才发现并没有真正把这些理论知识运用到代码中(惭愧啊)!有很多时候出于习惯直接就选择了,比如我习惯使用stringBuffer而不是stringBuilder,但是这种习惯并不是好习惯。所以,今天把Java中常见的几对“亲属”拎出来,巩固一下区别,并运用到代码中,把理论运用到实践中。 这里直介绍主要区别,也就是在我们需要进行选择的时候需要考量的点。其他的区别读者请自行google。(如vector和arraylist在缺省情况下自动增长长度不一样等就不做详细介绍了) 一、StringBuffer、StringBuilder、String 1) 都是 final 类, 都不允许被继承; 2) String 长度是不可变的, StringBuffer、StringBuilder 长度是可变的; 3) StringBuffer 是线程安全的, StringBuilder 不是线程安全的。 4) 在效率上,StringBuilder比StringBuffer更快 如果字符串不需要改变就使用String,如果字符串需要改变就使用StringBuffer或者StringBuilder,在StringBuffer和StringBuilder之间做选择时,如果涉及到多线程就使用StringBuffer,如果不涉及到多线程就使用StringBuilder(因为他更快)。 二、Integer.valueOf()和new Integer() 1) new Integer()方法会在内存中新开辟一个内存空间; 2)valueOf()方法只有在传入的参数小于-128或大于127时,才会去调用一个new Integer()方法去创建一个新的对象,否则会使用静态类IntegerCache中的cache里的对象。 可以通过启动参数-XX:AutoBoxCacheMax修改掉valueOf()缓存数字最大范围,但最大值也不会超过:Integer.MAX_VALUE +128 PS:int和Integer的主要区别:一个是基本数据类型,一个是对象(不进行初始化的情况下,int的默认值是0,而Integer的初始值是null,Integer是int的包装类) 三、Vector、LinkedList、ArrayList 1) Vector、ArrayList是使用数组实现的,LinkedList是使用链表实现的 2)Vector是线程安全的,LinkedList、ArrayList不是线程安全的 如果涉及到多线程,那么就选择Vector,如果不涉及到多线程就从LinkedList、ArrayList中选。 LinkedList更适合从中间插入或者删除(链表的特性)。 ArrayList更适合检索和在末尾插入或删除(数组的特性)。 PS: Collections.synchronizedList(List list)方法也可以用来返回一个线程安全的List。参见SynchronizedList和Vector的区别 四、HashMap、HashTable、ConcurentHashMap 1) HashMap和HashTable都实现了Map接口,ConcurrentHashMap实现了ConcurrentMap接口 2) HashMap 和 ConcurrentHashMap 都继承了AbstractMap类,HashTable继承了Dictionary类 3)HashTable和ConcurrentHashMap是线程安全的,HashMap不是线程安全的。 4) 当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。 5) ConcurrentHashMap使用锁分段技术,将数据分成一段一段的存储,给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。 如果不涉及到多线程处理的情况,就是用hashMap,因为他的效率比较高。在有并发请求的场景中,如果数据的强一致性比较重要,那么就请使用hashTable,因为ConcurrentHashMap的get,clear,iterator 都是弱一致性的。如果效率要求比较高,那么就使用ConcurrentHashMap,因为他不会像hashTable那样产生阻塞。 暂时想到这么多,如果觉得写的还可以,可以收藏。我会逐渐完善其中的内容。同时也欢迎补充和纠错!~
技术
# Java
酷游
1月22日
0
20
0
2025-01-22
全网17万浏览量的PDF免费下载!《〈Java开发手册〉灵魂13问》
一线大厂怎么用Java? 看千万阅读量技术博主给你分析! 《〈Java开发手册(泰山版)〉灵魂13问》电子书正式上线 带你剖析阿里巴巴一线团队开发思维  大家都知道2020年04月22日刚刚发布了《Java开发手册》泰山版,新增5条日期时间规约;新增2条表别名sql规约;新增统一错误码规约。 而《〈Java开发手册(泰山版)〉灵魂13问》则是为了帮助大家更好的理解这些规约背后的原理,从问题重现到原理分析再到解决问题,全网千万阅读量技术博主带你剖析阿里巴巴开发细节。 作者从开发者的角度结合自身所遇到的坑点,根据规约内容解读背后的思考,让新手对规约不仅知其然更能知其所以然~  这本电子书首发在阿里云开发者社区上,目前已经有17万浏览量,这里直接给出下载方式,欢迎大家下载阅读~! PC端下载链接:https://developer.aliyun.com/topic/download?id=80
技术
# Java
酷游
1月22日
0
21
0
2025-01-22
面试官问我同步容器(如Vector)的所有操作一定是线程安全的吗?我懵了!
为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器、并发容器、阻塞队列等。 最常见的同步容器就是Vector和Hashtable了,那么,同步容器的所有操作都是线程安全的吗? 这个问题不知道你有没有想过,本文就来深入分析一下这个问题,一个很容易被忽略的问题。 Java中的同步容器 在Java中,同步容器主要包括2类: 1、Vector、Stack、HashTable 2、Collections类中提供的静态工厂方法创建的类 本文拿相对简单的Vecotr来举例,我们先来看下Vector中几个重要方法的源码: public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; } public synchronized E remove(int index) { modCount++; if (index >= elementCount) throw new ArrayIndexOutOfBoundsException(index); E oldValue = elementData(index); int numMoved = elementCount - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--elementCount] = null; // Let gc do its work return oldValue; } public synchronized E get(int index) { if (index >= elementCount) throw new ArrayIndexOutOfBoundsException(index); return elementData(index); } 可以看到,Vector这样的同步容器的所有公有方法全都是synchronized的,也就是说,我们可以在多线程场景中放心的使用单独这些方法,因为这些方法本身的确是线程安全的。 但是,请注意上面这句话中,有一个比较关键的词:单独 因为,虽然同步容器的所有方法都加了锁,但是对这些容器的复合操作无法保证其线程安全性。需要客户端通过主动加锁来保证。 简单举一个例子,我们定义如下删除Vector中最后一个元素方法: public Object deleteLast(Vector v){ int lastIndex = v.size()-1; v.remove(lastIndex); } 上面这个方法是一个复合方法,包括size()和remove(),乍一看上去好像并没有什么问题,无论是size()方法还是remove()方法都是线程安全的,那么整个deleteLast方法应该也是线程安全的。 但是时,如果多线程调用该方法的过程中,remove方法有可能抛出ArrayIndexOutOfBoundsException。 Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 879 at java.util.Vector.remove(Vector.java:834) at com.hollis.Test.deleteLast(EncodeTest.java:40) at com.hollis.Test$2.run(EncodeTest.java:28) at java.lang.Thread.run(Thread.java:748) 我们上面贴了remove的源码,我们可以分析得出:当index >= elementCount时,会抛出ArrayIndexOutOfBoundsException ,也就是说,当当前索引值不再有效的时候,将会抛出这个异常。 因为removeLast方法,有可能被多个线程同时执行,当线程2通过index()获得索引值为10,在尝试通过remove()删除该索引位置的元素之前,线程1把该索引位置的值删除掉了,这时线程一在执行时便会抛出异常。  为了避免出现类似问题,可以尝试加锁: public void deleteLast() { synchronized (v) { int index = v.size() - 1; v.remove(index); } } 如上,我们在deleteLast中,对v进行加锁,即可保证同一时刻,不会有其他线程删除掉v中的元素。 另外,如果以下代码会被多线程执行时,也要特别注意: for (int i = 0; i < v.size(); i++) { v.remove(i); } 由于,不同线程在同一时间操作同一个Vector,其中包括删除操作,那么就同样有可能发生线程安全问题。所以,在使用同步容器的时候,如果涉及到多个线程同时执行删除操作,就要考虑下是否需要加锁。 同步容器的问题 前面说过了,同步容器直接保证耽搁操作的线程安全性,但是无法保证复合操作的线程安全,遇到这种情况时,必须要通过主动加锁的方式来实现。 而且,除此之外,同步容易由于对其所有方法都加了锁,这就导致多个线程访问同一个容器的时候,只能进行顺序访问,即使是不同的操作,也要排队,如get和add要排队执行。这就大大的降低了容器的并发能力。 并发容器 针对前文提到的同步容器存在的并发度低问题,从Java5开始,java.util.concurent包下,提供了大量支持高效并发的访问的集合类,我们称之为并发容器。  针对前文提到的同步容器的复合操作的问题,一般在Map中发生的比较多,所以在ConcurrentHashMap中增加了对常用复合操作的支持,比如”若没有则添加”:putIfAbsent(),替换:replace()。这2个操作都是原子操作,可以保证线程安全。 另外,并发包中的CopyOnWriteArrayList和CopyOnWriteArraySet是Copy-On-Write的两种实现。 Copy-On-Write容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。 CopyOnWriteArrayList中add/remove等写方法是需要加锁的,而读方法是没有加锁的。 这样做的好处是我们可以对CopyOnWrite容器进行并发的读,当然,这里读到的数据可能不是最新的。因为写时复制的思想是通过延时更新的策略来实现数据的最终一致性的,并非强一致性。 但是,作为代替Vector的CopyOnWriteArrayList并没有解决同步容器的复合操作的线程安全性问题。 总结 本文介绍了同步容器和并发容器。 同步容器是通过加锁实现线程安全的,并且只能保证单独的操作是线程安全的,无法保证复合操作的线程安全性。并且同步容器的读和写操作之间会互相阻塞。 并发容器是Java 5中提供的,主要用来代替同步容器。有更好的并发能力。而且其中的ConcurrentHashMap定义了线程安全的复合操作。 在多线程场景中,如果使用并发容器,一定要注意复合操作的线程安全问题。必要时候要主动加锁。 在并发场景中,建议直接使用java.util.concurent包中提供的容器类,如果需要复合操作时,建议使用有些容器自身提供的复合方法。
技术
# Java
酷游
1月22日
0
21
0
2025-01-22
不同时区的时间问题
public static void main(String[] args) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); Calendar calendar = Calendar.getInstance(); sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles")); System.out.println("Los_Angeles :"+sdf.format(calendar.getTime())); sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); System.out.println("Shanghai: "+sdf.format(calendar.getTime())); }
技术
# Java
酷游
1月22日
0
13
0
2025-01-22
为什么我墙裂建议大家使用枚举来实现单例。
关于单例模式,我的博客中有很多文章介绍过。作为23种设计模式中最为常用的设计模式,单例模式并没有想象的那么简单。因为在设计单例的时候要考虑很多问题,比如线程安全问题、序列化对单例的破坏等。 单例相关文章一览: 设计模式(二)——单例模式设计模式(三)——JDK中的那些单例单例模式的七种写法单例与序列化的那些事儿不使用synchronized和lock,如何实现一个线程安全的单例?不使用synchronized和lock,如何实现一个线程安全的单例?(二) 如果你对单例不是很了解,或者对于单例的线程安全问题以及序列化会破坏单例等问题不是很清楚,可以先阅读以上文章。上面六篇文章看完之后,相信你一定会对单例模式有更多,更深入的理解。 我们知道,单例模式,一般有七种写法,那么这七种写法中,最好的是哪一种呢?为什么呢?本文就来抽丝剥茧一下。 哪种写单例的方式最好 在StakcOverflow中,有一个关于What is an efficient way to implement a singleton pattern in Java?的讨论: 如上图,得票率最高的回答是:使用枚举。 回答者引用了Joshua Bloch大神在《Effective Java》中明确表达过的观点: 使用枚举实现单例的方法虽然还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。 如果你真的深入理解了单例的用法以及一些可能存在的坑的话,那么你也许也能得到相同的结论,那就是:使用枚举实现单例是一种很好的方法。 枚举单例写法简单 如果你看过《单例模式的七种写法》中的实现单例的所有方式的代码,那就会发现,各种方式实现单例的代码都比较复杂。主要原因是在考虑线程安全问题。 我们简单对比下“双重校验锁”方式和枚举方式实现单例的代码。 “双重校验锁”实现单例: public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } } 枚举实现单例: public enum Singleton { INSTANCE; public void whateverMethod() { } } 相比之下,你就会发现,枚举实现单例的代码会精简很多。 上面的双重锁校验的代码之所以很臃肿,是因为大部分代码都是在保证线程安全。为了在保证线程安全和锁粒度之间做权衡,代码难免会写的复杂些。但是,这段代码还是有问题的,因为他无法解决反序列化会破坏单例的问题。 枚举可解决线程安全问题 上面提到过。使用非枚举的方式实现单例,都要自己来保证线程安全,所以,这就导致其他方法必然是比较臃肿的。那么,为什么使用枚举就不需要解决线程安全问题呢? 其实,并不是使用枚举就不需要保证线程安全,只不过线程安全的保证不需要我们关心而已。也就是说,其实在“底层”还是做了线程安全方面的保证的。 那么,“底层”到底指的是什么? 这就要说到关于枚举的实现了。这部分内容可以参考我的另外一篇博文深度分析Java的枚举类型—-枚举的线程安全性及序列化问题,这里我简单说明一下: 定义枚举时使用enum和class一样,是Java中的一个关键字。就像class对应用一个Class类一样,enum也对应有一个Enum类。 通过将定义好的枚举反编译,我们就能发现,其实枚举在经过javac的编译之后,会被转换成形如public final class T extends Enum的定义。 而且,枚举中的各个枚举项同事通过static来定义的。如: public enum T { SPRING,SUMMER,AUTUMN,WINTER; } 反编译后代码为: public final class T extends Enum { //省略部分内容 public static final T SPRING; public static final T SUMMER; public static final T AUTUMN; public static final T WINTER; private static final T ENUM$VALUES[]; static { SPRING = new T("SPRING", 0); SUMMER = new T("SUMMER", 1); AUTUMN = new T("AUTUMN", 2); WINTER = new T("WINTER", 3); ENUM$VALUES = (new T[] { SPRING, SUMMER, AUTUMN, WINTER }); } } 了解JVM的类加载机制的朋友应该对这部分比较清楚。static类型的属性会在类被加载之后被初始化,我们在深度分析Java的ClassLoader机制(源码级别)和Java类的加载、链接和初始化两个文章中分别介绍过,当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的(因为虚拟机在加载枚举的类的时候,会使用ClassLoader的loadClass方法,而这个方法使用同步代码块保证了线程安全)。所以,创建一个enum类型是线程安全的。 也就是说,我们定义的一个枚举,在第一次被真正用到的时候,会被虚拟机加载并初始化,而这个初始化过程是线程安全的。而我们知道,解决单例的并发问题,主要解决的就是初始化过程中的线程安全问题。 所以,由于枚举的以上特性,枚举实现的单例是天生线程安全的。 枚举可解决反序列化会破坏单例的问题 前面我们提到过,就是使用双重校验锁实现的单例其实是存在一定问题的,就是这种单例有可能被序列化锁破坏,关于这种破坏及解决办法,参看单例与序列化的那些事儿,这里不做更加详细的说明了。 那么,对于序列化这件事情,为什么枚举又有先天的优势了呢?答案可以在Java Object Serialization Specification 中找到答案。其中专门对枚举的序列化做了如下规定: 大概意思就是:在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。 普通的Java类的反序列化过程中,会通过反射调用类的默认构造函数来初始化对象。所以,即使单例中构造函数是私有的,也会被反射给破坏掉。由于反序列化后的对象是重新new出来的,所以这就破坏了单例。 但是,枚举的反序列化并不是通过反射实现的。所以,也就不会发生由于反序列化导致的单例破坏问题。这部分内容在深度分析Java的枚举类型—-枚举的线程安全性及序列化问题中也有更加详细的介绍,还展示了部分代码,感兴趣的朋友可以前往阅读。 总结 在所有的单例实现方式中,枚举是一种在代码写法上最简单的方式,之所以代码十分简洁,是因为Java给我们提供了enum关键字,我们便可以很方便的声明一个枚举类型,而不需要关心其初始化过程中的线程安全问题,因为枚举类在被虚拟机加载的时候会保证线程安全的被初始化。 除此之外,在序列化方面,Java中有明确规定,枚举的序列化和反序列化是有特殊定制的。这就可以避免反序列化过程中由于反射而导致的单例被破坏问题。
技术
# Java
酷游
1月22日
0
3
0
上一页
1
...
34
35
36
...
39
下一页
易航博客