深入浅出java并发编程(Thread)

news2024/11/25 11:43:18

快速了解

package java.lang;

public class Thread implements Runnable {}

Thread是lang包下的一个类,实现了Runnable接口。源码如下

@FunctionalInterface
public interface Runnable {

    public abstract void run();
}

Runnable是一个函数式接口不会抛出异常没有返回值。

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    
    private Runnable target;
	
	this.target = target;

target通过init初始化方法赋值,通过run方法调用。

构造函数

    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    public Thread(Runnable target, String name) {
        init(null, target, name, 0);
    }
    public Thread(String name) {
        init(null, null, name, 0);
    }
    public Thread(ThreadGroup group, Runnable target) {
        init(group, target, "Thread-" + nextThreadNum(), 0);
    }
    public Thread(ThreadGroup group, Runnable target, String name) {
        init(group, target, name, 0);
    }
    public Thread(ThreadGroup group, Runnable target, String name,
                  long stackSize) {
        init(group, target, name, stackSize);
    }
    public Thread(ThreadGroup group, String name) {
        init(group, null, name, 0);
    }

所有的构造函数都调用了init方法进行初始化。

    private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
        init(g, target, name, stackSize, null, true);
    }
    
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {}

线程启动

    public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
            
        group.add(this);
        
        boolean started = false;
        
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {             
            }
        }
    }

    private native void start0();

run和start的区别

run没有启动线程的效果,仅仅是执行一段代码,start是真正的启动一个异步线程执行代码。

线程类方法弃用

Java线程原语弃用

常见的过时方法

public final void stop()
public final synchronized void stop(Throwable obj)
public void destroy()
public final void suspend() 
public final void resume()
public native int countStackFrames();

线程的状态

    public enum State {       
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }

案例1 线程的所有状态。

public class UseThreadState {

    private final static Logger logger = LoggerFactory.getLogger(UseThread.class);

    static UseThreadState obj = new UseThreadState();

    static Thread t1 = new Thread(UseThreadState::testBlock, "thread-01");

    static Thread t2 = new Thread(UseThreadState::testBlock, "thread-02");

    public static void testBlock() {
        synchronized (obj) {
            System.out.println(System.currentTimeMillis() + " " + Thread.currentThread().getName() + "抢到了锁");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                logger.info(e.toString());
            }
            System.out.println(System.currentTimeMillis() + " " + Thread.currentThread().getName() + "释放锁");
        }
    }


    public static void main(String[] args) throws Exception {
        t1.start();
        Thread.sleep(10);
        System.out.println(System.currentTimeMillis() + " " + t2.getName() + ":" + t2.getState());
        t2.start();
        System.out.println(System.currentTimeMillis() + " " + t2.getName() + ":" + t2.getState());
        Thread.sleep(10);
        System.out.println(System.currentTimeMillis() + " " + t2.getName() + ":" + t2.getState());
        Thread.sleep(200);
        System.out.println(System.currentTimeMillis() + " " + t2.getName() + ":" + t2.getState());
        t2.join();
        System.out.println(System.currentTimeMillis() + " " + t2.getName() + ":" + t2.getState());
    }
}
-----
1662540085314 thread-01抢到了锁
1662540085327 thread-02:NEW
1662540085327 thread-02:RUNNABLE
1662540085343 thread-02:BLOCKED
1662540085515 thread-01释放锁
1662540085515 thread-02抢到了锁
1662540085548 thread-02:TIMED_WAITING
1662540085720 thread-02释放锁
1662540085720 thread-02:TERMINATED

BLOCKED状态等待对象锁,TIMED_WAITING状态有限期等待。

等待和唤醒

常见方法

Object 类中相关实现

    public final void wait() throws InterruptedException {
        wait(0);
    }
    public final native void wait(long timeout) throws InterruptedException;
    
    public final void wait(long timeout, int nanos) throws InterruptedException
    
    public final native void notify();
    
    public final native void notifyAll();

native方法实现参考 openjdk\hotspot\src\share\vm\runtime\objectMonitor.cpp

虚假唤醒

在这里插入图片描述
虚假唤醒:线程可以在没有通知、中断或者超时的情况下唤醒。

为什么 wait 语句要放在循环里而不是 if 语句 ?

示例如下

public class FakeWake {

    private final static Logger logger = LoggerFactory.getLogger(FakeWake.class);

    static Integer size = 5;

    static Integer stock = 1;

    static BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(1);

    public static void main(String[] args) {
        consume.start();
        consume2.start();
        product.start();
        product2.start();
    }

    static Thread product = new Thread(() -> {
        synchronized (queue) {
            for (int i = 0; i < size; i++) {
                try {
                    while (queue.size() == 1) {
                        queue.wait();
                    }
                } catch (InterruptedException e) {
                    logger.info(e.toString());
                }
                stock++;
                logger.info("生产1号 生产 {}", stock);
                queue.add(stock);
                queue.notifyAll();
            }
        }
    }, "product-01");

    static Thread product2 = new Thread(() -> {
        synchronized (queue) {
            for (int i = 0; i < size; i++) {
                try {
                    while (queue.size() == 1) {
                        queue.wait();
                    }
                } catch (InterruptedException e) {
                    logger.info(e.toString());
                }
                stock++;
                logger.info("生产2号 生产 {}", stock);
                queue.add(stock);
                queue.notifyAll();
            }
        }
    }, "product");


    static Thread consume = new Thread(() -> {
        synchronized (queue) {
            for (int i = 0; i < size; i++) {
                try {
                    while (queue.size() == 0) {
                        queue.wait();
                    }
                    Integer poll = queue.poll();
                    logger.info("消费1 消费 {}", poll);
                    queue.notifyAll();
                } catch (InterruptedException e) {
                    logger.info(e.toString());
                }
            }
        }
    }, "consume");

    static Thread consume2 = new Thread(() -> {
        synchronized (queue) {
            for (int i = 0; i < size; i++) {
                try {
                    while (queue.size() == 0) {
                        queue.wait();
                    }
                    Integer poll = queue.poll();
                    logger.info("消费2 消费 {}", poll);
                    queue.notifyAll();
                } catch (InterruptedException e) {
                    logger.info(e.toString());
                }
            }
        }

    }, "consume");


}

-----
16:09:26.553 [product-01] INFO com.example.miccommon.juc.thread.FakeWake - 生产1号 生产 2
16:09:26.553 [consume] INFO com.example.miccommon.juc.thread.FakeWake - 消费2 消费 2
16:09:26.553 [product] INFO com.example.miccommon.juc.thread.FakeWake - 生产2号 生产 3
16:09:26.553 [consume] INFO com.example.miccommon.juc.thread.FakeWake - 消费1 消费 3
16:09:26.553 [product-01] INFO com.example.miccommon.juc.thread.FakeWake - 生产1号 生产 4
16:09:26.553 [consume] INFO com.example.miccommon.juc.thread.FakeWake - 消费2 消费 4
16:09:26.553 [product] INFO com.example.miccommon.juc.thread.FakeWake - 生产2号 生产 5
16:09:26.553 [consume] INFO com.example.miccommon.juc.thread.FakeWake - 消费1 消费 5
16:09:26.553 [product-01] INFO com.example.miccommon.juc.thread.FakeWake - 生产1号 生产 6
16:09:26.553 [consume] INFO com.example.miccommon.juc.thread.FakeWake - 消费2 消费 6
16:09:26.553 [product] INFO com.example.miccommon.juc.thread.FakeWake - 生产2号 生产 7
16:09:26.553 [consume] INFO com.example.miccommon.juc.thread.FakeWake - 消费1 消费 7
16:09:26.553 [product-01] INFO com.example.miccommon.juc.thread.FakeWake - 生产1号 生产 8
16:09:26.553 [consume] INFO com.example.miccommon.juc.thread.FakeWake - 消费2 消费 8
16:09:26.553 [product] INFO com.example.miccommon.juc.thread.FakeWake - 生产2号 生产 9
16:09:26.553 [consume] INFO com.example.miccommon.juc.thread.FakeWake - 消费1 消费 9
16:09:26.553 [product-01] INFO com.example.miccommon.juc.thread.FakeWake - 生产1号 生产 10
16:09:26.553 [consume] INFO com.example.miccommon.juc.thread.FakeWake - 消费2 消费 10
16:09:26.553 [product] INFO com.example.miccommon.juc.thread.FakeWake - 生产2号 生产 11
16:09:26.553 [consume] INFO com.example.miccommon.juc.thread.FakeWake - 消费1 消费 11

修改为 if 的后果

16:56:54.635 [product-01] INFO com.example.common.juc.thread.FakeWake - 生产1号 生产 2
16:56:54.638 [consume] INFO com.example.common.juc.thread.FakeWake - 消费2 消费 2
16:56:54.638 [consume] INFO com.example.common.juc.thread.FakeWake - 消费1 消费 null
16:56:54.638 [product] INFO com.example.common.juc.thread.FakeWake - 生产2号 生产 3
16:56:54.638 [consume] INFO com.example.common.juc.thread.FakeWake - 消费1 消费 3
16:56:54.638 [consume] INFO com.example.common.juc.thread.FakeWake - 消费2 消费 null
16:56:54.638 [product-01] INFO com.example.common.juc.thread.FakeWake - 生产1号 生产 4
16:56:54.638 [consume] INFO com.example.common.juc.thread.FakeWake - 消费2 消费 4
16:56:54.638 [consume] INFO com.example.common.juc.thread.FakeWake - 消费1 消费 null
16:56:54.638 [product] INFO com.example.common.juc.thread.FakeWake - 生产2号 生产 5
16:56:54.638 [consume] INFO com.example.common.juc.thread.FakeWake - 消费1 消费 5
16:56:54.638 [consume] INFO com.example.common.juc.thread.FakeWake - 消费2 消费 null
16:56:54.638 [product-01] INFO com.example.common.juc.thread.FakeWake - 生产1号 生产 6
16:56:54.639 [consume] INFO com.example.common.juc.thread.FakeWake - 消费2 消费 6
16:56:54.639 [consume] INFO com.example.common.juc.thread.FakeWake - 消费1 消费 null
16:56:54.639 [product] INFO com.example.common.juc.thread.FakeWake - 生产2号 生产 7
16:56:54.639 [product-01] INFO com.example.common.juc.thread.FakeWake - 生产1号 生产 8
Exception in thread "product-01" java.lang.IllegalStateException: Queue full
	at java.util.AbstractQueue.add(AbstractQueue.java:98)
	at java.util.concurrent.ArrayBlockingQueue.add(ArrayBlockingQueue.java:312)
	at com.example.common.juc.thread.FakeWake.lambda$static$0(FakeWake.java:38)
	at java.lang.Thread.run(Thread.java:748)

首先声明,这个例子不是虚假唤醒,真正的虚假唤醒不可预知。

原因分析,首先理解 wait 和 notify,线程一执行了百分之10的代码进入 wait,醒来后会继续执行后续 90的代码。使用 if 虽然第一次会进入 wait ,但是如果下一次还是它被唤醒,那么会跳过 if 包裹的代码,出现错误场景,而使用 while 可以使得唤醒后重复判断临界条件从而避免不可预知的唤醒。

由于虚假唤醒是不可预知的,所以 wait 出现的地方判断条件要使用 while 而不是 if。

wait 必须使用 while 循环来判断临界条件

中断和停止

stop

Forces the thread to stop executing.

强制停止线程执行,不推荐使用,原因参照 线程类方法弃用

interrupt

给一个线程发送中断信号

public class UseInterrupt {

    static Thread t1 = new Thread(() -> {
        while (true) {
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("有人希望我退出...");
                return;
            }
            // if (Thread.interrupted()) {
            //     System.out.println("有人希望我退出...");
            //     return;
            // }
        }
    });

    public static void main(String[] args) throws Exception {
        t1.start();
        TimeUnit.SECONDS.sleep(1);
        t1.interrupt();
        t1.join();
        System.out.println("end...");
    }
}
-----
有人希望我退出...
end...

主线程发起中断,线程收到中断信号,由实际实现来决定是否结束。

唤醒一个正在睡觉的线程

public class UseInterruptSleep {
    static Thread t1 = new Thread(() -> {
        try {
            System.out.println(System.currentTimeMillis() + "开始睡觉...");
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            System.out.println("t1:"+Thread.currentThread().isInterrupted());
            System.out.println(System.currentTimeMillis() + "被唤醒了...");
        }

    });

    public static void main(String[] args) throws Exception {
        t1.start();
        TimeUnit.SECONDS.sleep(1);
        System.out.println(t1.isInterrupted());
        t1.interrupt();
        System.out.println(t1.isInterrupted());
        t1.join();
        System.out.println("end...");
    }
}
-----
1662543320726开始睡觉...
false
true
t1:false
1662543321739被唤醒了...
end...

关键是这一行输出 t1:false,我们没有清除标记位,看起来好像是抛出异常的时候清除了,等下我们去源码寻找答案。

interrupt方法源码和注释

在这里插入图片描述
then its interrupt status will be cleared and it will receive an InterruptedException.

清除中断位置并抛出异常

在这里插入图片描述
实例方法 isInterrupted 和 静态方法 interrupted 源码。

在这里插入图片描述
主线程对子线程发起中断不代表子线程立刻结束,如果子线程不对标志位进行相应则中断不会产生任何效果。

yield

	public static native void yield();

当前线程让出cpu时间片,下一时刻仍有可能获取时间片。

join

Waits for this thread to die.

    public final void join() throws InterruptedException 
    
    public final synchronized void join(long millis) throws InterruptedException
    
    public final synchronized void join(long millis, int nanos) throws InterruptedException

一个简单的join例子

public class D5 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            try {
                System.out.println("我是子线程,我先睡一秒");
                Thread.sleep(1000);
                System.out.println("我是子线程,我睡完了一秒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread.start();
        thread.join(0);
        System.out.println("如果不加join方法,我会先被打出来,加了就不一样了");
    }
}

那么为什么 t1 调用 join 挂起的是主线程呢 ?

join的原理

我们点进去 join ,重点看蓝色选中区域的代码。
在这里插入图片描述
我们自定义一个类继承 Thread 并将 join的实现拿出来改造,来打印 this 和 Thread.currentThread()

public class D5 {

    public static void main(String[] args) throws Exception {
        MyThread t1 = new MyThread(() -> {
            try {
                System.out.println(System.currentTimeMillis() + "我是子线程,我先睡一秒");
                Thread.sleep(1000);
                System.out.println(System.currentTimeMillis() + "我是子线程,我睡完了一秒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "thread-01");
        t1.start();
        t1.join2(0);
        System.out.println(System.currentTimeMillis() + "如果不加join方法,我会先被打出来,加了就不一样了");
    }


    static class MyThread extends Thread {

        public MyThread(Runnable target, String name) {
            super(target, name);
        }

        public final synchronized void join2(final long millis)
                throws InterruptedException {
            System.out.println(this);
            if (millis > 0) {
                if (isAlive()) {
                    final long startTime = System.nanoTime();
                    long delay = millis;
                    do {
                        wait(delay);
                    } while (isAlive() && (delay = millis -
                            TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) > 0);
                }
            } else if (millis == 0) {
                while (isAlive()) {
                    Thread thread = Thread.currentThread();
                    wait(0);
                }
            } else {
                throw new IllegalArgumentException("timeout value is negative");
            }
        }
    }
}
------
this:Thread[thread-01,5,main]
Thread.currentThread:Thread[main,5,main]
1666281297418我是子线程,我先睡一秒
1666281298419我是子线程,我睡完了一秒
1666281298419如果不加join方法,我会先被打出来,加了就不一样了

分析:t1.join在主线程调用,所以在方法内部获取当前线程是主线程。

因为 wait 会挂起当前线程而不是当前对象,所以挂起了主线程。

那么主线程什么时候被唤醒的呢 ?

线程终结

在thread.cpp中
在这里插入图片描述

void JavaThread::exit(bool destroy_vm, ExitType exit_type) {}

exit调用ensure_join然后调用notify_all唤醒所有线程

static void ensure_join(JavaThread* thread) {
  lock.notify_all(thread);
}

用户线程和守护线程

daemon:守护,true 代表守护线程,false 代表用户线程,默认为false用户线程。

    /* Whether or not the thread is a daemon thread. */
    private boolean     daemon = false;

在初始化init方法中设置为当前线程的类型

        Thread parent = currentThread();
        this.daemon = parent.isDaemon();

可以调用set方法赋值,如果线程已经启动则会报错。

    public final void setDaemon(boolean on) {
        checkAccess();
        if (isAlive()) {
            throw new IllegalThreadStateException();
        }
        daemon = on;
    }
    
    public final native boolean isAlive();

案例1:当程序只剩下守护线程的时候,程序会退出

在这里插入图片描述

线程安全和synchronized关键字

一个经典的线程安全例子
在这里插入图片描述

javap 反编译字节码后的效果

在这里插入图片描述

m3静态变量自增的过程,获取静态变量,放入操作数栈顶,加操作,放回静态变量,返回。

在多线程访问的过程中,在每一行的间隔都应该拉大,会发生A线程还没有写回静态变量就被获取的情况。

加了 synchronized 关键字 ,5 8 9 10 行对应之前的操作。 其他详细的解释可以参考 虚拟机规范。

Java Language and Virtual Machine Specifications

线程退出

thread.cpp文件搜索JavaThread::exit
在这里插入图片描述
1870行 调用了 ensure_join(this)
在这里插入图片描述
ensure_join(this) 方法

static void ensure_join(JavaThread* thread) {
  // We do not need to grap the Threads_lock, since we are operating on ourself.
  Handle threadObj(thread, thread->threadObj());
  assert(threadObj.not_null(), "java thread object must exist");
  ObjectLocker lock(threadObj, thread);
  // Ignore pending exception (ThreadDeath), since we are exiting anyway
  thread->clear_pending_exception();
  // Thread is exiting. So set thread_status field in  java.lang.Thread class to TERMINATED.
  java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
  // Clear the native thread instance - this makes isAlive return false and allows the join()
  // to complete once we've done the notify_all below
  java_lang_Thread::set_thread(threadObj(), NULL);
  lock.notify_all(thread);
  // Ignore pending exception (ThreadDeath), since we are exiting anyway
  thread->clear_pending_exception();
}

第9行将线程的状态改为死亡。
倒数第4行唤醒所有阻塞等待的线程,也就是上面join提到的主线程。

子线程异常问题

如果子线程执行异常怎么办?

        Thread thread = new Thread(() -> {
            throw new RuntimeException("sdasds");
        });
        ----
        this is main
Exception in thread "Thread-0" java.lang.RuntimeException: sdasds
	at com.example.common.juc.thread.ThreadError.lambda$main$0(ThreadError.java:24)
	at java.lang.Thread.run(Thread.java:748)

处理方法一:在子线程中try-catch可能发生异常的代码块处理

        Thread thread = new Thread(() -> {
            try {
                throw new RuntimeException("sdasds");
            } catch (RuntimeException e) {
                System.out.println(Thread.currentThread().getName() + "发生了异常");
            }
        });

那么我如果想要在主线程处理呢 ? 尝试 try-catch子线程启动

        try {
            thread.start();
        } catch (Exception e) {
            System.out.println("error ..");
        }

结果发现在启动阶段添加try-catch不起作用,那么应该怎么处理?

在Thread类中有一个UncaughtExceptionHandler

public class ThreadError {

    static UncaughtExceptionHandler h = new UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("开始处理异常....");
            System.out.println(t.getName());
            System.out.println(e.toString());

        }
    };

    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(() -> {
                throw new RuntimeException("sdasds");
        });
        thread.setUncaughtExceptionHandler(h);

        thread.start();

        // try {
        //     thread.start();
        // } catch (Exception e) {
        //     System.out.println("error ..");
        // }

        System.out.println("this is main");
    }
}
-----
this is main
开始处理异常....
Thread-0
java.lang.RuntimeException: sdasds

activeCount

    public static int activeCount() {
        return currentThread().getThreadGroup().activeCount();
    }

返回当前线程的线程组中活跃的线程数量。

currentThread

public static native Thread currentThread();

返回当前线程。

ThreadGroup

线程在创建的过程中允许指定线程组,如下图部分方法所示。
在这里插入图片描述
而线程组的构造函数仍然可以指定线程组
在这里插入图片描述
不指定线程组的话 ,会获取当前线程的线程组
在这里插入图片描述
下面我们测试构造函数

public class UseThreadGroup {
    public static void main(String[] args) {
        ThreadGroup group = new ThreadGroup("group one");
        System.out.println(group);

    }
}

自定义线程组的parent是main,main的parent是system,而system以上就没有parent了。

在这里插入图片描述

批量中断

public class UseThreadGroup {
    static Run r = new Run();
    public static void main(String[] args) throws InterruptedException {
        ThreadGroup group1 = new ThreadGroup("first");
        ThreadGroup group2 = new ThreadGroup(group1, "second");
        ThreadGroup group3 = new ThreadGroup(group2, "third");
        Thread t1 = new Thread(group1, r, "t1");
        Thread t2 = new Thread(group1, r, "t2");
        Thread t3 = new Thread(group2, r, "t3");
        Thread t4 = new Thread(group2, r, "t4");
        Thread t5 = new Thread(group3, r, "t5");
        Thread t6 = new Thread(group3, r, "t6");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        t6.start();
        Thread.sleep(5);
        group1.interrupt();
    }

    public static class Run implements Runnable {
        @Override
        public void run() {
            Thread thread = Thread.currentThread();
            System.out.println("线程组:" + thread.getThreadGroup().getName() + ",线程名称:" + thread.getName());
            while (true) {
                if (thread.isInterrupted()) {
                    System.out.println("线程:" + thread.getName() + "停止了!");
                    break;
                }
            }
        }
    }
}
-------
线程组:first,线程名称:t1
线程组:third,线程名称:t5
线程组:second,线程名称:t4
线程组:third,线程名称:t6
线程组:second,线程名称:t3
线程组:first,线程名称:t2
线程:t1停止了!
线程:t2停止了!
线程:t5停止了!
线程:t6停止了!
线程:t4停止了!
线程:t3停止了!

在线程组 group1 中释放中断信号,所有的子线程组以及子线程都收到了中断信号退出了。

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

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

相关文章

Minecraft 1.19.2 Fabric模组开发 05.动画效果物品

我们本次在Fabric中实现一个具有动画效果的物品&#xff0c;本次演示的模型代码均在文末给出 效果演示效果演示效果演示 首先&#xff0c;请确保你的开发包中引入了geckolib依赖&#xff0c;相关教程请参考:Minecraft 1.19.2 Fabric模组开发 03.动画生物实体 1.首先我们要使用…

nvidia显卡编码并发session限制破解

对于服务器型显卡&#xff08;比如&#xff1a;Tesla T4、NVIDIA A100等&#xff09;&#xff0c;nvidia显卡编码并发session路数没有限制&#xff0c;对于消费型显卡&#xff08;比如&#xff1a;GeForce RTX 3070、GeForce RTX 3080等&#xff09;,nvidia显卡编码并发session…

Odoo 16 企业版手册 - 财务管理之会计仪表板

会计仪表板 财务管理是任何类型业务不可避免的一部分。无论您经营的业务规模或类型如何&#xff0c;如果财务流程没有得到适当的管理和监控&#xff0c;您将在未来面临严重的财务失败。手动管理所有会计操作不是一个好主意&#xff0c;因为它需要大量的时间和精力。在人工会计管…

esp32 Python开发快速入门--环境配置以及点亮LED灯

esp32 Python开发快速入门--环境配置以及点亮LED灯1. 环境配置2. 固件的烧录3 下面开始点灯1. 环境配置 需要配置两个环境&#xff0c; 开发的IDE Thonny 链接如下 https://thonny.org/ 安装串口助手&#xff0c;芯片是通过串口来实现通信的&#xff0c;需要驱动&#xff0c;…

Openresty宏观概述笔记

最近由于项目需要学习了安全代理的相关知识&#xff0c;其实刚开始的时候是非常需要一个入门的介绍&#xff0c;大概说明下这个到底是个什么东西&#xff0c;能干啥&#xff0c;简单的原理是什么&#xff0c;为此我记录下我看完用完的心得&#xff0c;记录成笔记。 一般我们代码…

Nginx基础01:安装和基本使用

背景Nginx是一个高性能的Web服务器&#xff0c;几乎所有的Web服务都需要使用Nginx。关于Nginx的功能特性这里不再赘述&#xff0c;让我们从0开始&#xff0c;了解Nginx的基本用法&#xff0c;学习它在Web服务中都有哪些应用。本文主要介绍Nginx的安装以及基础的控制命令。Nginx…

讨论| 电视行业已是落日夕阳?

如今&#xff0c;随着移动设备、互联网技术的发展&#xff0c;电视似乎逐渐淡出人们的视野。近期&#xff0c;与电视相关的热门讨论似乎都是对于各大视频平台的会员投屏制度&#xff0c;这似乎更加减少了大众对于电视的使用欲望。那么电视领域是否真的趋于淘汰了呢&#xff1f;…

PASCAL VOC 数据集的标注格式

PASCAL VOC 数据集的标注格式 PASCAL VOC 挑战赛 &#xff08; **The PASCAL Visual Object Classes &#xff09;是一个世界级的计算机视觉挑战赛。 PASCAL的全称是Pattern Analysis, Statistical Modelling and Computational Learning&#xff0c;很多优秀的计算机视觉模型…

区块链知识系列 - 系统学习EVM(一)

EVM有一个基于栈的架构&#xff0c;在一个栈中保存了所有内存数值。EVM的数据处理单位被定义为256位的“字”&#xff08;这主要是为了方便处理哈希运算和椭圆曲线运算操作&#xff09; 这里所说的内存数值是指那些EVM字节码运行所需要的输入、输出参数数据和智能合约程序运行中…

拉伯证券|锂离子动力电池有哪些优缺点?锂离子电池的优缺点详解

锂离子动力电池是20世纪开发成功的新型高能电池。这种电池的负极是石墨等资料&#xff0c;正极用磷酸铁锂、钴酸锂、钛酸锂等。70年代进入实用化。因其具有能量高、电池电压高、工作温度规模宽、贮存寿命长等优点&#xff0c;已广泛应用于军事和民用小型电器中。 锂离子动力电池…

jvm垃圾回收笔记

JVM基础知识笔记 1. 垃圾回收相关算法 标记清除-标记整理-复制 这三个看上面的文章 1.1 分代收集算法 将不同生命周期的对象采用不同的收集方式&#xff0c;以便提高回收效率&#xff0c;一般是将Java堆分为新生代和老年代&#xff0c;这样可以根据各个年代的特点使用不同的…

拉伯证券|A股涨势趋缓,个股分化,北向资金继续“买买买”

今天上午&#xff0c;A股商场涨势趋缓&#xff0c;主要指数涨跌互现。 个股方面也现较大分解&#xff0c;接连涨停股和接连大幅跌落股同时呈现。 值得注意的是&#xff0c;作为近期最为坚定买入A股的力量之一&#xff0c;北向资金今天上午继续“买买买”&#xff0c;上午半个交…

Wider Face+YOLOV7人脸检测

1 Wider Face标注格式转成YOLO格式1.1 Wider Face标注介绍The format of txt ground truth. File name Number of bounding box x1, y1, w, h, blur, expression, illumination, invalid, occlusion, pose0--Parade/0_Parade_marchingband_1_849.jpg 1 449 330 122 149 0 0 0 0…

Java开发 - Spring Test知多少?

前言 在前文中&#xff0c;我们也使用了测试代码来进行简单的单元测试&#xff0c;但是我们会发现&#xff0c;里面有大量的重复代码&#xff0c;实际给我们的体验并不是太好&#xff0c;所以这篇&#xff0c;我们来学习Spring Test&#xff0c;Spring Test不仅仅限于在Mybati…

AUTOSAR RTE 总结

1. Interface和在Interface下面包含哪些data element在SWC创立之前就定义好了&#xff0c;存储在一个arxml文件里面&#xff0c; 它相当于一个库文件&#xff0c;在新建AUTOSAR project的第一步就应该被导入进来 2. port在SWC创建的阶段被create&#xff0c;因为Interface没有…

某博数据挖掘:使用Scrapy构建自定义数据采集提取洞察信息

想要深入了解某博上最新的动态和信息吗?那么学习如何使用Scrapy构建一个某博数据采集将是不二之选。Scrapy是一个强大的框架,能够快速地爬取网站上的数据。 新版API构建的某博数据采集拥有最丰富的字段信息,能够更好地深入挖掘某博上的数据。提供了多种采集模式,包括用户、…

邮件定时发送java实现

本文总结如何通过java实现邮件接口的定时发送任务。1、邮箱服务器地址和端口以139邮箱为例&#xff0c;获取服务器地址和端口。139邮箱的路径&#xff1a;设置-常见设置-邮箱协议设置2、客户端配置工具&#xff1a;springboot2.4.3使用maven&#xff0c;使用java11pom.xml引入m…

华为机试题:HJ14 字符串排序(python)

文章目录知识点详解1、input()&#xff1a;获取控制台&#xff08;任意形式&#xff09;的输入。输出均为字符串类型。2、print() &#xff1a;打印输出。3、int() &#xff1a;将一个字符串或数字转换为整型&#xff08;强转&#xff09;。4、range() &#xff1a;输出指定范围…

《Linux Shell脚本攻略》学习笔记-第十二章

12.1 简介 我们可以通过关闭无用的服务、调整内核参数或是添加新的硬件来改善系统性能。 12.2 识别服务 Linux系统可以同时运行数百个任务&#xff0c;其中可能也会有那么一两个你不需要的守护进程。 有三种可以用于启动守护进程和服务的工具&#xff0c;Linux发行版支持其中任…

LeetCode题解 贪心(一):455 分发饼干;376 摆动序列;53 最大子序和

随想录 && LeetCode 贪心算法 贪心之于算法&#xff0c;内核是一个最优解是由多个局部最优解组合而成的 比如&#xff0c;如何在一个月之内最有效的减肥&#xff0c;子问题就是每周如何减肥&#xff0c;再拆分就是每一天如何减肥 如果能找到令每一天都有效减肥的策…