并发编程专题 2:使用多线程编程

Python并发编程:揭开多线程与异步编程的神秘面纱 在Python的世界里,多线程犹如魔法师手中的魔杖,通过threading模块我们可以轻松地编织出并发执行的神奇景象。这个模块提供了创建、管理线程的基本结构,允许我们定义线程任务,进而实现任务的并发执行。线程就像一个个独立的工人,在同一个进程的工厂里各司其职,共同推进整体工作的进度。想象一个热闹的餐厅,一位服务员在单一的工作台上接待众多客人。虽然只有一人服务,但服务员并不会因为等待某个客人的菜单确认而闲下来,而是会在等待的同时去询问其他客人的需求,然后高效地来回穿梭于厨房与餐桌之间。 阅读详情

1、基础梳理

  1. 进程和线程。1). 进程是操作系统正在执行的不同应用程序的一个实例,线程是操作系统分配处理器时间的基本单元。2). 每个进程运行在自己的地址空间,而线程共享数据内存和 IO 这些资源。
  2. 线程的优缺点。优点:1). 程序的运行效率可能会更高;2). 可以使用线程把占用时间较长的任务放在后台去执行;3). 在一些等待耗时任务和交互事件的时候同时可以执行其他任务。缺点:1).如果有大量的线程,会影响性能,因为操作系统需要在它们之间切换;2).更多的线程需要更多的内存空间;3).通常块模型数据是在多个线程间共享的,需要防止线程死锁情况的发生。

2、使用线程

2.1 使用 Thread 创建单线程

Java 中提供了 Thread 类用来创建线程。当我们调用 Thread 的 start() 方法的之后,Thread 的 run() 方法将会被调用。默认的,Thread 的 run() 方法中会调用传入的 Runnable 的 run() 方法执行任务,我们也可以覆写 Thread 的 run() 方法来让它直接执行我们的业务逻辑。所以,可以使用下面的两种方式使用线程,

    /**
    * 方式 1:覆写 run() 方法使用线程
    * 按照下面的方式来启动线程即可,
    * MyThread myThread = new MyThread(); 
    * myThread.start(); 
    */
    private class MyThread extends Thread {
        @Override
        public void run() {
            // 业务逻辑
        }
    }

    /**
    * 方式 2:使用 Runnable 使用线程
    */
    new Thread(new Runnable() {
        public void run() {
            // 业务逻辑
        }
    }).start();

对第二种方式,在创建多个线程的情况下,如果传入的 Runnable 实例是同一个实例的话,那么这几个线程是共享这个实例的数据的;如果不是同一个实例,则每个线程有一份自己的数据。当共享实例的时候,有时需要引入同步机制。

2.2 使用 Executor 创建线程池

2.2.1 线程池的基本使用

使用线程池的好处在于:因为线程的创建和销毁会占有一定的资源开销,尤其是当线程需要执行的逻辑耗时比较短,而创建和销毁的时间占用比较长的时候,对每个任务都创建和销毁线程就不太划算了。而线程池为我们提供了一种复用线程的机制,我们可以只创建执行数量的线程,然后将任务不断地提交到线程池中执行。

Executors 类有许多静态工程方法可以用来构建线程池:

  1. newCachedThreadPool():对每个任务,如果有空闲线程,立即让它执行任务,若无,则创建新线程;
  2. newFixedThreadPool():构建一个具有固定大小的线程池,若提交的任务数目大于空闲线程,得不到服务的任务放在队列中,执行完其他任务再执行这些任务;
  3. newSingleThreadPool():大小为1的线程池,提交的任务会按照提交的顺序依次地被执行(执行完毕一个,才去执行另一个);
  4. newWorkStealingPool():具有Work-Stealing (工作窃取) 的能力的线程池,Java8 中引入的,基于分治思想,Java8 中的并行流就是基于它来实现的。

使用线程池的时候,除了像线程那样启动任何,还可以获取到线程执行的结果。ExecutorService 的 submit() 方法提供了多个重载的版本,我们可以将 Runnable 或者 Callable 作为任务来执行。对于 Callable 类型的任务,该方法定义如下,

    <T> Future<T> submit(Callable<T> task)

也就是说,我们可以通过方法的返回结果得到一个 Future 实例。我们可以使用这个实例的方法来获取任务的执行结果,只是它的方法都是阻塞的。因此,我们又有了第三种使用线程的方式,

    // 方式 3:使用线程池并获取线程的执行结果
    // 定义固定大小的线程池
    ExecutorService executor = Executors.newFixedThreadPool(5);
    // 定义一个列表用来存储 submit 的返回实例
    List<Future<Integer>> results = new ArrayList<Future<Integer>>();
    for (int i=0; i<5; i++) {
        results.add(executor.submit(new CallableTask(i, i)));
    }
    // 输出每个任务的执行结果
    for (Future<Integer> result : results) {
        try {
            // 线程将会在 get() 方法上面阻塞
            System.out.println(result.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

此外,也可以使用 Future 的 isDone() 方法来判断任务是否完成,并决定是否要调用 get() 方法。

另外,还有一个类叫做 FutureTask。它同时实现了 Future 和 Runnable,所以,它设计的目的在于让我们可以同时从 FutureTask 实例中设置任务并获取结果。FutureTask 提供了两个构造方法,分别接受一个 Callable 和 Runnable 类型的实例。所以,我们可以把自己的任务放进 FutureTask 中包装了之后再传递给 ExectorService 或者 Thread 来执行。

2.2.2 线程池的参数选择

上面我们使用的是线程池的默认实现,也就是使用 Executors 的静态方法提供的线程池。这种方式是没有问题的,但是当我们使用阿里的插件的时候,会发现阿里并不推荐我们这样使用,而是使用手动创建线程池的方式。所以,在创建线程池的时候,我们有几个参数需要决定,

以 Android 为例,它并没有明确规定可以创建的线程的数量,但是每个进程的资源是有限的,线程本身会占有一定的资源,所以受内存大小的限制,会有数量的上限。通常,我们在使用线程或者线程池的时候,不会创建太多的线程。线程池的大小经验值应该这样设置:(其中 N 为 CPU 的核数)

  1. 如果是 CPU 密集型应用,则线程池大小设置为 N + 1;(大部分时间在计算)
  2. 如果是 IO 密集型应用,则线程池大小设置为 2N + 1;(大部分时间在读写,Android)

下面是 Android 中的 AysncTask 中创建线程池的代码(一些参数的意义已经添加到了注释种),

    // CPU 的数量
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    // 核心线程的数量:只有提交任务的时候才会创建线程,当当前线程数量小于核心线程数量,新添加任务的时候,会创建新线程来执行任务
    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
    // 线程池允许创建的最大线程数量:当任务队列满了,并且当前线程数量小于最大线程数量,则会创建新线程来执行任务
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    // 非核心线程的闲置的超市时间:超过这个时间,线程将被回收,如果任务多且执行时间短,应设置一个较大的值
    private static final int KEEP_ALIVE_SECONDS = 30;

    // 线程工厂:自定义创建线程的策略,比如定义一个名字
    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };

    // 任务队列:如果当前线程的数量大于核心线程数量,就将任务添加到这个队列中
    private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);

    public static final Executor THREAD_POOL_EXECUTOR;

    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                /*corePoolSize=*/ CORE_POOL_SIZE,
                /*maximumPoolSize=*/ MAXIMUM_POOL_SIZE, 
                /*keepAliveTime=*/ KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                /*workQueue=*/ sPoolWorkQueue, 
                /*threadFactory=*/ sThreadFactory
                /*handler*/ defaultHandler); // 饱和策略:AysncTask 没有这个参数
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

饱和策略:任务队列和线程池都满了的时候执行的逻辑,Java 提供了 4 种实现;
其他:

  1. 当调用了线程池的 prestartAllcoreThread() 方法的时候,线程池会提前启动并创建所有核心线程来等待任务;
  2. 当调用了线程池的 allowCoreThreadTimeOut() 方法的时候,超时时间到了之后,闲置的核心线程也会被移除。

3、线程状态和生命周期

3.1 线程的状态

线程的状态转换

Java中的线程的生命周期大体可分为 5 种状态:

  1. 新建 (NEW):新创建了一个线程对象。

  2. 可运行 (RUNNABLE):线程对象创建后,其他线程(比如 main 线程)调用了该对象的 start() 方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取 CPU 的使用权 。

  3. 运行 (RUNNING):RUNNABLE 状态的线程获得了 CPU 时间片(timeslice) ,执行程序代码。

  4. 阻塞 (BLOCKED):阻塞状态是指线程因为某种原因放弃了 CPU 使用权,也即让出了 CPU timeslice,暂时停止运行。直到线程进入 RUNNABLE 状态,才有机会再次获得 CPU timeslice 转到 RUNNING 状态。阻塞的情况分三种:

    1. 等待阻塞:RUNNING 的线程执行 o.wait() 方法,JVM 会把该线程放入等待队列 (waitting queue) 中。

    2. 同步阻塞:RUNNING 的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池 (lock pool) 中。

    3. 其他阻塞:RUNNING 的线程执行 Thread.sleep(long) 或 t.join() 方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep() 状态超时、join() 等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入 RUNNABLE 状态。

  5. 死亡 (DEAD):线程 run()、main() 方法执行结束,或者因异常退出了 run() 方法,则该线程结束生命周期。死亡的线程不可再次复生。

线程的 wait() 和 notify() 调度原理:

线程的调度

3.2 线程的启动 start()、停止 stop()、挂起 suspend() 和唤醒 resume()

通过对象的 start(), stop(), suspend(), resume() 方法可以分别用来启动/停止/挂起/继续线程,但是后面三种方法都已经过时,调用可能发生不可预料的结果。如果线程被停止或者挂起的时候,它仍然占有共享的资源,那么有可能会导致线程死锁。

说明:调用 start() 方法,线程处于 runnable,线程可运行,但是无法确定线程是否正在运行,这取决于操作系统提供的运行时间。

run()方法执行结束之后,线程自动终止;如果 run() 无限循环,可以考虑加加标识,在一定情况下退出,不推荐使用 stop() 方法。另外,如果线程终止了,将无法再次启动。

一个线程会结束的原因可能是下面两者之一:1).run() 方法正常退出而线程自然地死亡;2).一个没有被捕获的异常终止了 run() 方法而意外地死亡。

3.3 线程休眠 Thread.sleep()

静态方法 Thread.sleep(long millis) 和 Thread.sleep(long millis, int nanos) 强行将当前线程休眠(暂停执行)指定时间,睡眠结束,即返回可运行状态。

此外,也可以使用 TimeUnit.MILLISECONDS.sleep(1000); 方法来实现线程的休眠。

注意:

  1. 一个线程不能针对另一个线程调用 Thread.sleep(),即一个线程只能让自己睡眠;
  2. sleep() 方法会抛出一个 InterruptedException 异常。
  3. 如果当前线程获得了锁,sleep() 方法并不会使其失去锁。

另外,对于 sleep() 和 wait() 方法之间的区别,总结如下,

  1. 所属类不同:sleep() 方法是 Thread 的静态方法,而 wait() 是 Object 实例方法。
  2. 作用域不同:wait() 方法必须要在同步方法或者同步块中调用,也就是必须已经获得对象锁。而 sleep() 方法没有这个限制可以在任何地方种使用。
  3. 锁占用不同:wait() 方法会释放占有的对象锁,使得该线程进入等待池中,等待下一次获取资源。而 sleep() 方法只是会让出 CPU 并不会释放掉对象锁;
  4. 锁释放不同:sleep() 方法在休眠时间达到后如果再次获得 CPU 时间片就会继续执行,而 wait() 方法必须等待 Object.notift()/Object.notifyAll() 通知后,才会离开等待池,并且再次获得 CPU 时间片才会继续执行。

3.4 线程优先级 setPriority()

优先级使用正整数试着,通常为 0~10,默认为 5. Thread类中也定义了 3 个静态最终常量:Thread.MIN_PRIORITY (对应整数值为 1)、Thread.NORM_PRIORITY (对应整数值为 5) 和 Thread.MAX_PRIORITY (对应整数值为 10).

线程是根据优先级调度执行的,尽管 CPU 处理现有的线程集的顺序是不确定的,但是调度器倾向于让优先权最高的线程先执行。这不意味着优先权低的程序得不到执行,只是执行的频率较低。

说明:

  1. 默认情况下,一个线程继承它父线程的优先级,可以使用 setPriority() 方法提高或降低一个线程的优先级;
  2. 高优先级线程没有进入非活动状态,低优先级线程永远不可能执行。每当调用一个新线程时,首先会在具有高优先级的线程中选择。尽管这样可能会使低优先级线程完全饿死;
  3. 在绝大多数的时间里,线程都应该以默认的优先级运行,试图操纵线程优先级通常是一种错误。
  4. 在不同 JVM 以及操作系统上,线程规划存在差异,有些操作系统甚至会忽略线程优先级的设定。

3.5 让步 Thread.yield()

即暂停当前正在执行的线程对象,并执行其他线程。并非永久暂停,只是让步一次执行时间片。需要注意的是,让出的CPU并不是代表当前线程不再运行了,如果在下一次竞争中,又获得了 CPU 时间片当前线程依然会继续运行。另外,让出的时间片只会分配给当前线程相同优先级的线程。

yield() 是 Thread 的一个静态方法,它给线程调度机制一个暗示:当前线程(在 run() 方法中调用 yield() 方法的线程)的工作已经差不多了,可以让别的线程使用 CPU 了。

但是,大体上,对任何重要的控制或在调整应用时,都不能依赖于 yield().

另外需要注意的是,sleep() 和 yield() 方法,同样都是当前线程会交出处理器资源。它们不同的是,sleep() 交出来的时间片其他线程都可以去竞争,也就是说都有机会获得当前线程让出的时间片。而 yield() 方法只允许与当前线程具有相同优先级的线程能够获得释放出来的 CPU 时间片。

3.6 加入一个线程 join()

join() 是 Thread 的实例方法,它用来等待,直到指定线程结束。如果我们在线程 A 中调用了 B 的 join() 方法,就表示我们将 A 添加到了 B 的尾部,如果 B 不执行完 A 不继续执行。join() 重载版本,

void join()                         // 加入线程,等待该线程终止后运行
void join(long millis)              // 加入线程,等待该线程 millis 后运行,0 为无限等待
void join(long millis, int nanos)   // 加入线程,等待该线程 millis+nanos 后运行

线程的 join() 方法允许传入 long 型的时间,表示我们可以为线程设置等待的时间上限。当超过了指定的时间另一个线程仍然没有执行完毕任务,当前线程就继续执行自己的任务。否则,当前线程会一直阻塞。

注意,因为线程的 join() 方法的本意是等待另一份线程直到结束,所以,如果我们没有对指定的线程调用 start() 方法,那么 join() 是没有效果的(因为线程本来就没启动,所以也不用等待了)。

3.7 线程中断 interrupt()

线程中断需要注意两种情形,一个是未处于阻塞时期的中断,另一个是处于阻塞时期的中断

interrupt() 方法用来中断线程,而不是立即终止线程。对线程调用 interrupt() 方法时,线程的中断状态将被置位(设置为 true),这是每个线程都具有的 boolean 状态。如果想要知道一个线程是否被置位,可以使用 Thread.currentThread().isInterrupted() 来判断。

当线程由于调用了 sleep(), wait(), join() 等方法而进入阻塞状态;若此时调用线程的 interrupt() 将线程的中断标记设为 true。由于处于阻塞状态,中断标记会被清除,同时产生一个 InterruptedException 异常。

所以,当你希望让一个线程从阻塞状态中结束的时候,你可以按照下面这样去写,

@Override
public void run() {
    try {
        while (true) {
            // do something
        }
    } catch (InterruptedException ie) {  
        // 由于 InterruptedException 异常,退出 while 循环,线程终止!
    }
}

通常,我们把对 InterruptedException 的捕获务一般放在 while 循环体的外面,这样,在产生异常时就退出了 while 循环。

让线程结束,你还可以通过判断中断标志位来进行。此外,你还可以通过使用一个额外的布尔类型的变量来让线程退出。

    // 通过判断中断标志位来退出
    private static class MyRunnable implements Runnable {

        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                // do something
            }
        }
    }

    // 通过一个布尔类型的变量来退出
    private static class MyRunnable2 implements Runnable {
        // 注意使用 volatile 修饰
        private volatile boolean canceled = false;

        @Override
        public void run() {
            while (!canceled) {
                // do something
            }
        }

        public void cancel() {
            canceled = true;
        }
    }

上面的第一种方式还没有考虑线程被阻塞的情况,所以,我们需要综合线程是否处于阻塞来给出一个更完美的版本,

@Override
public void run() {
    try {
        while (!isInterrupted()) {
            // do something
        }
    } catch (InterruptedException ie) {  
        // 线程因为阻塞时被中断而结束了循环
    }
}

最后,注意 interrupted() 和 isInterrupted() 的区别

interrupted() 是属于 Thread 的静态方法,isInterrupted() 是属于 Thread 的实例方法。interrupted() 和 isInterrupted() 都能够用于检测对象的 “中断标记”。区别是,interrupted() 除了返回中断标记之外,它还会清除中断标记 (即将中断标记设为false);而 isInterrupted() 仅仅返回中断标记。

    public static boolean interrupted() {
        return currentThread().isInterrupted(/* ClearInterrupted= */ true);
    }

    public boolean isInterrupted() {
        return isInterrupted(/* ClearInterrupted= */ false);
    }

3.8 后台线程

Java 线程分为两类:用户线程和 Daemon 线程。

  1. 用户线程是通常意义的线程,Java 应用程序运行时,通过 main() 方法进入. 在主线程中可以创建和启动新线程,默认为用户线程. 只有所有用户线程结束后,应用程序才终止。
  2. 通过 setDaemon() 方法,可以设置线程为 Daemon 线程,在 Daemon 线程中创建的线程默认为 Daemon 线程. 通过方法 isDaemon() 可以判断一个线程是否为 Daemon 线程。
  3. Daemon 线程(守护线程)是一个服务线程,其 优先级最低,一般为其他线程提供服务. 通常 Daemon 线程体是一个无限循环,如果所有的非 Daemon 线程都结束了,则 Daemon 线程自动终止。
  4. Daemon 线程应该永远不访问固有资源,如文件、数据等,因为它会在任何时候,甚至任一个操作中间发生中断。
  5. Deamon 线程通常是系统服务类线程,比如垃圾回收线程,JIT线程就可以理解守护线程。

3.9 线程组

最好把线程组看作一次不好的尝试,忽略就好。

3.10 线程控制 wait()、notify() 和 notifyAll()

  1. wait()、notify() 和 notifyAll() 方法是 Object 的本地 final 方法,无法被重写。

  2. wait() 使当前线程阻塞,直到接到通知或被中断为止。前提是必须先获得锁,一般配合 synchronized 关键字使用,在 synchronized 同步代码块里使用 wait()、notify() 和 notifyAll() 方法。如果调用 wait() 或者 notify() 方法时,线程并未获取到锁的话,则会抛出 IllegalMonitorStateException 异常。再次获取到锁,当前线程才能从 wait() 方法处成功返回。

  3. 由于 wait()、notify() 和 notifyAll() 在 synchronized 代码块执行,说明当前线程一定是获取了锁的。当线程执行 wait() 方法时候,会释放当前的锁,然后让出 CPU,进入等待状态。只有当 notify()/notifyAll() 被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完 synchronized 代码块或是中途遇到 wait(),再次释放锁。
    也就是说,notify()/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况。所以在编程中,尽量在使用了 notify()/notifyAll() 后立即退出临界区,以唤醒其他线程。

  4. wait() 需要被 try catch 包围,中断也可以使 wait 等待的线程唤醒。

  5. notify() 和 wait() 的顺序不能错,如果 A 线程先执行 notify() 方法,B 线程再执行 wait() 方法,那么 B 线程是无法被唤醒的。

  6. notify() 和 notifyAll() 的区别:
    notify() 方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。
    notifyAll() 会唤醒所有等待 (对象的) 线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用 notifyAll() 方法。比如在生产者-消费者里面的使用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。

Java 多线程与并发编程全面指南 Java 作为一门成熟的企业级编程语言,提供了丰富的多线程与并发编程支持,使开发者能够构建高性能、高并发的应用程序。然而,多线程编程也带来了复杂性,包括线程安全、死锁、竞态条件等问题。本文将全面介绍 Java 多线程与并发编程的各个方面。 阅读详情

相关推荐

Python并发编程:多线程(threading模块)

多线程是一种并发编程方式,它允许在一个进程内同时运行多个线程,从而提高程序的运行效率。线程是轻量级的进程,拥有自己的栈空间,但共享同一个进程的内存空间。本文详细介绍了Python的threading模块,包括线程的创建、线程同步、线程池的使用,并通过多个示例展示了如何在实际项目中应用这些技术。通过学习这些内容,您应该能够熟练掌握Python中的多线程编程,提高编写并发程序的能力。多线程编程可以显著提高程序的并发性能,但也带来了新的挑战和问题。

互联网架构小马的博客 2031

深入理解Java多线程与并发编程

线程三大特性 多线程有三大特性,原子性、可见性、有序性 2.1什么是原子性              即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。 一个很经典的例子就是银行账户转账问题:  比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。这2个操作必须要具备原子性才能保证不出现一些意

u014071328的专栏 3782

VC Spyglass CDC验证避坑指南:如何正确配置同步复位信号(实验二重点解析)

本文深入解析VC Spyglass CDC验证中同步复位信号的正确配置方法,通过实验二案例详细展示错误配置的典型表现及修正过程。文章提供同步复位的最佳实践方案,包括约束文件修改指南、验证流程优化建议和高级调试技巧,帮助工程师有效避免CDC验证中的常见陷阱。

rock5的博客 678

并发编程(多线程)

标准库中的Timer类中有两个参数:1)Runnable2)时间dalay(就是多久之后完成Runnable中的任务)timer.schedule(安排)使用其功能.功能:1.能够让任务在指定的时间得到执行2.一个定时器可以注册N个任务,N个任务可以按照最初约定的时间,按顺序执行.根据任务我们能得出结论:1.我么需要一个线程扫描,负责判断时间是否到了/并执行任务.2.还需要一个数据结构,来保存所有注册的任务.

zyx22334的博客 6438

Oracle ERP EBS R12 GL总帐业务方案 系统操作

本文档是Oracle ERP系统的总账管理模块使用手册,主要包含以下内容:1.系统架构设计,详细介绍多组织架构、会计科目设置等核心概念;2.总账日常操作流程,包括凭证录入、审核、过账等全流程管理;3.特殊业务处理,涵盖预算控制、内部往来、合并报表等专项功能;4.期末处理,说明月结、年结的操作步骤;5.系统设置指南,提供会计科目表、权限控制等配置说明;6.附录包含各业务模块的分录示例和系统操作图标说明。本手册适用于财务人员学习和使用Oracle ERP总账系统,重点讲解系统功能要点和实操步骤。

anpeng2019的博客 1884

【博弈论基础与几大经典模型】古诺模型、斯塔克尔伯格模型Stackelberg Game、价格领导模型、Bertrand模型、Sweezy模型

博弈论中经典模型☞古诺模型、斯塔克尔伯格模型、价格领导模型等

学习记录 3万+

并发编程之多线程线程安全

当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。案例:需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。sale();System.out.println(Thread.currentThread().getName() + ",出售第" + (10 - trainCount) + "张票");Thread t1 = new Thread(threadTrain, "1台");

程序员阿飘 的博客 278

Android HTTPS抓包证书配置全解:Proxyman实战避坑指南

HTTPS抓包本质是中间人(MITM)通信的可信建立过程,其核心在于客户端对代理根证书的信任链配置。在Android平台,自7.0起系统默认不信任用户安装的CA证书,必须通过Network Security Config显式声明trust-anchors,将Proxyman导出的PEM格式根证书纳入App级信任锚点。这一机制区别于iOS和桌面浏览器,导致大量开发者卡在‘证书已安装却无法解密’的误区。技术价值在于打通开发调试闭环:精准定位SSLHandshakeException、绕过证书固定、支持React

weixin_32175665的博客 648

多线程与并发编程

多线程与并发编程 并发与并行 **并发:**多个任务在同一个 CPU 核上,按细分的时间片轮流(交替)执行,从逻辑上来看那些任务是 同时执行。 **并行:**单位时间内,多个处理器或多核处理器同时处理多个任务,是真正意义上的“同时进行”。 **串行:**有n个任务,由一个线程按顺序执行。由于任务、方法都在一个线程执行所以不存在线程不安全情况,也就不存在临界区的问题。 做一个形象的比喻: 并发 = 俩个人用一台电脑。 并行 = 俩个人分配了俩台电脑。 串行 = 俩个人排队使用一台电脑。 为什么使用并发编程

不吃鱼的小熊的博客 357

现代 AI Agent 编排技术全景(1)—— 分类体系与四大技术维度

本文介绍基于 LLM 的现代 Agent 分类体系与四大技术维度,形成能够刻画不同 Agent 产品架构特征的分类法

佚失的诗篇 231

多标签图像分类:标签依赖关系建模的最新论文解读与实战应用

多标签图像分类:标签依赖关系建模的最新论文解读与实战应用 ,人工智能,计算机视觉,大模型,AI,本文解读了最新相关论文,包括基于图神经网络(GCN)的ML-GCN方法,通过构建标签相关矩阵建模依赖关系;区域潜在语义依赖的RLSD模型,定位关键区域并利用RNN建模语义依赖;以及语义图表示学习的SSGRL框架,结合语义解耦与交互模块。通过实战应用,展示了基于PyTorch的ML-GCN模型实现,包括图像特征提取、标签依赖建模及训练评估流程,为多标签图像分类提供了有效解决方案。

不踩坑,不知道坑有多深;不总结,踩过的坑白踩。 18万+

并发编程-线程共享与协作

什么是进程和线程 进程是程序运行资源分配的最小单位:进程是操作系统进行资源分配的最小单位,其中资源包括:CPU、内存空间、 磁盘IO 等 线程是CPU 调度的最小单位,必须依赖于进程而存在:线程是进程的一个实体,是CPU 调度和分派的基本单位,它是比进程更小的、能独立运行的基本单位 进程是并发执行的程序在执行过程中分配和管理资源的基本单位。线程是进程的一个执行单元,是比进程还要小的独立运行的基本单...

Andy_Health的博客 301

YOLOv10精度上不去?QAT量化感知训练全流程,INT8推理mAP只掉0.8%

普通PTQ量化虽简单,但无法应对YOLOv10的小目标特征和激活分布波动,导致精度暴跌。QAT通过“模拟量化误差+联合优化权重与量化参数”,让INT8模型精度接近FP16,同时速度提升1.8倍,完美适配实时检测场景(如安防监控、自动驾驶)。该流程已在工业级项目中验证,针对YOLOv10的优化策略(敏感层保护、小目标加权、分阶段训练)可直接迁移至其他YOLO系列(v8/v9),量化后精度损失均可控制在1%以内。

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 915

Python并发编程:线程和多线程的使用

在编程中,并发编程允许程序同时执行多个独立的任务,这些任务可以在同一时间段内部分地重叠执行,从而提高程序的效率和响应性。在Python 中,并发编程可以通过多种方式实现,其中包括线程(Threads)和进程(Processes)。

TiYong的博客 2194

OpenClaw 子代理配对失败问题分析与解决方案

OpenClaw子代理配对失败问题分析与解决方案 问题核心在于agent设备权限不足,仅有operator.approvals权限而缺少operator.admin权限,导致子代理启动时形成权限死锁。本文提供三种解决方案: 直接编辑设备配对文件(推荐):修改本地JSON文件,为agent设备添加完整operator.admin权限,操作简单快捷。 标准重新配对流程:删除现有agent设备后重新配对,通过正常审批流程获取完整权限,符合系统安全设计。 修改源码降低权限要求(仅限开发环境):调整子代理代码避免强制

多媒体编程、网络编程、系统编程、网络安全编程、驱动编程 148

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

多线程与并发编程技术

并行与并发概念并行与并发概念在软件开发领域,特别是在构建高性能、高效率的应用程序时,并行和并发这两个词经常被提及。虽然它们在日常对话中通常可以互换使用,但其实并行和并发有着本质的区别,并且在理解多线程编程时非常重要。并行(Parallelism)并行编程是指通过同时执行多个任务来提高系统性能的一种技术。这里的同时指的是不同的处理单元(如处理器核心、GPU等)或相同的处理单元在同一时间内处理不同的数据流。并行编程依赖于硬件资源,例如多核处理器,使得能够同时处理多个任务,从而加速计算过程。

QT性能优化QT原理源码QT界面美化 1353

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

Java多线程并发编程技术详解

Java多线程并发编程是一项需要深入理解和熟练掌握的技术,只有合理地设计和使用多线程,才能真正发挥其在提升系统性能方面的巨大潜力。随着对Java并发机制及工具类库的深入学习和应用,开发者可以更好地构建高效、稳定的并发系统。

聚焦于深度解析最新的编程语言特性、框架升级、架构设计、开发工具和最佳实践,涵盖Web前端(如Vue3, React, Angular等)、后端开发(如Node.js, Java, Python等)、移动端开发(iOS, Android原生及跨平台开发) 3万+

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

多线程-并发编程

基础知识 并发编程的优缺点为什么要使用并发编程(并发编程的优点) 充分利用多核CPU的计算能力:通过并发编程的形式可以将多核CPU 的计算能力发挥到极致,性能得到提升 方便进行业务拆分,提升系统并发能力和性能:在特殊的业务场景下,先天的就适合于并发编程。现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。面对复杂业务模型,并行程序会比串行程序更适应业务需求,而并发编程更能吻合这种业务拆分 。 并发编程有什..

hero_xs的博客 1670

Python并发编程之多线程

本文介绍并发编程中另一个重要的知识 - 线程。我们知道一个程序的运行过程是一个进程,在操作系统中每个进程都有一个地址空间,而且每个进程默认有一个控制线程,打个比方,在一个车间中有很多原材料通过流水线加工产品,而线程就是这个车间中的流水线,而这个车间就是进程,原材料就是内存中的数据,每个车间至少有一条流水线。因此,进程只是将资源(原材料)集中到一起是资源单位,线程才是CPU具体的执行单位,一个进程中可以存在多个线程,这多个线程会共享该进程内的所有资源,因此同一个进程内开启多线程会产生资源争抢。

weixin_71807218的博客 2231

Python中的并发编程:多线程与多进程的比较

在Python编程领域中,处理并发任务是提高程序性能的关键之一。本文将探讨Python中两种常见的并发编程方式:多线程和多进程,并比较它们的优劣之处。通过代码实例和详细的解析,我们将深入了解这两种方法的适用场景和潜在问题。

一键难忘的博客 4556

多线程与并发编程 面试专题

线程的基础概念、线程的创建方式、线程的状态、线程的终止、线程的常用方法、锁、阻塞队列、线程池

Angle_Saber的博客 1060

并发编程专题(一)-并发与多线程

1. 并发与并行的区别,何为并发编程,并发编程的优势在哪 2. 多线程、多任务、多进程机制概述 3. 多线程、多任务、多进程机制与编程思想的关系

专业的计算机毕业设计指南 764

深入理解多线程并发编程

多线程并发编程的领域知识体系比较庞大,底层涵盖了CPU,内存,操作系统等多方面的内容,基于我目前掌握的知识和篇幅限制,不可能深入到每一个细节。本文旨在和大家一起探讨、研究多线程并发编程中的问题及其解决方案。本文受众主要是对多线程编程技术已经有一定使用经验或正在学习多线程编程的同学。文章分三个章节给大家展开,第一节是提出多线程编程要解决哪些核心问题。第二节讲这些问题背后的本质和原理。第三节讲如何使用C#语言提供的API进行多线程并发编程。

AndyT的博客 2061

【C++ 多线程编程|并发编程】

一个程序只有一个进程,该进程拥有至少一个线程。不同进程的地址空间不同,互不相关。同一进程下的不同线程共享同一个地址空间。C++11中引入了thread类和asyn函数来实现多线程。

weixin_41919587的博客 2966

多线程--JUC并发编程

多线程、并发相关知识点

weixin_45690338的博客 2095

并发编程:使用多线程与多进程提升 Python 程序性能

IO 密集型任务:使用多线程 (threading或线程池CPU 密集型任务:使用多进程 (或进程池线程池与进程池:推荐使用,自动管理线程/进程,简化代码。通过合理地使用多线程和多进程,可以大幅提升 Python 程序的性能,特别是在处理大规模数据、网络请求或计算任务时。希望本文的介绍与示例能帮助你更好地理解并发编程并将其应用到实际项目中。

一名热衷于技术的全栈开发者,专注于前端与后端的全面技术探索。在这里,我将分享我在技术领域的学习与成长,助力更多开发者的进步。 1766
上一篇: 并发编程专题-5:生产者和消费者模式
下一篇: Android 高级面试-1:Handler 相关
开发者如是说
博客等级 码龄10年 67粉丝 163原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值