JavaSE(10)_多线程 程序进程线程、线程创建使用(四种)、线程生命周期、线程同步、线程、死锁、线程通讯

【AI深究】合成数据(Synthetic Data)深度解析:原理、算法与工程实践——全网最详细流程|核心原理、主流方法、数学表达、工程实践与细节、未来趋势|GAN、VAE、语言模型LLM合成如GPT 大家好,我是爱酱。本篇延续将会系统梳理合成数据(Synthetic Data)的核心原理、主流方法、数学表达、工程实践与未来趋势,结合数学公式,帮助你全面理解这一AI建模的关键技术。 注:本文章含大量数学算式、详细例子说明及大量代码演示,大量干货,建议先收藏再慢慢观看理解。新频道发展不易,你们的每个赞、收藏跟转发都是我继续分享的动力! 阅读详情

目录

程序、进程、线程基本概念

进程与线程关系

单核CPU和多核CPU的理解

并行与并发

使用多线程的优点

何时需要多线程

线程的创建和使用

线程的创建和启动

Thread类常用的方法

JDK1.5之前创建新执行线程有两种方法

 创建多线程的方式一:继承Thread类

Thread类常用的方法

线程的调度

Thread类的拓展练习——买票

创建多线程的方式二:实现Runnable接口

Runnable接口拓展练习——买票 

继承方式和实现方式的联系与区别

(了解)线程分类

线程生命周期

线程的同步

需要线程同步的原因:

线程同步的优缺点:

触发同步机制的三种方法

1.同步代码块:

2.同步方法:

同步代码块与同步方法相比:

3.Lock锁(下面会讲)

买票实操举例(知识回顾)

使用同步代码块处理实现Runnable的线程安全问题

同步代码块处理继承Thread类的线程安全问题

同步方式原理图​编辑

线程安全的单例模式之懒汉式(完善之前的单例模式创建对象)

死锁的问题

演示死锁情况

Lock锁方式解决线程安全问题

实现接口Runnable方式演示Lock锁

线程的通信

涉及Object类的三个方法

线程通讯练习

(面试题)sleep()和wait()的异同

经典例题:生产者/消费者问题

JDK5.0新增线程创建方式

实现Callable接口创建线程

代码里面用的FutrueTask是Future接口唯一的实现类

使用线程池创建线程

线程池相关API


程序、进程、线程基本概念

程序(program):

  • 是为了完成特定任务,用某种语言编写的一组指令的集合,既指一段静态代码(没有cpu参与操作执行,处于停止状态的代码),也指静态对象

进程(process):

  • 是一个程序执行过程(因为分配到内存和cpu资源所以正在运行的程序——资源分配单位,例如运行中的qq占用了一个进程号和一定量的内存空间)
  • 它有自身的生命周期(产生——程序启动、存在——程序运行、消亡——程序关闭)
  • 一个java.exe应用程序中,会有至少三个线程:主线程(用户线程)main()、垃圾回收线程(主线程的守护线程,既与主线程同生共死)gc()、异常线程

线程(thread):

  • 线程是进程的进一步细化,是一个程序内部的一条执行路径(程序中的每一个运行的功能、Java中的main方法执行)。
  • 如果一个进程可以并行多个线程,则该进程为一个多线程进程(360是一个多线程进程,可以同时杀毒与清理垃圾,占用cpu和内存也会增加)。
  • 线程作为调度和执行的单位,每一个线程拥有独立的运行栈(本地方法栈和虚拟机栈)和程序计算器,线程切换开销小。
  • 一个进程中多个线程共享相同的内存单位/内存地址空间(JVM中的方法区和堆是线程共享的。他们从同一堆中分配对象,可以访问相同的变量和对象,使得线程之间通讯更加简便、高效、但多个线程操作共享的资源可能会带来安全隐患,可以参考下面的支付问题。

进程与线程关系

单核CPU和多核CPU的理解

  • cpu一个核心只能处理一个线程,分配给每个线程执行一段时间后挂起,再给另一个线程执行一段时间,但是因为CPU时间单元特别短,处理非常快,因此感觉不出多线程和单线程的区别
  • 如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
  • 一个java.exe应用程序中,会有至少三个线程:主线程main()、垃圾回收线程gc()——(主线程的守护线程,既与主线程同生共死。主线程在运行时,一些对象已经过期并且不再使用时,就由垃圾回收机制线程来回收,下面的"线程分类"有补充)、异常线程——(如果发生异常不处理就会影响主线程执行)

并行与并发

  • 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
  • 并发:一个CPU(采用时间片,快速切换线程)短时间内轮流执行多个任务。比如:秒杀、多个人做同一件事

使用多线程的优点

  • 以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方法),肯定比用多个线程来完成用的时间更短,减少CPU切换。但仍然需要多线程,因为多线程的优点是

  1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。(用户不用等待一个东西处理完才进行下一步操作,而是可以边处理这个,还可以处理另外一个。不管效率怎么样,我只要用户体验度增加)
  2. 提高计算机系统CPU的利用率(因为现在计算机cpu并不是单核的)
  3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改(每个模块独立运行)

何时需要多线程

  • 程序需要同时执行两个或多个任务。
  • 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
  • 需要一些后台运行的程序时。

线程的创建和使用

线程的创建和启动

  • Java语言的JVM允许程序同时运行多个线程,它通过java.lang.Thread类来体现。
  • Thread类的特性
    • 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体(一个线程对应一个Thread类型对象)
    • 通过该Thread对象的start()方法来启动这个线程,而非直接调用run()

Thread类常用的方法

  • Thread():创建新的Thread对象
  • Thread(String threadname):创建线程并指定线程实例名
  • Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法
  • Thread(Runnable target, String name):指定创建线程的目标对象,它实现了Runnable接口中的run方法,并设置线程名称
  • start():启动当前线程对象的线程,执行当前线程的run()
  • run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
  • currentThread(): 静态方法,可以获取当前线程的引用.或者信息类,似toString.
    • 格式:“Thread[main,5,main]:表示当前线程的名字为main,优先级为5,当前线程的主线程为main”
  • getName():获取当前线程的名字
  • setName():设置当前线程的名字,也可以写含参构造器(String name)来为其设置线程名
  • yield():就放弃当前线程掌握的cpu,并重新进入就绪状态与别的线程抢夺cpu资源,以获取cpu时间片
  • join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。也就是说这个方法用来在别的线程执行过程让cpu给指定线程,并进入阻塞状态,直至指定线程运行完毕后才结束阻塞
  • stop():。当执行此方法时,强制结束当前线程。已过时不推荐使用这种方式结束线程
  • sleep(long millitime):让当前线程“睡眠”指定时间的millitime毫秒)。在指定的millitime毫秒时间内,当前线程是阻塞状态的
  • isAlive():返回boolean,判断线程是否还活着
  • getPriority():获取当前线程的优先级
  • setPriority(int 1~10):设置线程优先级,默认为5,数越大优先级越高,获取cpu概率就越高,执行次数就越多,同级则随机调用
    • MAX_PRIORITY:10
    • MIN _PRIORITY:1

JDK1.5之前创建新执行线程有两种方法

  • 继承Thread类的方式
  • 实现Runnable接口的方式
  • 后面会讲1.5后的实现Callable接口
  • 后面还会将线程池创建管理线程

 创建多线程的方式一:继承Thread类

  1. 创建一个继承于Thread类的子类
  2. 重写Thread的run()方法
  3. 将此线程要执行的内容声明在run()中
  4. 主线程main方法下创建Thread类的子对象
  5. 主线程main方法下通过此对象调用start(),可以:
    1. 启动当前线程
    2. 当前线程调用当前线程的run()方法
  6. 此时第二个线程诞生开始可以独立执行run()里面的方法,只需要等待系统分配cpu时间片就可以运行执行run()里面的方法.主线程也会继续运行下去,两个线程同时运行
  • 问题1:不能直接调用run()而使用start()原因
    • 如果直接对象.run()方法,则就是当前main方法调用run()方法,没有分出多线程,不满足需求
  • 问题2:不能再次调用已经启动过线程的线程对象的start()方法
    • 通过底层代码得知线程对象第一次启动线程时需要判断线程状态是否为0,如果为0就是当前线程没有被启动过,因此.start()启动成功,并且状态会改成非0。如果线程启动过了再.start()启动线程就会抛出IllegalThreadStateException错误.
    • 因此我们必须重新在主线程main方法下创建Thread类的子对象,然后在主线程main方法下通过此对象调用start(),重新创建线程,线程执行run()方法。
package javase10;

public class Thread1 {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();//主线程main方法下创建Thread类的子对象myThread1
        myThread1.start();//主线程main方法下通过此对象myThread1调用start()启动第二个线程,然后第二个线程诞生开始独立执行run()里面的方法

        //问题1:不能直接调用run()而使用start()原因
//        myThread.run();//如果直接对象.run()方法,则就是当前main方法调用run()方法,没有分出多线程,不满足需求,因此不要

        //问题2:不能再次调用已经启动过线程的线程对象的start()方法
//        myThread1.start();//运行时会抛出IllegalThreadStateException错误,因为该线程对象已经启动过线程了
        MyThread myThread2 = new MyThread();//需要重新在主线程main重新在主线程main方法下创建Thread类的子对象myThread2
        myThread2.start();//在主线程main方法下通过此对象调用start(),重新创建线程,线程执行run()方法。

        //主线程打印[0,100)的奇数
        for(int i = 0;i < 100;i++){
            if(i % 2 != 0){
                System.out.println(i+"奇数"+Thread.currentThread().getName());//Thread.currentThread().getName(),获取当前执行线程名称
            }
        }
    }
}

//创建一个自定义继承Thread类的子类
class MyThread extends Thread{
    //重写Thread类的run()
    public void run() {
        for(int i = 0;i < 100;i++){//诞生的第二个线程打印[0,100)的偶数
            if(i % 2 == 0){
                System.out.println(i+"偶数"+this.getName());//this.getName()获取当前执行线程名称,Thread类的方法
            }
        }
    }
}

Thread类常用的方法

package javase10;

public class Thread2 {
    public static void main(String[] args) {
        HelloThread h1 = new HelloThread("Thread : 1");//创建一个线程对象,并给线程对象赋予实例名
//        h1.setName("线程一");//setName()也可以赋予名字,不过有了含参构造就不需要这个方法了.

        h1.start();//启动线程对象


        Thread.currentThread().setName("主线程");//给主线程命名

        for(int i = 0;i < 100; i++){//主线程打印偶数
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
            if(i == 20){
                try {
                    h1.join();//主线程进入阻塞状态,将cpu权限交给h1线程对象的线程执行,直到该h1线程对象的线程执行释放后才到它执行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        System.out.println(h1.isAlive());//判断h1线程对象线程是否存活
    }
}

class HelloThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {//循环执行100次
            try {
                sleep(10);//每次循环线程休眠10毫秒
            } catch (InterruptedException e) {//捕获中断异常对象
                e.printStackTrace();
            }

            if (i % 2 == 0) {//打印[0,100)的偶数
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
//            if (i % 20 == 0) {//一旦遇到能被20整除的数
//                yield();//放弃当前线程掌握的cpu,并重新进入就绪状态与别的线程抢夺cpu资源,以获取cpu时间片,有了上面的join()这里方法自然也无效了
//            }
        }
    }

    //创建线程并指定线程实例名
    public HelloThread(String name) {
        super(name);
    }
}

线程的调度

说明:由CPU选择执行哪个线程,执行多久

  • 调度策略
    • 时间片:同优先级线程组成先进先出队列(先到先服务),使用时间片策略
    • 抢占式:高优先级的线程抢占到CPU使用权的概率就越高
package javase10;

public class Thread3 {
    public static void main(String[] args) {
        //创建自定义线程独显
        PriorityThread t1 = new PriorityThread();
        PriorityThread t2 = new PriorityThread();

        //设置自定义线程名
        t1.setName("自定义线程1");
        t2.setName("自定义线程2");

        //设置自定义线程优先级,默认为5,优先级越小优先级越高,获取cpu概率就越高,执行次数就越多,同级则随机调用
        t1.setPriority(Thread.MAX_PRIORITY);//Thread.MAX_PRIORITY=10
        t2.setPriority(5);
        Thread.currentThread().setPriority(4);//修改主线程main的线程优先级

        //启动线程,执行run()方法
        t1.start();
        t2.start();

        //main方法的代码
        for (int i = 0; i < 10; i++) {
            if(i%2!=0) System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

class PriorityThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if(i%2==0) System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

Thread类的拓展练习——买票

package javase10;
/**多个线程去执行抢票,票有100张*/
public class Thread4 {
    public static void main(String[] args) {
        BuyThick b1 = new BuyThick();
        BuyThick b2 = new BuyThick();
        BuyThick b3 = new BuyThick();

        b1.setName("窗口1");
        b2.setName("窗口2");
        b3.setName("窗口3");

        b1.start();
        b2.start();
        b3.start();
    }
}

class BuyThick extends Thread{
    //设置成私密就是为了只能使用run()来修改该值,设置成static是为了多线程在内存中共享只有一份的资源
    private static int ticket = 100;

    //减票操作
    @Override
    public void run() {
        while(ticket!=0){
            if(ticket==0)break;
            ticket--;
            System.out.println(Thread.currentThread().getName()+"买到了,还剩"+ticket+"票");
        }
    }
}

创建多线程的方式二:实现Runnable接口

  • 创建一个实现了Runnable接口的实现类

  • 实现类去实现Runnable中的抽象方法:run()

  • 创建实现类的对象

  • 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象

package javase10;

public class Thread5 {
    public static void main(String[] args) {
        //创建实体类对象
        RunThread r1 = new RunThread();
        //将对象作为参数传入到Thread类的构造器中,用于创建Thread类的对象
        Thread t1 = new Thread(r1);
        //设置线程名字
        t1.setName("线程1");
        //通过Thread类的对象调用start():①启动线程 ②调用当前线程的run() --> 调用了Runnable类型的target的run()
        t1.start();

        //再启动一个线程
        Thread t2 = new Thread(r1);
        t2.setName("线程2");
        t2.start();

    }
}

class RunThread implements Runnable{

    @Override
    public void run() {
        //遍历[0,10)的偶数
        for(int i = 0;i < 10;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

Runnable接口拓展练习——买票 

package javase10;

public class Thread6 {
    public static void main(String[] args) {
        //实例化Runnable实现类
        RunByTicket r1 = new RunByTicket();
        //把Runnable实现类对象r1放入Thread构造器,此时的票数是一个实例变量,被b1,b2,b3线程对象共享,并且可以设置每个线程的名字
        Thread b1 = new Thread(r1,"窗口1");
        Thread b2 = new Thread(r1,"窗口2");
        Thread b3 = new Thread(r1,"窗口3");

        //这里就不用设置了
//        b1.setName("窗口1");
//        b2.setName("窗口2");
//        b3.setName("窗口3");

        b1.start();
        b2.start();
        b3.start();
    }
}

class RunByTicket implements Runnable{

    //不需要声明为static,因为...
    private int ticket = 100;

    //减票操作
    @Override
    public void run() {
        while(ticket!=0){
            if(ticket==0)break;
            ticket--;
            System.out.println(Thread.currentThread().getName()+"买到了,还剩"+ticket+"票");
        }
    }
}

继承方式和实现方式的联系与区别

  • 开发中:优先选择:实现Runnable接口的方式
    • 原因:
    1. 实现的方式没有类的单继承性的局限性(写run的子类可能继承了别的父类,导致它不能继承Thread类重写run()方法)
    2. 在继承方式下,买票的票数要声明成static静态变量,达到多线程共享一个资源的效果.而接口实现类的票数不需要声明成static静态变量,因为实现类实体化后,票数是一个实例变量,是唯一的,被接受该实现类对象而创建的Thread类共享。
    3. 实现的方式更适合来处理多个线程有共享数据的情况(当时测试觉得实现方式在多线程下更明显,参考2.解释)
  • 相同点:
    • 因为Thread类也实现了Runnable接口,重写了run()。两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。

(了解)线程分类

  • Java中的线程分为两类:一种是守护线程(gc()垃圾回收线程),一种是用户线程(主线程main)。它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
  • 守护线程是用来服务用户线程的,通过在start()方法前调用Thread.setDaemon(true),可以把一个用户线程变成一个守护线程。Java垃圾回收就是一个典型的守护线程。
  • 若JVM中都是守护线程,当前JVM将退出。
  • 守护线程,与主线程同生共死

线程生命周期

说明:JDK中用Thread.State类定义了线程的几种状态

  • 新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
  • 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
  • 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
  • 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
  • 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

线程的同步

需要线程同步的原因:

  1. 多个线程执行的不确定性引起执行结果的不稳定
  2. ​ 多个线程对账本的共享,会造成操作的不完整性,会破坏数据。

线程同步的优缺点:

  • 优点:操作同步代码时,只能有一个线程参与,其他线程等待。确保线程安全
  • 缺点:但实际工作只有一个线程,效率低

触发同步机制的三种方法

1.同步代码块:

  • 操作共享数据的代码,即为需要被同步的代码 --->不能包含代码多了,也不能包含代码少了
  • 共享数据:多个线程共同操作的变量。比如:ticket就是共享数据
  • 同步监视器,俗称:锁。任何一个类的对象,对象数据类型都可以来充当锁。
    在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
    要求:多个线程必须要共用同一把锁
synchronized(同步监视器){
      //需要被同步的代码
}

2.同步方法:

  • 如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的
  • 把synchronized像修饰符一样添加给方法

同步代码块与同步方法相比:

  • 同步代码块相对比较好控制范围,但写起来比较复杂
  • 同步方法就是在方法中加个修饰符,比较简单,但不好控制范围,范围大了性能就会有影响.

3.Lock锁(下面会讲)

买票实操举例(知识回顾)

  • 之前的卖票演示,理想状态下卖票是希望不会出现重卖票和多卖票的区别

  • 出现异常:但是当票数数量达到一定大时,出错概率就会增加,票数多卖重买的情况就会出现
  • 原因分析:当某个线程通过判断条件操作车票的过程中,尚未操作完成时,cpu时间片就到了,到别得线程执行,别得线程也参与进来通过判断条件进行操作车牌,等之前那个线程再次获得时间片是他只需要操作减票,而不需要判断条件,因此到最后面总会多卖几张票,让票数数量变成负数

使用同步代码块处理实现Runnable的线程安全问题

说明:通过同步机制,来解决线程的安全问题。以卖票为例子,解决票数多卖重买的情况,因此改写Runnable线程实现多线程方式的卖票例子,解决线程安全问题.

package javase10;

public class Thread6 {
    public static void main(String[] args) {
        //实例化Runnable实现类
        RunByTicket r1 = new RunByTicket();
        //把Runnable实现类对象r1放入Thread构造器,此时的票数是一个实例变量,被b1,b2,b3线程对象共享,并且可以设置每个线程的名字
        Thread b1 = new Thread(r1,"窗口1");
        Thread b2 = new Thread(r1,"窗口2");
        Thread b3 = new Thread(r1,"窗口3");

        //这里就不用设置了
//        b1.setName("窗口1");
//        b2.setName("窗口2");
//        b3.setName("窗口3");

        b1.start();
        b2.start();
        b3.start();
    }
}

class RunByTicket implements Runnable{

    //不需要声明为static,因为...
    private int ticket = 10000;

    //减票操作
    @Override
    public void run() {
        /*线程1获取到cpu,线程1进来,首先判断票数是否不为空-->然后获取对象锁-->然后再执行一次判断仓库是否为空-->在没有执行减票操作时间cpu时间片就到了
        * 线程2获取到了cpu,线程2进来,首先判断票数是否为空-->然后获取对象锁,发现对象锁被线程1占用-->线程2进入阻塞状态,让出cpu
        * 线程3获取到了cpu,线程3与线程2同理
        * 又回到了线程1获取cpu,执行之前没完成的减票操作,操作完成后释放锁,此时线程1cpu时间片又到了
        * 线程2获取到了cpu,线程2获取对象锁成功,然后再执行一次判断仓库是否为空-->为空则不再执行减票操作
        * 线程3与线程2同理
        * 线程1获取到cpu,先判断票数是否为空,为空则直接结束,就被第一个校验拦截结束了,而不是被同步锁进行阻塞,因此效率更快*/
        while(ticket!=0) {
            //这里使用了同步代码块实现代码同步,顺便加上了双重校验,组合起来称为双重校验锁,效率相对较高。this是指当前的唯一的RunByTicket类型对象
            synchronized (this) {
                if (ticket == 0) break;
                ticket--;
                System.out.println(Thread.currentThread().getName() + "买到了,还剩" + ticket + "票");
            }
        }
    }
}

同步代码块处理继承Thread类的线程安全问题

说明:修改原有的Thread代码

package javase10;
/**多个线程去执行抢票,票有100张*/
public class Thread4 {
    public static void main(String[] args) {
        BuyThick b1 = new BuyThick();
        BuyThick b2 = new BuyThick();
        BuyThick b3 = new BuyThick();

        b1.setName("窗口1");
        b2.setName("窗口2");
        b3.setName("窗口3");

        b1.start();
        b2.start();
        b3.start();
    }
}

class BuyThick extends Thread{
    //设置成私密就是为了只能使用run()来修改该值,设置成static是为了多线程在内存中共享只有一份的资源
    private static int ticket = 100;

    //减票操作
    @Override
    public void run() {
        while(ticket!=0){
            //此时this表示的是b1、b2、b3三个对象,不符合多线程下需要使用同一个对象锁,我们这里使用三个对象都使用的对象数据类型
            synchronized (BuyThick.class) {
                if (ticket == 0) break;
                ticket--;
                System.out.println(Thread.currentThread().getName() + "买到了,还剩" + ticket + "票");
            }
        }
    }
}

同步方式原理图

线程安全的单例模式之懒汉式(完善之前的单例模式创建对象)

package javase10;

public class Thread7 {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getSingleton();
        Singleton s2 = Singleton.getSingleton();
        System.out.println(s1==s2);//true

    }
}

class Singleton{

    private Singleton(){}

    private static Singleton s1;

    public static Singleton getSingleton(){
        if(s1==null) {
            synchronized (Singleton.class) {
                if(s1==null) {
                    s1 = new Singleton();
                }
            }
        }
        return s1;
    }
}

死锁的问题

  • 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁.
  • 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,代码无法执行下去。因此我们使用同步时,要避免出现死锁。

演示死锁情况

package javase10;

public class Thread8 {
    public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();

        new Thread() {
            @Override
            public void run() {
                synchronized (s1) {//当前线程使用s1对象作为同步代码块的同步监视器,然后为s1 s2 添加字符
                    s1.append("1");
                    s2.append("2");
                }
                try {
                    sleep(100);//线程休眠,增大两个线程交错的概率,线程休眠期间,依旧把握s1对象锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (s2){//此时线程休眠完毕,释放s1对象锁的同时也想获取s2对象锁
                    s1.append("3");
                    s2.append("4");
                }
            }
        }.start();//启动线程

        new Thread() {
            @Override
            public void run() {
                synchronized (s2) {//当前线程使用s2对象作为同步代码块的同步监视器,然后为s1 s2 添加字符
                    s1.append("5");
                    s2.append("6");
                }
                //这里不使用线程休眠
//                try {
//                    sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
                synchronized (s1){//此时线程休眠完毕,释放s2对象锁的同时也想获取s1对象锁
                    s1.append("3");
                    s2.append("4");
                }
            }
        }.start();//启动线程

        /*出现死锁的原因(上面代码时有概率发生线程死锁情况):
        *   线程1在执行时持有的是s1对象锁,线程2页同步执行,持有的是s2对象锁.线程1执行完后手持s1对象锁进入短暂休眠状态,
        * 线程2执行完上层代码后想要释放s2对象锁同时获取s1对象锁执行下一步代码操作,但是线程1正在休眠,待线程1休眠完成后,
        * 线程1执行完上层代码后想要释放s1对象锁同时获取s2对象锁执行下一步代码操作,线程1线程2都不愿意撒手自己当前持有的对象锁,
        * 同时也想获取对象手上的线程锁,这样僵持的局面就导致了死锁*/
    }
}

Lock锁方式解决线程安全问题

  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
  • ReentrantLock类实现了Lock ,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
  • 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
  • 面试题:synchronized 与 Lock的异同?
    • 相同:二者都可以解决线程安全问题
    • 不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
    • Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
    • 开发中大部分都是使用synchronize
  • 实现线程同步优先推荐使用顺序:Lock锁>同步代码块>同步方法

实现接口Runnable方式演示Lock锁

package javase10;

import java.util.concurrent.locks.ReentrantLock;

public class Thread9 {
    public static void main(String[] args) {
        Windows w = new Windows();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t3.start();
        t2.start();
    }
}

class Windows implements Runnable {

    private int ticket = 100;
    /*实例化ReentrantLock,ReentrantLock类实现了Lock,它的含参构造是(boolean fair),true参数就是线程遵循先启动先执行的轮询策略
    *  效果:
    *   t1.start();
        t3.start();
        t2.start();
        ==>
        窗口1:售票,票号为: 100
        窗口3:售票,票号为: 99
        窗口2:售票,票号为: 98
        窗口1:售票,票号为: 97 
        ......
          */
    private ReentrantLock lock = new ReentrantLock(true);


    @Override
    public void run() {
        while (true) {
            try {

                lock.lock();//手动上锁,调用锁定方法:lock()这里的方法类似线程获取一个同步代码块里面的同步监视器,才能执行后面的方法,否则就进入阻塞状态

                if (ticket > 0) {

                    try {
                        Thread.sleep(10);//线程休眠可以增大导致票多卖重卖的情况
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + ":售票,票号为: " + ticket);
                    ticket--;
                } else {
                    break;
                }
            } finally {

                lock.unlock();//手动解锁,调用解锁方法:unlock(),线程执行完后释放同步监视器给别的线程获取执行run()

            }
        }
    }
}

线程的通信

涉及Object类的三个方法

  • wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。sleep()不会释放同步监视器
  • notify():一旦执行此方法,就会通知被wait()且正在等待该对象监视器的单个线程即将被唤醒。但不会立即被唤醒。原因下面“线程通讯练习”案例有演示。如果有多个线程被wait,就唤醒优先级高的那个。
  • notifyAll():一旦执行此方法,就会通知被wait()且正在等待该对象监视器的全部线程即将被唤醒。但不会立即被唤醒。
  • 注意!!!!!!!!!!:
    • wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中
    • wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则,会出现IllegalMonitorStateException异常
    • wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中,并不是在Thread类中.

线程通讯练习

需求:使用两个线程打印1-3。线程1, 线程2 交替打印

提示:这里使用Lock锁也可以实现需求,线程轮流执行.只需要使用含参构造创建ReentrantLock对象就可以了(这里不做演示)

关于notify的使用提出的特别问题:这里不能设置while(num <= 3)而使用while(true)的原因是

package javase10;

public class Thread10 {
    public static void main(String[] args) {
        ThreadCommunication tc1 = new ThreadCommunication();
        Thread t1 = new Thread(tc1);
        t1.setName("线程1");
        Thread t2 = new Thread(tc1);
        t2.setName("线程2");

        t1.start();
        t2.start();
    }
}

class ThreadCommunication implements Runnable {

    private int num = 1;
    public Object obj = new Object();

    @Override
    public void run() {
        /*      这里不能设置num <= 3而使用true的原因是:
         *    假如线程1第二次运行,num值为3,线程1获取对象锁,执行obj.notify,通知线程2即将被唤醒,但是此时对象锁被线程1占用,线程1能继续运行,而线程2也在同步代码块中不能动.
         * 等线程1执行到在控制台打印语句"线程1,即将要操作数3",然后执行num++将num变成4,线程1再执行obj.wait()进入阻塞状态并且释放对象锁,此时线程2可以运行往下运行,
         * 进入while循环,获取对象锁,执行obj.notify()通知释放线程1,但是此时对象锁被线程2占用,线程2继续往下执行,进入if判断条件,此时num=4不符合if条件,
         * 则线程2执行break语句.退出了同步代码块并释放了对象锁,同时结束了while循环,线程2结束.因为对象锁没哟被占用了,并且线程1已经被通知被唤醒,因此开始往下执行,
         * 进入while循环,获取对象锁,执行了obj.notify(),实际没有线程要被通知释放了,线程1执行if(num<=3),此时num=4不符合if条件,则线程1执行break语句,退出了同步
         * 代码块退出了同步代码块并释放了对象锁,同时结束了while循环,线程1结束.
         *    如果这里使用了num <= 3,则线程2无法进入while循环执行obj.notify,因此线程2永远无法被释放,以至于该线程永远处于阻塞状态*/
        while (true /*num<=3*/) {
            synchronized (obj) {

                obj.notify();//一旦执行此方法,就会通知被wait()且正在等待该对象监视器的单个线程即将被唤醒。但不会立即被唤醒,如果是线程1唤醒线程2,但此时同步监视器被线程1占用,因此线程1继续往下执行,线程2不能动

                if (num <= 3) {
                    try {
                        Thread.sleep(10);//增加异常概率,因此要加同步锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + ",即将要操作数" + num);
                    num++;

                    try {
                        obj.wait();//使得调用如下wait()方法的线程进入阻塞状态,线程1执行完后进入阻塞状态并且释放同步监视器,如此循环便是两个线程轮流执行1~3的打印

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;//终止循环
                }
            }
        }
    }
}

(面试题)sleep()和wait()的异同

  • 相同点
    • 一旦执行方法,都可以使得当前的线程进入阻塞状态。
  • 不同点
    • 两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
    • 调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
    • 关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。

经典例题:生产者/消费者问题

需求:

  • 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,
  • 店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,
  • 店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;
  • 如果店中没有产品了,店员会告诉消费者等一下,
  • 如果店中有产品了再通知消费者来取走产品。
package javase10;

public class Thread11 {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Producer p1 = new Producer(clerk);
        p1.setName("生产者1");

        Consumer c1 = new Consumer(clerk);
        c1.setName("消费者1");
        Consumer c2 = new Consumer(clerk);
        c2.setName("消费者2");

        p1.start();
        c1.start();
        c2.start();
    }
}

class Clerk{

    private int productCount = 0;

    //生产产品
    public synchronized void produceProduct() {

        if(productCount < 20){
            productCount++;
            System.out.println(Thread.currentThread().getName() + ": 开始生产第" + productCount + "个产品");

            notify();
        }else{
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    //消费产品
    public synchronized void consumeProduct() {

        if(productCount > 0){
            System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品");
            productCount--;

            notify();
        }else{
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

class Producer extends Thread{//生产者
    private Clerk clerk;

    public Producer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName() + ": 开始生产产品......");

        while(true){

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.produceProduct();
        }
    }
}

class Consumer extends Thread{  //消费者
    private Clerk clerk;

    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName() + ": 开始消费产品......");

        while(true){

            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.consumeProduct();
        }

    }
}

JDK5.0新增线程创建方式

实现Callable接口创建线程

  • 实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大之处
    • call()可以有返回值的。
    • call()可以抛出异常,被外面的操作捕获,获取异常的信息
    • Callable是支持泛型的
    • 需要借助FutureTask类,比如获取返回结果
package javase10;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Thread12 {
    public static void main(String[] args) {
        //创建Callable接口实现类的对象
        CallableThread c1 = new CallableThread();

        //将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(c1);

        /*FutureTask同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
        将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()*/
        new Thread(futureTask).start();

        /*通过FutureTask类型对象的get()方法获取Callable实现类中的call()方法返回值,需要捕获异常.
        这里FutureTask类型对象的get()返回值实际上就是FutureTask构造器参数上的Callable实现类重写call()的返回值*/
        try {
            Object sum = futureTask.get();
            System.out.println("1~100的偶数和为:"+sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class CallableThread implements Callable{

    //实现call方法,将此线程需要执行的操作声明在call()中,还可以抛出异常
    @Override
    public Object call() throws Exception {//可以抛出异常
        int sum = 0;
        for(int i = 1;i <= 100;i++){
            if(i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;//有返回值
    }
}

代码里面用的FutrueTask是Future接口唯一的实现类

  • 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
  • FutrueTask是Futrue接口的唯一的实现类
  • FutureTask同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

使用线程池创建线程

  • 需要线程池的原因:
    • 一些经常创建和销毁、使用量特别大的资源会特别影响性能.比如并发情况下的线程
  • 思路:
    • 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。
  • 好处:
    • 提高响应速度(减少了创建新线程的时间)
    • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    • 便于线程管理
      • corePoolSize:设置线程池核心线程数量(即使不用,也要保持一定数量的线程伴随线程池同生共死)
      • maximumPoolSize:设置线程池最大线程数量,要求值比核心线程数与poolSize大
      • keepAliveTime:线程没有任务时最多保持多长时间后会终止(非核心线程空闲时存活的时间)
      • ......
  • 新提交一个任务时的处理流程

    1、如果线程池的当前大小还没有达到基本大小(poolSize < corePoolSize),那么就新增加一个线程处理新提交的任务;

    2、如果当前大小已经达到了基本大小,就将新提交的任务提交到阻塞队列排队,等候处理.workQueue.offer(command);

    3、如果工作队列已满,并且当前大小poolSize没有达到maximumPoolSize,那么就新增线程来处理任务;

    4、如果队列已满,并且当前线程数目也已经达到上限(maximumPoolSize),那么意味着线程池的处理能力已经达到了极限,此时需要拒绝新增加的任务。至于如何拒绝处理新增

线程池相关API

  • JDK 5.0起提供了线程池相关API:ExecutorService和Executors
  • ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
    • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
    • <T>Future<T> submit(Callable<T> task):执行任务,有返回值,一般又来执行Callable
    • void shutdown():关闭连接池
  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
    • Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
    • Executors.newFixedThreadPool(int);创建线程池的同时设定该线程池核心线程的数量
      ,返回的是ExecutorService接口实现类的对象
    • Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池
    • Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令
package javase10;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Thread13 {
    public static void main(String[] args) {

        //创建一个可重用固定线程数的线程池,设置poolSize=10,新增线程池大小
        ExecutorService threadPool = Executors.newFixedThreadPool(1);

        //验证出newFixedThreadPool()方法返回的是ExecutorService接口的实现类ThreadPoolExecutor
        System.out.println(threadPool.getClass());//验证出

        //父类对象threadPool强转成其实现类ThreadPoolExecutor,这样会多很多方法使用,比如执行时还可以传入Callable接口实现类的对象,还有线程池属性设置
        ThreadPoolExecutor service = (ThreadPoolExecutor) threadPool;

        //设置线程池的属性
        service.setCorePoolSize(2);//设置线程池核心线程数量
        service.setKeepAliveTime(10, TimeUnit.SECONDS);//设置非核心线程存活时间,参数分别为数字,单位
        service.setMaximumPoolSize(6);//设置线程池最大线程数量,要求值比核心线程数大


        //执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread1());//适用于执行实现Runnable接口的实现类实现类对象
        service.execute(new NumberThread2());//适用于执行实现Runnable接口的实现类实现类对象
//        service.submit(Callable callable);//适用于Callable实现类对象

        /*执行效果:
            pool-1-thread-1:0
            pool-1-thread-2:1
            pool-1-thread-1:2
            pool-1-thread-2:3
            ......
         */

        //关闭连接池
        service.shutdown();
    }
}

//业务
class NumberThread1 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

class NumberThread2 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if (i % 2 != 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

基于单片机控制双向可控硅灯光强度设计 *单片机设计介绍,基于单片机控制双向可控硅灯光强度设计。 阅读详情

相关推荐

STM32实战:手把手教你移植CanFestival协议栈(附完整代码)

本文提供了一份详尽的STM32平台移植CanFestival协议栈的实战指南。文章从工程搭建、定时器驱动、CAN硬件适配到对象字典配置,手把手讲解核心步骤,并附完整代码示例。重点剖析了时间管理、报文收发等关键环节的实现与常见问题解决方案,旨在帮助开发者深入理解并成功实现CANopen通信。

kiwi8的博客 273

Java day17:多线程(JavaSE最后一节知识点课)

一、程序、进程与多任务 程序(program)是对数据描述与操作的代码的集合,是应用程序执行的脚本。 进程( process)是程序的一次执行过程,是系统运行程序的基本单位。 程序是静态的,进程是动态的。系统运行一个程序即是一个进程从创建、运行到消亡的过程。 多任务(multi task)在一个系统中可以同时运行多个程序,即有多个独立运行的任务,每个任务对应一个进程。 什么是并发与并行 要想学习多线程,必须先理解什么是并发与并行 并行:指两个或多个事件在同一时刻发生(同时发生)。 并发:指两

weixin_44641804的博客 1527

国内合规开通ChatGPT Plus实操指南:银行卡绑卡与支付全解析

ChatGPT Plus 是 OpenAI 官方推出的高级订阅服务,基于 GPT-4o 多模态大模型提供语音交互、图像理解与长上下文处理能力。其核心使用前提在于完成合规的国际支付绑定,而非网络接入方式。技术原理上,OpenAI 采用 Stripe 支付网关进行风控校验,重点匹配银行跨境权限、卡片类型(仅支持 Visa/Mastercard/Amex 信用卡)、BIN 号段及地理位置一致性。该机制决定了95%的绑卡失败源于银行端配置缺失或卡片不兼容,而非网络问题。实际应用中,PayPal 作为中间支付层可显著

chuannaoxuan4674的博客 1916

javase-----(12)多线程的注意事项

目录 多线程的注意事项 【一】进程和线程的区别 【二】线程的几种状态转换 【三】阻塞的情况 对阻塞的解释: 注意 【四】线程的死锁 介绍 死锁的原因: 避免死锁的原因: 多线程的注意事项 【一】进程和线程的区别 1、进程是资源分配最小单位,线程是程序执行的最小单位; 2、进程有自己独立的地址空间,每启动一个进程,系统都会为其分配地址空间,建立数据表来维护代码段、堆栈段...

wenge1477的博客 395

【低空经济】无人机充电站设计方案

随着无人机技术的快速发展和广泛应用,其在物流、农业、巡检、救援等领域的应用日益普及。然而,无人机的续航能力一直是限制其大规模应用的关键因素之一。为了解决这一问题,无人机充电站的设计与建设显得尤为重要。本项目旨在设计一套高效、智能、可扩展的无人机充电站系统,以满足不同场景下无人机的充电需求,提升其作业效率和应用范围。无人机充电站的设计将围绕以下几个核心目标展开:首先,充电站应具备快速充电能力,以减少无人机的停机时间,提高作业效率;

2958

反激开关电源变压器设计实例讲解(定明芳)

内容来自《反激变压器的设计》(定明芳主讲)。 定名芳老师主要讲了10步,最后应该还有一步,线径的选择已经绕制方式。这里进行了记录整理,把一些公式来源进行了推导。所有的设计都是基于理论计算。 1.确定电源规格 2.确定工作频率和最大占空比 3.计算匝比(利用伏秒平衡原则) 此处插入解释一下伏秒平衡,根源是磁通量平衡。该原则可以用来检查MOS管耐压是否够。 4.计算初级线圈峰值电流 基于CCM模式计算的。ip1是开关管要关闭时的峰值电流,ip2是开关管开始导通时的起始电流。因为时CCM模式,所以,ip

ydgd118的博客 5万+

javaSE学习总结(8)多线程

章节练习题及面试题 java中有几种方法可以实现一个线程(jdk5.0之前)?用什么关键字修饰同步方法? stop()和suspend()方法为何不推荐使用? 答:有两种实现方法,分别是继承Thread类与实现Runnable接口。 用synchronized关键字修饰同步方法,反对使用stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在...

qq_44258191的博客 446

QGIS三维地形可视化实战:从DEM数据到炫酷3D场景(附Qgis2threejs插件配置)

本文详细介绍了如何利用QGIS和Qgis2threejs插件,将DEM数据转化为交互式三维地形场景的完整工作流。核心内容包括DEM数据准备、坐标系选择(重点对比WGS 84与UTM的差异及影响)、插件核心参数配置、卫星影像与等高线叠加等进阶技巧,并提供了成果导出与常见问题解决方案,帮助用户高效创建可用于网页展示的炫酷3D可视化成果。

mqtt6iot的博客 875

Java多线程编程

最近在重新整理学习Java的多线程。首先,考虑一下什么时候需要用到多线程。 通常是一些异步处理,后台任务,要尽量使资源最大化使用如CPU,I/O,Printer等等。可以参考以下 http://www.cnblogs.com/kenshinobiy/p/4671314.html 学习tutorialspoint的Java多线程编程后总结如下 http://www.tutor...

dmev7348的博客 102

AgentScope Java 2.0 项目实战:使用AgentScope Builder 构建企业级多租户 AI Agent 平台

本文基于 AgentScope Builder 源码深度分析,系统讲解多租户 AI Agent 构建平台的完整开发流程。涵盖 Spring Boot 3.x + WebFlux 后端架构、React 18 + TypeScript 前端实现、HarnessAgent 运行时、CompositeFilesystem 多租户隔离、SSE 流式对话、6 种 IM 渠道接入(钉钉/飞书/企微/GitHub/GitLab)、Git 与 Nacos 技能市场、JWT 认证、Docker Sandbox 隔离等核心技术。

program哲学 717

基于STM32的IMU惯性/姿态解算与多参数上位机可视化实现_资料编号38

本项目实现基于STM32F103C8T6标准库,对姿态/惯性(IMU)传感器姿态解算,通过USB-TTL转串口模块将姿态角/欧拉角、加速度、角速度、磁场、气压、海拔高度、磁力计温度、气压计温度等数据显示在PC端上位机,系统采用可充电锂电池供电。

嵌入式阿齐Archie 1万+

7月25日JavaSE学习笔记

读锁常用于读多写少的场景,例如缓存系统、数据库查询等。多个线程可以同时获取读锁,同时读取共享资源,互不干扰。这样可以提高并发性和读取的效率。写锁常用于写多读少的场景,例如数据更新、写入操作等。在写锁加锁期间,其他线程无法获取读锁或写锁,确保了对共享资源的一致性和完整性。写锁是排他的,只允许一个线程进行写入操作。线程池是一种管理和维护线程的机制,它可以在应用程序中预先创建一组可重复使用的线程,并在需要时分配这些线程来执行任务。

qq_63161848的博客 1662

DICOM图像知识:深入详解DICOM的层级关系

解析DICOM的四个主要层级:Patient(患者)、Study(检查)、Series(序列)和SOP(服务-对象对),探讨它们之间的关系、各自的唯一标识符以及在实际应用中的用途

编程技术探索者,分享C/C++、C#、Java、数据库等开发经验,聚焦实战技巧与AI兴趣,助力编程爱好者成长。 3304

面试JAVASE基础(八)——Java多线程

线程是操作系统进行CPU 调度的最小单位,它是进程内的一个执行路径。main线程(主线程)、GC 线程(垃圾回收)、异常处理线程。Java 多线程是一门 “知易行难” 的技术,从基础的线程创建到复杂的并发控制,每个环节都需要结合实战不断打磨。

2302_79797878的博客 267

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

javase知识点 ----20 (多线程)

1、进程:正在执行的程序称作一个进程。进程负责了内存空间的划分。    Windows号称是多任务的操作系统,那么Windows是同时运行多个应用程序吗?          从宏观角度:Windows确实是在同时运行多个应用程序。          从微观角度:cpu是做了一个快速切换执行的动作,由于速度太快,所以我们感觉不到切换而已。    线程:线程在一个进程中负责了代码执行。    多线程:...

Ren_gw的博客 276

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

JavaSE之线程Thread

package com.shuhuadream.thread; /** * * 进程:正在执行的程序称为一个进程 * * 线程:线程在一个进程中负责了代码的执行,就是进程中一个执行路径 * * 多线程:线程负责了代码的执行,我们之前没有学过线程,为啥代码可以执行? * 运行任何一个java程序,jvm在运行的时候都会创建一个main线程执行main方法中所有的代码 *

麦客子 507

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Java基础回顾系列-第四天-高级编程之多线程编程

多线程编程Java多线程编程进程、线程概念线程常用操作方法线程的同步与死锁综合实战:“生产者-消费者”模型多线程深入话题多线程综合案例 Java多线程编程 进程、线程概念 进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。 线程常用操作方法 线程的同步与死锁 综合实战:“生产者-...

java 603

学习日志day16(2021-07-29)(1、创建和启动线程 2、线程的生命周期 3、从浏览器多线程爬取文件)

学习内容:学习JavaSE(Day16) 1、创建和启动线程 2、线程的生命周期 1、创建和启动线程 (1)进程:应用程序的执行。进程和进程之间资源是相互独立的。 线程:一个进程包含多个线程,资源可以共享。 操作系统是多线程,多任务的,可以同时运行多个进程。 同步是指:当程序1调用程序2时,程序1停下不动,直到程序2完成回到程序1来,程序1才继续执行下去。 异步是指:当程序1调用程序2时,程序1径自继续自己的下一个动作,不受程序2的的影响。 同步是指:发送方发出数据后,等接收方发回响应以后才发下一个数据包

huzige_的博客 132

黑马程序员-JAVASE入门(多线程)

------- android培训、java培训、期待与您交流! ---------- 多线程 进程:是一个正在执行中的程序。 每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。 线程:就是进程中的一个独立的控制单元。 线程在控制着进程的执行。 一个进程中至少有一个线程。 Java VM 启动的时候会有一个进程java.exe. 该进程中至少一

a78926635的专栏 524

JAVASE核心知识体系规划(从0到1)

集合体系:Collection集合的通用方法,List(ArrayList、LinkedList、Vector)、Set(HashSet、LinkedHashSet、TreeSet)、Queue队列的实现差异与选型,Map(HashMap、TreeMap)的键值对存储机制,以及集合的遍历(普通for、增强for、迭代器)、泛型、可变参数等进阶知识,还有Arrays、Collections工具类的便捷操作;- 类与接口:内部类、匿名内部类的使用场景,抽象类与接口的设计原则,以及父子类之间的类型转换规则。

2501_90864902的博客 496

JavaSE知识体系目录

文章目录Java基础语法知识关键字运算符数据类型流程控制语句面向对象异常和常用类集合CollectionMapIO字节流字符流线程网络 Java基础语法知识 关键字 运算符 算数运算符,比较运算符,赋值运算符,逻辑运算符,三目运算符,位运算符 数据类型 基本数据类型 整型(byte,short,int ,long) 浮点型(float,double) 字符型(char) 布尔型(只占1位...

shixing的博客 663

Java系统学习,最全路线。全面系统学习Java

@[TOC]目录 第一阶段 JavaSE 第二阶段 数据库技术 什么是帅哥 我是大帅哥 帅哥的定义 我比你帅 孤独寂寞冷 人生真是寂寥如雪 第一阶段 JavaSE Java编程语言概述 Java基础语法 Java数组 计算机语言介绍 流程控制语句的介绍 static 修饰符 Java语言的前世今生 Java 编译器执行流程 通类的成员之代码块 Java技术体系平台 if 分支结构 静态代码块与非静态代码块 Java核心机制与JVM运行原理 switch 选择结构与相关规则 单例

Pausch的博客 1084

java se体系_JavaSE知识体系目录

文章目录Java基础语法知识关键字运算符算数运算符,比较运算符,赋值运算符,逻辑运算符,三目运算符,位运算符数据类型基本数据类型整型(byte,short,int ,long)浮点型(float,double)字符型(char)布尔型(只占1位)引用数据类型String数组对象接口流程控制语句顺序结构分支结构循环结构面向对象类和对象封装继承多态接口抽象类内部类异常和常用类集合CollectionL...

weixin_39874350的博客 145

Java 核心编程之并发简介

1、谈谈Java中的并发 自从java创建以来就已经支持并发的理念,如线程和锁。这篇指南主要是为帮助java多线程开发人员理解并发的核心概念以及如何应用这些理念。本文的主题是关于具有java语言风格的Thread、synchronized、volatile,以及J2SE5中新增的概念,如锁(Lock)、原子性(Atomics)、并发集合类、线程协作摘要、Executors。开发者通过这些基础的接

纸上得来终觉浅,绝知此事要躬行 1379

【备忘】Java菜鸟到大牛学习路线培训教程

这是一套Java菜鸟到大牛学习路线培训教程,由本站工作了10年的资深Java架构师整理。主要分5个阶段:Java程序员->Java初级软件工程师->Java中级软件工程师->Java高级软件工程师->Java系统架构师,从头学到尾即可成为大神!成为架构师是爱好编程的程序员的最终目标! 第1阶段(Java程序员) - Java语言基础     - 101 JavaSE        -

ajfkj的博客 532

java程序员进阶

任重道远啊…… 第1阶段(Java程序员) - Java语言基础     - 101 JavaSE        -01-常见Dos命令、Java历史、Java跨平台、配置Path环境变量、第一个HelloWorld例子        -02-配置JAVA_HOME、临时或classpath环境变量、关键字、自定义标识符、注释、常量、变量、进制之间的转换      

qq_38849721的博客 584
上一篇: JavaSE(9)_异常 异常描述、异常处理机制、自定义异常
下一篇: JavaSE(11)_常用类 String类、时间API、Java比较器comparable\comparator、System类、Math类、BigInteger\BigDecimal
自律彭于晏
博客等级 码龄5年 946粉丝 43原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值