JUC高并发编程4:集合的线程安全

1 内容概要

在这里插入图片描述

2 ArrayList集合线程不安全

2.1 ArrayList集合操作Demo

  • 代码演示
/**
 * list集合线程不安全
 */
public class ThreadDemo4 {
    public static void main(String[] args) {
        // 创建ArrayList集合
        List<String> list = new ArrayList<>();
        
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                // 向集合中添加内容
                list.add(UUID.randomUUID().toString().substring(0,8));
                // 从集合中获取内容
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}
  • 运行结果
    在这里插入图片描述

  • 异常内容
    java.util.ConcurrentModificationException

  • 问题:为什么会出现并发修改异常
    查看ArrayList的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 类型的线程安全问题?

2.2 解决方案:Vector

Vector 是矢量队列,它是 JDK1.0 版本添加的类。继承于 AbstractList,实现了 List, RandomAccess, Cloneable 这些接口。 Vector 继承了 AbstractList,实现了 List;所以,它是一个队列,支持相关的添加、删除、修改、遍历等功能。 Vector 实现了 RandmoAccess 接口,即提供了随机访问功能
RandmoAccess 是 java 中用来被 List 实现,为 List 提供快速访问功能的。在Vector 中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。 Vector 实现了 Cloneable 接口,即实现 clone()函数。它能被克隆。
和 ArrayList 不同,Vector 中的操作是线程安全的。

  • 代码修改1:Vector实现
/**
 * Vector实现:list集合线程安全
 */
public class ThreadDemo4 {
    public static void main(String[] args) {
        // 创建ArrayList集合
        List<String> list = new Vector<>();
        
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                // 向集合中添加内容
                list.add(UUID.randomUUID().toString().substring(0,8));
                // 从集合中获取内容
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}
  • 现在没有运行出现并发异常,为什么?
    查看 Vector 的 add 方法
/**
 * 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;
}

add 方法被 synchronized 同步修辞,线程安全!因此没有并发异常

2.3 解决方案:Collections

Collections 提供了方法 synchronizedList 保证 list 是同步线程安全的

  • 代码修改2:Collections实现
/**
 * Collections实现:list集合线程安全
 */
public class ThreadDemo4 {
    public static void main(String[] args) {
        // 创建ArrayList集合
        List<String> list = Collections.synchronizedList(new ArrayList<>());
        
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                // 向集合中添加内容
                list.add(UUID.randomUUID().toString().substring(0,8));
                // 从集合中获取内容
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}
  • 没有并发修改异常
  • 查看方法源码
/**
 * Returns a synchronized (thread-safe) list backed by the specified
 * list.  In order to guarantee serial access, it is critical that
 * <strong>all</strong> access to the backing list is accomplished
 * through the returned list.<p>
 *
 * It is imperative that the user manually synchronize on the returned
 * list when iterating over it:
 * <pre>
 *  List list = Collections.synchronizedList(new ArrayList());
 *      ...
 *  synchronized (list) {
 *      Iterator i = list.iterator(); // Must be in synchronized block
 *      while (i.hasNext())
 *          foo(i.next());
 *  }
 * </pre>
 * Failure to follow this advice may result in non-deterministic behavior.
 *
 * <p>The returned list will be serializable if the specified list is
 * serializable.
 *
 * @param  <T> the class of the objects in the list
 * @param  list the list to be "wrapped" in a synchronized list.
 * @return a synchronized view of the specified list.
 */
public static <T> List<T> synchronizedList(List<T> list) {
    return (list instanceof RandomAccess ?
            new SynchronizedRandomAccessList<>(list) :
            new SynchronizedList<>(list));
}

2.4 解决方案:CopyOnWriteArrayList

首先我们对 CopyOnWriteArrayList 进行学习,其特点如下:
它相当于线程安全的 ArrayList。和 ArrayList 一样,它是个可变数组;但是和ArrayList 不同的时,它具有以下特性:

  1. 它最适合于具有以下特征的应用程序:List 大小通常保持很小,只读操作远多
    于可变操作,需要在遍历期间防止线程间的冲突。
  2. 它是线程安全的。
  3. 因为通常需要复制整个基础数组,所以可变操作(add()、set() 和 remove()
    等等)的开销很大。
  4. 迭代器支持 hasNext(), next()等不可变操作,但不支持可变 remove()等操作。
  5. 使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照。

  • 思想和原理
  1. 独占锁效率低:采用读写分离思想解决
  2. 写线程获取到锁,其他写线程阻塞
  3. 复制思想:当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行 Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器
    这就是 CopyOnWriteArrayList 的思想和原理。就是拷贝一份。
  • 代码修改3:CopyOnWriteArrayList实现
/**
 * CopyOnWriteArrayList实现:list集合线程安全
 */
public class ThreadDemo4 {
    public static void main(String[] args) {
        // 创建ArrayList集合
        List<String> list = new CopyOnWriteArrayList<>();
        
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                // 向集合中添加内容
                list.add(UUID.randomUUID().toString().substring(0,8));
                // 从集合中获取内容
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}
  • 没有线程安全问题
  • 原因分析(重点):动态数组与线程安全

下面从“动态数组”和“线程安全”两个方面进一步对CopyOnWriteArrayList 的原理进行说明

  • “动态数组”机制
  • 它内部有个“volatile 数组”(array)来保持数据。在“添加/修改/删除”数据时,都会新建一个数组,并将更新后的数据拷贝到新建的数组中,最后再将该数组赋值给“volatile 数组”, 这就是它叫做CopyOnWriteArrayList 的原因
  • 由于它在“添加/修改/删除”数据时,都会新建数组,所以涉及到修改数据的操作,CopyOnWriteArrayList 效率很低;但是单单只是进行遍历查找的话,效率比较高。
  • “线程安全”机制
  • 通过 volatile 和互斥锁来实现的
  • 通过“volatile 数组”来保存数据的。一个线程读取 volatile 数组时,总能看到其它线程对该 volatile 变量最后的写入;就这样,通过 volatile 提供了“读取到的数据总是最新的”这个机制的保证
  • 通过互斥锁来保护数据。在“添加/修改/删除”数据时,会先“获取互斥锁”,再修改完毕之后,先将数据更新到“volatile 数组”中,然后再“释放互斥锁”,就达到了保护数据的目的

3 HashSet集合线程不安全

3.1 HashSet集合操作Demo

  • 代码演示
/**
 * HashSet集合线程不安全
 */
public class ThreadDemo4 {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                // 想集合添加内容
                set.add(UUID.randomUUID().toString().substring(0,8));
                // 从集合获取内容
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}
  • 运行结果
    在这里插入图片描述

  • 异常内容
    java.util.ConcurrentModificationException

  • 问题:为什么会出现并发修改异常
    查看HashSet的add方法源码:

/**
 * Adds the specified element to this set if it is not already present.
 * More formally, adds the specified element <tt>e</tt> to this set if
 * this set contains no element <tt>e2</tt> such that
 * <tt>(e==null&nbsp;?&nbsp;e2==null&nbsp;:&nbsp;e.equals(e2))</tt>.
 * If this set already contains the element, the call leaves the set
 * unchanged and returns <tt>false</tt>.
 *
 * @param e element to be added to this set
 * @return <tt>true</tt> if this set did not already contain the specified
 * element
 */
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

那么我们如何去解决Set类型的线程安全问题?

3.2 解决方案:Collections

Collections 提供了方法 synchronizedSet保证 list 是同步线程安全的

  • 代码修改1:Collections实现
/**
 * Collections实现:HashSet集合线程安全
 */
public class ThreadDemo4 {
    public static void main(String[] args) {
        Set<String> set = Collections.synchronizedSet(new HashSet<>());
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                // 想集合添加内容
                set.add(UUID.randomUUID().toString().substring(0,8));
                // 从集合获取内容
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}
  • 没有并发修改异常
  • 查看方法源码
/**
* Returns a synchronized (thread-safe) set backed by the specified
* set.  In order to guarantee serial access, it is critical that
* <strong>all</strong> access to the backing set is accomplished
* through the returned set.<p>
*
* It is imperative that the user manually synchronize on the returned
* set when iterating over it:
* <pre>
*  Set s = Collections.synchronizedSet(new HashSet());
*      ...
*  synchronized (s) {
*      Iterator i = s.iterator(); // Must be in the synchronized block
*      while (i.hasNext())
*          foo(i.next());
*  }
* </pre>
* Failure to follow this advice may result in non-deterministic behavior.
*
* <p>The returned set will be serializable if the specified set is
* serializable.
*
* @param  <T> the class of the objects in the set
* @param  s the set to be "wrapped" in a synchronized set.
* @return a synchronized view of the specified set.
*/
public static <T> Set<T> synchronizedSet(Set<T> s) {
  return new SynchronizedSet<>(s);
}

3.3 解决方案:CopyOnWriteArraySet

  • 代码修改2:CopyOnWriteArraySet实现
/**
 * CopyOnWriteArraySet实现:HashSet集合线程安全
 */
public class ThreadDemo4 {
    public static void main(String[] args) {
        Set<String> set = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                // 想集合添加内容
                set.add(UUID.randomUUID().toString().substring(0,8));
                // 从集合获取内容
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}
  • 没有线程安全问题
  • 实现原理
  1. 读操作无锁:
  • CopyOnWriteArraySet 的读操作(如 contains、iterator 等)不需要加锁,因为读操作总是基于当前的数组快照进行,不会受到写操作的影响。
  1. 写操作加锁并复制
  • 当进行写操作(如 add、remove 等)时,CopyOnWriteArraySet 会先获取锁,然后创建底层数组的新副本,在新副本上进行修改操作,最后将新副本替换原来的数组。
  • 由于写操作是在新副本上进行的,因此不会影响正在进行读操作的线程,从而保证了线程安全。
  1. 迭代器安全
  • CopyOnWriteArraySet 的迭代器是“快照”迭代器,它基于创建迭代器时的数组快照进行遍历,因此不会抛出ConcurrentModificationException。
  • 即使迭代器创建后集合发生了修改,迭代器仍然会基于创建时的快照进行遍历,不会受到后续修改的影响。

4 HashMap集合线程不安全

4.1 HasgMap集合操作Demo

  • 代码演示
/**
 * HashMap集合线程不安全
 */
public class ThreadDemo4 {
    public static void main(String[] args) {
        //演示HashMap
        Map<String,String> set = new HashMap<>();
        for (int i = 0; i < 30; i++) {
            String key = String.valueOf(i);
            new Thread(()->{
                // 想集合添加内容
                set.put(key,UUID.randomUUID().toString().substring(0,8));
                // 从集合获取内容
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}
  • 运行结果
    在这里插入图片描述

  • 异常内容
    java.util.ConcurrentModificationException

  • 问题:为什么会出现并发修改异常
    查看HashMap的put方法源码:

 /**
  * Associates the specified value with the specified key in this map.
  * If the map previously contained a mapping for the key, the old
  * value is replaced.
  *
  * @param key key with which the specified value is to be associated
  * @param value value to be associated with the specified key
  * @return the previous value associated with <tt>key</tt>, or
  *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
  *         (A <tt>null</tt> return can also indicate that the map
  *         previously associated <tt>null</tt> with <tt>key</tt>.)
  */
 public V put(K key, V value) {
     return putVal(hash(key), key, value, false, true);
 }

那么我们如何去解决 Map 类型的线程安全问题?

4.2 解决方案:HashTable

  • 代码修改1:HashTable实现
/**
 * HashTable实现:HashMap集合线程安全
 */
public class ThreadDemo4 {
    public static void main(String[] args) {
        //演示HashMap
        Map<String,String> set = new HashTable<>();
        for (int i = 0; i < 30; i++) {
            String key = String.valueOf(i);
            new Thread(()->{
                // 想集合添加内容
                set.put(key,UUID.randomUUID().toString().substring(0,8));
                // 从集合获取内容
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}
  • 现在没有运行出现并发异常,为什么?
    查看 HashTable 的 put方法
/**
 * Maps the specified <code>key</code> to the specified
 * <code>value</code> in this hashtable. Neither the key nor the
 * value can be <code>null</code>. <p>
 *
 * The value can be retrieved by calling the <code>get</code> method
 * with a key that is equal to the original key.
 *
 * @param      key     the hashtable key
 * @param      value   the value
 * @return     the previous value of the specified key in this hashtable,
 *             or <code>null</code> if it did not have one
 * @exception  NullPointerException  if the key or value is
 *               <code>null</code>
 * @see     Object#equals(Object)
 * @see     #get(Object)
 */
public synchronized V put(K key, V value) {
    // Make sure the value is not null
    if (value == null) {
        throw new NullPointerException();
    }

    // Makes sure the key is not already in the hashtable.
    Entry<?,?> tab[] = table;
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    Entry<K,V> entry = (Entry<K,V>)tab[index];
    for(; entry != null ; entry = entry.next) {
        if ((entry.hash == hash) && entry.key.equals(key)) {
            V old = entry.value;
            entry.value = value;
            return old;
        }
    }

    addEntry(hash, key, value, index);
    return null;
}

4.2 解决方案:Collections

  • 代码修改2:Collections实现
/**
 * Collections实现:HashMap集合线程安全
 */
public class ThreadDemo4 {
    public static void main(String[] args) {
        //演示HashMap
        Map<String,String> set = Collections.synchronizedMap(new HashMap<>());
        for (int i = 0; i < 30; i++) {
            String key = String.valueOf(i);
            new Thread(()->{
                // 想集合添加内容
                set.put(key,UUID.randomUUID().toString().substring(0,8));
                // 从集合获取内容
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}
  • 没有并发修改异常
  • 查看方法源码
 /**
  * Returns a synchronized (thread-safe) map backed by the specified
  * map.  In order to guarantee serial access, it is critical that
  * <strong>all</strong> access to the backing map is accomplished
  * through the returned map.<p>
  *
  * It is imperative that the user manually synchronize on the returned
  * map when iterating over any of its collection views:
  * <pre>
  *  Map m = Collections.synchronizedMap(new HashMap());
  *      ...
  *  Set s = m.keySet();  // Needn't be in synchronized block
  *      ...
  *  synchronized (m) {  // Synchronizing on m, not s!
  *      Iterator i = s.iterator(); // Must be in synchronized block
  *      while (i.hasNext())
  *          foo(i.next());
  *  }
  * </pre>
  * Failure to follow this advice may result in non-deterministic behavior.
  *
  * <p>The returned map will be serializable if the specified map is
  * serializable.
  *
  * @param <K> the class of the map keys
  * @param <V> the class of the map values
  * @param  m the map to be "wrapped" in a synchronized map.
  * @return a synchronized view of the specified map.
  */
 public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
     return new SynchronizedMap<>(m);
 }

4.3 解决方案:ConcurrentHashMap

  • 代码修改3:CopyOnWriteHashMap实现
/**
 *CopyOnWriteHashMap实现:HashMap集合线程安全
 */
public class ThreadDemo4 {
    public static void main(String[] args) {
        //演示HashMap
        Map<String,String> set = new ConcurrentHashMap<>();
        for (int i = 0; i < 30; i++) {
            String key = String.valueOf(i);
            new Thread(()->{
                // 想集合添加内容
                set.put(key,UUID.randomUUID().toString().substring(0,8));
                // 从集合获取内容
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}
  • 没有线程安全问题
  • 实现原理
  1. 读操作无锁:
  • 读操作(如 get、containsKey 等)不需要加锁,因为读操作总是基于当前的 HashMap 快照进行,不会受到写操作的影响。
  1. 写操作加锁并复制:
  • 当进行写操作(如 put、remove 等)时,CopyOnWriteHashMap 会先获取锁,然后创建底层 HashMap 的新副本,在新副本上进行修改操作,最后将新副本替换原来的 HashMap。
  • 由于写操作是在新副本上进行的,因此不会影响正在进行读操作的线程,从而保证了线程安全。
  1. 迭代器安全:
  • CopyOnWriteHashMap 的迭代器是“快照”迭代器,它基于创建迭代器时的 HashMap 快照进行遍历,因此不会抛出 ConcurrentModificationException。
  • 即使迭代器创建后集合发生了修改,迭代器仍然会基于创建时的快照进行遍历,不会受到后续修改的影响。

5 总结

  1. 线程安全与线程不安全集合
    集合类型中存在线程安全与线程不安全的两种,常见例如:
    ArrayList ----- Vector
    HashMap -----HashTable
    但是以上都是通过 synchronized 关键字实现,效率较低
  2. Collections 构建的线程安全集合
  3. java.util.concurrent 并发包下
    CopyOnWriteArrayList、CopyOnWriteArraySet 类型,通过动态数组与线程安全两个方面保证线程安全

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/884940.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【有啥问啥】具身智能(Embodied AI):人工智能的新前沿

具身智能&#xff08;Embodied AI&#xff09;&#xff1a;人工智能的新前沿 引言 在人工智能&#xff08;AI&#xff09;的进程中&#xff0c;具身智能&#xff08;Embodied AI&#xff09;正逐渐成为研究与应用的焦点。具身智能不仅关注于机器的计算能力&#xff0c;更强调…

count(1),count(*)与 count(‘列名‘) 的区别

文章目录 COUNT(expr)性能对比count(*) VS count(1)count(*) VS count(列名) count(*)会走索引吗MyISAM count优化InnoDB如何处理count(*)总结 参考官方文档&#xff1a; https://dev.mysql.com/doc/refman/8.4/en/aggregate-functions.html#function_count COUNT(expr) coun…

Skyeye 云这几年的经历

前言 我是 17 年毕业的&#xff0c;之前也是在学校的实验室 (做开发的) 待了两年多时间&#xff0c;期间学了不少东西&#xff0c;学的东西也算是与时俱进了。最近两年也算是开源中国的常客了&#xff0c;每周都会保持自己项目的一个更新进度。 项目地址&#xff1a;skyeye-o…

Chainlit集成LlamaIndex实现知识库高级检索(BM25全文检索器)

检索原理 BM25Retriever类是一个基于BM25算法设计的检索器&#xff0c;它主要用于从一组文档或节点中检索出与查询最相关的文档或节点。这个类的设计目的是为了提高文本检索的效率和准确性&#xff0c;尤其是在处理大量文本数据时。 BM25&#xff08;Best Matching 25&#x…

[uni-app]小兔鲜-03多端打包上线

小程序打包 打包上线流程 打包命令: pnpm build:mp-weixin效果预览: 把打包后的文件导入微信开发者工具 (dist\build\mp-weixin)代码上传: 点击微信开发者工具的上传按钮, 上传代码,审核发布: 登录微信公众平台, 提交审核, 审核后发布辅助工具: 有些团队会使用开发辅助工具 mi…

Android OpenGLES2.0开发(三):绘制一个三角形

我们总是对陌生人太客气&#xff0c;而对亲密的人太苛刻 上一篇文章中&#xff0c;我们已经将OpenGL ES环境搭建完成。接下来我们就可以开始我们的绘图之旅了。该篇我们讲解最基本图形三角形的绘制&#xff0c;这是一切绘制的基础。在OpenGL ES的世界里一切图形都可以由三角形拼…

基于nodejs+vue的农产品销售管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目…

基于微信小程序爱心领养小程序设计与实现(源码+参考文档+定制开发)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

计算机前沿技术-人工智能算法-大语言模型-最新论文阅读-2024-09-23

计算机前沿技术-人工智能算法-大语言模型-最新论文阅读-2024-09-23 本期&#xff0c;我们对大语言模型在表情推荐, 软件安全和 自动化软件漏洞检测等方面如何应用&#xff0c;提供几篇最新的参考文章。 1 Semantics Preserving Emoji Recommendation with Large Language Mod…

[深度学习]卷积神经网络CNN

1 图像基础知识 import numpy as np import matplotlib.pyplot as plt # 图像数据 #imgnp.zeros((200,200,3)) imgnp.full((200,200,3),255) # 可视化 plt.imshow(img) plt.show() # 图像读取 imgplt.imread(img.jpg) plt.imshow(img) plt.show() 2 CNN概述 卷积层convrelu池…

分布式数据库——HBase基本操作

启动HBase: 1.启动hadoop,进入hadoop的sbin中 cd /opt/hadoop/sbin/ 2.初始化namenode hdfs namenode -format 3.启动hdfs ./start-all.sh 4.启动hbase cd /opt/hbase/bin ./start-hbase.sh 5.使用jps查看进程 jps 以下图片则是hbase启动成功~ 运行HBase ./hbase sh…

64.【C语言】再议结构体(下)(未完)

本文衔接第63篇 目录 6.复习 7.修改默认对齐数 8.结构体传参 01.传递非指针参数 02.传递指针参数(传递地址) 03.对比 9.结构体实现位段 01.位段的定义 02.格式 03.例题 答案速查 分析 前置知识:位段的内存分配 解析 若按浪费空间处理 验证 6.复习 20.【C语言…

20.1 分析pull模型在k8s中的应用,对比push模型

本节重点介绍 : push模型和pull模型监控系统对比为什么在k8s中只能用pull模型的k8s中主要组件的暴露地址说明 push模型和pull模型监控系统 对比下两种系统采用的不同采集模型&#xff0c;即push型采集和pull型采集。不同的模型在性能的考虑上是截然不同的。下面表格简单的说…

全网最全软件测试面试题(含答案解析+文档)

一、软件测试基础面试题 1、阐述软件生命周期都有哪些阶段? 常见的软件生命周期模型有哪些? 软件生命周期是指一个计算机软件从功能确定设计&#xff0c;到开发成功投入使用&#xff0c;并在使用中不断地修改、增补和完善&#xff0c;直到停止该软件的使用的全过程(从酝酿到…

smb文件夹共享设置

UOS统信三种不同场景的文件夹共享,分别是:1、UOS系统间的文件共享;2、Windows7系统访问UOS共享的文件;3、UOS系统访问Windows7共享的文件 文章目录 功能概述功能介绍第一种场景:UOS系统之间的文件共享设置步骤一:打开共享文件夹步骤二:共享管理步骤三:设置共享密码步骤…

Linux使用systemd安排定期任务的操作详解

systemd 定时器是一种替代传统 cron 的方法&#xff0c;用于安排定时任务。 systemd 定时器由两部分组成&#xff1a;一个 .service 文件和一个 .timer 文件。.service 文件定义了要执行的任务&#xff0c;而 .timer 文件设定了何时执行这个任务。 通常位于 /etc/systemd/syste…

扩散模型(2)--1

1.简介 生成模型通过学习并建模输入数据的分布&#xff0c;从而采集生成新的样木&#xff0c;该模型广泛运用于图片视频生成、文本生成和药物分子生成。扩散模型是一类概率生成模型&#xff0c;扩散模型通过向数据中逐步加入噪声来破坏数据的结构&#xff0c;然后学习一个相对应…

【Linux的内存管理】

为什么需要内存管理 分段和分页内存分段内存分页 分页情况下&#xff0c;虚拟内存如何映射到物理地址页表原理多级页表 TLB快表段页式内存管理需要为什么进程地址空间Linux的进程虚拟地址空间管理进程地址空间如何分配虚拟内存虚拟内存的管理程序编译后的二进制文件如何映射到虚…

node-rtsp-stream、jsmpeg.min.js实现rtsp视频在web端播放

1. 服务地址&#xff08;私有&#xff09;&#xff1a;https://gitee.com/nnlss/video-node-server 2.node-rtsp-stream 需要安装FFMPEG&#xff1b; 3.给推拉流做了开关&#xff0c;可借助http请求&#xff0c;有更好方式可联系&#xff1b; 4.存在问题&#xff1a; 1&…

王道-计组

4 设相对寻址的转移指令占4字节,其中第1、第2字节是操作码,第3、第4字节是相对位移量(用补码表示)。设当前PC的内容为2008H,要求转移到2001H的地址,则该转移指令第3、第4字节的内容应为______ 答案:A 解析:由于指令占4字节,取指令之后(PC)+4。第3、第4字节的内容为:2…