首页 > 其他分享 >并发线程基础第六篇

并发线程基础第六篇

时间:2024-03-31 11:58:54浏览次数:27  
标签:void System 并发 线程 new ref public 第六篇

目录

共 享 模 型 之 无 锁

CAS与volatile

举个例子

CAS工作方式

volatile

为什么无锁效率高

CAS的特点

原子整数

原子引用

为什么需要原子引用类型?

J.U.C并发包提供了:

 比如上面取款的例子,我们这里把账户的类型改为小数BigDecimal类型

ABA问题

原子数组

为什么要有原子数组

函数式接口 

字段更新器 

为什么要有字段更新器

 示例

原子累加器

为什么要学原子累加器

示例

LongAdder的性能提升的原因 

源码之LongAdder

原理之伪共享

Unsafe

概述

获取 Unsafe对象

 使用自定义AtomicData实现之前线程安全的原子整数Account实现


共 享 模 型 之 无 锁

CAS与volatile

举个例子

问题提出,保证account.withdraw()取款方法的线程安全



interface Account {
    // 获取余额
    Integer getBalance();

    // 取款
    void withdraw(Integer amount);

    /**
     * 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
     * 如果初始余额为 10000 那么正确的结果应当是 0
     */
    static void demo(Account account) {
        List<Thread> ts = new ArrayList<>();
        long start = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            ts.add(new Thread(() -> {
                account.withdraw(10);
            }));
        }
        ts.forEach(Thread::start);
        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long end = System.nanoTime();
        System.out.println(account.getBalance()
                + " cost: " + (end - start) / 1000_000 + " ms");
    }
}

class AccountUnsafe implements Account {
    private Integer balance;

    public AccountUnsafe(Integer balance) {
        this.balance = balance;
    }

    @Override
    public Integer getBalance() {
        return balance;
    }

    @Override
    public void withdraw(Integer amount) {
        balance -= amount;
    }
}

执行测试代码

public static void main(String[]args){
        Account.demo(new AccountUnsafe(10000));
        }

290cost:115ms

 为什么不安全

因为这段代码被多个线程共享读写操作,存在安全问题。

解决思路

 加锁解决这个问题当然可以,下面我们采用一种不加锁的实现(乐观锁)

无锁实现,只需要把这个实现方法稍微修改一下就行了。

public class AccountSafe implements Account{
    private AtomicInteger balance;

    public AccountSafe(int balance) {
        this.balance = new AtomicInteger(balance);
    }

    @Override
    public Integer getBalance() {
        return balance.get();
    }

    @Override
    public void withdraw(Integer amount) {
        while (true) {
            //获取余额的最新值
            int prev = balance.get();
            //要修改的余额
            int next = prev - amount;
            //真正修改
            if (balance.compareAndSet(prev,next)) {
                break;
            }
        }
    }
}

CAS工作方式

 其中的关键是 compareAndSet,它的简称就是 CAS (也有 Compare And Swap 的说法),它必须是原子操作。其实 CAS 的底层是 lock cmpxchg 指令(X86 架构),在单核 CPU 和多核 CPU 下都能够保证【比较-交 换】的原子性。

volatile

获取共享变量时,为了保证该变量的可见性,需要使用volatile来修饰。

它可以用来修饰成员变量和静态成员变量,它可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作volatile的值都是直接操作主存,即一个线程对volatile变量的修改,对另外一个线程可见。

所以CAS必须借助volatile才能读取到共享变量的最新值来实现(比较并交换)的效果

为什么无锁效率高

经过上面代码的几轮测试,我们发现无锁运行时间更快,这是为什么呢?

无锁情况下,即使重试失败,但是线程始终在高速运行中,没有停歇,而synchronized在没有获得锁的情况下,进行上下文切换,进入阻塞状态。

CAS的特点

结合CAS和volatile可以实现无锁并发,适用于线程少,多核CPU的场景下。

CAS 体现的是无锁并发、无阻塞并发

  •  因为没有使用 synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一
  • 但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响

原子整数

J.U.C并发包提供了:

  • AtomicBoolean
  • AtomicInteger
  • AtomicLong

以AtomicInteger提供的API为示例

AtomicInteger i = new AtomicInteger(0);
 
// 获取并自增(i = 0, 结果 i = 1, 返回 0),类似于 i++
System.out.println(i.getAndIncrement());
 
// 自增并获取(i = 1, 结果 i = 2, 返回 2),类似于 ++i
System.out.println(i.incrementAndGet());
 
// 自减并获取(i = 2, 结果 i = 1, 返回 1),类似于 --i
System.out.println(i.decrementAndGet());
 
// 获取并自减(i = 1, 结果 i = 0, 返回 1),类似于 i--
System.out.println(i.getAndDecrement());
 
// 获取并加值(i = 0, 结果 i = 5, 返回 0)
System.out.println(i.getAndAdd(5));
 
// 加值并获取(i = 5, 结果 i = 0, 返回 0)
System.out.println(i.addAndGet(-5));
 
// 获取并更新(i = 0, p 为 i 的当前值, 结果 i = -2, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.getAndUpdate(p -> p - 2));
 
// 更新并获取(i = -2, p 为 i 的当前值, 结果 i = 0, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.updateAndGet(p -> p + 2));
 
// 获取并计算(i = 0, p 为 i 的当前值, x 为参数1, 结果 i = 10, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
// getAndUpdate 如果在 lambda 中引用了外部的局部变量,要保证该局部变量是 final 的
// getAndAccumulate 可以通过 参数1 来引用外部的局部变量,但因为其不在 lambda 中因此不必是 final
System.out.println(i.getAndAccumulate(10, (p, x) -> p + x));
 
// 计算并获取(i = 10, p 为 i 的当前值, x 为参数1, 结果 i = 0, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.accumulateAndGet(-10, (p, x) -> p + x));

 这些API,大家有兴趣可以自行练习实验,可以自己写一个线程安全的接口,自己实验。

原子引用

为什么需要原子引用类型?

因为我们想要保护的共享变量并不一定是基本类型,也可以是引用类型

J.U.C并发包提供了:

  • AtomicReference
  • AtomicMarkableReference
  • AtomicStampedReference

 比如上面取款的例子,我们这里把账户的类型改为小数BigDecimal类型

接口

public interface Account1 {
    //获取余额
    BigDecimal getBalance();
    //取款
    void withdraw(BigDecimal amount);

     static void demo(Account1 account){
         List<Thread> ts = new ArrayList<>();
         Long start = System.nanoTime();
         for (int i = 0; i < 1000; i++) {
             ts.add(new Thread(()->{
                     account.withdraw(BigDecimal.TEN);
             }));
         }
         ts.forEach(Thread::start);
         ts.forEach(t ->{
             try {
                 t.join();
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
         });
         long end = System.nanoTime();
         System.out.println(account.getBalance()+"cost:"+(end-start)/1000_000+"ms");
     }
}

实现类

public class AccountSafe1 implements Account1{
    private AtomicReference<BigDecimal> balance;

    public AccountSafe1(BigDecimal balance) {
        this.balance = new AtomicReference<>(balance);
    }

    @Override
    public BigDecimal getBalance() {
        return balance.get();
    }

    @Override
    public void withdraw(BigDecimal amount) {
        while(true){
            BigDecimal prev = balance.get();
            BigDecimal next = prev.subtract(amount);
            if (balance.compareAndSet(prev,next)) {
                break;
            }
        }
    }
}

 测试

@Slf4j
public class Test1 {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            Account1.demo(new AccountSafe1(new BigDecimal(10000)));
        }

    }
}

以上我是采用了CAS来解决并发中对共享资源为引用类型存在线程安全问题,还可以采用锁,自行实验。 

注意 

  • 我们这里采用线程数为1000个,是不符合CAS使用场景的,我们在这里只做测试用。
  • 线程最好不要超过你CPU核心数的时候 ,才能充分发挥它的特长

ABA问题


@Slf4j
public class Test2 {
    static AtomicReference<String> ref = new AtomicReference<>("A");

    public static void main(String[] args) {
        //拿到ref的值
        String prev = ref.get();
        
        //调用other,对ref的值进行修改。
        other();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        //把A的值修改为C,当只有主线程,ref的值肯定没有改变,可以修改成功
        // 但是调用other之后,ref的值被修改过两次,但是最终最新值还是和prev之前拿到的一样,所以也可以修改成功
        ref.compareAndSet(prev,"C");
        //打印ref 的值
        log.debug("{}",ref);

    }
    static void other(){
        Thread t1 = new Thread(() -> {
            ref.set("B");
        });
        Thread t2 = new Thread(() -> {
            try {
                t1.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            ref.set("A");
        });
        t1.start();
        t2.start();

    }
}

 虽然我们上面是否对ref做了修改,但只要prev和最新的ref一样,就可以修改成功。

所以主线程仅能判断出共享变量的值与最初值 A 是否相同,不能感知到这种从 A 改为 B 又 改回 A 的情况,

如果主线程 希望: 只要有其它线程【动过了】共享变量,那么自己的 cas 就算失败,这时,仅比较值是不够的,需要再加一个版本号

@Slf4j
public class Test2 {
    static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A",0);

    public static void main(String[] args) {
        //拿到ref的值
        String prev = ref.getReference();
        int stamp = ref.getStamp();

        //调用other,对ref的值进行修改。
        other();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        ref.compareAndSet(prev,"C",stamp,stamp+1);
        //打印ref 的值
        log.debug("{}",ref.getReference());

    }
    static void other(){
        Thread t1 = new Thread(() -> {
            ref.compareAndSet(ref.getReference(), "B", ref.getStamp(), ref.getStamp()+1);
        });
        Thread t2 = new Thread(() -> {
            try {
                t1.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            ref.compareAndSet(ref.getReference(), "A", ref.getStamp(), ref.getStamp()+1);
        });
        t1.start();
        t2.start();

    }

这时候我们可以看到,修改失败,原因是因为,我们加了版本号,不光要对比最新值,还要对比最新的版本号,只要期间被修改过,我们这里想要把它变成C就会失败。

但是有时候,并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了 AtomicMarkableReference ,这个可以自己动手实验一下。

原子数组

为什么要有原子数组

因为有时候我们想要修改的并不是对象本身,而是对象里面的内容,比如数组,有时候我们并不是想修改数组的引用,而是想修改数组对象里面的内容这时候AtomicReference并不能达到我们的要求,为此J.U.C提供了

  •         AtomicIntegerArray
  •         AtomicLongArray
  •         AtomicReferenceArray

函数式接口 

在Java中,函数式接口是指只包含一个抽象方法的接口。Java 8引入了函数式接口的概念,并提供了 java.util.function 包来定义一些常用的函数式接口,以便在Lambda表达式中使用。以下是几种常见的函数式接口: 

Supplier<T>: 代表一个供应商,它不接受任何参数,但返回一个结果。

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

Consumer<T>: 代表一个消费者,它接受一个参数但不返回结果。

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

 Function<T, R>: 代表一个函数,它接受一个参数并返回一个结果

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

示例:


/**
 * 参数1,提供数组、可以是线程不安全数组或线程安全数组
 * 参数2,获取数组长度的方法
 * 参数3,自增方法,回传 array, index
 * 参数4,打印数组的方法
 */
// supplier 提供者 无中生有 ()->结果
// function 函数 一个参数一个结果 (参数)->结果 , BiFunction (参数1,参数2)->结果
// consumer 消费者 一个参数没结果 (参数)->void, BiConsumer (参数1,参数2)->
private static<T> void demo(
        Supplier<T> arraySupplier,
        Function<T, Integer> lengthFun,
        BiConsumer<T, Integer> putConsumer,
        Consumer<T> printConsumer)
{
        List<Thread> ts=new ArrayList<>();
        T array=arraySupplier.get();
        int length=lengthFun.apply(array);
        for(int i=0;i<length; i++){
        // 每个线程对数组作 10000 次操作
        ts.add(new Thread(()->{
            for(int j=0;j< 10000;j++){
                putConsumer.accept(array,j%length);
                }
            }));
        }
        ts.forEach(t->t.start()); // 启动所有线程
        ts.forEach(t->{
            try{
            t.join();
            }catch(InterruptedException e){
                e.printStackTrace();
                }
            }); // 等所有线程结束
        printConsumer.accept(array);
}

不安全的数组

demo(
     ()->new int[10],
     (array)->array.length,
     (array, index) -> array[index]++,
     array-> System.out.println(Arrays.toString(array))
);

 安全数组

demo(
     ()-> new AtomicIntegerArray(10),
     (array) -> array.length(),
     (array, index) -> array.getAndIncrement(index),
     array -> System.out.println(array)
);

字段更新器 

为什么要有字段更新器

利用字段更新器,可以针对对象的某个域(field)进行原子操作,只能配合volatile修饰的字段使用,否则会出现异常。J.U.C提供了

  • AtomicReferenceFieldUpdater // 域 字段
  • AtomicIntegerFieldUpdater
  • AtomicLongFieldUpdater

 示例

下面我举一个例子,演示一下字段更新器的使用

public class Test {
    public static void main(String[] args) {
        Student stu = new Student();

        AtomicReferenceFieldUpdater updater =
                AtomicReferenceFieldUpdater.newUpdater(Student.class,String.class,"name");
        //打印为true,修改成功;打印false,说明name值已经被其他线程所修改过,不为null了
        System.out.println(updater.compareAndSet(stu, null, "李四"));
    }
}

class Student{
    volatile String name;//必须加上volatile,因为cas操作必须要和volatile一起使用,保证可见性,才能进行cas操作

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}

原子累加器

为什么要学原子累加器

虽然AtomicLong,AtomicInteger也可以做原子累加操作,但是J.U.C为我们提供了效率更高的LongAdder等累加器,我们可以学习一下大师的设计方式,然后针对不同需求,选择更优的API

示例


public class Test3 {
    public static void main(String[] args) {
        demo(
                ()->new AtomicLong(0),
                t->t.getAndIncrement()
        );
        demo(
                ()->new LongAdder(),
                t->t.increment()
        );

    }

    public static <T> void demo(
            Supplier<T> adderSupplied,
            Consumer<T> action
    ){
        //拿到提供的对象
        T t = adderSupplied.get();
        //开始记时
        long start = System.nanoTime();
        List<Thread> th = new ArrayList<>();
        //5个线程,每个累加50w
        for (int i = 0; i < 5; i++) {
            th.add(new Thread(()->{
                for (int j = 0; j < 500000; j++) {
                    action.accept(t);
                }
            }));
        }
        th.forEach(Thread::start);
        th.forEach(item ->{
            try {
                item.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });//主线程等待其他每个线程完成
        long end =System.nanoTime();
        System.out.println(t + "cost:"+(end-start)/1000_000);
    }
}

从上面示例中,我们可以看到LongAdder效率是比较高的,下面我们分析一下原因

LongAdder的性能提升的原因 

性能提升的原因很简单,我们知道cas操作在多个线程操作同一共享变量的时候,会一直while(true)进行判断,当竞争激烈的时候,cas重试次数也因此增加;但是LongAdder,在有竞争的时候,设置多个累加单元(Cells),线程一累加Thread-0,线程二累加Thread-1...,最后将结果累加汇总,这样它们在累加时操作不同的累加单元,减少了cas重试失败,从而提升性能。

源码之LongAdder

LongAdder 是并发大师 @author Doug Lea (大哥李)的作品,设计的非常精巧 LongAdder 类有几个关键域


// 累加单元数组, 懒惰初始化
transient volatile Cell[] cells;
 
// 基础值, 如果没有竞争, 则用 cas 累加这个域
transient volatile long base;
 
// 在 cells 创建或扩容时, 置为 1, 表示加锁
transient volatile int cellsBusy;

原理之伪共享

// 防止缓存行伪共享
@sun.misc.Contended 
static final class Cell {
     volatile long value;
     Cell(long x) { value = x; }
 
 // 最重要的方法, 用来 cas 方式进行累加, prev 表示旧值, next 表示新值
 final boolean cas(long prev, long next) {
     return UNSAFE.compareAndSwapLong(this, valueOffset, prev, next);
 }
 // 省略不重要代码
}

 伪共享(False Sharing)是一种与缓存相关的性能问题,它出现在多个线程同时访问同一缓存行的不同部分时。虽然这些线程可能在不同的变量上进行操作,但它们共享同一缓存行的存在导致了额外的缓存同步开销,降低了程序的性能。使用 @Contended 注解(JDK8+): JDK8 中引入了 @Contended 注解,可以在变量声明时使用该注解来告诉 JVM 在存储该变量时对其进行填充,从而避免伪共享问题。

下面我们从缓存来看一下

从cpu到大约需要的时钟周期
寄存器1 cycle (4GHz 的 CPU 约为0.25ns)
一级缓存3~4 cycle
二级缓存10~20 cycle
三级缓存40~45 cycle
内存120~240 cycle
  • 因为cpu从内存读取速度与从缓存读取速度差异很大,需要靠预读取数据到缓存中来提升效率。
  • 而缓存以缓存行为单位,每个缓存行对应着一块内存,一般是64byte.
  • 缓存的加入会造成数据副本的产生,即同一份数据会缓存在不同核心的缓存行中。
  • CPU要保证数据的一致性,如果某个CPU核心更改了数据,其他CPU核心对应的整个缓存行必须失效。

 

 因为 Cell 是数组形式,在内存中是连续存储的,一个 Cell 为 24 字节(16 字节的对象头和 8 字节的 value),因 此缓存行可以存下 2 个的 Cell 对象。这样问题来了:

  • Core-0 要修改 Cell[0]
  • Core-1 要修改 Cell[1]

无论谁修改成功,都会导致对方 Core 的缓存行失效,比如 Core-0 中 Cell[0]=6000, Cell[1]=8000 要累加 Cell[0]=6001, Cell[1]=8000 ,这时会让 Core-1 的缓存行失效

@sun.misc.Contended 用来解决这个问题,它的原理是在使用此注解的对象或字段的前后各增加 128 字节大小的 padding,从而让 CPU 将对象预读至缓存时占用不同的缓存行,这样,不会造成对方缓存行的失效  

Unsafe

概述

Unsafe对象提供了非常底层的,操作内存,线程的方法,Unsafe对象不能直接调用,只能通过反射获得。AtomicInteger,LockSupport中的park,unpark方法底层都是采用Unsafe实现的。

获取 Unsafe对象

public class Test4 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        //拿到域对象,由于theUnsafe为静态私有的,所以要用getDeclaredField(),不能用getField();
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        //设置让不让拿这个私有对象,允不允许访问
        theUnsafe.setAccessible(true);
        //拿到theUnsafe对象
        Unsafe o = (Unsafe) theUnsafe.get(null);
        System.out.println(o);

    }
}


sun.misc.Unsafe@27c170f0

Process finished with exit code 0

 演示Unsafe的一些API

public class Test4 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        //拿到域对象,由于theUnsafe为静态私有的,所以要用getDeclaredField(),不能用getField();
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        //设置让不让拿这个私有对象
        theUnsafe.setAccessible(true);
        //拿到theUnsafe对象
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
        //拿到每个域的偏移量,id域偏移量
        long idOffset = unsafe.objectFieldOffset(Teacter.class.getDeclaredField("id"));
        //name域偏移量
        long nameOffset = unsafe.objectFieldOffset(Teacter.class.getDeclaredField("name"));
        Teacter teacter = new Teacter();
        //cas操作,如果有其他线程,要把这部分代码放到while里面,不断尝试
        unsafe.compareAndSwapInt(teacter,idOffset,0,1);
        unsafe.compareAndSwapObject(teacter,nameOffset,null,"xiaoming");
        System.out.println(teacter);
    }
}

class Teacter{
    volatile int id;
    volatile String name;

    @Override
    public String toString() {
        return "Teacter{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

 使用自定义AtomicData实现之前线程安全的原子整数Account实现

获取Unsafe

public class UnsafeAccessor {
    static Unsafe unsafe;

    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            unsafe = (Unsafe) theUnsafe.get(null);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new Error(e);
        }
    }

    static Unsafe getUnsafe() {
        return unsafe;
    }
}

设置自定义AtomicData

class AtomicData {
    private volatile int data;
    static final Unsafe unsafe;
    static final long DATA_OFFSET;

    static {
        unsafe = UnsafeAccessor.getUnsafe();
        try {
            // data 属性在 DataContainer 对象中的偏移量,用于 Unsafe 直接访问该属性
            DATA_OFFSET = unsafe.objectFieldOffset(AtomicData.class.getDeclaredField("data"));
        } catch (NoSuchFieldException e) {
            throw new Error(e);
        }
    }

    public AtomicData(int data) {
        this.data = data;
    }

    public void decrease(int amount) {
        int oldValue;
        while(true) {
            // 获取共享变量旧值,可以在这一行加入断点,修改 data 调试来加深理解
            oldValue = data;
            // cas 尝试修改 data 为 旧值 + amount,如果期间旧值被别的线程改了,返回 false
            if (unsafe.compareAndSwapInt(this, DATA_OFFSET, oldValue, oldValue - amount)) {
                return;
            }
        }
    }

    public int getData() {
        return data;
    }
}

实现

Account.demo(new Account() {
    AtomicData atomicData = new AtomicData(10000);
    @Override
    public Integer getBalance() {
        return atomicData.getData();
    }
    @Override    
    public void withdraw(Integer amount) 
        {        atomicData.decrease(amount);    }
});

标签:void,System,并发,线程,new,ref,public,第六篇
From: https://blog.csdn.net/2401_83045332/article/details/137175709

相关文章

  • Java的心脏:深入解析Java虚拟机、进程与线程的精妙互动
    一、定义进程(Process)和线程(Thread)是操作系统中非常基础且重要的概念,它们对于理解程序的执行、资源分配和并发编程至关重要。我将从操作系统(OS)和Java编程语言的角度来详细解释这两个概念。从操作系统的角度进程:定义:进程是操作系统进行资源分配和调度的基本单位。它是一......
  • JUC并发编程(七)
    1、不可变对象1.1、概念        不可变类是指一旦创建对象实例后,就不能修改该实例的状态。这意味着不可变类的对象是不可修改的,其内部状态在对象创建后不能被更改。不可变类通常具有以下特征:实例状态不可改变:一旦不可变类的对象被创建,其内部状态(字段或属性)将不会改......
  • 如果有100个请求,如何控制并发?
    题目现有100个请求需要发送,请设计一个算法,使用Promise来控制并发(并发数量最大为10),来完成100个请求;首先先模拟下100个请求://请求列表constrequestList=[];//为了方便查看,i从1开始计数for(leti=1;i<=100;i++){requestList.push(()=>new......
  • 【Java多线程】7——阻塞队列&线程池
    7线程池⭐⭐⭐⭐⭐⭐Github主页......
  • 描述C语言中的进程和线程之间的区别
    描述C语言中的进程和线程之间的区别在C语言中,进程和线程是两个非常重要的概念,它们在操作系统中各自扮演着独特的角色。理解它们之间的区别对于编写高效、可维护的并发程序至关重要。下面将详细阐述进程和线程在C语言中的区别。首先,我们来探讨进程的概念。进程是操作系统分配......
  • 处理并发冲突
    处理并发冲突项目2023/10/0512个参与者反馈本文内容开放式并发本机数据库生成的并发令牌应用程序管理的并发令牌解决并发冲突显示另外2个提示可在GitHub上查看此文章的示例。在大多数情况下,数据库会由多个应用程序实例并发使用,每个实例对数据执行独立修改。在同一时间修......
  • 多线程的使用
    多线程并发和并行并行:在同一时刻,有多个任务在多个CPU上同时进行并发:在同一时刻,有多个任务在单个CPU上交替进行进程和线程进程:进程简单地说就是在多任务操作系统中,每个独立执行的程序,所以进程也就是“正在进行的程序”。(Windows系统中,我们可以在任务管理器中看到进程)线程......
  • Java(2) ----- 异常、多线程、同步安全、死锁、并发包、Lambda表达式、Stream流
    异常方法默认都可以自动抛出运行时异常!自定义异常:(1)自定义编译时异常1、定义一个异常类继承Exception2、重写构造器3、在出现异常的地方用thrownew自定义对象抛出4、编译时异常是编译阶段就报错,提醒跟家强烈,一定需要处理!(2)自定义运行时异常1、定义一个异常类继承RunTimeE......
  • 【并发编程】线程的基础概念
    一、基础概念1.1进程与线程A什么是进程?进程是指运行中的程序。比如我们使用钉钉,浏览器,需要启动这个程序,操作系统会给这个程序分配一定的资源(占用内存资源)。什么线程?线程是CPU调度的基本单位,每个线程执行的都是某一个进程的代码的某个片段。举个栗子:房子与人比如现......
  • MogDB学习笔记之 -- 了解pagewriter线程
    MogDB学习笔记之--了解pagewriter线程本文出处:https://www.modb.pro/db/183172在前面的MogDB学习系列中,我们了解了核心的bgwriter进程,今天继续来学习另外一个主要的线程,即pagewriter;首先来看下数据库相关的参数设置:postgres=#selectname,setting,category,context......