【Java】线程的理解和使用

Java线程:理解与实践 在多线程编程中,Java提供了一套强大的工具和API,使得并发编程变得更加容易和高效。类是Java中实现线程的基础,它允许我们创建和管理线程,执行并行任务。以下是对Java线程的深入理解和实践的总结。线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。在Java中,每个线程都是类的实例或者继承了类。 javapublic class MyThread extends Thread { private int number; ​ public MyThread 阅读详情

重要声明:本文章仅仅代表了作者个人对此观点的理解和表述。读者请查阅时持自己的意见进行讨论。

本文更新非常不及时,建议到原文地址浏览: 《【Java】线程的理解和使用》。

一、认识线程

任何一个程序至少有一个线程。这个线程是主线程,维持程序执行的线程。有时候我们在主线程中执行某个任务(方法)时,使主线程卡住或者执行缓慢。这时候,就非常有必要另外在创建一个新线程,将任务(方法)放在这个新的线程里面去执行。这样就可以减少主线程的负担并将最终结果完成得更有效率。

二、创建并使用线程

1、创建线程

线程的创建离不开Thread类,因此首先必须要先学习Thread类,才能明白如何创建线程。Thread类提供了一个无参数的构造函数,意味着可以直接通过new Thread()来创建一个新的线程。

Thread nthd = new Thread();

上述代码创建了一个空线程,这个线程什么事情都不会做。现在,通过start()方法就可以让这个线程运行起来。

nthd.start();

但是因为这是空线程,没有任何任务,因此,线程开启后马上就会退出。现在的任务就是如何让这个新创建的线程运行一个任务。

要给新线程添加一个任务,其实非常简单。新线程的创建,除了可以使用Thread的无参构造函数,还可以使用其提供的另一个构造函数Thread(Runnable target)。意味着可以传入一个Runnable来创建线程。在加入任务创建新线程之前,先了解一下Runnable。

2、Runnable

它是一个接口,里面只有一个run方法,并且是无参数的。要实现这个接口就必须实现这个方法。你可以把你的任务放在这个run方法中去执行。通常实现这个接口的类都是有任务要放在新的线程里去执行的。

比如说,打印1...100这些所有数字。可以这样实现:

Runnable print100 = new Runnable() {
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(i);
        }
    }
};

// 调用run方法,打印。
print100.run();

此程序输出1...100没有任何问题。但是注意,上述代码并没有新开任何线程。要让这个任务在新线程里面执行,必须要结合Thread才可以。下面将进行介绍。

3、新线程执行任务

现在我们对Runnable有了认识,对Thread也有了认识,只需要将Runnable与Thread结合,就可以让任务在新线程里面执行。

Runnable print100 = new Runnable() {
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(i);
        }
    }
};
Thread print100Thread = new Thread(print100);
// 启动新开启的线程。即可执行 print100 的任务
print100Thread.start();

上述代码,就向你展示了如何使用Runnable与Thread结合来完成新线程执行指定的任务。

4、继承Thread

上面我们通过了Runnable和Thread的结合来完成了新线程的创建并指定了具体任务。其实有另一种更加方便的方法来实现。

继承Thread类,然后重写run方法,直接将你的任务代码写在run方法里面,就可以完成和上面一样的效果。

public class Print100Thread extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(i);
        }
    }
}

然后就可以直接通过new Print100Thread()来创建一个线程执行对应的任务:

Print100Thread print100Thread = new Print100Thread();
// 启动新开启的线程。即可执行任务
print100Thread.start();
5、注意事项

现在我们了解了如何创建线程,并且熟悉了如何将任务放在线程里去执行。但是要精通这项技能,光了解这点知识点是远远不够的。还有许多要注意的地方。

  1. 只有通过调用start()方法启动的线程,run方法才会在子线程里面执行。调用其他任何方法使run方法得到了执行,都不是新开的线程。
  2. Thread类里面,仅有run方法里面的代码,或在run方法里面被调用的方法会在子线程里面执行。也就是,Thread里面子线程执行入口有且只有一个run方法。
  3. run方法执行完成,线程随之退出。run方法一直执行,此线程会一直等待直到run方法执行完成。

三、线程的进阶知识

上面讲述了基本的线程知识,你明白了如何创建线程,和在线程内执行你希望的任务。但是在实际开发中,遇到的需求往往比案列中要复杂得多。为了应对更为复杂的需求场景,不得不祭出和线程操作有关的更多方法。

1、Thread.sleep()

这是一个静态公共方法。由Thread类提供的。它可以使当前线程直接睡眠指定的时间(毫秒),相当于你给他传递多长时间,这行代码就要卡住多长时间。比如:

for (int i = 0; i < 60; i++) {
    System.out.println(i);
    try {
        Thread.sleep(1000); // 每次循环输出了数字后,睡眠1000毫秒(1秒)的时间。
    }catch (Exception ignore) {/* 这里暂时忽略异常 */}
}

看起来非常简单,就是让当前线程卡住(睡眠)指定长度的时间。不过还是有些点是需要注意到。

  1. 这是一个静态方法。建议你经通过Thread.sleep()的方式去调用。
  2. 起作用永远都是作用于这行代码所处的那个线程。例如下面这种极端场景也不例外:
// 创建一个新线程A
Thread threadA = new Thread();

// 创建一个新线程B
Thread threadB = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("线程B开始了。");

        try {
            // 尝试通过A线程对象来调起 sleep 方法。
            threadA.sleep(1000);
        }catch (Exception ignore) {/*暂时忽略异常*/}
        System.out.println("线程B结束了。");
    }
});
threadB.start();

即便是这样的代码。最终的效果是:先输出线程B开始了。。然后等待1秒钟之后输出线程B结束了。。可以证明sleep方法是一个静态方法,它不在乎是谁引用调起了它,它的作用也永远是:这行代码在哪儿执行的,就把对应的线程给睡眠指定时间。

2、synchronized

synchronized 不是一个方法。em~~~。可以理解为它是一种语法。用法示列如下:

public class Test {
    static Object obj = new Object(); // 标记1
    public static void main(String[] args) {
        int number = 0;

        // 使用 synchronized 代码块 包围 里面那段代码。
        synchronized (obj) {       // 标记2
            number = 10 + number;  // 标记3
        }
    }
}

在我还没介绍前不必担心看不明白。首先需要对里面进行标记的三处代码进行介绍。

  • 标记1:创建了一个同步锁对象。
  • 标记2:使用同步锁对象和synchronized结合的语法形成同步代码块。
  • 标记3:要同步执行的代码。

十分有必要再进一步解释为什么需要这样的代码,你可能才会完全领会到它的用途。为了解释清楚,先不用代码直接说这件事。不妨比喻一个场景:

有一个公共卫生间,里面只有一个坑,有时候客流量大的时候会有很多人同时冲进去蹲坑。相关部门就看不下去了,说这也不是一个办法。必须解决这个问题。有人就提出一个解决方案,说每次只能进去一个人蹲坑,通过卫生间外面的一个“坑票循环机”,要蹲坑,先从这台机器里取票,而且总共只有一张票。蹲坑完成后将票塞回这台机器的回收口,下一个人才能又从这台机器的取票口拿到这张票。必须有票才能进去蹲坑。没票说明上一个人还在蹲,就只能在外面等着,乖乖排队。

相信这样说你应该就明白了。所谓同步代码块,就相当于这个“坑票循环机”。obj就相当于“票”。整个代码块由 synchronized、“票”、和一个{}代码块构成。当有一个线程正在执行标记3的代码时,相当于有个人进去蹲坑了,票(obj)也被拿了,这时候就不能有其它的线程再进来蹲坑了。

这就解决了一个问题啊,什么问题?比如说,如果那个number的含义是共有多少个促销商品,商品一旦开始促销,就会瞬间有很多人抢购,反应到程序里,就变成瞬间有多个线程同时要来操作这个number,厕所可以做到一个坑位即便来了2个人,另一个人也没办法蹲,而程序可不会,程序是来了就干了。这样可能出现同一个商品被卖出去多次啊,这问题可就大了去了。所以,为了解决这个问题,同步代码块产生了。

同步锁

通过上面的介绍,你要知道。同步代码锁其实就是一个 obj 对象,它允许的类型是Object的,也就是你可以使用任何类对象来作为这把锁。不管你用什么类、什么对象,你都要保证“坑票”只能有一个,在整块同步代码块上,那个 obj 只能是唯一的。你不能每一个同步代码块都用一个新的obj对象,那就没有意义了。那就等同于“坑票循环机”要产出多张票了。这是万万不应该的。

比如下面示列 错误 的用法(语法没问题):

public class TestLock {
    public static void main(String[] args) {
        TestLock t = new TestLock();

        new Thread(new Runnable() {
            public void run() {
                t.test();
        }}).start();

        new Thread(new Runnable() {
            public void run() {
                t.test();
        }}).start();
    }

    int number = 0;

    void test() {
        // 创建同步锁
        Object obj = new Object();
        synchronized (obj) { // 代码块
            number = 10 + number;
        }
        System.out.println(number);
    }
}

上述代码中,test 方法里有一个同步代码块,并且在main函数中使用2个线程分别调用这个方法。而这个方法里面的同步锁对象都是新创建的,意味着多个线程用的同步锁不是同一个,这就导致同步代码块毫无意义。所以不能这样使用。必须优化修改,只能使用一个锁对象。如下:

public class TestLock {
    public static void main(String[] args) {
        TestLock t = new TestLock();

        new Thread(new Runnable() {
            public void run() {
                t.test();
        }}).start();

        new Thread(new Runnable() {
            public void run() {
                t.test();
        }}).start();

    }

    // 创建同步锁. 同步锁应该只用同一个,因此将其创建为final不能修改,且作为Test的全局变量。
    private final Object obj = new Object();

    int number = 0;
    void test() {
        synchronized (obj) { // 代码块
            number = 10 + number;
        }
        System.out.println(number);
    }
}

将锁对象提取为全局对象,让test方法中使用的 obj 都保证是同一个锁对象。不过你依然要注意,并不是永远都是锁对象保持唯一,此处的示列代码,对于一个TestLock对象,里面是同步有效的。假如你new了2个TestLock出来,当然这两个里面的obj分别是各种的obj对象,其对应同步代码块用的锁也是各自的锁。按某种理解上,这里产生了2个锁对象出来,不过这两个也锁对象也保证了只会用在各自的代码块里。

所以,最终,你必须要弄清楚,同步代码块,你用在什么地方,锁对象的创建根据自己的需求创建唯一锁。

3、Object#wait()

wait方法可以让线程进入等待状态。也就是一但你用了这个方法,这行代码就会一直卡在这儿不会继续向下执行了。如果你希望这行代码的位置处继续执行下去,你可以调用notify或notifyAll方法,这两个方法将在下文讲解。wait方法是Object类的成员方法,不是静态方法。因此必须创建一个对象,在对象上使用此方法。要注意wait方法的使用必须在synchronized代码块里面,在其它地方使用都会报错的。

int number = 0;
void test() {
    synchronized (obj) {
        number = 10 + number;
        try {
            obj.wait(); // 标记1
        }catch (Exception ignore) {/*暂时忽略异常*/}
    }
    System.out.println(number);
}

上述代码中,在标记1的位置处使用了wait方法,那么这个时候,这个代码就会永远的卡在这里,等待下去…。现在我们先不关心它后续会怎么样。先来剖析一下这个代码。细心的你肯定会发现,我们是直接使用同步锁对象来调用wait方法的。这也是为什么必须在synchronized里面使用wait方法的原因。就拿上面的“蹲坑”事情来说吧,相当于这个进去蹲坑的人突然被那张票施了冰冻魔法(wait),将其冻住了,蹲坑的人就一直不动,必须等这张票再次施展火球魔法(notify)解冻,才可以继续执行下去。而此期间,进去的人一直在里面,外面也拿不到票进不来。蹲坑线程冻住了没关系,别的线程没冻住嘛,别的线程拿到同步锁,调用notify方法施展火球魔法,这张票施展火球魔法将其解冻,又继续愉快的蹲坑了。所以说,通常情况下,wait是自己线程发现某情况下需要wait了,进行自行wait。而自己都卡住了就没法运行了,也就不可能谈什么恢复了。因此通常是还有另一个线程,发现你的某情况又再一次符合标准了,它给你notify一下,你就又开始继续执行了。

这样就就形成了一种很协调的机制,与现实世界的事件发生顺序可以完全吻合了。比方说:你找包工头要工资,包工头发现上头还没拨款下来,包工头也没钱。你就赖着(wait)不走了,就一直等啊等,突然包工头收到了钱了,包工头给你一notify,你就美滋滋拿钱走人了。又比如说:你是工厂,你在生产手机。有个超市要从你这儿进购一批手机,你发现手机不够啊。这时候,人家就wait在哪儿了,你就不停的生产啊生产。唉!突然手机生产够了,你就又给人家来个notify。人家美滋滋拿着货走人。

4、Object#notify()\notifyAll()

notify和notifyAll方法作用相似,只不过前者只会唤醒一个在wait的线程。而后者会唤醒所有在wait的线程。我们知道wait方法的使用必须要在同步锁对象上使用。因此这里所说的唤醒一个或者唤醒所有是指的唤醒使用对应同步锁进行wait的一个或者所有线程。示列如下:

// 假设现已有同步锁 obj
final Object obj = new Object();
// 开启线程
new Thread(new Runnable() {
    public void run() {
        synchronized(obj) {
            System.out.println("开始...");

            try {
                // 进入等待状态。
                obj.wait();
            }catch(Exception ignore) {
                /*暂时忽略异常*/
            }

            System.out.println("完成...");
        }
    }
}).start();
// 同时也开启另一个线程
new Thread(new Runnable() {
    public void run() {
        // 先 sleep 2秒钟,
        // 这样2秒过后左边都等很久了。
        // 再调notify就能把左边唤醒。
        try {
            Thread.sleep(2 * 1000);
        }catch(Exception ignore) {
            /*暂时忽略异常*/
        }

        synchronized (lock) {
            // 用obj同步锁调用唤醒,
            // 让左边的线程继续执行。
            obj.notify();
        }
    }
}).start();

为了直观感受,我将排版改成横向的了。运行上面的代码,左右两边的线程同时开启,左边一开始就进入wait状态,后面一开始就先睡眠2秒钟,等2秒钟过后,右边的线程唤醒了左边的wait状态,左边进行输出。集合wait那一小节的知识,香型。很快就能理解。

四、生产者与消费者

学会了上面的知识点之后,不如进行一次简单的实战。生产者与消费者的关系基本上是学习线程的首个demo案列。就如同你要学习某个语言之后第一个程序是“hello world.”一样。

生产者作为一个线程。 消费者作为一个线程。这就形成了最简单的供求关系了。而且可能有多个消费者线程、也有多个生产者线程。

1、程序设计

这个案例涉及到的东西相对较多,我们必须有一个合适的程序结构,才能更容易理解,更符合程序规范,更加容易维护我们写出来的程序。首先我们要考虑到的就是“生产者”可以有多个人的,“消费者”也可以有多个人。那么,生产者生产的“产品”,是要设计在生产者里面,还是要设计在一个单独的地方?

这个问题很好回答,如果我们只支持一个生产者,当然可以把“产品”就放在生产者里面。但我们要设计支持多个生产者同时生产的,如果还把产品放在各自生产者里自己维护,那么每个生产者要去关心自己有没有足够的产品提供。相当于生产者除了要做生产这件事、它还要管理产品够不够给的事情。这无疑有点让生产者的设计变得臃肿且没有必要。

所以,将产品放在一个单独的管理区,相当于有一个仓库。生产者生产的产品都放在这个仓库里面。这样生产者只需要生产,而不需在乎产品具体有多少。即便有多个生产者,产出的产品也直接往仓库里面放就好了。而对于消费者,直接从仓库取。这样一来,无论是生产者还是消费者,都可以支持多个生产者或消费者。

2、开发产品仓库

不论是消费者还是生产者,都是要和仓库打交道的。首先开发出仓库,可以同时为生产者和消费者提供更多便利之处。以ProductRoom类作为仓库:

public class ProductRoom {
    // 同步锁对象。
    private final Object lock = new Object();
    // 所有产品都放置在这个集合里。
    private List<Object> products;

    public ProductRoom () {
        products = new ArrayList<>();
    }

    // 从仓库中取一个产品出来。
    public Object takeAProduct() throws Exception{
        // 使用同步代码块进行同步,保证每个产品不会被多次获取。
        synchronized (lock) {
            // 只要仓库里没有产品,就在这儿等着。
            while (products.isEmpty()) {
                lock.wait();
            }
            // 能运行到这里,说明必然是有产品的。
            return products.remove(0);
        }
    }

    // 放一个产品到仓库里面。
    public void putAProduct(Object obj) {
        synchronized (lock) {
            products.add(obj);
            // 放了一个产品进去后,有可能有些消费者还在等着获取产品呢。
            // 所以,这里就调一次唤醒,让等待的消费者能继续获取产品。
            lock.notifyAll();
        }
    }
}
3、开发生产者

一个生产者就是一个新线程,所以,我们的生产者肯定是要继承Thread来实现的。并且我们定义生产者每5秒钟生产一个产品。我们以ProductMaker作为生产者类:

/**
 * 生产者。
 */
public class ProductMaker extends Thread{

    // 为了识别生产者,我们给生产者一个名字。
    private String mName;

    // 仓库。
    private ProductRoom productRoom;

    // 构造时,指定将生产的产品放到对应的仓库里。
    public ProductMaker (String name, ProductRoom productRoom) {
        this.mName = name;
        this.productRoom = productRoom;
    }

    @Override
    public void run() {
        super.run();

        // 生产者只要已启动,就不停的每5秒生产一个产品。
        while (true) {
            try {
                // 每五秒生产一个,只需要sleep5秒钟,来模拟耗时5秒钟才生产完成。
                Thread.sleep(5 * 1000);
            }catch (Exception ignore) {/* 暂时忽略异常 */}

            Object o = new Object();
            // 生产了一个。就放到仓库里
            productRoom.putAProduct(o);
            // 顺便打印一个提示。
            System.out.println("[" + new Date().toLocaleString() + "] " + this.mName + "生产了一个产品:@" + o.hashCode());
        }

    }

    public String getmName() {
        return mName;
    }
}
4、开发消费者

一个消费者也是一个线程,所以也还继承Thread来实现,我们定义消费者每3秒就消费一个产品。定义消费者ProductUser类如下:

/**
 * 消费者类
 */
public class ProductUser extends Thread {

    // 为了记录是哪一个消费者,我们定一个姓名。
    private String mName;

    // 定义消费者要消费哪一个仓库里的产品。
    private ProductRoom productRoom;

    public ProductUser(String mName, ProductRoom productRoom) {
        this.mName = mName;
        this.productRoom = productRoom;
    }

    @Override
    public void run() {
        super.run();

        while (true) {

            try {
                // 从仓库取出消耗一个产品。
                System.out.println("[" + new Date().toLocaleString() + "] " + mName + "获取产品..");
                Object o = productRoom.takeAProduct();
                System.out.println("[" + new Date().toLocaleString() + "] " + "成功并消费了:@" + o.hashCode());

                // 每3秒就消耗一个产品。
                Thread.sleep(3 * 1000);
            } catch (Exception ignore) {/*暂时忽略异常*/}
        }
    }
}
5、测试

现在,一切就绪,可以进行测试了:

public static void main(String[] args) {
    // 创建一个仓库
    ProductRoom room = new ProductRoom();

    // 创建2个生产者
    ProductMaker maker = new ProductMaker("生产者A", room);
    ProductMaker maker2 = new ProductMaker("生产者B", room);

    // 创建一个消费者
    ProductUser user = new ProductUser("消费者Z", room);

    // 开启生产和消费
    maker.start();
    maker2.start();
    user.start();
}

来看一下运行结果:

{"class":"useDialog"}

ANSYS Workbench实战:用网格自适应搞定超弹性橡胶大变形仿真(附避坑命令流) 本文深入探讨了ANSYS Workbench中超弹性橡胶大变形仿真的网格自适应技术,重点解析了混合U-P单元的应用、非线性自适应网格的实战技巧及材料模型选择的注意事项。通过具体案例和命令流示例,帮助工程师有效解决橡胶仿真中的收敛问题和网格畸变挑战,提升仿真精度和效率。 阅读详情

相关推荐

Linux下OpenFire+Spark即时通讯环境搭建全攻略(含证书错误解决方案)

本文提供了一份详尽的Linux环境下OpenFire服务器与Spark客户端即时通讯系统的搭建与配置全攻略。内容涵盖从环境规划、服务端部署、数据库配置到客户端连接的全流程,并重点解决了Spark连接时常见的证书验证错误问题,为企业构建私有化、高可用的XMPP通讯平台提供了实战指导。

weixin_29251131的博客 165

java常用类库——“存储类的仓库”

java常用类库 本文介绍的类都可以在JDK文档中查看细节部分。 一、API概念 API(Application Programming Interface)应用程序接口。 比如在一个厂家购买一个机器人,该机器人可以跑,跳、握手等的动作,但这些操纵机器实现的动作不会逐一编写的,厂家会提供控制机器人执行这些动作的java类,我们在编写某一个机器人的程序时,只需要把这些java类实例化出一个对象就...

weixin_41550144的博客 701

vivado生成及调用自定义IP的方法

开发环境:vivado 2018.2 1.将整个工程封装为自定义IP 将整个工程封装为自定义IP主要有以下步骤: 步骤1.选中工程中的top模块。点击Tools ------> Create and Package New IP,如图1所示 图1 步骤2.弹出界面后,点击next至图2界面,选择Package your current project,然后继续点击next 图2 步骤3.选择生成的IP路径,如图3所示。这个路径就是后续添加该IP的路径 图3 步骤4.继续点击next,然后选择fi

进击的阿日比 4730

java中的线程如何理解——精简

经过前面的学习,我们已经了解到了IO流的知识,那么今天我们就将要开始学习Java中的线程,首先我们应该理解“线程”是什么意思?线程(thread)是一个程序内部的一条执行路径,我们所熟悉的main方法其实就是一条单独执行路径,若程序中只有一条执行路径那么这个程序就是单线程程序;既然有单线程,那么也相对的会有多线程,字面意思可以理解为“相对单线程从软硬件上多条执行流程的技术”,多线程的好处是提高CPU的利用率。 在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,大大提高程序的效率。.

weixin_50635856的博客 6301

Jetson设备EXT4文件系统修复实战:从superblock损坏到系统恢复

本文详细介绍了Jetson设备因异常断电等原因导致EXT4文件系统superblock损坏后的诊断与修复实战。通过从健康设备移植fsck工具、使用备份超级块等步骤,指导用户逐步恢复系统,并提供了数据备份与预防措施,帮助用户有效应对嵌入式AI开发中的存储故障。

theta的博客 592

神经元细胞结构刨析(持续更新)

神经细胞的学习笔记

weixin_46596227的博客 2221

java 进程 线程_Java 进程线程理解

一大波讨论进程,线程,多线程,系统服务,虚机等等感觉如果单个概念没理解清楚,掺杂在一起更加理解不清楚了什么是应用程序在Java中,一个应用程序对应着一个JVM实例(也有地方称为JVM进程)JVM是Java Virtual Machine的简写, Java虚拟机一般来说名字默认为 java.exe 或者 javaw.exe(windows下可以通过任务管理器查看)Java单线程编程模型我们自己的程序...

weixin_42601608的博客 120

PySpark 本地开发环境搭建与实践

本文详细介绍了 PySpark 本地开发环境的搭建过程,包括 JDK、Hadoop、Anaconda、PySpark 的安装以及 Pycharm 工程的创建。同时,深入讲解了代码编写、本地开发案例(如 WordCount、处理特殊分隔符、读取 hdfs 数据、获取外部变量)、Spark 程序的监控和 local 模式下结果文件数量问题等内容。通过掌握这些知识和技能,读者可以在 Windows 本地环境中高效地进行 PySpark 开发,处理大规模数据,解决实际业务中的数据分析和处理问题。

天冬忘忧的博客 3012

JAVA多线程理解

线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。多线程程序应用能够在同一时间执行多于一个任务,进而提升整体处理性能。本文将从最基础的部分开始讲解JAVA线程的知识,最后深入了解JAVA多线程的高级应用以及优化。

qq_22610595的博客 823

Win10环境借助DockerDesktop部署Open web UI集成DeepSeek

Win10环境借助DockerDesktop部署Open web UI集成DeepSeek,并借助DuckDuckGo实现联网查询

虎鲸不是鱼 1700

毕设助力!从0到1构建基于YOLO的家居安防异常活动监测系统,让你的毕设守护家庭安全

做完这个项目,你会发现自己完整走过了“数据采集→标注→模型训练→功能集成→界面开发”的毕设全流程,这不仅是技术能力的证明,更是对“技术守护生活”这一理念的实践。答辩时,你可以重点讲这几点:系统解决了家居安防的哪些痛点、对比了不同YOLO模型的性能差异、系统未来能怎么优化(比如对接智能门锁/报警器、实现语音交互)。未来,这个系统还能往“更智能”的方向发展:比如结合行为分析判断异常意图、开发移动端APP实现全屋设备联动、在智慧社区中实现多家庭联防……想象空间很大。

LXY_LOVE36的博客 122

对线程、进程和Java线程池的理解

人生最大的悲哀,莫过于我本可以。我钻研操作系统,后选修Linux操作系统,做过父进程生子进程的作业,还做过IO多路复用、多线程的网络游戏,自认为对线程、进程的理解颇深。然,多次面试,面对面试官:“你对Java的线程池机制有所了解吗?我不由噤声。面试官再问:“你说一说进程与线程的区别。我更是大惊失色,支支吾吾,拾了一些“进程不共享资源”、“进程对应多个线程”、“线程是执行最小单位”、“一个进程至少一个线程”来答,看着面试官越来越失望的脸色,我自知面试必挂。苦也!

weixin_62411896的博客 980

079、镜头阴影校正(LSC)精讲:径向衰减模型、增益矩阵生成与产线标定技巧

《镜头阴影校正(LSC)实战精要》 本文从实际案例出发,深入解析镜头阴影校正的核心技术与产线应用。作者通过一个典型故障案例(0.05mm镜片偏移导致LSC失效)揭示了LSC不仅是数学问题,更是光学-机械-工艺耦合系统。 【关键技术点】 模型优化:提出"径向+切向"双分量模型替代传统cos⁴θ模型,采用5阶多项式拟合径向衰减,1阶线性补偿切向误差 增益矩阵生成:采用自适应网格技术,在边缘区域加密网格,结合中值滤波与高斯平滑消除噪声干扰 产线标定:强调光源均匀性校准(积分球vs平板光源)、坏点预处理、温度补偿等

zzfmlxs的博客 102

java 线程 理解 解析

1 线程的概述   进程:正在运行的程序,负责了这个程序的内存分配,代表了内存中的执行区域。   线程:就是在一个进程中负者一个执行路径。   多线程:就是在一个进程中多个执行路径同时执行。 假象:   电脑上的程序同时在运行。”多任务“操作系统同时运行多个进程(程序)——但实际 是由于CPU分时机制的作用,使每个进程都能循环获得自己的CPU时间片。但由于轮换速 度非常快,使得所有程序好像是在”...

YiGE_snail的专栏 173

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

java多线程的理解

对于多线程的理解,我从已开始听的很模糊。 线程就是一个进程里多个线程执行同一个数据的不同路径,简单来说,就是多个人在一个桌上吃同一个碗里的同一种菜,不知道这样大家能看明白不,我在理解这里的代码时,有许多困难,因为毕竟没有现实场景的辅助很难理解这些概念型性的知识, 好的,废话不多说,咋直接上代码吧! 首先先给各位上一个线程的继承类,就继承Thread吧 /*首先咋先自定义类Mythread...

weixin_45770080的博客 171

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

如何理解Java多线程?

线程和进程 进程:是一个正在执行的程序 每个进程执行都有一个执行顺序,改顺序是一个执行路径,或者叫一个控制单元 线程:就是进程中的一个独立控制单元,线程控制着进程的执行 一个进程中至少有一个线程 多线程:CPU轮流执行每个进程(我们所谓的同时启动几个APP,看上去是同时运行,其实CPU在进程之间做着高速切换) Java中的多线程 如何在自定义的代码中,自定义一个线程呢? 查找API,发现Java中...

bigestboss的博客 772

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

JAVA关于线程自己的一些理解

学生党,在之前开发android软件的时候不太了解线程,导致UI线程阻塞所以特地回来学习下关于线程的知识. 我理解线程的概念就是 各自、同时 做一 些事情,达到一个目的,比如说: 我在吃饭的时候看电视,即在吃饭,又在看电视,吃饭是一个线程,看电视又是一个线程,同时发生. 概念也不想多讲,直接切入主题 Thread(线程)及相关信息 在 java.lang

CimZzz的博客 428

由浅入深理解Java线程池及线程池的如何使用

由浅入深理解Java线程池及线程池的如何使用

libufan的博客 140

【深入理解Java线程池】

/ 定义自定义线程工厂 ThreadFactory namedThreadFactory = new ThreadFactory() {} };// 定义自定义拒绝策略 RejectedExecutionHandler customRejectedHandler = new RejectedExecutionHandler() {} };

L-Blog 1596

记录Java多线程理解

记录了我在学习多线程的时候的一些看法。

想看什么文章都可以私信给我,综合性博客。 924

理解Java - 线程池

Executors创建线程池 Executors提供几种创建线程池的方法: Executors.newScheduledThreadPool(); //定时任务线程池 Executors.newCachedThreadPool(); //可缓存的线程池 Executors.newSingleThreadExecutor(); //单个线程的线程池 Executors.newFix...

qgbihc的专栏 140

Java线程的一点理解

1.线程和进程的区别 进程是资源进行分配和调度的基本单位 进程包含线程 进程有自己 线程cpu调度的最小单位 2.线程的生命周期 一个线程被创建变成了new,在获取到除了cpu之外的资源后变成了runnable,在等待检测锁的时候是blocked,在被执行wait() sleep()以及join()等方法后变为waiting以及等待特定的时间时是timed_waiting,终结后变为terminated. 理解一下blocked:当一个线程被调用了wait()/join()之类的方..

backer 657

java线程池_JAVA的那些事儿(线程池的理解和一点生活化)

今天来聊聊JAVA的线程池,然后联系上一点生活中的小常识,从而加深理解和记忆...先上图:JAVA线程池JAVA线程池结构很简单,有两个组件,一个是线程集合,一个是任务队列,线程集合里的线程不断地从队列里面获取任务来执行。为什么用这样的结构,早期的时候我们用一个 new Thread也能实现多线程。核心线程加任务队列的方案,能够消除多线程的资源消耗和线程安全问题。两者在源码里的实现一个是HashS...

weixin_39676348的博客 87

【java】对进程、线程的简单理解以及java实现多线程

问:进程、线程的基本概念? 答:进程、线程都是操作系统的一些基本概念。进程是指计算机中已运行的程序,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。线程是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是行程中的实际运作单位。 问:概念太抽象,能举个例子么?? 答:概念总是抽象的。不妨直接举个简单的例子,上图: 可以简单的认为电脑管家是一个进程,清理垃圾、扫描病毒、电脑加速等这些都是线程。 这些都是IE浏览器的线程,一个进程对应一个或多个线程。 问:每个线程都是平等地被cpu调度么?

qq_42425551的博客 311

java线程池理解

1.java使用线程池的优势 线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。 2.java使用线程池的特点 线程复用;控制最大并发数;管理线程。 第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的销耗。 第二:提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。 第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会

luo1054的博客 248

Java中关于多线程的一些概念理解

进程: 是指内存中的一个应用程序,拥有独立的内存空间。一个进程可以包含多个线程。 线程: 是进程中的一个执行路径,共享一个内存空间(每个线程都拥有自己的栈空间,共用一份堆空间) 由一个线程调用的方法,那么这个方法也会执行在这个线程内。 线程调度 : (1)分时调度: 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。 (2)抢占式调度 :优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),。 Java使用的为 抢占式调度。 CPU

weixin_44273388的博客 379
上一篇: 超详细的实现上传文件功能教程,文件上传实现。
下一篇: 解决layui的table组件更新数据后每行toolbar工具栏不更新的问题
Microanswer
博客等级 码龄10年 38粉丝 40原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值