java 并发编程学习笔记(五)之 不可变对象、同步容器、juc并发容器

网友投稿 595 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 map1 = Maps.newHashMap();

static {

map1.put(1, 2);

map1.put(3, 4);

map1.put(5, 6);

}

private static Map map2 = Maps.newHashMap();

private static List list2 = new ArrayList<>();

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 list3 = ImmutableList.of(11, 2, 3, 6);

private static final List list4 = new ArrayList();

private static final ImmutableMap unMap = ImmutableMap.of(1, 2, 3, 4);

private static final ImmutableMap unMap1 = ImmutableMap.builder().put(1, 6).build();

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 requestHolder = new 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 list = new ArrayList();

private static Vector vector =new Vector<>();

static {

vector.add(1);

vector.add(2);

vector.add(3);

}

private static List safeList = Collections.synchronizedList(new ArrayList());

private static Set set =new HashSet();

private static Set safeSet = Collections.synchronizedSet(new HashSet());

private static Map map = new HashMap();

private static Map safeMap = new Hashtable<>();

private static Map safeMap1 = Collections.synchronizedMap(new HashMap());

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) {

java 并发编程学习笔记(五)之 不可变对象、同步容器、juc并发容器

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 iterator = vector.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小时内删除侵权内容。

上一篇:全网疯传的Spring学习笔记【IOC、DI和Spring工厂】,看完我也想写一个了!
下一篇:从上云到深度用云,江苏财政全面提升资金管理质效
相关文章