Java多线程--避免同步机制带来的死锁问题及用Lock锁解决线程安全问题

文章目录

  • 一、死锁
    • (1)说明
    • (2)案例
      • 1、案例1
      • 2、案例2
      • 3、案例3
    • (3)诱发死锁的原因及解决方案
      • 1、诱发死锁的原因
      • 2、避免死锁
  • 二、JDK5.0新特性:Lock(锁)
    • (1)介绍
    • (2)案例
    • (3)synchronized与Lock的对比

一、死锁

(1)说明

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

image.png

比如线程1拿着同步监视器(锁),它拿着锁1等着锁2;但是线程2拿着锁2等着锁1。两个线程僵持着,互相等待,这就构成了死锁

我们编写程序时,要避免出现死锁

一旦出现死锁,整个程序既不会发生异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

(2)案例

1、案例1

StringBuilder:跟字符串相关的类(后边说,这里看成字符串就可以了,里面没有任何数据)

用它造两个对象:

public class DeadLockTest {public static void main(String[] args) {StringBuilder s1=new StringBuilder();StringBuilder s2=new StringBuilder();}
}

然后new一个线程:

new Thread(){@Overridepublic void run() {}
}.start();

线程里面调用了run方法,把s1当作同步监视器(锁),然后用s1调用append方法(用于添加),添加一个“a”,如下:

new Thread(){@Overridepublic void run() {synchronized (s1){s1.append("a");	//理解为空字符串" "+"a"}}
}.start();

然后s2也添加一个“1”,如下:

new Thread(){@Overridepublic void run() {synchronized (s1){s1.append("a");s2.append("1");}}
}.start();

s2当作一个锁,给s1添加一个“b”,给s2添加一个“2”。如下:

new Thread(){@Overridepublic void run() {synchronized (s1){s1.append("a");s2.append("1");synchronized (s2){s1.append("b");s2.append("2");System.out.println(s1);System.out.println(s2);}}}
}.start();

然后在这里sleep睡一下,便于演示死锁的问题:

new Thread(){@Overridepublic void run() {synchronized (s1){s1.append("a");s2.append("1");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (s2){s1.append("b");s2.append("2");System.out.println(s1);System.out.println(s2);}}}
}.start();

说一下大致过程:

线程1进入run,获得锁s1,然后稍微sleep一会,醒来之后再拿着锁s2,执行后面的操作,执行结束后打印s1与s2。

现在我们再写一个线程,原理与上面的类似,只不过这个线程2先获得锁s2,再获得锁s1,如下:

//线程2
new Thread(){@Overridepublic void run() {synchronized (s2){s1.append("c");s2.append("3");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (s1){s1.append("d");s2.append("4");System.out.println(s1);System.out.println(s2);}}}
}.start();

若此时没有sleep,如下:

public class DeadLockTest {public static void main(String[] args) {StringBuilder s1=new StringBuilder();StringBuilder s2=new StringBuilder();//线程1new Thread(){@Overridepublic void run() {synchronized (s1){s1.append("a");s2.append("1");synchronized (s2){s1.append("b");s2.append("2");System.out.println(s1);System.out.println(s2);}}}}.start();//线程2new Thread(){@Overridepublic void run() {synchronized (s2){s1.append("c");s2.append("3");synchronized (s1){s1.append("d");s2.append("4");System.out.println(s1);System.out.println(s2);}}}}.start();}
}

运行如下:

image.png

可以发现并没有出现死锁,这就是问题所在,能执行成功不意味着这个程序没有问题。

其实它是存在问题的可能性的。

从输出结果上来看,先执行的是线程1,s1获得a,s2获得1,然后s1获得b,s2获得2。最终s1得到ab,s2得到12。

然后线程2执行,在原有基础上,又添加了数据,最终s1得到abcd,s2得到1234。

也就是说,线程1执行结束,线程2才 开始执行。

线程1执行结束的时候,s1与s2两个同步监视器都被释放了,所以线程2执行的时候才能顺利拿到s1与s2。

如果先执行的是第2个线程,再执行第1个线程,结果就是:cd,34,cdab,3412。


现在我们加上sleep,让死锁的概率高一点,注意这里只是让它出现的概率变高了,并不是从无到有(只是数量上的,并不是质变)。

🌱代码

package yuyi04.lock;/*** ClassName: DeadLockTest* Package: yuyi04.lock* Description:** @Author 雨翼轻尘* @Create 2024/2/1 0001 15:18*/
public class DeadLockTest {public static void main(String[] args) {StringBuilder s1=new StringBuilder();StringBuilder s2=new StringBuilder();//线程1new Thread(){@Overridepublic void run() {synchronized (s1){s1.append("a");s2.append("1");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (s2){s1.append("b");s2.append("2");System.out.println(s1);System.out.println(s2);}}}}.start();//线程2new Thread(){@Overridepublic void run() {synchronized (s2){s1.append("c");s2.append("3");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (s1){s1.append("d");s2.append("4");System.out.println(s1);System.out.println(s2);}}}}.start();}
}

🍺输出

image.png

可以看到,出现了死锁

线程1拿着同步监视器s1,在sleep的时候,线程2执行。

然后线程2拿着同步监视器s2往后执行,也碰到了sleep。

线程1醒来之后,去拿同步监视器s2,但是s2在线程2手里。大家就僵持住了。

这就构成了死锁,如下:

image.png

2、案例2

案例1很容易看出来会出现死锁,现在再来看一个例子:

🌱代码

package yuyi04.lock;/*** ClassName: DeadLock* Package: yuyi04.lock* Description:** @Author 雨翼轻尘* @Create 2024/2/1 0001 18:01*/class A {public synchronized void foo(B b) {System.out.println("当前线程名: " + Thread.currentThread().getName()+ " 进入了A实例的foo方法"); // ①try {Thread.sleep(200);} catch (InterruptedException ex) {ex.printStackTrace();}System.out.println("当前线程名: " + Thread.currentThread().getName()+ " 企图调用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().getName()+ " 进入了B实例的bar方法"); // ②try {Thread.sleep(200);} catch (InterruptedException ex) {ex.printStackTrace();}System.out.println("当前线程名: " + Thread.currentThread().getName()+ " 企图调用A实例的last方法"); // ④a.last();}public synchronized void last() {System.out.println("进入了B类的last方法内部");}
}public class DeadLock implements Runnable {	//用实现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();	//分线程创建,并调用start()方法,即调用run()方法dl.init();	//主线程调用init()方法}
}

🍰分析

可以看到,类A与类B里面定义了两个方法,都加了synchronized,就是同步方法,同步监视器就是当前类的对象。

如下:

image.png

然后在实现类里面声明了a与b两个成员变量,还有两个方法,如下:

image.png

在main方法种,创建了当前实现类DeadLock的对象dl,然后将dl当作形参传入Thread()中,用实现Runnable接口的方式创建一个线程,调用start()方法,于是这个分线程就去执行run()方法。

然后又使用dl调用init()方法,这就是主线程的代码。就将dl当作普通的对象,它调用init()方法。

现在就是,主线程调用init()方法,分线程调用run()方法

分线程调用run方法,主要执行b.bar(a);

public void run() {Thread.currentThread().setName("副线程");// 调用b对象的bar方法b.bar(a);System.out.println("进入了副线程之后");
}

主线程调用init方法,主要执行a.foo(b);

public void init() {Thread.currentThread().setName("主线程");// 调用a对象的foo方法a.foo(b);System.out.println("进入了主线程之后");
}

可以得出主线程与分线程分别调用的方法如下:

image.png


分线程调用bar方法,需要拿着A类的一个对象a作为锁,这个锁就是B的对象。

image.png

然后执行a.last(),如下:

image.png

注意锁是当前对象this,用b调用bar(a),this就是b,也就是拿着锁b进入了同步方法bar中,顺便将参数a带进来了。

在执行最后有一个a.last(),就是用对象a调用last()方法,last方法也是一个同步方法a是传进来的A的对象,相当于又需要握着a这个同步监视器。如下:

image.png

也就是说,分线程握着B的对象,还要握A的对象。

主线程正好相反,握着A的对象a又需要b的同步监视器。
image.png

🍺输出结果

image.png

可以看见输出结果在这里出现了死锁,僵持不动了。

3、案例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("再给钱");}}}
}

(3)诱发死锁的原因及解决方案

1、诱发死锁的原因

互斥条件(一定会出现)

比如线程1握着同步监视器(锁),另外一个线程就无法获得这个锁。(必然的,这是同步机制,加同步的目的就是为了某个线程能获得锁而另一个线程握不住)

占用且等待

比如线程1握着同步监视器(锁),然后又等待另外一个锁。另外一个线程又拿着这个锁。

不可抢夺(或不可抢占)

某线程等待的时候不能将其他线程拥有的锁抢过来。

循环等待

这种情况会一直僵持着,解不开。

以上4个条件,同时出现就会触发死锁。

2、避免死锁

死锁一旦出现,基本很难人为干预,只能尽量规避

可以考虑打破上面的任意一个诱发条件

①针对条件1:互斥条件基本上无法被破坏。因为线程需要通过互斥解决安全问题。

②针对条件2:可以考虑一次性申请所有所需的资源,这样就不存在等待的问题。

③针对条件3:占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源,让别人先来,这样其他的线程不需要等待,不会僵持了

④针对条件4:可以将资源改为线性顺序。申请资源时,先申请序号较小的,这样避免循环等待问题(按顺序获取,先获取序号小的,才能获取后边大的序号)。

二、JDK5.0新特性:Lock(锁)

(1)介绍

以前说的“同步机制”,其实就是synchronized的使用方式。

除了使用synchronized同步机制处理线程安全问题之外,还可以使用jdk5.0提供的Lock锁的方式。

  • 除了synchronized的方式,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();}
}

(2)案例

【步骤】

步骤1:创建Lock的实例,需要确保多个线程共用同一个Lock实例。

需要考虑将此对象声明为static final

步骤2.:执行lock()方法,锁定对共享资源的调用。

步骤3.:unlock()的调用,释放对共享数据的锁定。


以下面代码(继承的方式)为例:

🌱代码

package yuyi04.lock;/*** ClassName: WindowTest2* Package: yuyi04.lock* Description:*      使用继承Thread类的方式,实现卖票* @Author 雨翼轻尘* @Create 2024/2/2 0002 9:54*/public class WindowTest2 {public static void main(String[] args) {//3.创建3个窗口  创建当前Thread的子类的对象Window w1=new Window();Window w2=new Window();Window w3=new Window();//命名w1.setName("窗口1");w2.setName("窗口2");w3.setName("窗口3");//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法w1.start();w2.start();w3.start();}}class Window extends Thread{    //卖票  1.创建一个继承于Thread类的子类//票static int ticket=100;//2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中@Overridepublic void run() {while (true){if(ticket>0){   //如果票数大于0就可以售票try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//哪个窗口卖票了,票卖了多少System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100ticket--;}else{break;}}}
}

🍺输出

image.png

上面代码有线程安全问题

现在我们用Lock来解决线程安全问题。

image.png

concurrent”就是并发的意思,Java的并发编程其实就是说这个包里面的API。

现在我们就可以看到这个包里面的一个API,叫Lock,它是一个接口,目前已知的实现类如下:

image.png

我们需要使用ReentrantLock可重入锁)这个实现类,接下来创建这个实现类的对象

如下:

class Window extends Thread{    //卖票  1.创建一个继承于Thread类的子类ReentrantLock lock=new ReentrantLock();//...
}

①权限修饰

我们需要先考虑权限修饰

首先不希望在外部可以被访问,因为这纯粹是为了保证线程安全的,所以加一个private

其次这个锁有要求,需要保证线程的安全,多个线程需要共用同一个lock,所以还要再加一个static

最后再加一个final,保证给lock赋值之后不要再修改了,是唯一的不能改变的。

如下:

private static final ReentrantLock lock=new ReentrantLock();

Runnable接口一般不需要static的,一般是把这个接口的一个实例作为多个线程对象的形参,一般情况只会有一个接口的实例。

②用方法限制

这个锁定操作,不像同步代码块,有一个大括号,里面是需要被同步的代码。

lock比较灵活,它只需要用两个方法去限制,两个方法执行当中的代码就是需要被同步的代码。

针对案例,需要被同步的代码如下:(蓝色部分)

image.png

在这一段代码之前,调用一个方法lock();然后在代码执行结束,调用unlock()方法。如下:

image.png

两个方法中间的代码就是之前我们放到同步代码块当中的代码。


🌱代码

package yuyi04.lock;import java.util.concurrent.locks.ReentrantLock;/*** ClassName: WindowTest2* Package: yuyi04.lock* Description:*      使用继承Thread类的方式,实现卖票* @Author 雨翼轻尘* @Create 2024/2/2 0002 9:54*/public class WindowTest2 {public static void main(String[] args) {//3.创建3个窗口  创建当前Thread的子类的对象Window w1=new Window();Window w2=new Window();Window w3=new Window();//命名w1.setName("窗口1");w2.setName("窗口2");w3.setName("窗口3");//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法w1.start();w2.start();w3.start();}}class Window extends Thread{    //卖票  1.创建一个继承于Thread类的子类//票static int ticket=100;private static final ReentrantLock lock=new ReentrantLock();//2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中@Overridepublic void run() {while (true){lock.lock();if(ticket>0){   //如果票数大于0就可以售票try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//哪个窗口卖票了,票卖了多少System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100ticket--;}else{break;}lock.unlock();}}
}

🍺输出

image.png

可以看见线程安全了。

🗳️这里可能出现了没有解锁的场景:

image.png

如果死锁的话就执行不到unlock,虽然关闭了程序,但是如果这些资源是指向别的资源的话就无法释放了。

unlock()方法一定要保证会被执行,可以考虑写入finally中。

现在我们在这里加一个try,将下面蓝色部分移入try里面:

image.png

然后再写一个finally,将unlock写入,确保它一定会被执行。如下:

image.png


🌱代码

package yuyi04.lock;import java.util.concurrent.locks.ReentrantLock;/*** ClassName: WindowTest2* Package: yuyi04.lock* Description:*      使用继承Thread类的方式,实现卖票* @Author 雨翼轻尘* @Create 2024/2/2 0002 9:54*/public class WindowTest2 {public static void main(String[] args) {//3.创建3个窗口  创建当前Thread的子类的对象Window w1=new Window();Window w2=new Window();Window w3=new Window();//命名w1.setName("窗口1");w2.setName("窗口2");w3.setName("窗口3");//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法w1.start();w2.start();w3.start();}}class Window extends Thread{    //卖票  1.创建一个继承于Thread类的子类//票static int ticket=100;//①创建Lock的实例(Lock是一个接口,现在用的是可重入锁ReentrantLock)// 需要确保多个线程共用同一个Lcok实例,需要考虑将此对象声明为static final//若没有加static,Window一共造了3个对象,相当于现在就有3把锁,每一个对象锁自己的,就不好使private static final ReentrantLock lock=new ReentrantLock();    //2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中@Overridepublic void run() {while (true){try {//②执行lock()方法,锁定对共享资源的调用lock.lock();if(ticket>0){   //如果票数大于0就可以售票try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//哪个窗口卖票了,票卖了多少System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100ticket--;}else{break;}}finally{//③unlock()的调用,释放对共享数据的锁定,解锁之后其他线程就可以来操作了lock.unlock();}}}
}

🍺输出

可以看到,程序结束了,而且没有线程安全问题。如下:

image.png

(3)synchronized与Lock的对比

synchronized与Lock的对比

1、Lock显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized隐式锁,出了作用域、遇到异常等自动解锁。

2、Lock只有代码块锁synchronized代码块锁和方法锁

3、使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类),更体现面向对象。

4、(了解)Lock锁可以对读不加锁,对写加锁,synchronized不可以。

5、(了解)Lock锁可以有多种获取锁的方式,可以从sleep的线程中抢到锁,synchronized不可以。

说明:

开发建议中处理线程安全问题优先使用顺序为:

Lock ----> 同步代码块 ----> 同步方法

【面试题】

🎲synchronized同步的方式 与Lock的对比 ?

  • synchronized不管是同步代码块还是同步方法,都需要在结束一对{}之后,释放对同步监视器的调用。
  • Lock是通过两个方法控制需要被同步的代码,更灵活一些。
  • Lock作为接口,提供了多种实现类(也就是很多锁),适合更多更复杂的场景,效率更高

image.png

对于Lock,JUC里面再详细说,这里不做深入。

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

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

相关文章

小白水平理解面试经典题目_数组类LeetCode 118 Pascal‘s Triangle【回归解法】

LeetCode 118 生成杨辉三角(Pascal’s Triangle) 小白渣翻译 给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。 在杨辉三角中,每个数是它左上方和右上方的数的和。 例子 这里是小白理解 那么这种题目一上来看&#xf…

4-树-合并两个有序链表

这是树的第4篇算法,力扣链接。 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1: 输入:l1 [1,2,4], l2 [1,3,4] 输出:[1,1,2,3,4,4]示例 2: 输入&#xf…

Linux下gcc的使用与程序的翻译

gcc和程序的翻译过程 gcc介绍程序的翻译过程预编译编译汇编链接 命令行式宏定义 gcc介绍 gcc是一款编译C语言编译器,可以把我们用vim写的代码编译成可执行程序。编译C用g进行编译,C的文件后缀是test.cc或test.cpp或test.cxx 如果要安装g就执行以下命令 …

C# winform 多语言(json)方式实现

前后对比 使用nuget json工具包1.总体思路 创建对应的json字典对照表 { "测试":"Test", "语言":"Language", "设置":"Set", "中文(默认)":"Chinese (default)", "英文":"E…

【HarmonyOS应用开发】ArkTS 属性动画的使用(十二)

一、概述 属性动画,是最为基础的动画,其功能强大、使用场景多,应用范围较广。常用于如下场景中: 一、页面布局发生变化。例如添加、删除部分组件元素。二、页面元素的可见性和位置发生变化。例如显示或者隐藏部分元素&#xff0…

CANoe学习笔记——窗口类型

CANoe中的窗口类型,共分为三种 1:MDI windows 2:Standard Windows 3:Docking Windows 窗口有多种类型,每种类型都定义了特定的窗口行为。通过点击窗口顶部的区域,可以更改窗口类型。 如下图&#xff0…

Python的requests库与HTTP代理的使用:魔法般的网络探险之旅

嘿,各位魔法探险家们!今天我们要一起探索Python的requests库与HTTP代理的神奇组合,开启一段魔法般的网络探险之旅! 首先,我们要明白什么是requests库。简单说,requests库就是Python中的魔法飞毯&#xff0…

Linux基础知识合集

整理了一下学习的一些关于Linux的一些基础知识,同学们也可以通过公众号菜单栏查看! 一、基础知识 Linux基础知识 Linux命令行基础学习 Linux用户与组概念初识 Linux文件与目录权限基础 Linux中文件内容的查看 Linux系统之计划任务管理 二、服务器管理 Vm…

MySql主从同步,同步SQL_ERROR 1032解决办法

1.登录从库 mysql -u root -p 2.输入命令查看状态 SHOW SLAVE STATUS\G; 3.找到对应的错误数据位置 Slave_IO_Running: YesSlave_SQL_Running: NoReplicate_Do_DB: app_push_centerReplicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Tabl…

“欢天喜地迎新春”下姜村邻里守望写对联活动

卯兔追冬去,辰龙报春来。空谷幽香谱佳期,红联金句寄吉祥。春联是我国特有的文学形式,贴春联是继承传统习俗的一种方式,是对祖先的尊敬,对传统的继承。春节前夕,家家户户贴上红红的春联,一副副透…

西圣Olite开放式耳机持续100+天霸榜:品质优势再掀数码狂潮

随着开放式耳机的市场竞争加剧,用户对耳机的音质和配置要求越来越高。而西圣开放式耳机的不断推陈出新,正是对客户需求的完美回应!西圣开放式耳机,在现在鱼龙混杂的市场上,能够获得着卓越的研发成果并且还在不断的追求…

qt学习:停车场管理系统+摄像头+http识别车牌+sqlite3数据库

目录 参考前面发的几篇文章http识别车牌,sqlite3数据库、摄像头的文章 步骤 部分代码 新建一个项目,加入前面用到的http和image两个文件,和加入用到的模块和头函数和成员,加入前面用到的全局变量 配置ui界面 在构造函数中初…

Qt5 基于OpenGL实现六轴机械臂三维仿真

需求 在Qt中通过OPenGL方式加载三维模型STL文件,然后将多个结构的STL文件类型的模型进行组装,形成6轴机械臂三维模型的显示,并且可以对每个关节进行关节角度的控制。 新建一个C类STLFileLoader,用于加载STL文件,并进…

IP协议(2) 和 数据链路层协议基础

IP协议续 1.路由选择 在复杂的网络结构中,我们需要找到一个通往终点的路线,这就是路由选择 举个例子:我们在没有手机导航之前,想去一个地方得是到一个地方问一下路的方式最终找到目的地 路由的过程,其实就是样子问路的过程 1.当IP数据包到达路由器的时候,会查看目的IP 2.路由器…

Jmeter高级使用

文章目录 JMeter之计数器JMeter之集合点JMeter之断言JMeter之动态关联后置处理器:正则表达式提取器 JMeter之分布式测试JMeter之组件执行顺序元件的作用域元件的执行顺序配置元件Http Cookie管理器 多协议接口的性能测试Debug采样器Http请求中文乱码的解决Post参数设…

[晓理紫]每日论文分享(有中文摘要,源码或项目地址)

专属领域论文订阅 关注{晓理紫|小李子},每日更新论文,如感兴趣,请转发给有需要的同学,谢谢支持 如果你感觉对你有所帮助,请关注我,每日准时为你推送最新论文。 为了答谢各位网友的支持,从今日起…

Java_简单模拟实现ArrayList_学习ArrayList

文章目录 一、 了解线性表和顺序表区别1.线性表2.顺序表 二、模拟实现1.定义接口2.定义MyArrayList3.成员变量以及构造方法4.实现打印数组5.实现add方法6.实现查找某个数是否存在contains或者某个数的下标indexOf7.获取或更改pos位置的值 get和set8.获取数组大小 size9.删除某个…

Git版本管理工具(实战进阶):零基础到起飞实战项目完整篇 →Git学习一篇就够 从基本指令、到本地仓库、远程仓库、实战项目开发演练介绍超详细!

heima 李师傅最新版 Git的讲解 文章目录 Git在实战项目开发使用功能学习01.Git 初识02.Git 仓库03.Git 的三个区域04.Git 文件状态05.Git 暂存区作用06.练习-登录页面07.Git-切换版本08.删除文件09.忽略文件10.分支的概念11.练习-登录 bug 修复12.分支-合并与删除13.分支-合并与…

循环神经网络RNN专题(01/6)

一、说明 RNN用于处理序列数据。在传统的神经网络模型中,是从输入层到隐含层再到输出层,层与层之间是全连接的,每层之间的节点是无连接的。但是这种普通的神经网络对于很多问题却无能无力。例如,你要预测句子的下一个单词是什么&a…

【演讲比赛流程管理系统(C++版)】

一、演讲比赛程序需求 1.1、比赛规则 学校举行一场演讲比赛,共有12个人参加。比赛共两轮,第一轮为淘汰赛,第二轮为决赛 每名选手都有对应的编号,如10001~10012 比赛方式:分组比赛,每组6个人 第一轮分为两个小组&a…