【昕宝爸爸定制】如何将集合变成线程安全的?

news2024/11/24 16:56:29

在这里插入图片描述

如何将集合变成线程安全的?

  • ✅典型解析
  • 🟢拓展知识仓
    • ☑️Java中都有哪些线程安全的集合?
    • 🟠线程安全集合类的优缺点是什么
    • 🟡如何选择合适的线程安全集合类
    • ☑️如何解决线程安全集合类并发冲突问题
      • ✔️乐观锁实现方式 (具体步骤)。
      • ✅乐观锁在数据量大的情况下性能如何
      • ✅乐观锁的优缺点
  • ✅ 什么是写时复制?
    • ✅ 什么是COW,如何保证的线程安全?


✅典型解析


1 . 在调用集合前,使用synchronized或者 ReentrantLock 对代码加锁 (读写都要加锁)


public class SynchronizedCollectionExample {

	private List<Integer> list = new ArrayList<>();

	public void add(int value) {
		
		synchronized (SynchronizedCollectionExample.class) {
		
			list.add(value);
		}
	}

	public int get(int index) {
	
		synchronized (SynchronizedCollectionExample.class) {
		
			return list.get(index);
		}
	}
}

2 . 使用 ThreadLocal ,将集合放到线程内访问,但是这样集合中的值就不能被其他线程访问了


public class ThreadlocalCollectionExample {
	
	private ThreadLocal<list<Integer>> threadlocallist = Threadlocal.withInitial(Arraylist::new);

	public void add(int value) {
		
		threadLocalList.get( ) .add(value);
	}

	public int get(int index) {
	
		return threadLocalList.get().get(index);
	}
}

3 . 使用Collections.synchronizedXXX()方法,可以获得一个线程安全的集合


import java.util.Collections;
import java.util.List;
import java.util.ArrayList;

public class CollectionssynchronizedExample {

	List<Integer> synchronizedList = Collections.synchronizedList(new ArrayList<Integer>());

	public void add(int value) {
	
		synchronizedList.add(value);
	}	

	public int get(int index) {
		
		return synchronizedList.get(index);
	}
}

4 . 使用不可变集合进行封装,当集合是不可变的时候,自然是线程安全的


import com.google.common.collect.ImmutableList;

public class ImmutableCollectionExample {
	private Immutablelist<Integer> immutablelist = Immutablelist.of(123);



	public int get(int index) {
	
		return immutablelist .get(index);
	}	
}

或者(or)


import java.util.List;

public class ImmutableJava9Example {
	
	private List<Integer> immutableList = List.of(1,23);

	public int get(int index) {
		
		return immutablelist.get(index);
	}
}

🟢拓展知识仓


☑️Java中都有哪些线程安全的集合?


Java1.5并发包 (java.util.concurrent) 包含线程安全集合类,允许在迭代时修改集合。


1. ConcurrentHashMap

2.ConcurrentLinkedDeque

3. ConcurrentLinkedQueue

4.ConcurrentSkipListMap

5. ConcurrentSkipSet

6.CopyOnWriteArrayList

7.CopyOnWriteArraySet


🟠线程安全集合类的优缺点是什么


线程安全集合类的优缺点如下:

优点

1. 线程安全:线程安全集合类可以在多线程环境中安全地使用,不会出现数据不一致或竞争条件的问题

2. 高性能:线程安全集合类在处理大量数据时通常具有较好的性能,因为它们内部进行了优化以减少同步的开销

3. 可靠性:由于线程安全集合类具有内置的同步机制,因此它们可以避免因并发问题而导致的数据损坏或程序崩溃

缺点

1. 资源竞争:由于线程安全集合类的同步机制,当多个线程同时访问这些集合类时,会导致资源竞争,从而影响程序的性能

2. 死锁风险:线程安全集合类可能会增加死锁的风险,尤其是在复杂的并发环境中。

3. 过度同步:线程安全集合类的同步机制可能会导致过度同步,从而限制了并发性能的进一步提高。

4. 性能调优难度:由于线程安全集合类的内部实现较为复杂,因此对它们的性能调优可能会更加困难。


线程安全集合类在多线程环境中是必不可少的,但使用时需要注意它们的优缺点,并根据实际需求进行选择和调整。


我们来看一个场景的Demo:


import java.util.concurrent.*; 
/**
* @author xinbaobaba
* 涉及多个线程和多个线程安全集合类
* 这个例子使用 java.util.concurrent 包中的 ExecutorService 
* 和 BlockingQueue 来实现一个线程安全的生产者-消费者模型
*/    
public class ProducerConsumerExample {  
    // 定义最大队列大小  
    private static final int MAX_QUEUE_SIZE = 10;  
    // 定义线程安全的阻塞队列  
    private static final BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(MAX_QUEUE_SIZE);  
  
    public static void main(String[] args) {  
        // 创建一个固定大小的线程池  
        ExecutorService executorService = Executors.newFixedThreadPool(2);  
        // 创建生产者线程  
        Producer producer = new Producer(queue);  
        // 创建消费者线程  
        Consumer consumer = new Consumer(queue);  
        // 将生产者线程提交到线程池中运行  
        executorService.submit(producer);  
        // 将消费者线程提交到线程池中运行  
        executorService.submit(consumer);  
    }  
  
    // 生产者线程内部类  
    static class Producer implements Runnable {  
        private final BlockingQueue<Integer> queue; // 生产者使用的队列  
  
        public Producer(BlockingQueue<Integer> queue) {  
            this.queue = queue; // 初始化队列  
        }  
  
        @Override  
        public void run() { // 生产者的运行方法  
            try {  
                for (int i = 0; i < 100; i++) { // 循环生产100个元素  
                    queue.put(i); // 将元素放入队列中,如果队列满则阻塞等待空间可用  
                    System.out.println("Produced: " + i); // 打印已生产的元素  
                    Thread.sleep(100); // 生产一个元素后休眠100毫秒,模拟生产时间  
                }  
            } catch (InterruptedException e) { // 如果生产过程中被中断,则捕获异常并打印堆栈信息  
                e.printStackTrace();  
            }  
        }  
    }  
  
    // 消费者线程内部类  
    static class Consumer implements Runnable {  
        private final BlockingQueue<Integer> queue; // 消费者使用的队列  
  
        public Consumer(BlockingQueue<Integer> queue) { // 初始化队列  
            this.queue = queue;  
        }  
  
        @Override  
        public void run() { // 消费者的运行方法  
            try {  
                while (true) { // 无限循环消费元素,直到程序被终止或异常发生  
                    Integer value = queue.take(); // 从队列中取出元素,如果队列空则阻塞等待有元素可用  
                    System.out.println("Consumed: " + value); // 打印已消费的元素  
                }  
            } catch (InterruptedException e) { // 如果消费过程中被中断,则捕获异常并打印堆栈信息  
                e.printStackTrace();  
            }  
        }  
    }  
}

Demo中,创建一个生产者和一个消费者线程。生产者线程将数字0到99放入队列中,而消费者线程从队列中取出数字并打印出来。我们使用 BlockingQueue 来实现线程安全的队列,它提供了 put() take()方法,这些方法在队列满或队列空时会自动阻塞,直到队列不再满或不再空为止。这样就可以保证生产者和消费者线程在访问队列时不会发生并发冲突


🟡如何选择合适的线程安全集合类


选择合适的线程安全集合类需要考虑以下几个方面:

  1. 并发级别:首先需要了解应用程序的并发级别,即同时访问集合的线程数量。高并发级别需要选择性能更好的线程安全集合类,如 ConcurrentHashMap
  2. 读写比例:根据应用程序对读和写的需求,选择适合的线程安全集合类。如果读操作远多于写操作,则可以选择 CopyOnWriteArrayListCopyOnWriteArraySet,它们在读操作时不需要同步,而在写操作时复制底层数组,保证了线程安全。
  3. 锁粒度:不同的线程安全集合类使用的锁粒度不同,这会影响并发性能。例如,Hashtable 使用单个锁保护整个表,而 ConcurrentHashMap 使用多个锁保护不同的段,从而提供更好的并发性能。
  4. 元素访问顺序:根据对元素访问顺序的需求,选择具有合适迭代器的线程安全集合类。例如,VectorHashtable 提供了稳定的迭代器,而 ConcurrentHashMap 不保证迭代顺序。
  5. 内存效率:线程安全集合类的内存效率也是一个重要的考虑因素。例如,HashtableVector 在添加元素时需要扩容,这可能会导致额外的内存开销。
  6. 自定义需求:根据应用程序的特定需求,可以选择具有自定义功能的线程安全集合类。例如,如果需要自定义的锁策略或特殊的数据结构,可以自行实现线程安全的集合类。

选择合适的线程安全集合类需要综合考虑应用程序的并发级别、读写比例、锁粒度、元素访问顺序、内存效率和自定义需求等因素。在选择时,可以参考Java标准库提供的线程安全集合类,并根据实际情况进行选择和调整。


☑️如何解决线程安全集合类并发冲突问题


解决线程安全集合类并发冲突问题可以采用以下几种方法:

  1. 使用锁:在访问线程安全集合类时,可以使用适当的锁机制来避免并发冲突。例如,可以使用 synchronized 关键字或ReentrantLock 来实现同步访问。

  2. 使用并发集合类:Java标准库提供了一些并发集合类,如ConcurrentHashMapCopyOnWriteArrayList等,这些集合类内部已经实现了线程安全,可以避免并发冲突。

  3. 使用乐观锁:乐观锁采用乐观策略,即在读取数据时不加锁,在更新数据时通过版本号或CAS(Compare and Swap)机制来保证数据的一致性。乐观锁可以减少锁的竞争,提高并发性能。

  4. 使用读写锁:读写锁允许多个线程同时读取数据,但在写入数据时需要独占式的访问。Java标准库中的ReadWriteLock接口提供了读写锁机制。

  5. 使用分段锁:分段锁是将数据分成多个段,每个段使用独立的锁来保护。这样可以减少锁的竞争范围,提高并发性能。例如,ConcurrentHashMap就是使用分段锁实现的。

  6. 使用无锁机制:无锁机制是一种基于CAS操作的并发控制方法,它不需要显式的锁机制即可实现线程安全。无锁机制的优点是避免了锁的竞争和死锁问题,但实现起来较为复杂。

解决线程安全集合类并发冲突问题需要根据实际情况选择合适的并发控制方法。在选择时,需要考虑应用程序的并发级别、读写比例、数据一致性需求等因素,并权衡性能、可读性和可维护性等方面


✔️乐观锁实现方式 (具体步骤)。


乐观锁是一种并发控制策略,它假设多个事务在同一时间对同一数据进行操作时,不会产生冲突,只有在提交更新时才会检查是否有冲突。如果发现冲突,则回滚事务。乐观锁的实现方式主要有两种:数据版本记录机制和基于CAS操作的机制。

数据版本记录机制是乐观锁最常用的实现方式。具体步骤如下:

  1. 将需要锁定的资源以键值对的方式存储在数据库中。
  2. 给资源添加一个版本号字段,用于记录资源的版本信息。
  3. 当需要访问资源时,先读取资源的当前版本号,并将该版本号保存在本地。
  4. 处理完需要访问的数据后,将本地保存的版本号与数据库中的版本号进行比较。如果两个版本号相等,则提交事务;否则,表示资源已经被其他事务修改,需要回滚事务并重新处理。

基于CAS操作的机制是一种无锁机制,它通过比较并交换(Compare-And-Swap, CAS)操作来实现并发控制。具体步骤如下:

  1. 将需要锁定的资源以键值对的方式存储在内存中。
  2. 当需要更新资源时,使用CAS操作来比较并交换资源的新旧值。如果资源没有被其他线程修改过,则CAS操作成功,更新资源并提交事务;否则,表示资源已经被其他线程修改过,需要回滚事务并重新处理。

注意:乐观锁在处理大量并发请求时可能会有较高的回滚率,因此需要根据实际情况选择是否使用乐观锁,或者采用其他并发控制策略。


乐观锁的实现方式有很多种,具体选择哪种方式还需要根据实际业务场景和技术栈来决定。


✅乐观锁在数据量大的情况下性能如何


乐观锁在数据量大的情况下,性能表现主要取决于具体实现方式数据访问模式

乐观锁机制不会像悲观锁一样在操作系统中挂起,而是允许失败的线程重试,也允许自动放弃退出操作。因此,乐观锁相比悲观锁来说,不会带来死锁、饥饿等活性故障问题,线程间的相互影响也远远比悲观锁要小。


在乐观锁的实现方式中,使用数据版本(Version)记录机制是比较常用的一种。当大量数据需要更新时,每次更新都需要判断版本号是否匹配,这会增加一定的开销。但是,由于乐观锁避免了长事务中的数据库加锁开销,所以在高并发的情况下,乐观锁的性能表现可能优于悲观锁。

对于使用Redis实现乐观锁的情况,如果数据量非常大,频繁地读取和比较版本号可能会对性能产生一定的影响。但是,通过合理的设计和优化,比如使用有序集合(sorted set)来存储版本号,可以有效地提高性能。

乐观锁在数据量大的情况下,性能表现取决于具体实现方式和数据访问模式。通过合理的设计和优化,乐观锁可以提供较好的性能和并发控制能力。


乐观锁的核心思想是:在数据读取时,不对数据进行加锁,但在数据提交更新时,会判断在此期间是否有其他线程对数据进行了修改。下面是一个使用Java实现的乐观锁示例:


import java.util.concurrent.atomic.AtomicInteger;  
  
public class OptimisticLocker {  
    // 定义一个版本号字段  
    private AtomicInteger version = new AtomicInteger(0);  
  
    // 获取当前版本号  
    public int getVersion() {  
        return version.get();  
    }  
  
    // 更新数据,并递增版本号  
    public boolean updateData(int newData, int expectedVersion) {  
        while (true) {  
            int currentVersion = version.get();  
            if (currentVersion != expectedVersion) {  
                // 如果当前版本号与期望的版本号不一致,说明数据在此期间被其他线程修改过  
                return false; // 返回false表示更新失败  
            } else {  
                // 更新数据并递增版本号  
                if (version.compareAndSet(currentVersion, currentVersion + 1)) {  
                    // 数据更新成功,返回true表示更新成功  
                    return true;  
                } else {  
                    // 如果在更新过程中,其他线程也修改了版本号,则重新获取当前版本号并继续尝试更新  
                    continue;  
                }  
            }  
        }  
    }  
}

可以看到:我们使用 AtomicInteger 作为版本号的存储,利用其原子性来保证并发访问时的正确性。updateData方法 接受两个参数:newData表示要更新的新数据,expectedVersion表示期望的版本号。方法中的 while循环 是为了确保数据的一致性。如果在更新过程中发现当前版本号与期望的版本号不一致,则认为数据被其他线程修改过,更新失败;否则,更新数据并递增版本号。


✅乐观锁的优缺点


乐观锁的优点主要体现在以下几个方面:

  1. 提高并发性能:乐观锁机制不会对读操作造成阻塞,只有在提交更新时才会进行版本检查,因此可以最大程度地提高并发性能,允许多个用户同时读取同一份数据,不会出现读阻塞的情况。
  1. 减少锁冲突:乐观锁采用版本号机制,可以降低锁冲突的概率,提高并发性能。
  1. 解决数据一致性问题:乐观锁通过版本检查的方式来解决并发修改可能导致的数据一致性问题。在提交更新时,会再次检查版本号是否一致,如果不一致则说明有其他用户修改了数据,避免了数据被覆盖或丢失的情况。
  1. 灵活性:乐观锁相对于悲观锁来说更加灵活,不需要给整个事务加锁,只在提交更新时进行版本检查。这样可以避免长时间的锁等待,提高系统的响应速度。

乐观锁缺点:


  1. 更新失败的概率较高:一旦出现锁冲突,可能会导致更新失败。如果锁的粒度掌握不好,可能会出现大量更新失败的情况,进而影响业务的正常运行。
  1. 适用场景有限:乐观锁适用于读操作频繁的场景,可以提高系统的吞吐量。但在写操作频繁的场景下,悲观锁可能更加适合,因为它能避免写操作的阻塞。
  1. 可能引发业务失败:由于乐观锁在更新时进行版本检查,一旦出现版本不一致的情况,可能会导致业务失败。这需要开发者在编写业务逻辑时充分考虑这种情况,并进行相应的处理。

总结:乐观锁的优点是可以提高并发性能、减少锁冲突、解决数据一致性问题以及提供更高的灵活性;而缺点是更新失败的概率较高、适用场景有限以及可能引发业务失败。在实际应用中,需要根据具体场景和需求来选择使用乐观锁还是悲观锁


## ✅如何解决线程安全缓存集合类并发冲突问题

线程安全缓存集合类并发冲突问题可以通过以下几种方式解决:


  1. 使用同步机制:可以使用synchronized关键字或Lock接口来实现同步机制,确保同一时间只有一个线程可以访问集合类,从而避免并发冲突。

  1. 使用并发集合类:Java提供了多种并发集合类,如ConcurrentHashMap、CopyOnWriteArrayList等,这些集合类在多线程环境下表现出较好的性能和线程安全性。

  1. 使用乐观锁:乐观锁通过版本号机制来解决并发修改可能导致的数据一致性问题。在提交更新时,会再次检查版本号是否一致,如果不一致则说明有其他用户修改了数据,避免了数据被覆盖或丢失的情况。

  1. 使用读写锁:读写锁允许多个线程同时读取数据,但在写入数据时需要独占式访问,这样可以减少锁的竞争,提高并发性能。

  1. 使用分布式缓存系统:分布式缓存系统可以将数据分散到多个节点上,每个节点都有自己的锁机制,从而避免了单点故障和并发冲突问题。

解决线程安全缓存集合类并发冲突问题需要根据具体场景和需求来选择适合的解决方案。


Demo:


import java.util.concurrent.ConcurrentHashMap;  
import java.util.concurrent.atomic.AtomicInteger;  

/**
*  @author xinbaobaba
*  如何使用乐观锁来解决线程安全缓存集合类的并发冲突问题
*/  
public class ThreadSafeCache<K, V> {  
    private final ConcurrentHashMap<K, V> cache;  
    private final AtomicInteger version;  
  
    public ThreadSafeCache() {  
        cache = new ConcurrentHashMap<>();  
        version = new AtomicInteger(0);  
    }  
  
    public V get(K key) {  
        return cache.get(key);  
    }  
  
    public void put(K key, V value) {  
        while (true) {  
            int currentVersion = version.get();  
            if (currentVersion != 0) {  
                // 如果当前版本号不为0,说明有其他线程正在更新数据,需要重新尝试更新  
                continue;  
            } else {  
                // 更新数据并递增版本号  
                if (version.compareAndSet(currentVersion, currentVersion + 1)) {  
                    V oldValue = cache.putIfAbsent(key, value);  
                    if (oldValue == null) {  
                        // 如果缓存中原本没有该键,则更新成功,返回  
                        return;  
                    } else {  
                        // 如果缓存中原本已经有该键,则表示其他线程已经更新了数据,需要重新尝试更新  
                        version.compareAndSet(currentVersion + 1, currentVersion + 2); // 增加版本号以表示冲突  
                        continue; // 重新尝试更新  
                    }  
                } else {  
                    // 如果在更新过程中,其他线程也修改了版本号,则重新获取当前版本号并继续尝试更新  
                    continue;  
                }  
            }  
        }  
    }  
}

示例中,使用ConcurrentHashMap作为缓存的存储,它提供了线程安全的并发访问。我们使用AtomicInteger作为版本号的存储,并利用其原子性来保证并发访问时的正确性。在put方法中,我们使用while循环来不断尝试更新数据,直到成功为止。在每次尝试更新之前,我们首先获取当前版本号,如果版本号不为0,则说明有其他线程正在更新数据,需要重新尝试更新。如果版本号为0,则表示当前没有其他线程正在更新数据,可以进行更新操作。在更新数据时,我们递增版本号,并使用compareAndSet方法来原子性地更新版本号和缓存数据。如果更新成功,则进一步判断缓存中原本是否已经有该键。如果缓存中原本没有该键,则表示更新成功,返回。如果缓存中原本已经有该键,则表示其他线程已经更新了数据,增加版本号以表示冲突,并重新尝试更新。这样就可以利用乐观锁的机制来解决线程安全缓存集合类的并发冲突问题。


✅ 什么是写时复制?


✅ 什么是COW,如何保证的线程安全?


Copy-On-Write简称COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略


从DK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是 CopyOnWriteArrayListCopyOnWriteArraySetCopyOnWrite容器非常有用,可以在非常多的并发场景中使用到。


CopyOnWriteArrayList 相当于线程安全的ArrayListCopyOnWriteArrayList 使用了一种叫写时复制的方法,当有新元素addCopyOnWriteArrayList时,先从原有的数组中拷贝一份出来,然后在新的数组做写操作,写完之后再将原来的数组引用指向到新数组。


这样做的好处是我们可以对 CopyOnWrite 容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以 CopyOnWrite 容器也是一种读写分离的思想,读和写不同的容器。


注意: CopyOnWriteArrayList的整个add操作都是在锁的保护下进行的。也就是说add方法是线程安全的


CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景。


和ArrayList不同的是,它具有以下特性:


支持高效率并发且是线程安全


因为通常需要复制整个基础数组,所以可变操作 (add()、set() 和 remove() 等等) 的开销很大。

选代器支持hasNext() ,next() 等不可变操作,但不支持可变 remove() 等操作

使用迭代器进行遍历的速度很快,并目不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于 Validator 类实现 ParamValidator,用于校验函数参数

目录 一、前置说明1、总体目录2、相关回顾3、本节目标 二、操作步骤1、项目目录2、代码实现3、测试代码4、日志输出 三、后置说明1、要点小结2、下节准备 一、前置说明 1、总体目录 《 pyparamvalidate 参数校验器&#xff0c;从编码到发布全过程》 2、相关回顾 使用 TypeV…

dubbo的springboot集成

1.什么是dubbo&#xff1f; Apache Dubbo 是一款 RPC 服务开发框架&#xff0c;用于解决微服务架构下的服务治理与通信问题&#xff0c;官方提供了 Java、Golang 等多语言 SDK 实现。使用 Dubbo 开发的微服务原生具备相互之间的远程地址发现与通信能力&#xff0c; 利用 Dubbo …

vmlinux, System.map; cmake的find_package(Clang)产生的变量们; geogebra单位切向量(简单例子)

linux4.15.y内核中的函数个数 依赖关系: vmlinux, vmlinux.bin, bzImage cd /bal/linux-stable/ file vmlinux #vmlinux: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, BuildID[sha1]b99bbd9dda1ec2751da246d4a7ae4e6fcf7d789b, not str…

Git远端删除的分支,本地依然能看到 git remote prune origin

在远端已经删除ylwang_dev_786等三四个分支&#xff0c;本地git branch -a 时 依然显示存在。 执行 git remote show origin 会展示被删除的那些分支 当你在Git远程仓库&#xff08;如GitLab&#xff09;上删除一个分支后&#xff0c;这个变更不会自动同步到每个开发者的本地…

【教学类-45-01】X-Y之间的“三连加“题(a+b+c=)

作品展示&#xff1a; 背景需求&#xff1a; 我常去的大4班孩子们基本都适应了0-5之间的加法题&#xff0c;做题速度极快。 为了增加“花样”&#xff0c;吸引幼儿参与&#xff0c;修改参数&#xff0c;从二连加12变为三连加111。 素材准备: 代码重点 代码展示 X-Y 之间的3…

springboot基于java的小区物业管理系统(保安巡逻绿化消防)设计+jsp

小区物业管理系统采用的是JAVA语言开发&#xff0c;利用MySQL为数据库&#xff0c; 使用IDEA平台来编写代码&#xff0c;框架方面选择的是springbootweb框架&#xff0c;采用B/S结构实现系统。本系统的设计与开发过程中严格遵守软件工程的规范&#xff0c;运用软件设计模式&…

uniapp最简单的底部兼容安全区域显示

效果图&#xff1a; 1.html写上动态padding-bottom <view class"button-wrap" :style"padding-bottom:bottomPaddingrpx"><view class"com-btn cencel-btn">取消</view><view class"com-btn confirm-btn " cl…

Xcalibur软件Qual Brower程序的使用

找到Qual Brower&#xff1a;在System>Program里 打开采集的数据文件*.RAW&#xff0c;软件界面主窗口能查看色谱图和质谱图&#xff1a; 1、图形的放大和拷贝、色谱中查看峰的质谱信息&#xff1a; 点亮如图图像右上角的按钮&#xff0c;可以激活该图形并进行操作&#x…

前端 Node 项目迁徙为桌面 Electron 应用笔记

起因 我的服务器到期了&#xff0c;服务器上有几个服务&#xff0c;人家问这几个网站怎么不好使了&#xff0c;奈何服务器续费太贵租不起了… 但是服务还是要提供的&#xff0c;所以我在想如何把 node 的项目变成桌面端应用&#xff0c;于是有了这个笔记 效果展示 页面没啥…

强化学习10——免模型控制Q-learning算法

Q-learning算法 主要思路 由于 V π ( s ) ∑ a ∈ A π ( a ∣ s ) Q π ( s , a ) V_\pi(s)\sum_{a\in A}\pi(a\mid s)Q_\pi(s,a) Vπ​(s)∑a∈A​π(a∣s)Qπ​(s,a) &#xff0c;当我们直接预测动作价值函数&#xff0c;在决策中选择Q值最大即动作价值最大的动作&…

如何寻找到相对完整的真正的游戏的源码 用来学习?

在游戏开发的学习之路上&#xff0c;理论与实践是并重的两个方面。对于许多热衷于游戏开发的学习者来说&#xff0c;能够接触到真实的、完整的游戏源码无疑是一个极好的学习机会。但问题来了&#xff1a;我们该如何寻找到这些珍贵的资源呢&#xff1f; 开源游戏项目 GitHub:地…

BUUCTF ---> Encrypto

转眼就一月十号了&#xff0c;本来今天不想更的&#xff0c;&#xff08;因为我懒&#xff09;是因为明天要考python&#xff0c;好像还不止 但是呢&#xff0c;发现BUUCTF的密码学模块刚好可以用到py的脚本&#xff0c;那就当时复习一下吧&#xff01;&#xff01; 这里就要介…

http跟https有什么区别?

HTTPS和HTTP的概念&#xff1a; HTTP&#xff1a;是互联网上应用最为广泛的一种网络协议&#xff0c;是一个客户端和服务器端请求和应答的标准&#xff08;TCP&#xff09;&#xff0c;用于从WWW服务器传输超文本到本地浏览器的传输协议&#xff0c;它可以使浏览器更加高效&am…

2024--Django平台开发-Django知识点(六)

day06 Django知识点 今日概要&#xff1a; Form和ModelForm组件【使用】【源码】缓存【使用】ORM【使用】其他&#xff1a;ContentTypes、Admin、权限、分页、信号等 1.Form和ModelForm组件 背景&#xff1a;某个公司后台管理项目。 垃圾 def register(request):"&quo…

Qt/QML编程学习之心得:hicar手机投屏到车机中控的实现(32)

hicar,是华为推出的一款手机APP,有百度地图、华为音乐,更多应用中还有很多对应手机上装在的其他APP,都可以在这个里面打开使用,对开车的司机非常友好。但它不仅仅是用在手机上,它还可以投屏到车机中控上,这是比较神奇的一点。 HiCar本质上是一套智能投屏系统,理论上所有…

人工智能复习

机器学习中线性回归和逻辑回归&#xff1a; 机器学习的分类&#xff1a; 监督学习和无监督学习&#xff0c;半监督学习 监督学习&#xff08;Supervised Learning&#xff09;&#xff1a; 监督学习是一种利用带有标签&#xff08;标记&#xff09;的数据进行训练的机器学习…

用友U8流程审批效率-SQLServer+SSRS

文章目录 @[TOC]1、 需求及效果1.1 需求1.2 效果2、 思路及SQL语句3、实现折叠明细表4、结语1、 需求及效果 1.1 需求 想要查看U8的审批流程,查看流程在哪个节点或人停留的时间,这个单据整个流程走下来需要的时间。可以更加直观方便的查看审批效率 1.2 效果 采用了SSRS上…

【算法每日一练]-动态规划 (保姆级教程 篇15) #纸带 #围栏木桩 #四柱河内塔

目录 今日知识点&#xff1a; 计算最长子序列的方案个数&#xff0c;类似最短路径个数问题 四柱河内塔问题&#xff1a;dp[i]min{ (p[i-k]f[k])dp[i-k] } 纸带 围栏木桩 四柱河内塔 纸带 思路&#xff1a; 我们先设置dp[i]表示从i到n的方案数。 那么减法操作中&#xff…

TensorRt(5)动态尺寸输入的分割模型测试

文章目录 1、固定输入尺寸逻辑2、动态输入尺寸2.1、模型导出2.2、推理测试2.3、显存分配问题2.4、完整代码 这里主要说明使用TensorRT进行加载编译优化后的模型engine进行推理测试&#xff0c;与前面进行目标识别、目标分类的模型的网络输入是固定大小不同&#xff0c;导致输入…

Docker中镜像的相关操作

1.辅助操作 docker version&#xff1a;用查看docker客户端引擎和server端引擎版本信息。 docker info&#xff1a;用来查看docker引擎的详细信息。 docker --help&#xff1a;用来查看帮助信息。 2.镜像Image docker images&#xff1a;查看当前本地仓库中存在哪些镜像。 …