Java的语言特点是什么(java语言的主要特点是什么)
612
2022-05-30
不可变对象、同步容器、juc并发容器
(1)不可变对象:
不可变对象需要满足的条件
对象创建以后其状态就不能修改
对象所有域都是final类型
对象时正确创建(在对象创建期间,this引用没有溢出)
final 关键字:类、方法、变量
修饰类:不能被继承
修饰方法:锁定方法不能被继承类修改 ,效率
修饰变量:基本数据类型变量,引用类型变量
@Slf4j
public class ImmutableExample1 {
private final static Integer a = 1;
private final static String b = "2";
private final static Map
static {
map1.put(1, 2);
map1.put(3, 4);
map1.put(5, 6);
}
private static Map
private static List
static {
map2.put(1, 2);
map2.put(3, 4);
map2.put(5, 6);
//通过Collections获取一个不可被修改的map
map2 = Collections.unmodifiableMap(map2);
list2.add(1);
list2.add(2);
//通过Collections获取一个不可被修改的list
list2 = Collections.unmodifiableList(list2);
}
//通过guava 也可以获取不可变list,set ,map
private static final ImmutableList
private static final List
private static final ImmutableMap
private static final ImmutableMap
static {
list4.add(1);
list4.add(2);
}
public static void main(String[] args) {
// a = 2 ;
// b ="3";
// map =Maps.newHashMap();
map1.put(1, 3);
log.info("{}", map1.get(1));
map2.put(1, 3);
list2.add(4);
log.info("{}", map2.get(1));
list3.add(56);
ImmutableSet list2 = ImmutableSet.copyOf(list4);
list2.add(7);
unMap.put(1, 6);
unMap1.put(1, 8);
}
}
(2) 线程 封闭
public class RequestHolder {
private final static ThreadLocal
public static void add(Long id) {
requestHolder.set(id);
}
public static Long getId() {
return requestHolder.get();
}
public static void remove() {
requestHolder.remove();
}
}
(3)常见的线程不安全的类
public class StringExample1 {
//请求次数
private static int clientTotal = 5000;
//允许同时运行的线程数
private static int threadTotal = 200;
/**
* stringBuilder 线程不安全
* stringBuffer 线程安全
*/
//public static StringBuilder stringBuilder=new StringBuilder();
public static StringBuffer stringBuffer = new StringBuffer();
/**
* simpleDateFormat 不是线程安全的
* joda-time 的dateTimeFormatter 是线程安全的
*/
public static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd");
public static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyy/MM/dd");
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
append();
semaphore.release();
} catch (InterruptedException e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.shutdown();
log.info("count:{}", stringBuffer.length());
}
private static void append() {
DateTime dateTime = DateTime.parse("2018/06/07", dateTimeFormatter);
log.info(dateTime.toDate().toString());
}
}
(4)同步容器
@Slf4j
public class ContainExample {
//请求次数
private static int clientTotal = 5000;
//允许同时运行的线程数
private static int threadTotal = 200;
private static List
private static Vector
static {
vector.add(1);
vector.add(2);
vector.add(3);
}
private static List
private static Set
private static Set
private static Map
private static Map
private static Map
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
final int index = i;
executorService.execute(() -> {
try {
semaphore.acquire();
putValue(index);
semaphore.release();
} catch (InterruptedException e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.shutdown();
log.info("count:{}", safeMap.size());
// testVector();
testVector1();
}
private static void putValue(int i) {
safeMap.put(i,i);
}
public static void testVector(){
//ExecutorService executorService = Executors.newCachedThreadPool();
//在这种情况下 线程的 vector 也会变得线程不安全
while(true) {
new Thread(() -> {
for (int i = 0, l = vector.size(); i < l; i++) {
vector.remove(i);
}
}).start();
new Thread(() -> {
for (int i = 0, l = vector.size(); i < l; i++) {
vector.get(i);
}
}).start();
}
}
public static void testVector1(){
try {
for (Integer i : vector) { //不推荐
if(i == 3){
vector.remove(i);
}
}
}catch (Exception e){
log.error("foreach循环删除时报错",e);
}
try {
Iterator
while(iterator.hasNext()){ //推荐
if(iterator.next() == 3){
iterator.remove();
}
}
}catch (Exception e){
log.error("iterator循环删除时报错",e);
}
try {
for (int i =0; i< vector.size();i++) { //推荐
if(i == 3){
vector.remove(i);
}
}
}catch (Exception e){
log.error("for循环删除时报错",e);
}
}
}
(5)并发容器
CopyOnWriteArrayList
CopyOnWriteArrayList是ArrayList的一个线程安全的变体,其中所有可变操作(add、set等等)都是通过对底层数组进行一次新的复制来实现的。与ArrayList不同处就在于是否会拷贝数组和加锁.
CopyOnWriteArrayList顾名思义就是写时复制的ArrayList,其意思就是在修改容器的元素时,并不是直接在原数组上修改,而是先拷贝了一份数组,然后在拷贝的数组上进行修改,修改完后将其引用赋值给原数组的引用。这样体现了读写分离,这样无论在任何时候我们都可以对容器进行读取。
所谓动态数组操作机制:即通过volatile修饰的Object类型数组来进行数组的CRUD操作。在进行add,set,remove等可变操作的时候,都会先新建一个数组把更新的值赋给该数组,然后再传递给上面的array数组来保持该次操作的可见性。这也是CopyOnWriteArrayList命名的由来。这一般需要很大的开销,但是当遍历操作的数量大大超过可变操作的数量时,即在进行读操作时的效率要远远高于写或是修改操作,这种方法可能比其他替代方法更 有效。
CopyOnWriteArraySet
它是线程安全的无序的集合,可以将它理解成线程安全的HashSet。有意思的是,CopyOnWriteArraySet和HashSet虽然都继承于共同的父类AbstractSet;但是,HashSet是通过“散列表(HashMap)”实现的,而CopyOnWriteArraySet则是通过“动态数组(CopyOnWriteArrayList)”实现的,并不是散列表。
和CopyOnWriteArrayList类似,CopyOnWriteArraySet具有以下特性:
1. 它最适合于具有以下特征的应用程序:Set 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突。
2. 它是线程安全的。
3. 因为通常需要复制整个基础数组,所以可变操作(add()、set() 和 remove() 等等)的开销很大。
4. 迭代器支持hasNext(), next()等不可变操作,但不支持可变 remove()等 操作。
5. 使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照。
ConcurrentSkipListSet
ConcurrentSkipListSet是线程安全的有序的集合,适用于高并发的场景。
ConcurrentSkipListSet和TreeSet,它们虽然都是有序的集合。但是,第一,它们的线程安全机制不同,TreeSet是非线程安全的,而ConcurrentSkipListSet是线程安全的。第二,ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的,而TreeSet是通过TreeMap实现的。
ConcurrentHashMap
ConcurrentHashMap是线程安全且高效的HashMap。正常业务场景中,我们会经常会用到HashMap,而在多线程环境下,Java.util.Hashmap进行put操作时会导致死循环,是因为多线程会导致HashMap的Entry链表形成环形数据结构,一旦形成环形数据结构,Entry的next节点永远不为空,就会死循环获取Entry。HashMap put时,发生死循环的原因是因为rehash时导致
而线程安全的HashTable 使用synchronized来保证线程安全,在线程锁竞争激烈的情况下 HashTable的效率非常低下。在Hashtable里,同一把锁连get都会使用synchronized来保证线程安全,Hashtable会竞争同一把锁,所以效率低下。若是能够变成多把锁,就能有效提升并发的效率。ConcurrentHashMap采用了锁分段技术,并且设计与实现非常精巧,大量的利用了volatile,final,CAS等lock-free技术来减少锁竞争对于性能的影响
ConcurrentSkipListMap
ConcurrentSkipListMap提供了一种线程安全的并发访问的排序映射表。内部是SkipList(跳表)结构实现,在理论上能够在O(log(n))时间内完成查找、插入、删除操作。
SkipList是一种红黑树的替代方案,由于SkipList与红黑树相比无论从理论和实现都简单许多,所以得到了很好的推广。SkipList是基于一种统计学原理实现的,有可能出现最坏情况,即查找和更新操作都是O(n)时间复杂度,但从统计学角度分析这种概率极小。
使用SkipList类型的数据结构更容易控制多线程对集合访问的处理,因为链表的局部处理性比较好,当多个线程对SkipList进行更新操作(指插入和删除)时,SkipList具有较好的局部性,每个单独的操作,对整体数据结构影响较小。而如果使用红黑树,很可能一个更新操作,将会波及整个树的结构,其局部性较差。因此使用SkipList更适合实现多个线程的并发处理。
在非多线程的情况下,应当尽量使用TreeMap。此外对于并发性相对较低的并行程序可以使用Collections.synchronizedSortedMap将TreeMap进行包装,也可以提供较好的效率。对于高并发程序,应当使用ConcurrentSkipListMap,能够提供更高的并发度。
所以在多线程程序中,如果需要对Map的键值进行排序时,请尽量使用ConcurrentSkipListMap,可能得到更好的并发度。
注意,调用ConcurrentSkipListMap的size时,由于多个线程可以同时对映射表进行操作,所以映射表需要遍历整个链表才能返回元素个数,这个操作是个O(log(n))的操作。
在JDK1.8中,ConcurrentHashMap的性能和存储空间要优于ConcurrentSkipListMap,但是ConcurrentSkipListMap有一个功能: 它会按照键的自然顺序进行排序。
总结:
线程限制 : 一个被线程限制的对象,由线程独占,并且只能被占有它的线程修改
共享只读:一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问,但是
任何线程都不能修改它
线程安全对象: 一个线程安全的对象或者容器,在内部通过同步机制保证线程安全,所以其他线程
无需额外的同步就可以通过公共接口随意访问它
被守护对象:被守护对象只能通过获取特定的锁来访问
Java 任务调度 容器 数据结构
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。