第9章 多线程

第9章 多线程

学习目标

  • 了解进程和线程的区别
  • 能够理解并发与并行的区别
  • 能够使用继承类的方式创建多线程
  • 能够使用实现接口的方式创建多线程
  • 能够说出实现接口方式的好处
  • 能够解释安全问题的出现的原因
  • 能够使用同步代码块解决线程安全问题
  • 能够使用同步方法解决线程安全问题
  • 能够理解线程通信概念
  • 能够理解等待唤醒机制
  • 能够说出线程的生命周期

第九章 多线程

我们在之前,学习的程序在没有跳转语句的前提下,都是由上至下依次执行,那现在想要设计一个程序,边打游戏边听歌,怎么设计?

要解决上述问题,咱们得使用多进程或者多线程来解决.

9.1 相关概念(了解)

9.1.1 线程与进程

  • 程序:为了完成某个任务和功能,选择一种编程语言编写的一组指令的集合。

  • 软件1个或多个应用程序+相关的素材和资源文件等构成一个软件系统。

  • 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。

  • 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

    简而言之:一个软件中至少有一个应用程序,应用程序的一次运行就是一个进程,一个进程中至少有一个线程。

  • 面试题:进程是操作系统调度和分配资源的最小单位,线程是CPU调度的最小单位。不同的进程之间是不共享内存的。进程之间的数据交换和通信的成本是很高。不同的线程是共享同一个进程的内存的。当然不同的线程也有自己独立的内存空间。对于方法区,堆中中的同一个对象的内存,线程之间是可以共享的,但是栈的局部变量永远是独立的。另外进程之前切换的复杂度要远远高于线程之间的切换调度。

9.1.2 查看进程和线程

我们可以再电脑底部任务栏,右键----->打开任务管理器,可以查看当前任务的进程:

1、每个应用程序的运行都是一个进程

2、一个应用程序的多次运行,就是多个进程

1563267431480

3、一个进程中包含多个线程

1563270525077

9.1.3 并发与并行

  • 并行(parallel):指两个或多个事件在同一时刻发生(同时发生)。指在同一时刻,有多条指令在多个处理器上同时执行。
  • 并发(concurrency):指两个或多个事件在同一个时间段内发生。指在同一个时刻只能有一条指令执行,但多个进程的指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。

在操作系统中,启动了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一个程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。

而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核越多,并行处理的程序越多,能大大的提高电脑运行的效率。

例子:

  • 并行:多项工作一起执行,之后再汇总,例如:泡方便面,电水壶烧水,一边撕调料倒入桶中

  • 并发:同一时刻多个线程在访问同一个资源,多个线程对一个点,例如:春运抢票、电商秒杀…

注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。

单核CPU:只能并发

多核CPU:并行+并发

9.1.4 线程调度

  • 分时调度

    所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

  • 抢占式调度

    优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

    • 抢占式调度详解

    大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是在同时运行,”感觉这些软件好像在同一时刻运行着“。

    实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。
    其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高

    抢占式调度

9.2 另行创建和启动线程

当运行Java程序时,其实已经有一个线程了,那就是main线程。

1563281796505

那么如何创建和启动main线程以外的线程呢?

9.2.1 继承Thread类

Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建启动多线程的步骤如下:

  1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
  2. 创建Thread子类的实例,即创建了线程对象
  3. 调用线程对象的start()方法来启动该线程

代码如下:

自定义线程类:

package com.atguigu.thread;public class MyThread extends Thread {//定义指定线程名称的构造方法public MyThread(String name) {//调用父类的String参数的构造方法,指定线程的名称super(name);}/*** 重写run方法,完成该线程执行的逻辑*/@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(getName()+":正在执行!"+i);}}
}

测试类:

package com.atguigu.thread;public class TestMyThread {public static void main(String[] args) {//创建自定义线程对象MyThread mt = new MyThread("新的线程!");//开启新线程mt.start();//在主方法中执行for循环for (int i = 0; i < 10; i++) {System.out.println("main线程!"+i);}}
}

9.2.2 实现Runnable接口

Java有单继承的限制,当我们无法继承Thread类时,那么该如何做呢?在核心类库中提供了Runnable接口,我们可以实现Runnable接口,重写run()方法,然后再通过Thread类的对象代理启动和执行我们的线程体run()方法

步骤如下:

  1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正
    的线程对象。
  3. 调用线程对象的start()方法来启动线程。
    代码如下:

自定义线程类:

package com.atguigu.thread;public class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 20; i++) {System.out.println(Thread.currentThread().getName() + " " + i);}}
}

测试类:

package com.atguigu.thread;public class TestMyRunnable {public static void main(String[] args) {//创建自定义类对象  线程任务对象MyRunnable mr = new MyRunnable();//创建线程对象Thread t = new Thread(mr, "长江");t.start();for (int i = 0; i < 20; i++) {System.out.println("黄河 " + i);}}
}

通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个执行目标。所有的多线程
代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。

在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。

实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是继承Thread类还是实现
Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

tips:Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。
而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。

9.2.3 使用匿名内部类对象来实现线程的创建和启动

    new Thread("新的线程!"){@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(getName()+":正在执行!"+i);}}}.start();
    new Thread(new Runnable(){@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+":" + i);}}}).start();

9.3 Thread类

9.3.1 构造方法

public Thread() :分配一个新的线程对象。
public Thread(String name) :分配一个指定名字的新的线程对象。
public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。

9.3.2 常用方法系列1

  • public void run() :此线程要执行的任务在此处定义代码。

  • public String getName() :获取当前线程名称。

  • public static Thread currentThread() :返回对当前正在执行的线程对象的引用。

  • public final boolean isAlive():测试线程是否处于活动状态。如果线程已经启动且尚未终止,则为活动状态。

  • public final int getPriority() :返回线程优先级

  • public final void setPriority(int newPriority) :改变线程的优先级

    • 每个线程都有一定的优先级,优先级高的线程将获得较多的执行机会。每个线程默认的优先级都与创建它的父线程具有相同的优先级。Thread类提供了setPriority(int newPriority)和getPriority()方法类设置和获取线程的优先级,其中setPriority方法需要一个整数,并且范围在[1,10]之间,通常推荐设置Thread类的三个优先级常量:
    • MAX_PRIORITY(10):最高优先级
    • MIN _PRIORITY (1):最低优先级
    • NORM_PRIORITY (5):普通优先级,默认情况下main线程具有普通优先级。
	public static void main(String[] args) {Thread t = new Thread(){public void run(){System.out.println(getName() + "的优先级:" + getPriority());}};t.setPriority(Thread.MAX_PRIORITY);t.start();System.out.println(Thread.currentThread().getName() +"的优先级:" + Thread.currentThread().getPriority());}

9.3.3 常用方法系列2

  • public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。

  • public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。

  • public static void yield():yield只是让当前线程暂停一下,让系统的线程调度器重新调度一次,希望优先级与当前线程相同或更高的其他线程能够获得执行机会,但是这个不能保证,完全有可能的情况是,当某个线程调用了yield方法暂停之后,线程调度器又将其调度出来重新执行。

  • void join() :等待该线程终止。

    void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。如果millis时间到,将不再等待。

    void join(long millis, int nanos) :等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。

package com.atguigu.api;public class TestThreadStateChange {public static void main(String[] args) {Thread te = new Thread() {@Overridepublic void run() {for (int i = 2; i <= 100; i += 2) {System.out.println("偶数线程:" + i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};te.start();Thread to = new Thread() {@Overridepublic void run() {for (int i = 1; i <= 100; i += 2) {System.out.println("奇数线程:" + i);if (i == 5) {
//                        Thread.yield();try {te.join();} catch (InterruptedException e) {e.printStackTrace();}}}}};to.start();}
}

9.3.4 如何让线程提前结束

一个线程如何让另一个线程提前结束呢?

线程的死亡有两种:

自然死亡:当一个线程的run方法执行完,线程自然会停止。

意外死亡:当一个线程遇到未捕获处理的异常,也会挂掉。

我们肯定希望是让线程自然死亡更好。

  • public final void stop():强迫线程停止执行。 该方法具有固有的不安全性,已经标记为@Deprecated==(已过时、已废弃)==不建议再使用,那么我们就需要通过其他方式来停止线程了,其中一种方式是使用变量的值的变化来控制线程是否结束。

  • 标记法

案例:

声明一个PrintEvenThread线程类,继承Thread类,重写run方法,实现打印[1,100]之间的偶数,要求每隔1毫秒打印1个偶数。

声明一个PrintOddThread线程类,继承Thread类,重写run方法,实现打印[1,100]之间的奇数。

在main线程中:

(1)创建两个线程对象,并启动两个线程

(2)当打印奇数的线程结束了,让偶数的线程也停下来,就算偶数线程没有全部打印完[1,100]之间的偶数。

package com.atguigu.api;public class PrintEvenThread extends Thread{private boolean flag = true;@Overridepublic void run() {for (int i = 2; i <= 100 && flag; i += 2) {System.out.println("偶数线程:" + i);try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}}public void setFlag(boolean flag) {this.flag = flag;}
}
package com.atguigu.api;public class PrintOddThread extends Thread {@Overridepublic void run() {for (int i = 1; i <= 100; i += 2) {System.out.println("奇数线程:" + i);}}
}
package com.atguigu.api;public class TestThreadStop {public static void main(String[] args) {PrintEvenThread pe = new PrintEvenThread();PrintOddThread po = new PrintOddThread();pe.start();po.start();try {po.join();} catch (InterruptedException e) {e.printStackTrace();}pe.setFlag(false);}
}

9.3.5 守护线程(了解)

有一种线程,它是在后台运行的,它的任务是为其他线程提供服务的,这种线程被称为“守护线程”。JVM的垃圾回收线程就是典型的守护线程。

守护线程有个特点,就是如果所有非守护线程都死亡,那么守护线程自动死亡。

调用setDaemon(true)方法可将指定线程设置为守护线程。必须在线程启动之前设置,否则会报IllegalThreadStateException异常。

调用isDaemon()可以判断线程是否是守护线程。

public class TestThread {public static void main(String[] args) {MyDaemon m = new MyDaemon();m.setDaemon(true);m.start();for (int i = 1; i <= 100; i++) {System.out.println("main:" + i);}}
}class MyDaemon extends Thread {public void run() {while (true) {System.out.println("我一直守护者你...");try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}}
}

9.4 线程安全

当我们使用多个线程访问同一资源(可以是同一个变量、同一个文件、同一条记录等)的时候,若多个线程只有读操作,那么不会发生线程安全问题,但是如果多个线程中对资源有读和写的操作,就容易出现线程安全问题。

我们通过一个案例,演示线程的安全问题:
电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”,本次电影的座位共100个
(本场电影只能卖100张票)。
我们来模拟电影院的售票窗口,实现多个窗口同时卖 “葫芦娃大战奥特曼”这场电影票(多个窗口一起卖这100张票)

9.4.1 同一个资源问题和线程安全问题

1、局部变量不能共享

示例代码:

package com.atguigu.unsafe;public class SaleTicketDemo1 {public static void main(String[] args) {Window w1 = new Window();Window w2 = new Window();Window w3 = new Window();w1.start();w2.start();w3.start();}
}class Window extends Thread {public void run() {int total = 100;while (total > 0) {System.out.println(getName() + "卖出一张票,剩余:" + --total);}}
}

结果:发现卖出300张票。

问题:局部变量是每次调用方法都是独立的,那么每个线程的run()的total是独立的,不是共享数据。

2、不同对象的实例变量不共享
package com.atguigu.unsafe;public class SaleTicketDemo2 {public static void main(String[] args) {TicketSale t1 = new TicketSale();TicketSale t2 = new TicketSale();TicketSale t3 = new TicketSale();t1.start();t2.start();t3.start();}
}class TicketSale extends Thread {private int total = 100;public void run() {while (total > 0) {System.out.println(getName() + "卖出一张票,剩余:" + --total);}}
}

结果:发现卖出300张票。

问题:不同的实例对象的实例变量是独立的。

3、静态变量是共享的

示例代码:

package com.atguigu.unsafe;public class SaleTicketDemo3 {public static void main(String[] args) {TicketSaleThread t1 = new TicketSaleThread();TicketSaleThread t2 = new TicketSaleThread();TicketSaleThread t3 = new TicketSaleThread();t1.start();t2.start();t3.start();}
}
class TicketSaleThread extends Thread{private static int total = 100;public void run(){while(total>0) {try {Thread.sleep(10);//加入这个,使得问题暴露的更明显} catch (InterruptedException e) {e.printStackTrace();}System.out.println(getName() + "卖出一张票,剩余:" + --total);}}
}

运行结果:

Thread-1卖出一张票,剩余:99
Thread-2卖出一张票,剩余:98
Thread-0卖出一张票,剩余:99
Thread-2卖出一张票,剩余:96
Thread-0卖出一张票,剩余:95
Thread-1卖出一张票,剩余:97
Thread-1卖出一张票,剩余:94
Thread-2卖出一张票,剩余:92
Thread-0卖出一张票,剩余:93
Thread-1卖出一张票,剩余:91
Thread-2卖出一张票,剩余:89
Thread-0卖出一张票,剩余:90
Thread-0卖出一张票,剩余:87
Thread-1卖出一张票,剩余:86
Thread-2卖出一张票,剩余:88
Thread-2卖出一张票,剩余:85
Thread-0卖出一张票,剩余:85
Thread-1卖出一张票,剩余:84
Thread-1卖出一张票,剩余:83
Thread-0卖出一张票,剩余:82
Thread-2卖出一张票,剩余:81
Thread-1卖出一张票,剩余:80
Thread-0卖出一张票,剩余:78
Thread-2卖出一张票,剩余:79
Thread-1卖出一张票,剩余:77
Thread-2卖出一张票,剩余:77
Thread-0卖出一张票,剩余:77
Thread-1卖出一张票,剩余:76
Thread-0卖出一张票,剩余:74
Thread-2卖出一张票,剩余:75
Thread-1卖出一张票,剩余:73
Thread-0卖出一张票,剩余:71
Thread-2卖出一张票,剩余:72
Thread-2卖出一张票,剩余:70
Thread-0卖出一张票,剩余:69
Thread-1卖出一张票,剩余:68
Thread-2卖出一张票,剩余:66
Thread-1卖出一张票,剩余:67
Thread-0卖出一张票,剩余:65
Thread-1卖出一张票,剩余:64
Thread-2卖出一张票,剩余:62
Thread-0卖出一张票,剩余:63
Thread-0卖出一张票,剩余:60
Thread-1卖出一张票,剩余:59
Thread-2卖出一张票,剩余:61
Thread-1卖出一张票,剩余:58
Thread-0卖出一张票,剩余:56
Thread-2卖出一张票,剩余:57
Thread-2卖出一张票,剩余:55
Thread-0卖出一张票,剩余:54
Thread-1卖出一张票,剩余:53
Thread-2卖出一张票,剩余:52
Thread-0卖出一张票,剩余:50
Thread-1卖出一张票,剩余:51
Thread-2卖出一张票,剩余:49
Thread-1卖出一张票,剩余:48
Thread-0卖出一张票,剩余:48
Thread-2卖出一张票,剩余:47
Thread-1卖出一张票,剩余:47
Thread-0卖出一张票,剩余:46
Thread-2卖出一张票,剩余:45
Thread-1卖出一张票,剩余:43
Thread-0卖出一张票,剩余:44
Thread-1卖出一张票,剩余:42
Thread-0卖出一张票,剩余:40
Thread-2卖出一张票,剩余:41
Thread-1卖出一张票,剩余:39
Thread-2卖出一张票,剩余:38
Thread-0卖出一张票,剩余:37
Thread-2卖出一张票,剩余:36
Thread-0卖出一张票,剩余:34
Thread-1卖出一张票,剩余:35
Thread-2卖出一张票,剩余:33
Thread-1卖出一张票,剩余:31
Thread-0卖出一张票,剩余:32
Thread-1卖出一张票,剩余:29
Thread-2卖出一张票,剩余:30
Thread-0卖出一张票,剩余:30
Thread-2卖出一张票,剩余:28
Thread-1卖出一张票,剩余:27
Thread-0卖出一张票,剩余:26
Thread-2卖出一张票,剩余:25
Thread-0卖出一张票,剩余:24
Thread-1卖出一张票,剩余:23
Thread-2卖出一张票,剩余:21
Thread-0卖出一张票,剩余:20
Thread-1卖出一张票,剩余:22
Thread-2卖出一张票,剩余:18
Thread-1卖出一张票,剩余:19
Thread-0卖出一张票,剩余:18
Thread-2卖出一张票,剩余:17
Thread-1卖出一张票,剩余:17
Thread-0卖出一张票,剩余:16
Thread-1卖出一张票,剩余:14
Thread-2卖出一张票,剩余:15
Thread-0卖出一张票,剩余:13
Thread-2卖出一张票,剩余:12
Thread-0卖出一张票,剩余:10
Thread-1卖出一张票,剩余:11
Thread-1卖出一张票,剩余:9
Thread-2卖出一张票,剩余:8
Thread-0卖出一张票,剩余:7
Thread-0卖出一张票,剩余:5
Thread-1卖出一张票,剩余:4
Thread-2卖出一张票,剩余:6
Thread-2卖出一张票,剩余:3
Thread-0卖出一张票,剩余:2
Thread-1卖出一张票,剩余:1
Thread-1卖出一张票,剩余:0
Thread-2卖出一张票,剩余:-1
Thread-0卖出一张票,剩余:-2

结果:发现卖出近100张票。

问题(1):但是有重复票或负数票问题。

原因:线程安全问题

问题(2):如果要考虑有两场电影,各卖100张票等

原因:TicketThread类的静态变量,是所有TicketThread类的对象共享

4、同一个对象的实例变量共享

示例代码:多个Thread线程使用同一个Runnable对象

package com.atguigu.safe;package com.atguigu.unsafe;public class SaleTicketDemo4 {public static void main(String[] args) {TicketSaleRunnable tr = new TicketSaleRunnable();Thread t1 = new Thread(tr, "窗口一");Thread t2 = new Thread(tr, "窗口二");Thread t3 = new Thread(tr, "窗口三");t1.start();t2.start();t3.start();}
}class TicketSaleRunnable implements Runnable {private int total = 100;public void run() {while (total > 0) {try {Thread.sleep(10);//加入这个,使得问题暴露的更明显} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余:" + --total);}}
}

结果:发现卖出近100张票。

问题:但是有重复票或负数票问题。

原因:线程安全问题

5、抽取资源类,共享同一个资源对象

示例代码:

package com.atguigu.unsafe;public class SaleTicketDemo5 {public static void main(String[] args) {//2、创建资源对象Ticket ticket = new Ticket();//3、启动多个线程操作资源类的对象Thread t1 = new Thread("窗口一") {public void run() {while (true) {ticket.sale();}}};Thread t2 = new Thread("窗口二") {public void run() {while (true) {ticket.sale();}}};Thread t3 = new Thread(new Runnable() {public void run() {ticket.sale();}}, "窗口三");t1.start();t2.start();t3.start();}
}//1、编写资源类
class Ticket {private int total = 100;public void sale() {if (total > 0) {try {Thread.sleep(10);//加入这个,使得问题暴露的更明显} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余:" + --total);} else {throw new RuntimeException("没有票了");}}public int getTotal() {return total;}
}

结果:发现卖出近100张票。

问题:但是有重复票或负数票问题。

原因:线程安全问题

9.4.2 尝试解决线程安全问题

要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制
(synchronized)来解决。

1563372934332

根据案例简述:

窗口1线程进入操作的时候,窗口2和窗口3线程只能在外等着,窗口1操作结束,窗口1和窗口2和窗口3才有机会进入代码去执行。也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。

为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。

1、同步机制的原理

同步解决线程安全的原理:

同步机制的原理,其实就相当于给某段代码加“锁”,任何线程想要执行这段代码,都要先获得“锁”,我们称为它同步锁。因为Java对象在堆中的数据分为分为对象头、实例变量、空白的填充。而对象头中包含:

  • Mark Word:记录了和当前对象有关的GC、锁标记等信息。
  • 指向类的指针:每一个对象需要记录它是由哪个类创建出来的。
  • 数组长度(只有数组对象才有)

哪个线程获得了“同步锁”对象之后,”同步锁“对象就会记录这个线程的ID,这样其他线程就只能等待了,除非这个线程”释放“了锁对象,其他线程才能重新获得/占用”同步锁“对象。

2、同步代码块和同步方法

同步方法:synchronized 关键字直接修饰方法,表示同一时刻只有一个线程能进入这个方法,其他线程在外面等着。

public synchronized void method(){可能会产生线程安全问题的代码
}

同步代码块:synchronized 关键字可以用于某个区块前面,表示只对这个区块的资源实行互斥访问。
格式:

synchronized(同步锁){需要同步操作的代码
}
3、同步锁对象的选择

同步锁对象可以是任意类型,但是必须保证竞争“同一个共享资源”的多个线程必须使用同一个“同步锁对象”。

对于同步代码块来说,同步锁对象是由程序员手动指定的,但是对于同步方法来说,同步锁对象只能是默认的,

  • 静态方法:当前类的Class对象

  • 非静态方法:this

4、同步代码的范围选择

锁的范围太小:不能解决安全问题

锁的范围太大:因为一旦某个线程抢到锁,其他线程就只能等待,所以范围太大,效率会降低,不能合理利用CPU资源。

5、代码演示
示例一:静态方法加锁
package com.atguigu.safe;public class SaleTicketDemo3 {public static void main(String[] args) {TicketSaleThread t1 = new TicketSaleThread();TicketSaleThread t2 = new TicketSaleThread();TicketSaleThread t3 = new TicketSaleThread();t1.start();t2.start();t3.start();}
}class TicketSaleThread extends Thread{private static int total = 100;public void run(){//直接锁这里,肯定不行,会导致,只有一个窗口卖票while(total>0) {saleOneTicket();}}public synchronized static void saleOneTicket(){//锁对象是TicketSaleThread类的Class对象,而一个类的Class对象在内存中肯定只有一个if(total > 0) {//不加条件,相当于条件判断没有进入锁管控,线程安全问题就没有解决System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余:" + --total);}}
}
示例二:非静态方法加锁
package com.atguigu.safe;public class SaleTicketDemo4 {public static void main(String[] args) {TicketSaleRunnable tr = new TicketSaleRunnable();Thread t1 = new Thread(tr, "窗口一");Thread t2 = new Thread(tr, "窗口二");Thread t3 = new Thread(tr, "窗口三");t1.start();t2.start();t3.start();}
}class TicketSaleRunnable implements Runnable {private int total = 1000;public void run() {//直接锁这里,肯定不行,会导致,只有一个窗口卖票while (total > 0) {saleOneTicket();}}public synchronized void saleOneTicket(){//锁对象是this,这里就是TicketSaleRunnable对象,因为上面3个线程使用同一个TicketSaleRunnable对象,所以可以if(total > 0) {//不加条件,相当于条件判断没有进入锁管控,线程安全问题就没有解决System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余:" + --total);}}
}
示例三:同步代码块
package com.atguigu.safe;public class SaleTicketDemo5 {public static void main(String[] args) {//2、创建资源对象Ticket ticket = new Ticket();//3、启动多个线程操作资源类的对象Thread t1 = new Thread("窗口一") {public void run() {//不能给run()直接假设,因为t1,t2,t3的三个run方法分别属于三个Thread类对象,// run方法是非静态方法,那么锁对象默认选this,那么锁对象根本不是同一个while (true) {synchronized (ticket) {ticket.sale();}}}};Thread t2 = new Thread("窗口二") {public void run() {while (true) {synchronized (ticket) {ticket.sale();}}}};Thread t3 = new Thread(new Runnable() {public void run() {synchronized (ticket) {ticket.sale();}}}, "窗口三");t1.start();t2.start();t3.start();}
}//1、编写资源类
class Ticket {private int total = 1000;public  void sale() {//也可以直接给这个方法加锁,锁对象是this,这里就是Ticket对象if (total > 0) {System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余:" + --total);} else {throw new RuntimeException("没有票了");}}public int getTotal() {return total;}
}

9.4.6 单例设计模式的线程安全问题

1、饿汉式没有线程安全问题

饿汉式:在类初始化时就直接创建单例对象,而类初始化过程是没有线程安全问题的

形式一:

/*
public class HungryOne{public static final HungryOne INSTANCE = new HungryOne();private HungryOne(){}
}*/
public enum HungryOne{INSTANCE
}

形式二:

package com.atguigu.single.hungry;public class HungrySingle {private static final HungrySingle INSTANCE = new HungrySingle();private HungrySingle(){}public static HungrySingle getInstance(){return INSTANCE;}
}

测试类:

package com.atguigu.single.hungry;public class TestHungry {public static void main(String[] args) {HungryOne h1 = HungryOne.INSTANCE;HungryOne h2 = HungryOne.INSTANCE;System.out.println(h1 == h2);System.out.println("----------------------");HungrySingle s1 = HungrySingle.getInstance();HungrySingle s2 = HungrySingle.getInstance();System.out.println(s1 == s2);}
}
2、懒汉式线程安全问题

懒汉式:延迟创建对象,第一次调用getInstance方法再创建对象

形式一:

package com.atguigu.single.lazy;public class LazyOne {private static LazyOne instance;private LazyOne(){}public static synchronized LazyOne getInstance(){if(instance == null){instance = new LazyOne();}return instance;}//有指令重排问题
/*    public static LazyOne getInstance(){if(instance == null){synchronized (LazyOne.class) {try {Thread.sleep(10);//加这个代码,暴露问题} catch (InterruptedException e) {e.printStackTrace();}if(instance == null){instance = new LazyOne();}}}return instance;}*/
}

形式二:

package com.atguigu.single.lazy;public class LazySingle {private LazySingle instance;private LazySingle(){}private static class Inner{static final LazySingle INSTANCE = new LazySingle();}public static LazySingle getInstance(){return Inner.INSTANCE;}
}

测试类:

package com.atguigu.single.lazy;import org.junit.Test;public class TestLazy {@Testpublic void test01(){LazyOne s1 = LazyOne.getInstance();LazyOne s2 = LazyOne.getInstance();System.out.println(s1);System.out.println(s2);System.out.println(s1 == s2);}//把s1和s2声明在外面,是想要在线程的匿名内部类中为s1和s2赋值LazyOne s1;LazyOne s2;@Testpublic void test02(){Thread t1 = new Thread(){public void run(){s1 = LazyOne.getInstance();}};Thread t2 = new Thread(){public void run(){s2 = LazyOne.getInstance();}};t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(s1);System.out.println(s2);System.out.println(s1 == s2);}LazySingle obj1;LazySingle obj2;@Testpublic void test03(){Thread t1 = new Thread(){public void run(){obj1 = LazySingle.getInstance();}};Thread t2 = new Thread(){public void run(){obj2 = LazySingle.getInstance();}};t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(obj1);System.out.println(obj2);System.out.println(obj1 == obj2);}
}

9.5 等待唤醒机制

9.5.1 线程间通信

为什么要处理线程间通信:

多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。而多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些通信机制,可以协调它们的工作,以此来帮我们达到多线程共同操作一份数据。

比如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,此时B线程必须等到A线程完成后才能执行,那么线程A与线程B之间就需要线程通信,即—— 等待唤醒机制。

9.5.2 等待唤醒机制

什么是等待唤醒机制

这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。

就是在一个线程满足某个条件时,就进入等待状态(wait()/wait(time)), 等待其他线程执行完他们的指定代码过后再将其唤醒(notify());或可以指定wait的时间,等时间到了自动唤醒;在有多个线程进行等待时,如果需要,可以使用 notifyAll()来唤醒所有的等待线程。wait/notify 就是线程间的一种协作机制。

  1. wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING或TIMED_WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”或者等待时间到,在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
  2. notify:则选取所通知对象的 wait set 中的一个线程释放;
  3. notifyAll:则释放所通知对象的 wait set 上的全部线程。

注意:

被通知线程被唤醒后也不一定能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。

总结如下:

  • 如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE(可运行) 状态;
  • 否则,线程就从 WAITING 状态又变成 BLOCKED(等待锁) 状态

调用wait和notify方法需要注意的细节

  1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
  2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
  3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

9.5.3 生产者与消费者问题

等待唤醒机制可以解决经典的“生产者与消费者”的问题。

生产者与消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个(多个)共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。

生产者与消费者问题中其实隐含了两个问题:

  • 线程安全问题:因为生产者与消费者共享数据缓冲区,不过这个问题可以使用同步解决。
  • 线程的协调工作问题:
    • 要解决该问题,就必须让生产者线程在缓冲区满时等待(wait),暂停进入阻塞状态,等到下次消费者消耗了缓冲区中的数据的时候,通知(notify)正在等待的线程恢复到就绪状态,重新开始往缓冲区添加数据。同样,也可以让消费者线程在缓冲区空时进入等待(wait),暂停进入阻塞状态,等到生产者往缓冲区添加数据之后,再通知(notify)正在等待的线程恢复到就绪状态。通过这样的通信机制来解决此类问题。
1、一个厨师一个服务员问题

案例:有家餐馆的取餐口比较小,只能放10份快餐,厨师做完快餐放在取餐口的工作台上,服务员从这个工作台取出快餐给顾客。现在有1个厨师和1个服务员。

package com.atguigu.thread5;public class TestCommunicate {public static void main(String[] args) {// 1、创建资源类对象Workbench workbench = new Workbench();// 2、创建和启动厨师线程new Thread("厨师") {public void run() {while (true) {workbench.put();}}}.start();// 3、创建和启动服务员线程new Thread("服务员") {public void run() {while (true) {workbench.take();}}}.start();}}// 1、定义资源类
class Workbench {private static final int MAX_VALUE = 10;private int num;public synchronized void put() {if (num >= MAX_VALUE) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}num++;System.out.println(Thread.currentThread().getName() + "制作了一份快餐,现在工作台上有:" + num + "份快餐");this.notify();}public synchronized void take() {if (num <= 0) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}num--;System.out.println(Thread.currentThread().getName() + "取走了一份快餐,现在工作台上有:" + num + "份快餐");this.notify();}
}
2、多个厨师多个服务员问题

案例:有家餐馆的取餐口比较小,只能放10份快餐,厨师做完快餐放在取餐口的工作台上,服务员从这个工作台取出快餐给顾客。现在有多个厨师和多个服务员。

package com.atguigu.thread5;public class TestCommunicate2 {public static void main(String[] args) {// 1、创建资源类对象WindowBoard windowBoard = new WindowBoard();// 2、创建和启动厨师线程// 3、创建和启动服务员线程Cook c1 = new Cook("张三",windowBoard);Cook c2 = new Cook("李四",windowBoard);Waiter w1 = new Waiter("小红",windowBoard);Waiter w2 = new Waiter("小绿",windowBoard);c1.start();c2.start();w1.start();w2.start();}}
//1、定义资源类
class WindowBoard {private static final int MAX_VALUE = 10;private int num;public synchronized void put() {while (num >= MAX_VALUE) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}num++;System.out.println(Thread.currentThread().getName() + "制作了一份快餐,现在工作台上有:" + num + "份快餐");this.notifyAll();}public synchronized void take() {while (num <= 0) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}num--;System.out.println(Thread.currentThread().getName() + "取走了一份快餐,现在工作台上有:" + num + "份快餐");this.notifyAll();}
}//2、定义厨师类
class Cook extends Thread{private WindowBoard windowBoard;public Cook(String name,WindowBoard windowBoard) {super(name);this.windowBoard = windowBoard;}public void run(){while(true) {windowBoard.put();}}
}//3、定义服务员类
class Waiter extends Thread{private WindowBoard windowBoard;public Waiter(String name,WindowBoard windowBoard) {super(name);this.windowBoard = windowBoard;}public void run(){while(true) {windowBoard.take();}}
}

9.6 线程生命周期

9.6.1 观点1:5种状态(JDK1.5之前)

简单来说,线程的生命周期有五种状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)。CPU需要在多条线程之间切换,于是线程状态会多次在运行、阻塞、就绪之间切换。

image-20210829150633574

1. 新建

当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状。此时它和其他Java对象一样,仅仅由JVM为其分配了内存,并初始化了实例变量的值。此时的线程对象并没有任何线程的动态特征,程序也不会执行它的线程体run()。

2. 就绪

但是当线程对象调用了start()方法之后,就不一样了,线程就从新建状态转为就绪状态。JVM会为其创建方法调用栈和程序计数器,当然,处于这个状态中的线程并没有开始运行,只是表示已具备了运行的条件,随时可以被调度。至于什么时候被调度,取决于JVM里线程调度器的调度。

注意:

程序只能对新建状态的线程调用start(),并且只能调用一次,如果对非新建状态的线程,如已启动的线程或已死亡的线程调用start()都会报错IllegalThreadStateException异常。

3. 运行

如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程体代码,则该线程处于运行状态。如果计算机只有一个CPU,在任何时刻只有一个线程处于运行状态,如果计算机有多个处理器,将会有多个线程并行(Parallel)执行。

当然,美好的时光总是短暂的,而且CPU讲究雨露均沾。对于抢占式策略的系统而言,系统会给每个可执行的线程一个小时间段来处理任务,当该时间用完,系统会剥夺该线程所占用的资源,让其回到就绪状态等待下一次被调度。此时其他线程将获得执行机会,而在选择下一个线程时,系统会适当考虑线程的优先级。

4. 阻塞

当在运行过程中的线程遇到如下情况时,线程会进入阻塞状态:

  • 线程调用了sleep()方法,主动放弃所占用的CPU资源;
  • 线程试图获取一个同步监视器,但该同步监视器正被其他线程持有;
  • 线程执行过程中,同步监视器调用了wait(),让它等待某个通知(notify);
  • 线程执行过程中,同步监视器调用了wait(time)
  • 线程执行过程中,遇到了其他线程对象的加塞(join);
  • 线程被调用suspend方法被挂起(已过时,因为容易发生死锁);

当前正在执行的线程被阻塞后,其他线程就有机会执行了。针对如上情况,当发生如下情况时会解除阻塞,让该线程重新进入就绪状态,等待线程调度器再次调度它:

  • 线程的sleep()时间到;
  • 线程成功获得了同步监视器;
  • 线程等到了通知(notify);
  • 线程wait的时间到了
  • 加塞的线程结束了;
  • 被挂起的线程又被调用了resume恢复方法(已过时,因为容易发生死锁);

5. 死亡

线程会以以下三种方式之一结束,结束后的线程就处于死亡状态:

  • run()方法执行完成,线程正常结束
  • 线程执行过程中抛出了一个未捕获的异常(Exception)或错误(Error)
  • 直接调用该线程的stop()来结束该线程(已过时,因为容易发生死锁)

9.6.2 观点2:6种状态(JDK1.5之后)

在java.lang.Thread.State的枚举类中这样定义:

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

首先它没有区分:就绪和运行状态,因为对于Java对象来说,只能标记为可运行,至于什么时候运行,不是JVM来控制的了,是OS来进行调度的,而且时间非常短暂,因此对于Java对象的状态来说,无法区分。只能我们人为的进行想象和理解。

其次根据Thread.State的定义,阻塞状态是分为三种的:BLOCKED、WAITING、TIMED_WAITING。

  • BLOCKED:是指互有竞争关系的几个线程,其中一个线程占有锁对象时,其他线程只能等待锁。只有获得锁对象的线程才能有执行机会。
  • TIMED_WAITING:当前线程执行过程中遇到Thread类的sleep或join,Object类的wait,LockSupport类的park方法,并且在调用这些方法时,设置了时间,那么当前线程会进入TIMED_WAITING,直到时间到,或被中断。
  • WAITING:当前线程执行过程中遇到遇到Object类的wait,Thread类的join,LockSupport类的park方法,并且在调用这些方法时,没有指定时间,那么当前线程会进入WAITING状态,直到被唤醒。
    • 通过Object类的wait进入WAITING状态的要有Object的notify/notifyAll唤醒;
    • 通过Condition的await进入WAITING状态的要有Conditon的signal方法唤醒;
    • 通过LockSupport类的park方法进入WAITING状态的要有LockSupport类的unpark方法唤醒
    • 通过Thread类的join进入WAITING状态,只有调用join方法的线程对象结束才能让当前线程恢复;

说明:当从WAITING或TIMED_WAITING恢复到Runnable状态时,如果发现当前线程没有得到监视器锁,那么会立刻转入BLOCKED状态。

image-20210829150651198

9.7 释放锁操作与死锁

任何线程进入同步代码块、同步方法之前,必须先获得对同步监视器的锁定,那么何时会释放对同步监视器的锁定呢?

1、释放锁的操作

当前线程的同步方法、同步代码块执行结束。

当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致当前线程异常结束。

当前线程在同步代码块、同步方法中执行了锁对象的wait()方法,当前线程被挂起,并释放锁。

2、不会释放锁的操作

线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行。

线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该该线程挂起,该线程不会释放锁(同步监视器)。应尽量避免使用suspend()和resume()这样的过时来控制线程。

3、死锁

不同的线程分别锁住对方需要的同步监视器对象不释放,都在等待对方先放弃时就形成了线程的死锁。一旦出现死锁,整个程序既不会发生异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

public class TestDeadLock {public static void main(String[] args) {Object g = new Object();Object m = new Object();Owner s = new Owner(g,m);Customer c = new Customer(g,m);new Thread(s).start();new Thread(c).start();}
}
class Owner implements Runnable{private Object goods;private Object money;public Owner(Object goods, Object money) {super();this.goods = goods;this.money = money;}@Overridepublic void run() {synchronized (goods) {System.out.println("先给钱");synchronized (money) {System.out.println("发货");}}}
}
class Customer implements Runnable{private Object goods;private Object money;public Customer(Object goods, Object money) {super();this.goods = goods;this.money = money;}@Overridepublic void run() {synchronized (money) {System.out.println("先发货");synchronized (goods) {System.out.println("再给钱");}}}
}

4、面试题:sleep()和wait()方法的区别

(1)sleep()不释放锁,wait()释放锁

(2)sleep()指定休眠的时间,wait()可以指定时间也可以无限等待直到notify或notifyAll

(3)sleep()在Thread类中声明的静态方法,wait方法在Object类中声明

因为我们调用wait()方法是由锁对象调用,而锁对象的类型是任意类型的对象。那么希望任意类型的对象都要有的方法,只能声明在Object类中。

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

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

相关文章

多维时序 | Matlab实现WOA-TCN-Multihead-Attention鲸鱼算法优化时间卷积网络结合多头注意力机制多变量时间序列预测

多维时序 | Matlab实现WOA-TCN-Multihead-Attention鲸鱼算法优化时间卷积网络结合多头注意力机制多变量时间序列预测 目录 多维时序 | Matlab实现WOA-TCN-Multihead-Attention鲸鱼算法优化时间卷积网络结合多头注意力机制多变量时间序列预测效果一览基本介绍程序设计参考资料 效…

C++核心编程:C++ 中的引用 笔记

2.引用 2.1 引用的基本使用 - 作用&#xff1a;给变量起别名 - 语法&#xff1a;数据类型 &别名 原名 #include<iostream> using namespace std; int main() {// 引用基本语法// 数据类型 &别名 原名int a 10;// 创建引用int &ref_a a;cout<<&qu…

2024017期传足14场胜负前瞻

2024017期赛事由亚洲杯2场、英总杯2场、德甲2场、意甲4场、西甲4场组成。售止时间为1月28日&#xff08;周日&#xff09;19点00分&#xff0c;敬请留意&#xff1a; 本期深盘场次同样适中&#xff0c;1.5以下赔率3场&#xff0c;1.5-2.0赔率6场&#xff0c;其他场次基本皆是平…

【鸿蒙】大模型对话应用(一):大模型接口对接与调试

Demo介绍 本demo对接阿里云和百度的大模型API&#xff0c;实现一个简单的对话应用。 DecEco Studio版本&#xff1a;DevEco Studio 3.1.1 Release HarmonyOS API版本&#xff1a;API9 关键点&#xff1a;ArkTS、ArkUI、UIAbility、网络http请求、列表布局 官方接口文档 此…

vue3使用vue-diff插件实现文本对比

前面介绍过vue3通过monaco-editor实现文本对比功能 但因为业务需要自定义左右两侧文本的底色及高亮颜色&#xff0c;考虑换一个插件&#xff1a;vue-diff 1、下载插件&#xff1a; npm i vue-diff1.2.4 2、main.js中引入并注册插件&#xff1a; // Diff对比 import VueDiff f…

ZYNQ AC7020C的“点LED”实验

一、创建 Vivado 工程 1、启动 Vivado 2、在 Vivado 开发环境里点击“Create New Project”&#xff0c;创建一个新的工程 3、弹出一个建立新工程的向导&#xff0c;点击“Next” 4、在弹出的对话框中输入工程名和工程存放的目录。需要注意工程路径“Project location”不能有…

Zookeeper3.5.7基础学习

文章目录 一、Zookeeper入门1、概述2、特点3、数据结构4、应用场景 二、Zookeeper 安装部署1、本地模式安装1.1 基础操作1.2 配置参数解读 2、集群部署2.1 集群安装2.2 选举机制(面试重点)2.3 ZK 集群启动停止脚本 三、ZK客户端相关操作1、客户端命令行操作1.1 命令行语法1.2 z…

【第七在线】数字化转型:智能商品计划管理的核心要素

随着科技的快速发展&#xff0c;数字化转型已经成为企业适应市场变化、提高运营效率的必由之路。尤其在服装行业&#xff0c;快速的市场反应和精准的供应链管理显得尤为重要。其中&#xff0c;智能商品计划管理作为数字化转型的核心要素&#xff0c;正在重塑整个行业的竞争格局…

【RH850U2A芯片】Reset Vector和Interrupt Vector介绍

目录 前言 正文 1. 什么是Reset Vector 1.1 S32K144芯片的Reset Vector 1.2 RH850芯片的Reset Vector 2. 什么是Interrupt Vector 2.1 S32K144芯片的Interrupt Vector 2.2 RH850芯片的Interrupt Vector 3. Reset Vector等价于Interrupt Vector吗 4. 总结 前言 最近在…

$monitor和$strobe都看的是啥

注&#xff1a;本文来自硅芯思见 在编写测试平时&#xff0c;经常会用到$monitor和$strobe监测某些信号&#xff0c;并且使用格式上与$display比较类似&#xff0c;但是它们之间还是存在差异的&#xff0c;它们在当前仿真时间槽&#xff08;time-slot&#xff09;中被执行的区间…

常见电源电路(LDO、非隔离拓扑和隔离拓扑结构)

一、常见电路元件和符号 二、DC-DC转换器 DC-DC转换器&#xff1a;即直流-直流转换器&#xff0c;分为三类&#xff1a;①线性调节器&#xff1b;②电容性开关解调器&#xff1b;③电感性开关调节器&#xff1b; 2.1线性稳压器&#xff08;LDO&#xff09; 2.1.1 NMOS LDO…

如何将前后端分离(vue2+SpringBoot)项目部署到腾讯云服务器

如何将前后端分离&#xff08;vue2SpringBoot&#xff09;项目部署到腾讯云服务器 目录 如何将前后端分离&#xff08;vue2SpringBoot&#xff09;项目部署到腾讯云服务器 1、在选中目录地下新建2个文件夹 2、将打包好的前端项目和后端jar包上传到相应的目录下 3、将路径切…

Gin 应用多实例部署session问题、session参数与刷新

文章目录 一、Gin Session 存储的实现方案二、memstore&#xff1a;基于内存的实现2.1 基本使用2.2 关键参数 三、使用redis&#xff1a;多实例部署3.1 使用redis优势3.2 基本使用 四、信息安全的三个核心概念五、Gin Session 参数5.1 参数介绍 六、Session 自动刷新 一、Gin S…

语图奇缘:林浩然与杨凌芸的哲学漫画大冒险

语图奇缘&#xff1a;林浩然与杨凌芸的哲学漫画大冒险 Language Odyssey: The Philosophical Comic Adventure of Lin Haoran and Yang Lingyun 在一个充满逻辑谜题和言语陷阱的城市——逻言市&#xff0c;住着两位热衷于探索语言奥秘的年轻人&#xff0c;林浩然和杨凌芸。林浩…

一篇文章带你了解C++中隐含的this指针

文章目录 一、this指针的引出二、this指针的特性【面试题】 一、this指针的引出 我们先来定义一个日期类Date&#xff0c;下面这段代码执行的结果是什么呢&#xff1f; class Date { public:void Init(int year, int month, int day){_year year;_month month;_day day;}v…

2024新版68套Axure RP大数据可视化大屏模板及通用组件+PSD源文件

Axure RP数据可视化大屏模板及通用组件库2024新版重新制作了这套新的数据可视化大屏模板及通用组件库V2版。新版本相比于V1版内容更加丰富和全面&#xff0c;但依然秉承“敏捷易用”的制作理念&#xff0c;这套作品也同样延续着我们对细节的完美追求&#xff0c;整个设计制作过…

【PythonRS】Rasterio库安装+基础函数使用教程

Rasterio是一个Python库&#xff0c;专门用于栅格数据的读写操作。它支持多种栅格数据格式&#xff0c;如GeoTIFF、ENVI和HDF5&#xff0c;为处理和分析栅格数据提供了强大的工具。RasterIO适用于各种栅格数据应用&#xff0c;如卫星遥感、地图制作等。通过RasterIO&#xff0c…

Two-factor authentication (2FA) is required for your GitHub account解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

SkyWalking介绍与使用docker-compose部署服务

一、Skywalking概述 1、Skywalking介绍 Skywalking是分布式系统的应用程序性能监视工具,专为微服务,云原生架构和基于容器(Docker,K8S,Mesos)架构而设计,它是一款优秀的APM(Application Performance Management)工具,包括了分布式追踪,性能指标分析和服务依赖分析等…

gitee仓库使用中的警告

当 Git 执行 git pull 命令时&#xff0c;有时候会出现类似下面的警告信息&#xff1a; warning: ----------------- SECURITY WARNING ---------------- warning: | TLS certificate verification has been disabled! | warning: ------------------------------------------…