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

news/2024/5/20 9:11:32 标签: java, list, juc

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

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

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

list_6">list

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

java">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 ,这个是一个是并发异常,其实前面我也可以理解,就是数据在没有加锁,而发生的异常,但是具体是不是我们可以通过下面代码测试。

java">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方法。

java"> /**
     * 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,这个是一个也是一个集合,可以看其源码

java">  /**
     * 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试一下:

java">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版本,当然不是我随口说的,看源码注解即可得知。

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

java">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同步方法。可以下面源码。

java">  /**
     * @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。

在这里插入图片描述

java">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方法是如何保证数据安全。

java">    /**
     * 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,也是常用的一种集合。所以还是老规矩看代码

java">
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

java">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)]所以现在看代码:

java">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多线程插入数据。

java">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

java">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。

java">
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方法进行同步的,保证了数据的安全


http://www.niftyadmin.cn/n/619734.html

相关文章

linux基础之软件安装:jdk安装

前面说了两种安装软件的方式&#xff0c;rpm&#xff08;包含yum&#xff09;&#xff0c;还有就是源码安装。在后面又提了一嘴&#xff0c;有些软件的安装像是绿色版的&#xff0c;直接修改配置文件即可&#xff0c;还有就是通过安装包的sh文件进行安装。 现在看一下centos中…

为什么连接图片不能正常显示

IplImage* matchedimg0cvCreateImage(cvSize(pFrame10->widthpFrame20->widthband_w,pFrame10->height),IPL_DEPTH_8U,3); //cvZero(matchedimg0 );//这一句记得加上去。不然呈现如下效果 cvSetImageROI(matchedimg0, cvRect( 0, 0, pFrame1->width, pFrame2…

linux基础之软件安装 mysql5.7安装

对于开发很难避免的数据库&#xff0c;而mysql是最常用的数据库之一&#xff0c;本章就是安装MySQL的具体步骤&#xff0c;其中mysql最稳定的版本是之一5.7版本&#xff0c;本章就是按照mysql5.7版本。 在安装mysql的之前&#xff0c;需要提前做一些准备&#xff0c;因为cento…

linux基础之软件安装 idea安装

前面一直说还有一种安装时直接运行sh文件的&#xff0c;就演示一下&#xff0c;安装一个idea&#xff0c;也是方便开发Java。 这个有一个特点&#xff0c;这个无法通过xshell远程安装&#xff0c;这个时一个开发软件&#xff0c;有可视化&#xff0c;自然也是需要有桌面的。 …

JavaScript基础之 了解基本类型

js的发展历史&#xff0c;不在赘述&#xff0c;毕竟资料很容易找&#xff0c;百度百科即可&#xff0c;同样也只需了解一下其历史即可&#xff0c;没必要背诵下来。 JavaScript是一种轻量级的编程语言&#xff0c;其是可以插入到HTML页面中&#xff0c;让所有的浏览器执行&…

Debug Error! R6010 -abort() has been called

网上说的什么因为多线程调用 额的貌似跟那个毫不相关 最后发现是 points1 cvCreateMat(2,numPoints,CV_32FC1); points2 cvCreateMat(2,numPoints,CV_32FC1); cvZero(points1); cvZero(points2); matchingslist::iterator ptr matchings.begin(); for(int i0;i<n…

linux基础之 进程

windows有任务管理器可以方便用户查看进程&#xff0c;还可以强制结束进程&#xff0c;而linux自然也有命令方便用户查看和结束进程。 而进程是系统任何行为都需要的 &#xff0c;比如浏览网页&#xff0c;浏览器作为进程&#xff0c;打开qq聊天&#xff0c;那qq就有相应的进程…

Linux基础之虚拟机硬盘扩展

用虚拟机安装centos7&#xff0c;网上的教程一般分配的硬盘20G&#xff0c;但是后面用的时候发现现在你不够用了&#xff0c;当前前面的话直接挂载即可&#xff0c;但是挂载新建的空间&#xff0c;只能通过某个文件下面添加&#xff0c;而其他的地方比如通过yum安装的软件会安装…