并发程序的噩梦——数据竞争

Java内存马攻防实战——攻击基础篇 在红蓝对抗中,攻击方广泛应用webshell等技术在防守方提供的服务中植入后门,防守方也发展出各种技术来应对攻击,传统的落地型webshell很容易被攻击方检测和绞杀。而内存马技术则是通过在运行的服务中直接插入运行攻击者的webshell逻辑,利用中间件的进程执行某些恶意代码,而不依赖实体文件的存在实现的服务后门,因此具有更好的隐蔽性,也更容易躲避传统安全监测设备的检测。 本文以Java语言为基础讨论内存马技术的攻防,分为两个篇章,分别是攻击基础篇和防护应用篇。本篇攻击基础篇介绍了Java Servlet 阅读详情

并发程序的噩梦——数据竞争

前言

在本文当中我主要通过不同线程对同一个数据进行加法操作的例子,层层递进,使用忙等待、 synchronized 和锁去解决我们的问题,切实体会为什么 数据竞争 是并发程序的噩梦。

问题介绍

在本文当中会有一个贯穿全文的例子:不同的线程会对一个全局变量不断的进行加的操作!然后比较结果,具体来说我们设置一个静态类变量 data ,然后使用两个线程循环10万次对 data 进行加一操作!!!

像这种多个线程会存在同时对同一个数据进行修改操作的现象就叫做 数据竞争 。 数据竞争 会给程序造成很多不可预料的结果,让程序存在许多漏洞。而我们上面的任务就是一个典型的 数据竞争 的问题。

并发不安全版本

在这一小节我们先写一个上述问题的并发不安全的版本:

public class Sum {

    public static int data;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100000; i++)
                data++;
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100000; i++)
                data++;
        });

        t1.start();
        t2.start();
        // 让主线程等待 t1 和 t2
        // 直到 t1 和 t2 执行完成
        t1.join();
        t2.join();
        System.out.println(data);
    }
}
// 输出结果
131888

上面两个线程执行的结果最终都会小于 200000 ,为什么会出现这种情况呢?

我们首先来看一下内存的逻辑布局图:

data 全局变量保存在 主内存 当中,当现成开始执行的时候会从 主内存 拷贝一份到线程的 工作内存 当中,也就是线程的 本地内存 ,在本地进行计算之后就会将 本地内存 当中的数据同步到 主内存

我们现在来模拟一下出现问题的过程:

  • 主内存 data 的初始值等于0,两个线程得到的 data 初始值都等于0。

  • 现在线程一将 data 加一,然后线程一将 data 的值同步回主内存,整个内存的数据变化如下:

  • 现在线程二 data 加一,然后将 data 的值同步回主内存(将原来主内存的值覆盖掉了):

我们本来希望 data 的值在经过上面的变化之后变成 2 ,但是线程二覆盖了我们的值,因此在多线程情况下,会使得我们最终的结果变小。

忙等待(Busy waiting)

那么我们能在不使用锁或者 synchronized 的情况实现上面这个任务吗?答案是可以,这种方法叫做 忙等待 。具体怎么做呢,我们可以用一个布尔值 flag 去标识是那个线程执行 sum++ ,我们先看代码然后进行分析:

public class BusyWaiting {

    public static int sum;
    public static boolean flag;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                // 当 flag == false 的时候这个线程进行
                // sum++ 操作然后让 flat = true
                // 让另外一个线程进行 sum++ 操作
                while (flag);
                sum++;
                flag = true;
                System.out.println("Thread 1 : " + sum);
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                // 当 flag = true 的之后这个线程进行
                // sum++ 操作然后让 flat = false
                // 让另外一个线程进行 sum++ 操作
                while (!flag) ;
                sum++;
                flag = false;
                System.out.println("Thread 2 : " + sum);
            }
        });
        t1.start();
        t2.start();
        // 让主线程等待 t1 和 t2
        // 直到 t1 和 t2 执行完成
        t1.join();
        t2.join();
        System.out.println(sum);
    }
}

上面代码的流程是一个线程进行完 sum++ 操作之后会将自己的 flag 变成另外一个值,然后自己的 while 循环当中的条件会一直为 true 自己就会一直处于 while 循环当中,然后另外一个线程的 while 循环条件会变成 false ,则另外一个线程会执行 sum++ 操作,然后将 flag 变成另外一个值,然后线程又开始执行了......

忙等待中数据竞争的BUG

但是上面的代码会出现问题,就是在执行一段时间之后两个线程都会卡死,都会一直处于死循环当中。这是因为一个线程在更新完 flag 之后,另外一个线程的 flag 值没有更新,也就是说两个线程的 flag 值不一样,这样就都会处于死循环当中。出现这个问题的原因是一个线程更新完 flag 之后,另外一个线程的 flag 使用的还是旧值。

比如在某个时刻 主内存 当中的 flag 的值等于 false , 线程t1 和 线程t2 当中的 flag 的值都等于 false ,这个情况下 线程t1 是可以进行 sum++ 操作的,然后 线程t1 进行 sum++ 操作之后将 flag 的值改成 true ,然后将这个值同步更新回 主内存 ,但是此时 线程t2 拿到的还是旧值 false ,他依旧处于死循环当中,就这样两个线程都处于死循环了。

上面的问题本质上是一个数据可见性的问题,也就是说一个线程修改了 flag 的值,另外一个线程拿不到最新的值,使用的还是旧的值,而在 java 当中给我们提供了一种机制可以解决数据可见性的问题。在 java 当中我们可以使用 volatile 去解决数据可见性的问题。当一个变量被volatile关键字修饰时,对于共享资源的写操作先要修改工作内存,但是修改结束后会立刻将其刷新到主内存中。当其他线程对该 volatile 修饰的共享资源进行了修改,则会导致当前线程在工作内存中的共享资源失效,所以必须从主内存中再次获取。这样的话就可以保证共享数据的可见性了。因为某个线程如果修改了 volatile 修饰的共享变量时,其他线程的值会失效,然后重新从主内存当中加载最新的值,关于 volatile 的内容还是比较多,在本文当中我们只谈他在本文当中作用。

修改后的正确代码如下:

public class BusyWaiting {

    public static volatile int sum;
    public static volatile boolean flag;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                // 当 flag == false 的时候这个线程进行
                // sum++ 操作然后让 flat = true
                // 让另外一个线程进行 sum++ 操作
                while (flag);
                sum++;
                flag = true;
                System.out.println("Thread 1 : " + sum);
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                // 当 flag = true 的之后这个线程进行
                // sum++ 操作然后让 flat = false
                // 让另外一个线程进行 sum++ 操作
                while (!flag) ;
                sum++;
                flag = false;
                System.out.println("Thread 2 : " + sum);
            }
        });
        t1.start();
        t2.start();
        // 让主线程等待 t1 和 t2
        // 直到 t1 和 t2 执行完成
        t1.join();
        t2.join();
        System.out.println(sum);
    }
}

上面的 sum 也需要使用 volatile 进行修饰,因为如果某个线程 ++ 之后,如果另外一个线程没有更新最新的值就进行 ++ 的话,在数据更新回主内存的时候就会覆盖原来的值,最终的结果就会变小,因此需要使用 volatile 进行修饰。

在上文当中我们主要分析了如何使用 忙等待 来解决我们的问题,但是忙等待有一个很大的问题就是线程会不断的进行循环,这很消耗CPU资源,在下文当中我们将主要介绍两种方法解决本文开头提出的问题,而且可以不进行循环操作,而是将线程挂起,而不是一直在执行。

synchronized并发安全版本

synchronized 是 java 语言的关键字,它可以用来保证程序的原子性。在并发的情况下我们可以用它来保证我们的程序在某个时刻只能有一个线程执行,保证同一时刻只有一个线程获得 synchronized 锁对象。

public class Sum01 {
    public static int sum;

    public static synchronized void addSum() {
        for (int i = 0; i < 100000; i++)
            sum++;
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(Sum01::addSum);
        Thread t2 = new Thread(Sum01::addSum);

        t1.start();
        t2.start();
        // 让主线程等待 t1 和 t2
        // 直到 t1 和 t2 执行完成
        t1.join();
        t2.join();

        System.out.println(sum);
    }
}

// 输出结果
200000

上面的代码 addSum 方法加入了 synchronized 进行修饰,在 java 当中被 synchronized 的静态方法在同一个时刻只能有一个线程能够进入,也就是说上面的代码会让线程 t1 或者 t2 先执行 addSum 函数,然后另外一个线程在进行执行,那这个跟串行执行就一样了。那么我们就可以不在静态方法上加 synchronized 的关键字,可以使用静态代码块:

public class Sum02 {

    public static int sum;
    public static Object lock = new Object();

    public static void addSum() {
        for (int i = 0; i < 100000; i++)
            synchronized (lock) {
            sum++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(Sum02::addSum);
        Thread t2 = new Thread(Sum02::addSum);
        t1.start();
        t2.start();
        // 让主线程等待 t1 和 t2
        // 直到 t1 和 t2 执行完成
        t1.join();
        t2.join();

        System.out.println(sum);
    }
}

上面代码虽然没有使用用 synchronized 修饰的静态方法,但是上面的代码使用了用 synchronized 修饰的同步代码块,在每一个时刻只能有一个线程执行下面这段代码:

// synchronized 修饰的代码块称作同步代码块 ,
// lock 是一个 全局的静态类变量 只有竞争到 lock 对象的线程
// 才能够进入同步代码块 同样的每一个时刻只能有一个线程进入
synchronized (lock) {
    sum++;
}

上面代码虽然没有使用静态同步方法( synchronized 修饰的静态方法),但是有同步代码块( synchronized 修饰的代码块),在一个代码当中会有 100000 次进入同步代码块,这里也花费很多时间,因此上面的代码的效率也不高。

其实我们可以用一个临时变量存储 100000 次加法的结果,最后一次将结果加入到 data 当中:

public class Sum03 {
    public static int sum;
    public static Object lock = new Object();

    public static void addSum() {
        int tempSum = 0;
        for (int i = 0; i < 100000; i++)
            tempSum++;
        synchronized (lock) {
            sum += tempSum;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(Sum03::addSum);
        Thread t2 = new Thread(Sum03::addSum);
        t1.start();
        t2.start();
        // 让主线程等待 t1 和 t2
        // 直到 t1 和 t2 执行完成
        t1.join();
        t2.join();

        System.out.println(sum);
    }
}

使用锁进行临界区的保护

临界区:像这种与数据竞争有关的代码块叫做临界区,比如上文代码当中的 sum++ 。

在 java 当中除了使用 synchronized 形成同步方法或者同步代码块的方法保证多个线程访问临界区的顺序,还可以使用锁对临界区进行保护。在 java 当中一个非常常用的锁就是可重入锁 ReentrantLock ,可以保证在 lock 和 unlock 之间的区域在每一个时刻只有一个线程在执行。

import java.util.concurrent.locks.ReentrantLock;

public class Sum04 {

    public static int sum;
    public static ReentrantLock lock = new ReentrantLock();


    public static void addSum() {
        int tempSum = 0;
        for (int i = 0; i < 100000; i++)
            tempSum++;
        lock.lock();
        try {
            sum += tempSum;
        }catch (Exception ignored) {

        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(Sum04::addSum);
        Thread t2 = new Thread(Sum04::addSum);
        t1.start();
        t2.start();
        // 让主线程等待 t1 和 t2
        // 直到 t1 和 t2 执行完成
        t1.join();
        t2.join();

        System.out.println(sum);
    }
}

synchronized和锁对可见性的影响

在上文当中我们仅仅提到了 synchronized 和锁可以保证在每一个时刻只能有一个线程在临界区当中执行,这一点其实前面的忙等待也可以实现,但是后面我们提到了第一版忙等待的实现是有错误的,在程序运行的时候程序会陷入死循环。因此 synchronized 和锁也会有这样的问题,但是为什么 synchronized 和锁可以使得程序能够正常执行呢?原因如下:

  • synchronized 关键字能够保证可见性, synchronized 关键字能够不仅能够保证同一时刻只有一个线程获得锁,并且还会确保在锁释放之前,会将对变量的修改刷新到主内存当中。
  • JDK 给我们提供的锁工具(比如 ReentrantLock )也能够保证可见性,锁的lock方法能够保证在同一时刻只有一个线程获得锁然后执行同步方法,并且会确保在锁释放(锁的unlock方法)之前会将对变量的修改刷新到主内存当中。

数据竞争的知名后果——死锁

死锁是由于多个线程相互之间竞争资源而形成的一种相互僵持的局面。

在前面的忙等待当中的那个错误如果不仔细思考是很容易忽略的,由此可见 数据竞争 给并发编程带来的难度。下面一个典型的存在死锁可能的代码:

public class DeadLock {

    public static Object fork = new Object();
    public static Object chopsticks = new Object();

    public static void fork() {
        System.out.println(Thread.currentThread().getName() +  "想获得叉子");
        synchronized (fork) {
            System.out.println(Thread.currentThread().getName() +  "已经获得叉子");
            System.out.println(Thread.currentThread().getName() +  "想获得筷子");
            synchronized (chopsticks) {
                System.out.println(Thread.currentThread().getName() +  "已经获得筷子");
            }
        }
    }

    public static void chopsticks() {
        System.out.println(Thread.currentThread().getName() +  "想获得筷子");
        synchronized (chopsticks) {
            System.out.println(Thread.currentThread().getName() +  "已经获得筷子");
            System.out.println(Thread.currentThread().getName() +  "想获得叉子");
            synchronized (fork) {
                System.out.println(Thread.currentThread().getName() +  "已经获得叉子");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(DeadLock::fork);

        Thread t2 = new Thread(DeadLock::chopsticks);
        t1.start();
        t2.start();

        t1.join();
        t2.join();
    }
}
// 输出结果
Thread-0想获得叉子
Thread-1想获得筷子
Thread-0已经获得叉子
Thread-0想获得筷子
Thread-1已经获得筷子
Thread-1想获得叉子
// 在这里已经卡死
  • 线程1想先获得叉子,再获得筷子。
  • 线程2想先获得筷子,再获得叉子。

假如有一个状态线程1已经获得叉子,线程2已经获得筷子,现在线程1想获得线程2手中的筷子,线程2想获得线程1的叉子,由此产生一个窘境,两个线程都想从对方获得东西,也就是说两个线程都无法得到资源,两个线程都卡死了,因而产生了死锁。根据上面的分析我们可以发现死锁产生最根本的原因还是数据竞争。

死锁产生的条件

  • 互斥条件:一个资源只能被一个线程占有,如果其他线程想得到这个资源必须由其他线程释放。
  • 不剥夺条件:一个线程获得的资源在没有使用完之前不能被其他线程强行获得,只能由线程主动释放。
  • 请求保持条件:至少保持了一个资源而且提出新的资源请求,比如上面线程1获得了叉子又请求筷子。
  • 循环等待条件:线程1请求线程2的资源,线程2请求线程3的资源,....,线程 \(N\) 请求线程1的资源。

如果想破坏死锁,那就去破坏上面四个产生死锁的条件即可。

总结

在本篇文章当中主要通过一道并发的题,通过各种方法去解决,在本文当中最重要的例子就是 忙等待 了,在 忙等待 的例子当中我们分析我们程序的问题,体会了 数据竞争 问题的复杂性,他会给我们的并发程序造成很多的问题,比如后面提到的死锁的问题。除此之外我们还介绍了关于 volatile 和 synchronized 还有锁对数据可见性的影响。

C++并发编程实战:从数据竞争到线程池的硬核开发指南 并发编程是现代软件开发的核心技术,它允许多个任务同时执行,从而充分利用多核处理器的计算潜力。其基本原理是通过线程、进程或协程实现任务的并行处理,但随之而来的是数据竞争、死锁和内存可见性等挑战。这项技术的核心价值在于提升程序性能、响应能力和资源利用率,广泛应用于服务器后端、游戏引擎、音视频处理和高性能计算等领域。针对数据竞争问题,C++标准库提供了互斥量(Mutex)和锁(Lock)等同步原语来保护共享数据;而线程池模式则能有效管理线程生命周期,避免频繁创建销毁的开销。本文聚焦于C++并发编程的实战应用,深入 阅读详情

相关推荐

C++11并发编程入门:从线程创建到数据竞争与互斥锁实战

并发编程是现代软件开发中提升程序性能和响应能力的关键技术。其核心原理在于允许程序同时处理多个任务,在单核CPU上通过快速任务切换实现宏观并行,在多核CPU上则能实现真正的物理并行。这项技术的核心价值在于充分利用多核硬件资源,解决阻塞操作导致的效率低下问题,并简化如网络服务器等天然并发场景的建模。在实际应用场景中,C++11标准库引入了 `std::thread` 等工具,极大简化了跨平台多线程开发。然而,实现高效并发也带来了数据竞争和死锁等挑战,这要求开发者深入理解并正确使用互斥锁等同步机制。本文将以C++

weixin_30540691的博客 607

C++并发编程调试实战:六步排查法解决数据竞争与死锁

并发编程是现代软件开发中提升性能的核心技术,它允许多个任务同时执行以充分利用多核处理器资源。其基本原理是通过线程、进程等机制实现任务并行,但随之而来的是对共享数据安全访问的挑战,若同步机制不当,便会引发数据竞争和死锁等问题,导致程序崩溃或行为异常。从技术价值看,掌握并发调试能力是构建高可靠、高性能系统的关键,尤其在后台服务、实时交易等对稳定性要求极高的领域。应用场景广泛,从Web服务器处理海量请求到数据库系统的并发控制,都离不开稳健的并发设计。本文聚焦C++并发调试的工程实践,针对数据竞争、内存错误等典型热

weixin_29212867的博客 227

C++高性能并发编程实战:从锁优化到无锁数据结构与内存管理

并发编程是现代软件开发的核心技术之一,它允许多个任务同时执行,以充分利用多核处理器的计算能力。其原理在于通过线程、进程或协程实现任务并行,关键技术包括锁、原子操作、内存屏障和无锁数据结构。在C++等系统级语言中,高性能并发能显著提升吞吐量和响应速度,技术价值巨大,广泛应用于服务器后端、游戏引擎、金融交易和高性能计算等场景。本文聚焦C++高性能并发实战,深入探讨如何通过优化锁粒度、避免伪共享、设计无锁队列以及实施精细化的内存管理策略(如使用线程局部存储和高性能内存池)来解决实际开发中的性能瓶颈与稳定性问题,帮

weixin_30702441的博客 281

Java线程中的问题——竞态条件、数据竞争

1.竞态条件 说得通俗一点,就是线程A 需要判断一个变量的状态,然后根据这个变量的状态来执行某个操作。在执行这个操作之前,这个变量的状态可能会被其他线程修改。 典型情况: (1)check-then-act if(a == 10.0) { b = a / 2.0; } 在单线程的环境下,不会出现线程安全问题。 在多线程环境下,如果a和b是局部变量,也不会出现线程安全问题,因为...

zhm1563550235的博客 1745

fpga在线升级 linux_仅5000行Verilog代码、可在FPGA上跑轻量级Linux系统的RISC-V内核

本文发布于www.hackster.io,由东京理工大学的计算机学院开发,专用于教学与加速用(苏老师也一直认为这是RISC-V当前的主要方向)。目前市场上开源的RISC-V内核已经很多,但很少有公开的RISC-V计算系统是非常轻型且能跑Linux系统的。这一款RISC-V内核就可以运行在FPGA上,用Verilog编写的,需要很少硬件的资源就能跑起来,所以可以运行在一个低成本的FPGA上。来看一下...

weixin_34083807的博客 1867

SimMechanics二连杆仿真避坑指南:Solid模块坐标系设置与Rigid Transform常见错误

本文详细解析了SimMechanics中二连杆平面臂仿真的常见问题,重点探讨Solid模块坐标系设置与Rigid Transform模块的配置陷阱。通过实战案例和MATLAB代码示例,揭示多体系统仿真中的关键错误点,包括坐标系方向定义、参数匹配、端口连接规则等,帮助工程师有效规避仿真风险,提升建模效率。

weixin_29309213的博客 413

JMM对数据竞争的定义

----------------------------------------------------------------------------摘自 书名:Java并发编程的艺术 作者:方腾飞;当程序未正确同步时,就可能会存在数据竞争。当代码中包含数据竞争时,程序的执行往往产生违反直觉的结果(在一个线程中写一个变量,在另一个线程读同一个变量,而且写和读没有通过同步来排序。如果一个多线程程序能正确同步,这个程序将是一个没有数据竞争的程序。就是算错了,这里说的委婉了。

张紫娃的博客 608

Flink CDC 1.18.1 Oracle 数据同步到postgresql

8、修改Flink的配置文件,主要是各种服务的绑定地址,默认为localhost,统统改为0.0.0.0,如:rest.address: 0.0.0.0 #localhost。将flink-sql-connector-oracle-cdc-3.0.1.jar复制到flink-1.18.1/lib目录中。6、下载 flink-connector-jdbc-3.1.1-1.17.jar,postgresql-42.7.3.jar。7、安装postgresql就不说了,相信你已经有了数据库了。

ynzhangyao的专栏 1800

Commons Collections Java反序列化漏洞利用

0x00 漏洞原理与成因  漏洞作者写的技术分析:http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/ 腾讯TSRC:  https://security.t

parker的专栏 2685

ICT模拟零件测试方法--电容测试

通常,小到 100 pF 的电容需要补偿,因为夹具和系统的电容(20 到 50 pF)相对于被测设备的值是显著的。软件在需要补偿的电容测试中加入 comp 选项,而在不需要补偿的电容测试中加入 nocomp 选项。在调试期间,通过向电容语句添加 pc 选项验证您的电容测试。在测试单独的电容时,从测量结果中减去学到的值,然后与测试限制进行比较以决定通过或失败。请注意,如果用于需要补偿的电容测试上的感应总线使用了单独的探针,测量将无效。在测试电容时,应注意两个方面:电容的电阻元件,以及夹具和系统本身的电容。

可可南木的博客 1220

C# 进阶:打造WinForms中的动态交互式UserControl

本文深入探讨了如何在C# WinForms中创建动态交互式UserControl,以提升桌面应用的用户体验和代码复用性。通过剖析UserControl生命周期、事件体系,并结合一个可拖拽缩放的分割面板实战案例,详细讲解了鼠标键盘事件处理、自定义绘制、状态管理以及性能优化等核心技巧,帮助开发者封装复杂交互逻辑,打造专业级自定义控件。

weixin_29279153的博客 462

C++多线程编程实战:从数据竞争到线程安全设计

并发编程是现代软件开发的核心技术之一,它通过允许多个执行流同时运行来提升程序性能。其基本原理在于操作系统对线程的调度与共享内存模型,这带来了执行效率的提升,但也引入了数据竞争和死锁等同步挑战。这项技术的核心价值在于能够充分利用多核处理器资源,显著提高吞吐量和响应速度,广泛应用于高性能服务器、实时系统和游戏引擎等场景。在实践中,C++标准库提供了std::thread、std::mutex等工具来管理线程生命周期和共享状态,而理解如何避免数据竞争和设计线程安全的类则是实际工程中的关键。本文将深入探讨这些同步原

abc20899的专栏 458

别再为UE5像素流部署踩坑了!Windows服务器单实例保姆级避坑指南(含Node.js证书、coturn空文件夹等常见问题解决)

本文提供了UE5像素流在Windows服务器单实例部署中的全面解决方案,涵盖Node.js证书过期、coturn空文件夹等常见问题。通过详细的步骤指导和配置建议,帮助开发者高效完成部署,避免常见陷阱,特别适合局域网环境下的应用场景。

weixin_30315435的博客 466

Java上进了,JDK21 要来了,并发编程再也不是噩梦了

之前写过,还没过多久,JDK 21 马上就出来了,看来 Java 这几年真的是长进了。目前 Java 的最新稳定版是 JDK 20,但这是个过渡版,JDK21就是 LTS 版的了,也快要发布了,在今年9月份(也就是2023年9月)就要正式发布了。但是,猜都不用猜,你肯定还在用 Java 8 吧!

weixin_42907150的博客 1424

Cisco Firepower FMCv修改管理Ip方法

Cisco Firepower FMCv修改管理Ip方法

funnycoffee123的博客 1091

手把手教你在openclaw中白嫖英伟达羊毛调用GLM5/Minimax2.1/Kimi K 2.5等模型

本文介绍如何在OpenClaw中免费调用英伟达平台的GLM5、Minimax2.1和Kimi K2.5等模型。通过在openclaw.json配置文件中添加NVIDIA的API信息,设置免费模型参数(如contextWindow和maxTokens),并执行简单命令即可切换默认模型。该方法可节省token消耗成本,最后需重启gateway使配置生效。文中还提供了使用其他API平台时的配置模板。

Science prince的博客 5103

从‘单线程’到‘多进程’:聊聊程序并发执行时,你的代码为啥会‘跑飞’(不可再现性详解)

本文深入探讨了程序并发执行时出现的不可再现性问题,解释了为何同样的代码在多线程环境下会产生不同结果。通过分析进程、前趋图等概念,揭示了并发编程中的间断性和资源竞争问题,并提供了互斥锁、信号量等同步机制的实战解决方案,帮助开发者更好地理解和控制并发程序的行为。

weixin_29210727的博客 305

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

多线程世界的协奏曲:深入理解并发编程与锁机制

深刻理解多线程与锁机制,将使你不仅能更好地使用这些高级工具,更能从容地应对任何复杂的并发挑战,编写出真正可靠的高性能应用。:对于图形界面程序,后台耗时任务(如文件读写、网络请求)由工作线程承担,可避免阻塞主线程(UI线程),保证用户界面的流畅响应。然而,真正的 mastery 在于意识到锁带来的副作用(死锁、性能),并遵循最佳实践来规避它们。,程序的最终结果取决于线程调度的时序,这种不确定性是并发编程中最棘手的Bug。,可创建一个公平锁,按照请求顺序分配锁,避免线程饥饿(但性能有开销)。

qq_74898152的博客 353

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

Linux并发编程核心:从互斥锁到分布式锁的深度解析与实践

在并发编程中,锁是协调多线程或多进程访问共享资源的核心同步机制,其本质是通过原子性操作解决数据竞争问题。从原理上看,锁的设计需要在正确性、活性与性能之间做出权衡,这直接催生了互斥锁、读写锁、自旋锁等多样化的技术实现。在工程实践中,合理运用锁能有效保障高并发系统数据一致性,避免死锁与性能瓶颈,是构建数据库、Web服务器等高性能服务的基石。本文聚焦Linux环境,深入探讨了从用户态基础锁到内核锁、文件锁乃至分布式锁的应用场景与避坑指南,特别是针对读写锁在缓存系统、自旋锁在短临界区场景下的优化实践,为开发者提供了

weixin_32642109的博客 285

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

C 20 协程初探

【导读】:C 20 终于引入了协程特性,给库作者提供了一个实现协程的机制,让用户方便使用协程来编写异步逻辑,降低了异步并发编程的难度。结合我最近协程的学习,在这里记录一下相关内容。以下...

轻松学C语言 565
上一篇: 透过Redis源码探究Hash表的实现
下一篇: JavaScript进阶内容——BOM详解
JavaMonsterr
博客等级 码龄4年 211粉丝 481原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值