Java 高级应用-多线程-(二)线程安全问题及解决

news2024/11/24 19:30:40

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

5.1 同一个资源问题和线程安全问题
案例:
火车站要卖票,我们模拟火车站的卖票过程。因为疫情期间,本次列车的座位
共 100 个(即,只能出售 100 张火车票)。我们来模拟车站的售票窗口,实现
多个窗口同时售票的过程。注意:不能出现错票、重票。
5.1.1 局部变量不能共享
示例代码:

package com.atguigu.unsafe;
class Window extends Thread {
 public void run() {
 int ticket = 100;
 while (ticket > 0) {
 System.out.println(getName() + "卖出一张票,票号:" + ticke
t);
 ticket--;
 }
 }
}
public class SaleTicketDemo1 {
 public static void main(String[] args) {
 Window w1 = new Window();
 Window w2 = new Window();
 Window w3 = new Window();
 w1.setName("窗口 1");
 w2.setName("窗口 2");
 w3.setName("窗口 3");
 w1.start();
 w2.start();
 w3.start();
 }
}

结果:发现卖出 300 张票。 问题:局部变量是每次调用方法都是独立的,那么每个线程的 run()的 ticket 是独立的,不是共享数据。

5.1.2 不同对象的实例变量不共享

package com.atguigu.unsafe;
class TicketWindow extends Thread {
 private int ticket = 100;
 public void run() {
 while (ticket > 0) {
 System.out.println(getName() + "卖出一张票,票号:" + ticke
t);
 ticket--;
 }
 }
}
public class SaleTicketDemo2 {
 public static void main(String[] args) {
 TicketWindow w1 = new TicketWindow();
 TicketWindow w2 = new TicketWindow();
 TicketWindow w3 = new TicketWindow();
 w1.setName("窗口 1");
 w2.setName("窗口 2");
 w3.setName("窗口 3");
 w1.start();
 w2.start();
 w3.start();
 }
}

结果:发现卖出 300 张票。 问题:不同的实例对象的实例变量是独立的。

5.1.3 静态变量是共享的
示例代码:

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

运行结果:

窗口 1 卖出一张票,票号:100
窗口 2 卖出一张票,票号:100
窗口 3 卖出一张票,票号:100
窗口 3 卖出一张票,票号:97
窗口 1 卖出一张票,票号:97
窗口 2 卖出一张票,票号:97
窗口 1 卖出一张票,票号:94
窗口 3 卖出一张票,票号:94
窗口 2 卖出一张票,票号:94
窗口 2 卖出一张票,票号:91
窗口 1 卖出一张票,票号:91
窗口 3 卖出一张票,票号:91
窗口 3 卖出一张票,票号:88
窗口 1 卖出一张票,票号:88
窗口 2 卖出一张票,票号:88
窗口 3 卖出一张票,票号:85
窗口 1 卖出一张票,票号:85
窗口 2 卖出一张票,票号:85
窗口 3 卖出一张票,票号:82
窗口 1 卖出一张票,票号:82
窗口 2 卖出一张票,票号:82
窗口 2 卖出一张票,票号:79
窗口 3 卖出一张票,票号:79
窗口 1 卖出一张票,票号:79
窗口 3 卖出一张票,票号:76
窗口 1 卖出一张票,票号:76
窗口 2 卖出一张票,票号:76
窗口 1 卖出一张票,票号:73
窗口 2 卖出一张票,票号:73
窗口 3 卖出一张票,票号:73
窗口 2 卖出一张票,票号:70
窗口 1 卖出一张票,票号:70
窗口 3 卖出一张票,票号:70
窗口 2 卖出一张票,票号:67
窗口 3 卖出一张票,票号:67
窗口 1 卖出一张票,票号:67
窗口 1 卖出一张票,票号:64
窗口 3 卖出一张票,票号:64
窗口 2 卖出一张票,票号:64
窗口 2 卖出一张票,票号:61
窗口 3 卖出一张票,票号:61
窗口 1 卖出一张票,票号:61
窗口 1 卖出一张票,票号:58
窗口 2 卖出一张票,票号:58
窗口 3 卖出一张票,票号:58
窗口 2 卖出一张票,票号:55
窗口 1 卖出一张票,票号:55
窗口 3 卖出一张票,票号:55
窗口 3 卖出一张票,票号:52
窗口 1 卖出一张票,票号:52
窗口 2 卖出一张票,票号:52
窗口 2 卖出一张票,票号:49
窗口 1 卖出一张票,票号:49
窗口 3 卖出一张票,票号:49
窗口 2 卖出一张票,票号:46
窗口 3 卖出一张票,票号:46
窗口 1 卖出一张票,票号:46
窗口 2 卖出一张票,票号:43
窗口 3 卖出一张票,票号:43
窗口 1 卖出一张票,票号:43
窗口 3 卖出一张票,票号:40
窗口 1 卖出一张票,票号:40
窗口 2 卖出一张票,票号:40
窗口 2 卖出一张票,票号:37
窗口 3 卖出一张票,票号:37
窗口 1 卖出一张票,票号:37
窗口 2 卖出一张票,票号:34
窗口 1 卖出一张票,票号:34
窗口 3 卖出一张票,票号:34
窗口 3 卖出一张票,票号:31
窗口 2 卖出一张票,票号:31
窗口 1 卖出一张票,票号:31
窗口 1 卖出一张票,票号:28
窗口 2 卖出一张票,票号:28
窗口 3 卖出一张票,票号:28
窗口 2 卖出一张票,票号:25
窗口 1 卖出一张票,票号:25
窗口 3 卖出一张票,票号:25
窗口 2 卖出一张票,票号:22
窗口 3 卖出一张票,票号:22
窗口 1 卖出一张票,票号:22
窗口 3 卖出一张票,票号:19
窗口 1 卖出一张票,票号:19
窗口 2 卖出一张票,票号:19
窗口 2 卖出一张票,票号:16
窗口 3 卖出一张票,票号:16
窗口 1 卖出一张票,票号:16
窗口 2 卖出一张票,票号:13
窗口 1 卖出一张票,票号:13
窗口 3 卖出一张票,票号:13
窗口 2 卖出一张票,票号:10
窗口 1 卖出一张票,票号:10
窗口 3 卖出一张票,票号:10
窗口 3 卖出一张票,票号:7
窗口 1 卖出一张票,票号:7
窗口 2 卖出一张票,票号:7
窗口 3 卖出一张票,票号:4
窗口 1 卖出一张票,票号:4
窗口 2 卖出一张票,票号:4
窗口 3 卖出一张票,票号:1
窗口 2 卖出一张票,票号:1
窗口 1 卖出一张票,票号:1

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

问题 1:但是有重复票或负数票问题。
原因:线程安全问题

问题 2:如果要考虑有两场电影,各卖 100 张票等
原因:TicketThread 类的静态变量,是所有 TicketThread
类的对象共享

5.1.4 同一个对象的实例变量共享
示例代码:多个 Thread 线程使用同一个 Runnable 对象

package com.atguigu.safe;
class TicketSaleRunnable implements Runnable {
 private int ticket = 100;
 public void run() {
 while (ticket > 0) {
 try {
 Thread.sleep(10);//加入这个,使得问题暴露的更明显
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 System.out.println(Thread.currentThread().getName() + "卖
出一张票,票号:" + ticket);
 ticket--;
 }
 }
}
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();
 }
}

结果:发现卖出近 100 张票。
问题:但是有重复票或负数票问题。 原因:线程安全问题

5.1.5 抽取资源类,共享同一个资源对象
示例代码:


package com.atguigu.unsafe;
//1、编写资源类
class Ticket {
 private int ticket = 100;
 public void sale() {
 if (ticket > 0) {
 try {
 Thread.sleep(10);//加入这个,使得问题暴露的更明显
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 System.out.println(Thread.currentThread().getName() + "卖
出一张票,票号:" + ticket);
 ticket--;
 } else {
 throw new RuntimeException("没有票了");
 }
 }
 public int getTicket() {
 return ticket;
 }
}
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();
 }
}

结果:发现卖出近 100 张票。
问题:但是有重复票或负数票问题。 原因:线程安全问题

5.2 同步机制解决线程安全问题

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

根据案例简述:
窗口 1 线程进入操作的时候,窗口 2 和窗口 3 线程只能在外等着,窗口 1 操作
结束,窗口 1 和窗口 2 和窗口 3 才有机会进入代码去执行。也就是说在某个线
程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,
才能去抢夺 CPU 资源,完成对应的操作,保证了数据的同步性,解决了线程不
安全的现象。
为了保证每个线程都能正常执行原子操作,Java 引入了线程同步机制。注意:在
任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程
只能在外等着(BLOCKED)。

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

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

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

5.2.2 同步代码块和同步方法

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

synchronized(同步锁){
需要同步操作的代码
}

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

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

5.2.3 同步锁机制
在《Thinking in Java》中,是这么说的:对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。 防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁之时,另一个任务就可以锁定并使用它了。

5.2.4 synchronized 的锁是什么
同步锁对象可以是任意类型,但是必须保证竞争“同一个共享资源”的多个线程必须使用同一个“同步锁对象”。
对于同步代码块来说,同步锁对象是由程序员手动指定的(很多时候也是指定为 this 或类名.class),但是对于同步方法来说,同步锁对象只能是默认的:
• 静态方法:当前类的 Class 对象(类名.class)
• 非静态方法:this

5.2.5 同步操作的思考顺序
1、如何找问题,即代码是否存在线程安全?(非常重要) (1)明确哪些代码
是多线程运行的代码 (2)明确多个线程是否有共享数据 (3)明确多线程运
行代码中是否有多条语句操作共享数据
2、如何解决呢?(非常重要) 对多条操作共享数据的语句,只能让一个线程
都执行完,在执行过程中,其他线程不可以参与执行。 即所有操作共享数据的
这些语句都要放在同步范围中
3、切记:
范围太小:不能解决安全问题
范围太大:因为一旦某个线程抢到锁,其他线程就只能等待,所以范围太大,效率会降低,不能合理利用 CPU 资源。

5.2.6 代码演示

示例一:静态方法加锁
package com.atguigu.safe;
class TicketSaleThread extends Thread{
 private static int ticket = 100;
 public void run(){//直接锁这里,肯定不行,会导致,只有一个窗口卖票
 while (ticket > 0) {
 saleOneTicket();
 }
 }
 public synchronized static void saleOneTicket(){//锁对象是 TicketS
aleThread 类的 Class 对象,而一个类的 Class 对象在内存中肯定只有一个
 if(ticket > 0) {//不加条件,相当于条件判断没有进入锁管控,线程安全
问题就没有解决
 System.out.println(Thread.currentThread().getName() + "卖
出一张票,票号:" + ticket);
 ticket--;
 }
 }
}
public class SaleTicketDemo3 {
 public static void main(String[] args) {
 TicketSaleThread t1 = new TicketSaleThread();
 TicketSaleThread t2 = new TicketSaleThread();
 TicketSaleThread t3 = new TicketSaleThread();
 t1.setName("窗口 1");
 t2.setName("窗口 2");
 t3.setName("窗口 3");
 t1.start();
 t2.start();
 t3.start();
 }
}
示例二:非静态方法加锁
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 ticket = 100;
 public void run() {//直接锁这里,肯定不行,会导致,只有一个窗口卖票
 while (ticket > 0) {
 saleOneTicket();
 }
 }
 public synchronized void saleOneTicket() {//锁对象是 this,这里就是
TicketSaleRunnable 对象,因为上面 3 个线程使用同一个 TicketSaleRunnable 对
象,所以可以
 if (ticket > 0) {//不加条件,相当于条件判断没有进入锁管控,线程安
全问题就没有解决
 System.out.println(Thread.currentThread().getName() + "卖
出一张票,票号:" + ticket);
 ticket--;
 }
 }
}
示例三:同步代码块
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() {
 while (true) {
 synchronized (ticket) {
 ticket.sale();
 }
 }
 }
 }, "窗口三");
 t1.start();
 t2.start();
 t3.start();
 }
}
//1、编写资源类
class Ticket {
 private int ticket = 1000;
 public void sale() {//也可以直接给这个方法加锁,锁对象是 this,这里就Ticket 对象
 if (ticket > 0) {
 System.out.println(Thread.currentThread().getName() + "卖
出一张票,票号:" + ticket);
 ticket--;
 } else {
 throw new RuntimeException("没有票了");
 }
 }
 public int getTicket() {
 return ticket;
 }
}

附: 死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
【小故事】
面试官:你能解释清楚什么是死锁,我就录取你! 面试者:你录取
我,我就告诉你什么是死锁! …. 恭喜你,面试通过了
一旦出现死锁,整个程序既不会发生异常,也不会给出任何提示,只是所有线
程处于阻塞状态,无法继续。
举例 1:

public class DeadLockTest {
public static void main(String[] args) {
StringBuilder s1 = new StringBuilder();
StringBuilder s2 = new StringBuilder();
new Thread() {
public void run() {
synchronized (s1) {
s1.append("a");
s2.append("1");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2) {
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread() {
public void run() {
synchronized (s2) {
s1.append("c");
s2.append("3");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1) {
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
}
}

举例 2:

class A {
public synchronized void foo(B b) {
System.out.println("当前线程名: " + Thread.currentThread().get
Name()
+ " 进入了 A 实例的 foo 方法"); // ①
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("当前线程名: " + Thread.currentThread().get
Name()
+ " 企图调用 B 实例的 last 方法"); // ③
b.last();
}
public synchronized void last() {
System.out.println("进入了 A 类的 last 方法内部");
}
}
class B {
public synchronized void bar(A a) {
System.out.println("当前线程名: " + Thread.currentThread().get
Name()
+ " 进入了 B 实例的 bar 方法"); // ②
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("当前线程名: " + Thread.currentThread().get
Name()
+ " 企图调用 A 实例的 last 方法"); // ④
a.last();
}
public synchronized void last() {
System.out.println("进入了 B 类的 last 方法内部");
}
}
public class DeadLock implements Runnable {
A a = new A();
B b = new B();
public void init() {
Thread.currentThread().setName("主线程");
// 调用 a 对象的 foo 方法
a.foo(b);
System.out.println("进入了主线程之后");
}
public void run() {
Thread.currentThread().setName("副线程");
// 调用 b 对象的 bar 方法
b.bar(a);
System.out.println("进入了副线程之后");
}
public static void main(String[] args) {
DeadLock dl = new DeadLock();
new Thread(dl).start();
dl.init();
}
}

举例 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;
}
@Override
public 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;
}
@Override
public void run() {
synchronized (money) {
System.out.println("先发货");
synchronized (goods) {
System.out.println("再给钱");
}
}
}
}

诱发死锁的原因:

• 互斥条件
• 占用且等待
• 不可抢夺(或不可抢占)
• 循环等待

以上 4 个条件,同时出现就会触发死锁。
解决死锁:
死锁一旦出现,基本很难人为干预,只能尽量规避。可以考虑打破上面的诱发条件。
针对条件 1:互斥条件基本上无法被破坏。因为线程需要通过互斥解决安全问题。
针对条件 2:可以考虑一次性申请所有所需的资源,这样就不存在等待的问题。
针对条件 3:占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源。
针对条件 4:可以将资源改为线性顺序。申请资源时,先申请序号较小的,这样避免循环等待问题。

JDK5.0 新特性:Lock(锁)

• JDK5.0 的新增功能,保证线程的安全。与采用 synchronized 相比,Lock 可提供多种
锁方案,更灵活、更强大。Lock 通过显式定义同步锁对象来实现同步。同步锁使用Lock 对象充当。
• java.util.concurrent.locks.Lock 接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对 Lock 对象加锁,线程开始访问共享资源之前应先获得 Lock 对象。
• 在实现线程安全的控制中,比较常用的是 ReentrantLock,可以显式加锁、释放锁。
– ReentrantLock 类实现了 Lock 接口,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。
• Lock 锁也称同步锁,加锁与释放锁方法,如下:
– public void lock() :加同步锁。
– public void unlock() :释放同步锁。
代码结构:

class A{
 //1. 创建 Lock 的实例,必须确保多个线程共享同一个 Lock 实例
private final ReentrantLock lock = new ReenTrantLock();
public void m(){
 //2. 调动 lock(),实现需共享的代码的锁定
lock.lock();
try{
//保证线程安全的代码;
}
finally{
 //3. 调用 unlock(),释放共享代码的锁定
lock.unlock(); 
}
}
}

注意:如果同步代码有异常,要将 unlock()写入 finally 语句块。
举例:

import java.util.concurrent.locks.ReentrantLock;
class Window implements Runnable{
int ticket = 100;
 //1. 创建 Lock 的实例,必须确保多个线程共享同一个 Lock 实例
private final ReentrantLock lock = new ReentrantLock();
public void run(){
while(true){
try{
 //2. 调动 lock(),实现需共享的代码的锁定
lock.lock();
if(ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticket--);
}else{
break;
}
}finally{
 //3. 调用 unlock(),释放共享代码的锁定
lock.unlock();
}
}
}
}
public class ThreadLock {
public static void main(String[] args) {
Window t = new Window();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
t2.start();
}
}

synchronized 与 Lock 的对比

  1. Lock 是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized 是隐式锁,出了作用域、遇到异常等自动解锁
  2. Lock 只有代码块锁,synchronized 有代码块锁和方法锁
  3. 使用 Lock 锁,JVM 将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类),更体现面向对象。
  4. (了解)Lock 锁可以对读不加锁,对写加锁,synchronized 不可以
  5. (了解)Lock 锁可以有多种获取锁的方式,可以从 sleep 的线程中抢到锁,synchronized 不可以

说明:开发建议中处理线程安全问题优先使用顺序为:
Lock ----> 同步代码块 ----> 同步方法

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

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

相关文章

四段论提问让ChatGPT更懂你心!

用户故事是描述客户需求的方法,通常记为四段论的格式: 角色-功能-目的-验收标准。 如: 作为一个家庭主妇,我需要一个30平方米的餐厅,用以招待10位客人聚餐。 我希望这个餐厅&…

Spring/SpringBoot与RabbitMQ整合

具体代码 依赖&#xff1a; <dependencies><!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client --><dependency><groupId>com.rabbitmq</groupId><artifactId>amqp-client</artifactId><version>5.7.0</v…

定薪17K*15,阿里测开岗上岸面经分享....

先简单介绍一下我自己吧&#xff0c;等会大家以为我是什么学历狂人&#xff0c;技术大牛&#xff0c;我毕业于广东一个普通本科院校&#xff0c;绝对不是什么双一流大学&#xff0c;大家不要有距离感&#xff0c;这也是我为什么来分享的原因&#xff0c;因为我觉得我这段经验还…

github SSH 生成和使用(详细)

通过ssh连接github&#xff0c;可以有效的提升安全性 1.设置位置 2.生成ssh密钥&#xff08;windows&#xff09; 打开git bash&#xff0c;输入以下命名&#xff0c;把your_emailexample.com换成自己的github账号 ssh-keygen -t rsa -b 4096 -C "your_emailexample.co…

【计算几何】判断多边形边界顺逆时针 C++代码实现

文章目录 一、多边形边界顺序二、数学原理2.1 Green公式2.2 鞋带公式 三、代码实现 一、多边形边界顺序 多边形可以由一个点集 { v 1 , v 2 , . . . , v n } \{v_1,v_2,...,v_n\} {v1​,v2​,...,vn​} 表示&#xff0c;构成多边形的点集确定&#xff0c;多边形边界的顺序也就…

InsCode AI 创作助手围绕《程序员应如何应付AI带来的影响》为主题

InsCode AI 创作助手围绕《程序员应如何应付AI带来的影响》为主题 作者&#xff1a;i阿极 作者简介&#xff1a;Python领域新星作者、多项比赛获奖者&#xff1a;博主个人首页 &#x1f60a;&#x1f60a;&#x1f60a;如果觉得文章不错或能帮助到你学习&#xff0c;可以点赞&a…

Fabric模块功能介绍(一)

主要有5个模块,分别是peer、orderer、cryptogen、configtxgen、configtxlator 模块功能peer主节点模块&#xff0c;负责存储区块链数据、运行维护链码orderer交易打包&#xff0c;排序模块cryptogen组织和证书生成模块configtxgen区块和交易生成模块configtxlator区块和交易解…

【小程序开发日记】django学习笔记4

在没有学完django的情况下匆匆上阵。。。 这次的任务主要是管理数据库并实现浏览的功能。 首先介绍一下微信小程序连接后端的接口&#xff1a;wx.request 基本上输入后会比较经常使用的就是这几个参数。 接下来明确一下这次的工作&#xff1a; 我需要管理两个数据库&#x…

Python - numpy basic

目录 数组array的创建 1 通过list创建array 2 通过list创建二维数组 3 通过arange函数创建 等差数组 4 通过zeros函数创建 零矩阵 5 通过eyes函数创建 单位矩阵 数组array的访问 1 访问形状/元素个数/数据类型 2 访问一维数组的位置/范围 3 访问二维数组的位置/范围 4…

Unity Emisson 自发光属性全局照明

给想要自发光的物体&#xff0c;选择Unity自带Standard Shader。然后勾选Emisson 如下图&#xff1a; 属性&#xff1a; Color指定发光的颜色和强度。单击 Color 框可打开 HDR Color 拾色器。在此处可以更改光照的颜色和发光的强度 (Intensity)。要指定材质的哪些区域发光&…

蓝库云:让销售人员搭配客服工单系统,已成销售企业必备的组合

让销售人员搭配客服工单系统&#xff0c;已成了众多销售企业必备的组合&#xff0c;这不但可以大大提高客户满意度和转化率&#xff0c;还有效跟踪客户及时收到客户的反馈&#xff0c;从而进一步优化产品及策略。站在企业的角度来说企业也可以可以进行数据分析和优化&#xff0…

Mybaits Oracle CLob类型处理

问题描述: 使用的是Oracle 数据库, 表中有一个字段类型为clob类型 问题 : 当使用mybatis查询返回map类型时, 该字段的值为clob对象,而不是数据库里面的字符串 解决方案: 1.手动进行转换,把clob类型转换为字符串(这种比较简单) if(map.get("MAIN_BIZ") instanceo…

【Linux】iptables 防火墙(SNAT/DNAT)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、SNAT 原理与应用二、SNAT转换三、DNAT的介绍1.DNAT概述2.DNAT转换前提条件 四、DNAT转换五、防火墙规则的备份和还原六、tcpdump抓包工具的运用 一、SNAT 原理与…

【Java 方法重写】@Override 注解,一般用法,重写的具体要求

&#x1f340; 博主&#xff1a;_LJaXi Or 東方幻想郷 &#x1f338; 专栏&#xff1a; Java | 从入门到入坟 从前有一只小鸟&#xff0c;它的父母都是优秀的飞行员&#xff0c;它也很想成为一名出色的飞行员。于是&#xff0c;它开始学习飞行技巧&#xff0c;不断练习&#xf…

微信支付, 小程序,公众号, 商户号 需要进行的配置

目录 一 微信公众号1.1 公众号基础信息配置1.2 白名单配置1.3 公众号开发人员配置1.4 域名授权配置1.5 服务器配置 二 微信小程序2.1 开发设置2.2 域名设置2.3 开发者管理2.4 版本管理 三 微信商户号3.1 支付服务3.2 授权域名3.3 配置API安全,支付证书申请3.4 设置支付信息3.5 …

CSDN上海城市开发者社区线下活动纪实

引言 5月27号中午&#xff0c;很高兴能和现CSDN副总裁、前微软 Azure 工程团队首席研发经理、技术畅销书《编程之美》及《构建之法》的作者邹欣邹老师&#xff0c;以及CSDN的 “上海城市开发者社区” 的部分成员齐聚一堂&#xff0c;参加CSDN上海城市开发者社区自5月初成立以来…

在windows环境下使用winsw将jar包注册为服务(实现开机自启和配置日志输出模式)

前言 Windows系统使用java -jar m命令行运行Java项目会弹出黑窗。首先容易误点导致程序关闭&#xff0c;其次我们希望能在Windows系统做到开机自动启动。因此对于SpringBoot程序&#xff0c;目前主流的方法是采用winsw&#xff0c;简单容易配置 1.下载winsw工具 https://git…

新手自媒体找素材必备的5个素材网站。

自媒体需要用到视频、音频、图片、字体等各类素材&#xff0c;对于新手朋友来说&#xff0c;网上搜素材怕侵权&#xff0c;商用素材又不知道哪里找。作为一个老手的我收藏了各类优秀的免费素材网站&#xff0c;下面分享几个&#xff0c;对你一定有帮助&#xff0c;记得收藏起来…

安科瑞预付费管理系统对于学生公寓的设计

安科瑞 徐浩竣 江苏安科瑞电器制造有限公司 zx acrelxhj 摘要&#xff1a;论文设计了适用于学生公寓的自助式预付费控电控水管理系统&#xff0c;采用多种智能功能&#xff0c;可以监测和显示漏电现象&#xff0c;通过短路、跳线、零线接地等方式防范和记录用户的偷电行为&a…

zookeeper机制及消息队列kafka

目录 一、zookeeper1、zookeeper简介2、zookeeper特点3、zookeeper工作模式及机制4、zookeeper应用场景及选举机制5、zookeeper集群部署 二、消息队列kafka1、为什么要有消息队列2、使用消息队列的好处3、kafka简介4、kafka特点5、kafka系统架构名词介绍6、Kafka架构及流程7、k…