java基础之JUC中的list,set,map的安全类

前面一篇我们讲到了lock使用,但是有没有发现其引入发包位置:java.util.concurrent。

为什么要单独说这个包,因为这个包下的一些方法我们简称为juc,这是一个工具包,而且最重要的是处理多线程的一个工具包,可能会有疑问,和多线程有什么关系?

所以这次我们单独弄一篇来浅讲一下juc中的常用集合类以及其方法。

list

对于这个理解,我们首先要举例子,作为一个开始。那就是我们常用的list ,大家应该在学的

public class JucTest{
    
    
	public static void main(String[] args) {
    
    
		List<String> list=new ArrayList<String>();
	
			for(int t=0;t<5;t++) {
    
    
		new Thread(()->{
    
    
			for (int i = 0; i < 10; i++) {
    
    
		
				list.add(UUID.randomUUID().toString().substring(0, 5));

				System.out.println(list);

			}
		}).start(); 
        }

	}
}
//输出
[66572, cd7e8]
[66572, cd7e8]
[66572, 98f34, cd7e8]
[66572, 98f34, cd7e8, feb54, 14f89]
[66572, 98f34, cd7e8, 14f89]
[64031, 66572, 46555, 98f34, cd7e8, feb54, 14f89]
[64031, 66572, 46555, 98f34, cd7e8, 76f56, feb54, 14f89]
[64031, 66572, 46555, 98f34, cd7e8, 76f56, c1b41, feb54, 14f89]
[64031, 66572, 46555, 98f34, cd7e8, 76f56, ac054, c1b41, feb54, 14f89]
[64031, 66572, 46555, 98f34, 4845f, cd7e8, 76f56, ac054, c1b41, feb54, 14f89]
[64031, 66572, 46555, 98f34, 4845f, cd7e8, e6144, 76f56, ac054, c1b41, feb54, 14f89]
[64031, 66572, 46555, cd7e8, e6144, 76f56, ac054, 691b3, feb54, 14f89, 98f34, 4845f, c1b41]
[64031, 66572, 46555, cd7e8, e6144, 76f56, ac054, 691b3, feb54, 14f89, 98f34, 4845f, c1b41, 0652e]
Exception in thread "Thread-0" java.util.ConcurrentModificationException

我们用两个线程想list 中加入数据,最后报错ConcurrentModificationException ,这个是一个是并发异常,其实前面我也可以理解,就是数据在没有加锁,而发生的异常,但是具体是不是我们可以通过下面代码测试。

public class JucTest{
    
    
	public static void main(String[] args) {
    
    
		List<String> list=new ArrayList<String>();
//		System.out.println(UUID.randomUUID());
		//		
	

		for(int t=0;t<5;t++) {
    
    
		new Thread(()->{
    
    
			for (int i = 0; i < 10; i++) {
    
    
			synchronized (list) {
    
    
				list.add(UUID.randomUUID().toString().substring(0, 5));

				System.out.println(list);
			}

			}
		}).start(); 
        }

	}
}

如果加入同步代码块,最后没有报错,可以完整的运行完程序。

现在我们可以再看一下list中的add方法。

 /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
    
    
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

可见list 中的add没有加锁,所以才会报错并发错误。当然我们能想到,难道java不会发现此事?

Vector,这个是一个也是一个集合,可以看其源码

  /**
     * Appends the specified element to the end of this Vector.
     *
     * @param e element to be appended to this Vector
     * @return {@code true} (as specified by {@link Collection#add})
     * @since 1.2
     */
    public synchronized boolean add(E e) {
    
    
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

可见 vector中的add方法是一个synchronized方法,所以是一个同步的方法,数据是安全的。

所以我们使用vector试一下:

扫描二维码关注公众号,回复: 13231262 查看本文章
public class JucTest{
    
    
	public static void main(String[] args) {
    
    
		Vector<String> list=new Vector<String>();
	for(int t=0;t<5;t++) {
    
    
		new Thread(()->{
    
    
			for (int i = 0; i < 10; i++) {
    
    

				list.add(UUID.randomUUID().toString().substring(0, 5));

				System.out.println(list);


			}
		}).start(); 
        }

	}
}

输出的结果没有报并行错误,可见是可以的。这是一种解决方式,不过vector再jdk1.0版本就出现了,而list是在1.2版本,当然不是我随口说的,看源码注解即可得知。

当然还有其他的方式可以解决这个。

public class JucTest{
    
    
	public static void main(String[] args) {
    
    
		List<String> list= java.util.Collections.synchronizedList(new ArrayList<String>());
	
	for(int t=0;t<5;t++) {
    
    
		new Thread(()->{
    
    
			for (int i = 0; i < 10; i++) {
    
    

				list.add(UUID.randomUUID().toString().substring(0, 5));

				System.out.println(list);


			}
		}).start(); 
        }

	}
}

//输出也是没有报出异常,当然其中的add方法也可以看出是synchronied同步方法。可以下面源码。

  /**
     * @serial include
     */
    static class SynchronizedList<E>
        extends SynchronizedCollection<E>
        implements List<E> {
    
    
        private static final long serialVersionUID = -7754090372962971524L;

        final List<E> list;

        SynchronizedList(List<E> list) {
    
    
            super(list);
            this.list = list;
        }
        SynchronizedList(List<E> list, Object mutex) {
    
    
            super(list, mutex);
            this.list = list;
        }

        public boolean equals(Object o) {
    
    
            if (this == o)
                return true;
            synchronized (mutex) {
    
    return list.equals(o);}
        }
        public int hashCode() {
    
    
            synchronized (mutex) {
    
    return list.hashCode();}
        }

        public E get(int index) {
    
    
            synchronized (mutex) {
    
    return list.get(index);}
        }
        public E set(int index, E element) {
    
    
            synchronized (mutex) {
    
    return list.set(index, element);}
        }
        public void add(int index, E element) {
    
    
            synchronized (mutex) {
    
    list.add(index, element);}
        }
        public E remove(int index) {
    
    
            synchronized (mutex) {
    
    return list.remove(index);}
        }

        public int indexOf(Object o) {
    
    
            synchronized (mutex) {
    
    return list.indexOf(o);}
        }
        public int lastIndexOf(Object o) {
    
    
            synchronized (mutex) {
    
    return list.lastIndexOf(o);}
        }

        public boolean addAll(int index, Collection<? extends E> c) {
    
    
            synchronized (mutex) {
    
    return list.addAll(index, c);}
        }

        public ListIterator<E> listIterator() {
    
    
            return list.listIterator(); // Must be manually synched by user
        }

        public ListIterator<E> listIterator(int index) {
    
    
            return list.listIterator(index); // Must be manually synched by user
        }

        public List<E> subList(int fromIndex, int toIndex) {
    
    
            synchronized (mutex) {
    
    
                return new SynchronizedList<>(list.subList(fromIndex, toIndex),
                                            mutex);
            }
        }

        @Override
        public void replaceAll(UnaryOperator<E> operator) {
    
    
            synchronized (mutex) {
    
    list.replaceAll(operator);}
        }
        @Override
        public void sort(Comparator<? super E> c) {
    
    
            synchronized (mutex) {
    
    list.sort(c);}
        }

前面我们讲了两种安全的方式一个synchronied,另一个就是lock。那么就没有lock加锁的类似与list的类吗?

既然我们讲juc,所以我们直接看其下面有什么工具类。其中我们找到了CopyOnWriteArrayList。

在这里插入图片描述

public class JucTest{
    
    
	public static void main(String[] args) {
    
    
		List<String> list=new CopyOnWriteArrayList<String>();		
	 for(int t=0;t<5;t++) {
    
    
		new Thread(()->{
    
    
			for (int i = 0; i < 10; i++) {
    
    

				list.add(UUID.randomUUID().toString().substring(0, 5));

				System.out.println(list);


			}
		}).start(); 
        }

	}
}

使用CopyOnWriteArrayList,程序可以运行完毕同时也没有报出异常。那样我们就可以看起add方法是如何保证数据安全。

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
    
    
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
    
    
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
    
    
            lock.unlock();
        }
    }

通过看起底层代码我没有可以看出CopyOnWriteArrayList是通过lock保证数据安全的。看其源码提示其开始与jdk1.5版本,那么就有一个疑问了,既然前面已经解决后面还多一个lock版本的list有何用?

**注意:因为同样保证数据安全,lock比synchronied更快。 **

通过源码我们可以看出CopyOnWriteArrayList,就如同其名一样,其中在Object[] newElements = Arrays.copyOf(elements, len + 1);复制一个新数组,然后再将其添加在原来的list中。因为有锁,所以不会无限复制,不让内存就会溢出了。

所以相对于list来说CopyOnWriteArrayList必然尤优缺点。

  • CopyWriteArrayList比list安全。
  • CopyOnWriteArrayList因为是先复制一个副本进行添加,所以其自然要比List更加占用内存,同时CopyOnWriteArrayList在读取的时候不会有锁,所以也就会造成一个就是读取的时候,如果正在插入那么读取的还是在插入或删除之前的数据。也就是CopyOnWriteArrayList保证数据的最终的一致性,但是不会保证实时一致性。也就是常说的读写分离。

所以CopyOnWriteArrayList适合的是读多写少的场景,比如黑白名单等。

set

set,也是常用的一种集合。所以还是老规矩看代码


public class JucTest{
    
    
	public static void main(String[] args) {
    
    
		Set<String> set =new HashSet<String>();
			for(int t=0;t<5;t++) {
    
    
		new Thread(()->{
    
    
			for (int i = 0; i < 10; i++) {
    
    

				set.add(UUID.randomUUID().toString().substring(0, 5));

				System.out.println(set);


			}
		}).start(); 
        }

	}
}
//输出
[c9690, 7e0a9]
[c9690, 7e0a9]
[e4b62, c9690, 7e0a9]
[e4b62, c9690, eba98, 7e0a9]
[f736f, e4b62, c9690, eba98, 7e0a9]
[f736f, e4b62, c9690, eba98, 29af1, 7e0a9]
[f736f, e4b62, c9690, 87ab4, eba98, 29af1, 7e0a9]
[8708e, f736f, e4b62, c9690, 87ab4, eba98, 29af1, 7e0a9]
[8708e, f736f, f71f7, e4b62, c9690, 87ab4, eba98, 29af1, 7e0a9]
[8708e, f736f, f71f7, e4b62, 0b08b, c9690, 87ab4, eba98, 29af1, 7e0a9]
[8708e, f736f, f71f7, e4b62, 0b08b, c9690, 87ab4, 5a3ad, eba98, 29af1, 7e0a9]
[8708e, f736f, f71f7, e4b62, 69273, 0b08b, c9690, 87ab4, 5a3ad, eba98, 29af1, 7e0a9]
[94248, f736f, f71f7, e4b62, 7e0a9, 8708e, 69273, 0b08b, c9690, 87ab4, 5a3ad, eba98, 29af1]
[94248, f736f, f71f7, e4b62, 71ad0, 7e0a9, 8708e, 69273, 0b08b, c9690, 221b3, 87ab4, 5a3ad, eba98, 29af1]
[94248, f736f, f71f7, e4b62, b2d68, 71ad0, 7e0a9, 8708e, 69273, 0b08b, c9690, 221b3, 87ab4, 5a3ad, eba98, 29af1]
[94248, 2c0b0, f736f, f71f7, e4b62, b2d68, 71ad0, 7e0a9, 8708e, 69273, 0b08b, c9690, 221b3, 87ab4, 5a3ad, eba98, 29af1]
[94248, 2c0b0, f736f, f71f7, e4b62, b2d68, 71ad0, 7e0a9, f23e9, 8708e, 69273, 0b08b, c9690, 221b3, 87ab4, 5a3ad, eba98, 29af1]
Exception in thread "Thread-1" java.util.ConcurrentModificationException
	at java.util.HashMap$HashIterator.nextNode(Unknown Source)
	at java.util.HashMap$KeyIterator.next(Unknown Source)
	at java.util.AbstractCollection.toString(Unknown Source)
	at java.lang.String.valueOf(Unknown Source)
	at java.io.PrintStream.println(Unknown Source)
	at test.JucTest.lambda$1(JucTest.java:45)
	at java.lang.Thread.run(Unknown Source)

也会报错同步异常(有时候须多运行几次才会报异常或再加几个并发线程),和list一样的异常。我们自己加入同步代码块也就不会报错了,所以不再写。

set 没有vector这样的类,所以直接使用Collections中的synchronizedSet

public class JucTest{
    
    
	public static void main(String[] args) {
    
    
		Set<String> set=Collections.synchronizedSet(new HashSet<String>()); 
		
		for(int t=0;t<5;t++) {
    
    
		new Thread(()->{
    
    
			for (int i = 0; i < 10; i++) {
    
    

				set.add(UUID.randomUUID().toString().substring(0, 5));

				System.out.println(set);


			}
		}).start(); 
        }


	}

}

synchronizedSet和synchronizedList差不多也是使用了同步方法保证数据的安全。

juc中有一个CopyOnWriteArraySet,所以看官方文档

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oqu4HE3X-1632659005904)(C:\Users\jhfan\AppData\Roaming\Typora\typora-user-images\image-20201111152507381.png)]所以现在看代码:

public class JucTest{
    
    
	public static void main(String[] args) {
    
    
		Set<String> set=new CopyOnWriteArraySet<String>(); 
		for(int t=0;t<5;t++) {
    
    
		new Thread(()->{
    
    
			for (int i = 0; i < 10; i++) {
    
    
				set.add(UUID.randomUUID().toString().substring(0, 5));
				System.out.println(set);
			}
		}).start(); 
		}


	}

}

看起官方文档,可以看出CopyOnWriteArraySet包含CopyOnWriteArrayList对象,它是通过CopyOnWriteArrayList实现的。所以就不看源码了。补充一点,其实set的本质也是一个map,只不过只存储map的key,所以set的数据不可重复,毕竟key值是无法重复的。

所以CopyOnWriteArraySet优缺点和CopyOnWriteArrayList大致也是一样。

  • 数据是安全的
  • 读写也是分离的,毕竟其本质也是复制了一个list进行操作。最终数据一致性,但是无法保证数据实时一致性。也会占用内存。
  • 最适合于具有以下特征的应用程序:Set 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突

map

和上面两个一样,先使用map多线程插入数据。

public class JucTest{
    
    
	public static void main(String[] args) {
    
    

		Map<String, String> map=new HashMap<>();

		for(int t=0;t<5;t++) {
    
    
			new Thread(()->{
    
    
				for (int i = 0; i < 10; i++) {
    
    
					map.put(UUID.randomUUID().toString().substring(0, 5), UUID.randomUUID().toString().substring(0, 5));

					System.out.println(map);


				}
			}).start(); 
		}


	}

}
//输出
………
……
java.util.ConcurrentModificationException
	at java.util.HashMap$HashIterator.nextNode(Unknown Source)
	at java.util.HashMap$EntryIterator.next(Unknown Source)
	at java.util.HashMap$EntryIterator.next(Unknown Source)
	at java.util.AbstractMap.toString(Unknown Source)
	at java.lang.String.valueOf(Unknown Source)
	at java.io.PrintStream.println(Unknown Source)
	at test.JucTest.lambda$0(JucTest.java:40)
	at java.lang.Thread.run(Unknown Source)
Exception in thread "Thread-2" java.util.ConcurrentModificationException
	at java.util.HashMap$HashIterator.nextNode(Unknown Source)
	at java.util.HashMap$EntryIterator.next(Unknown Source)

同样报错,也是并发错误,下面也是Collections.synchronizedMap

public class JucTest{
    
    
	public static void main(String[] args) {
    
    

		Map<String, String> map=Collections.synchronizedMap(new HashMap<>());

		for(int t=0;t<5;t++) {
    
    
			new Thread(()->{
    
    
				for (int i = 0; i < 10; i++) {
    
    
					map.put(UUID.randomUUID().toString().substring(0, 5), UUID.randomUUID().toString().substring(0, 5));

					System.out.println(map);


				}
			}).start(); 
		}


	}

}

现在我们看一下juc中的替代品ConcurrentHashMap。


public class JucTest{
    
    
	public static void main(String[] args) {
    
    
		Map<String, String> map=new ConcurrentHashMap();
		for(int t=0;t<5;t++) {
    
    
			new Thread(()->{
    
    
				for (int i = 0; i < 10; i++) {
    
    
					map.put(UUID.randomUUID().toString().substring(0, 5), UUID.randomUUID().toString().substring(0, 5));

					System.out.println(map);


				}
			}).start(); 
		}


	}

}

其运行也不会报错。但是入看底层的话需要,先理解一下map的底层,可以看之前的一篇博客:java基础 浅析MAP原理以及1.7与1.8的区别

具体看其put方法:

在这里插入图片描述

可以看出concurrentHashMap中使用但是同步关键字synchronized方法进行同步的,保证了数据的安全

猜你喜欢

转载自blog.csdn.net/u011863822/article/details/120496388