java判断线程是否wait_Java并发编程之线程间通讯(上)wait/notify机制

线程间通信

如果一个线程从头到尾执行完也不和别的线程打交道的话,那就不会有各种安全性问题了。但是协作越来越成为社会发展的大势,一个大任务拆成若干个小任务之后,各个小任务之间可能也需要相互协作最终才能执行完整个大任务。所以各个线程在执行过程中可以相互通信,所谓通信就是指相互交换一些数据或者发送一些控制指令,比如一个线程给另一个暂停执行的线程发送一个恢复执行的指令,下边详细看都有哪些通信方式。

volatile和synchronized

可变共享变量是天然的通信媒介,也就是说一个线程如果想和另一个线程通信的话,可以修改某个在多线程间共享的变量,另一个线程通过读取这个共享变量来获取通信的内容。

由于原子性操作、内存可见性和指令重排序的存在,java提供了volatile和synchronized的同步手段来保证通信内容的正确性,假如没有这些同步手段,一个线程的写入不能被另一个线程立即观测到,那这种通信就是不靠谱的~

wait/notify机制

故事背景

也不知道是那个遭天杀的给我们学校厕所的坑里塞了个塑料瓶,导致楼道里如黄河泛滥一般,臭味熏天。更加悲催的是整个楼只有这么一个厕所,比这个更悲催的是这个厕所里只有一个坑!!!!!好吧,让我们用java来描述一下这个厕所:

public class Washroom {

private volatile boolean isAvailable = false; //表示厕所是否是可用的状态

private Object lock = new Object(); //厕所门的锁

public boolean isAvailable() {

return isAvailable;

}

public void setAvailable(boolean available) {

this.isAvailable = available;

}

public Object getLock() {

return lock;

}

}

isAvailable字段代表厕所是否可用,由于厕所损坏,默认是false的,lock字段代表这个厕所门的锁。需要注意的是 isAvailable字段被volatile修饰,也就是说有一个线程修改了它的值,它可以立即对别的线程可见~

由于厕所资源宝贵,英明的学校领导立即拟定了一个修复任务:

public class RepairTask implements Runnable {

private Washroom washroom;

public RepairTask(Washroom washroom) {

this.washroom = washroom;

}

@Override

public void run() {

synchronized (washroom.getLock()) {

System.out.println("维修工 获取了厕所的锁");

System.out.println("厕所维修中,维修厕所是一件辛苦活,需要很长时间。。。");

try {

Thread.sleep(5000L); //用线程sleep表示维修的过程

} catch (InterruptedException e) {

throw new RuntimeException(e);

}

washroom.setAvailable(true); //维修结束把厕所置为可用状态

System.out.println("维修工把厕所修好了,准备释放锁了");

}

}

}

这个维修计划的内容就是当维修工进入厕所之后,先把门锁上,然后开始维修,维修结束之后把Washroom的isAvailable字段设置为true,以表示厕所可用。

与此同时,一群急得像热锅上的蚂蚁的家伙在厕所门前打转转,他们想做神马不用我明说了吧😏😏:

public class ShitTask implements Runnable {

private Washroom washroom;

private String name;

public ShitTask(Washroom washroom, String name) {

this.washroom = washroom;

this.name = name;

}

@Override

public void run() {

synchronized (washroom.getLock()) {

System.out.println(name + " 获取了厕所的锁");

while (!washroom.isAvailable()) {

// 一直等

}

System.out.println(name + " 上完了厕所");

}

}

}

这个ShitTask描述了上厕所的一个流程,先获取到厕所的锁,然后判断厕所是否可用,如果不可用,则在一个死循环里不断的判断厕所是否可用,直到厕所可用为止,然后上完厕所释放锁走人。

然后我们看看现实世界都发生了什么吧:

public class Test {

public static void main(String[] args) {

Washroom washroom = new Washroom();

new Thread(new RepairTask(washroom), "REPAIR-THREAD").start();

try {

Thread.sleep(1000L);

} catch (InterruptedException e) {

throw new RuntimeException(e);

}

new Thread(new ShitTask(washroom, "狗哥"), "BROTHER-DOG-THREAD").start();

new Thread(new ShitTask(washroom, "猫爷"), "GRANDPA-CAT-THREAD").start();

new Thread(new ShitTask(washroom, "王尼妹"), "WANG-NI-MEI-THREAD").start();

}

}

学校先让维修工进入厕所维修,然后包括狗哥、猫爷、王尼妹在内的上厕所大军就开始围着厕所打转转的旅程,我们看一下执行结果:

维修工 获取了厕所的锁

厕所维修中,维修厕所是一件辛苦活,需要很长时间。。。

维修工把厕所修好了,准备释放锁了

王尼妹 获取了厕所的锁

王尼妹 上完了厕所

猫爷 获取了厕所的锁

猫爷 上完了厕所

狗哥 获取了厕所的锁

狗哥 上完了厕所

看起来没有神马问题,但是再回头看看代码,发现有两处特别别扭的地方:

在main线程开启REPAIR-THREAD线程后,必须调用sleep方法等待一段时间才允许上厕所线程开启。

如果REPAIR-THREAD线程和其他上厕所线程一块儿开启的话,就有可能上厕所的人,比如狗哥先获取到厕所的锁,然后维修工压根儿连厕所也进不去。但是真实情况可能真的这样的,狗哥先到了厕所,然后维修工才到。不过狗哥的处理应该不是一直待在厕所里,而是先出来等着,啥时候维修工说修好了他再进去。所以这点有些别扭~

在一个上厕所的人获取到厕所的锁的时候,必须不断判断Washroom的isAvailable字段是否为true。

如果一个人进入到厕所发现厕所仍然处在不可用状态的话,那它应该在某个地方休息,啥时候维修工把厕所修好了,再叫一下等着上厕所的人就好了嘛,没必要自己不停的去检查厕所是否被修好了。

总结一下,就是一个线程在获取到锁之后,如果指定条件不满足的话,应该主动让出锁,然后到专门的等待区等待,直到某个线程完成了指定的条件,再通知一下在等待这个条件完成的线程,让它们继续执行。如果你觉得上边这句话比较绕的话,我来给你翻译一下:当上狗哥获取到厕所门锁之后,如果厕所处于不可用状态,那就主动让出锁,然后到等待上厕所的队伍里排队等待,直到维修工把厕所修理好,把厕所的状态置为可用后,维修工再通知需要上厕所的人,然他们正常上厕所。

具体使用方式

为了实现这个构想,java里提出了一套叫wait/notify的机制。当一个线程获取到锁之后,如果发现条件不满足,那就主动让出锁,然后把这个线程放到一个等待队列里等待去,等到某个线程把这个条件完成后,就通知等待队列里的线程他们等待的条件满足了,可以继续运行啦!

如果不同线程有不同的等待条件肿么办,总不能都塞到同一个等待队列里吧?是的,java里规定了每一个锁都对应了一个等待队列,也就是说如果一个线程在获取到锁之后发现某个条件不满足,就主动让出锁然后把这个线程放到与它获取到的锁对应的那个等待队列里,另一个线程在完成对应条件时需要获取同一个锁,在条件完成后通知它获取的锁对应的等待队列。这个过程意味着锁和等待队列建立了一对一关联。

怎么让出锁并且把线程放到与锁关联的等待队列中以及怎么通知等待队列中的线程相关条件已经完成java已经为我们规定好了。我们知道,锁其实就是个对象而已,在所有对象的老祖宗类Object中定义了这么几个方法:

public final void wait() throws InterruptedException

public final void wait(long timeout) throws

InterruptedException

public final void wait(long timeout, int nanos) throws InterruptedException

public final void notify();

public final void notifyAll();

6e7c455679b89ee564329008074beb19.png

了解了这些方法的意思以后我们再来改写一下ShitTask:

public class ShitTask implements Runnable {

// ... 为节省篇幅,省略相关字段和构造方法

@Override

public void run() {

synchronized (washroom.getLock()) {

System.out.println(name + " 获取了厕所的锁");

while (!washroom.isAvailable()) {

try {

washroom.getLock().wait(); //调用锁对象的wait()方法,让出锁,并把当前线程放到与锁关联的等待队列

} catch (InterruptedException e) {

throw new RuntimeException(e);

}

}

System.out.println(name + " 上完了厕所");

}

}

}

看,原来我们在判断厕所是否可用的死循环里加了这么一段代码:

washroom.getLock().wait();

这段代码的意思就是让出厕所的锁,并且把当前线程放到与厕所的锁相关联的等待队列里。

然后我们也需要修改一下维修任务:

public class RepairTask implements Runnable {

// ... 为节省篇幅,省略相关字段和构造方法

@Override

public void run() {

synchronized (washroom.getLock()) {

System.out.println("维修工 获取了厕所的锁");

System.out.println("厕所维修中,维修厕所是一件辛苦活,需要很长时间。。。");

try {

Thread.sleep(5000L); //用线程sleep表示维修的过程

} catch (InterruptedException e) {

throw new RuntimeException(e);

}

washroom.setAvailable(true); //维修结束把厕所置为可用状态

washroom.getLock().notifyAll(); //通知所有在与锁对象关联的等待队列里的线程,它们可以继续执行了

System.out.println("维修工把厕所修好了,准备释放锁了");

}

}

}

大家可以看出来,我们在维修结束后加了这么一行代码:

washroom.getLock().notifyAll();

这个代码表示将通知所有在与锁对象关联的等待队列里的线程,它们可以继续执行了。

在使用java的wait/notify机制修改了ShitTask和RepairTask后,我们在复原一下整个现实场景:

public class Test {

public static void main(String[] args) {

Washroom washroom = new Washroom();

new Thread(new ShitTask(washroom, "狗哥"), "BROTHER-DOG-THREAD").start();

new Thread(new ShitTask(washroom, "猫爷"), "GRANDPA-CAT-THREAD").start();

new Thread(new ShitTask(washroom, "王尼妹"), "WANG-NI-MEI-THREAD").start();

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

throw new RuntimeException(e);

}

new Thread(new RepairTask(washroom), "REPAIR-THREAD").start();

}

}

在这个场景中,我们可以刻意让着急上厕所的先到达了厕所,维修工最后抵达厕所,来看一下加了wait/notify机制的代码的执行结果是:

狗哥 获取了厕所的锁

猫爷 获取了厕所的锁

王尼妹 获取了厕所的锁

维修工 获取了厕所的锁

厕所维修中,维修厕所是一件辛苦活,需要很长时间。。。

维修工把厕所修好了,准备释放锁了

王尼妹 上完了厕所

猫爷 上完了厕所

狗哥 上完了厕所

从执行结果可以看出来,狗哥、猫爷、王尼妹虽然先到达了厕所并且获取到锁,但是由于厕所处于不可用状态,所以都先调用wait()方法让出了自己获得的锁,然后躲到与这个锁关联的等待队列里,直到维修工修完了厕所,通知了在等待队列中的狗哥、猫爷、王尼妹,他们才又开始继续执行上厕所的程序~

通用模式

经过上边的厕所案例,大家应该对wait/notify机制有了大致了解,下边我们总结一下这个机制的通用模式。首先看一下等待线程的通用模式:

获取对象锁。

如果某个条件不满足的话,调用锁对象的wait方法,被通知后仍要检查条件是否满足。

条件满足则继续执行代码。

通用的代码如下:

synchronized (对象) {

处理逻辑(可选)

while(条件不满足) {

对象.wait();

}

处理逻辑(可选)

}

除了判断条件是否满足和调用wait方法以外的代码,其他的处理逻辑是可选的。

下边再来看通知线程的通用模式:

获得对象的锁。

完成条件。

通知在等待队列中的等待线程。

synchronized (对象) {

完成条件

对象.notifyAll();、

}

小贴士:

别忘了同步方法也是使用锁的喔,静态同步方法的锁对象是该类的`Class对象`,成员同步方法的锁对象是`this对象`。所以如果没有刻意强调,下边所说的同步代码块也包含同步方法。

了解了wait/notify的通用模式之后,使用的时候需要特别小心,需要注意下边这些方面:

必须在同步代码块中调用wait、 notify或者notifyAll方法。

有的童鞋会有疑问,为啥wait/notify机制的这些方法必须都放在同步代码块中才能调用呢?wait方法的意思只是让当前线程停止执行,把当前线程放在等待队列里,notify方法的意思只是从等待队列里移除一个线程而已,跟加锁有什么关系?

答:因为wait方法是运行在等待线程里的,notify或者notifyAll是运行在通知线程里的。而执行wait方法前需要判断一下某个条件是否满足,如果不满足才会执行wait方法,这是一个先检查后执行的操作,不是一个原子性操作,所以如果不加锁的话,在多线程环境下等待线程和通知线程的执行顺序可能是这样的:

7cd996488a945176110787c2cf493cbe.png

也就是说当等待线程已经判断条件不满足,正要执行wait方法,此时通知线程抢先把条件完成并且调用了notify方法,之后等待线程才执行到wait方法,这会导致等待线程永远停留在等待队列而没有人再去notify它。所以等待线程中的判断条件是否满足、调用wait方法和通知线程中完成条件、调用notify方法都应该是原子性操作,彼此之间是互斥的,所以用同一个锁来对这两个原子性操作进行同步,从而避免出现等待线程永久等待的尴尬局面。

如果不在同步代码块中调用wait、notify或者notifyAll方法,也就是说没有获取锁就调用wait方法,就像这样:

对象.wait();

是会抛出IllegalMonitorStateException异常的。

在同步代码块中,必须调用获取的锁对象的wait、 notify或者notifyAll方法。

也就是说不能随便调用一个对象的wait、notify或者notifyAll方法。比如等待线程中的代码是这样的:

synchronized (对象1) {

while(条件不满足) {

对象2.wait(); //随便调用一个对象的wait方法

}

}

通知线程中的代码是这样的:

synchronized (对象1) {

完成条件

对象2.notifyAll();

}

对于代码对象2.wait(),表示让出当前线程持有的对象2的锁,而当前线程持有的是对象1的锁,所以这么写是错误的,也会抛出IllegalMonitorStateException异常的。意思就是如果当前线程不持有某个对象的锁,那它就不能调用该对象的wait方法来让出该锁。所以如果想让等待线程让出当前持有的锁,只能调用对象1.wait()。然后这个线程就被放置到与对象1相关联的等待队列中,在通知线程中只能调用对象1.notifyAll()来通知这些等待的线程了。

在等待线程判断条件是否满足时,应该使用while,而不是if。

也就是说在判断条件是否满足的时候要使用while:

while(条件不满足) { //正确✅

对象.wait();

}

而不是使用if:

if(条件不满足) { //错误❌

对象.wait();

}

这个是因为在多线程条件下,可能在一个线程调用notify之后立即又有一个线程把条件改成了不满足的状态,比如在维修工把厕所修好之后通知大家上厕所吧的瞬间,有一个小屁孩以迅雷不及掩耳之势又给厕所坑里塞了个瓶子,厕所又被置为不可用状态,等待上厕所的还是需要再判断一下条件是否满足才能继续执行。

在调用完锁对象的notify或者notifyAll方法后,等待线程并不会立即从wait()方法返回,需要调用notify()或者notifyAll()的线程释放锁之后,等待线程才从wait()返回继续执行。。

也就是说如果通知线程在调用完锁对象的notify或者notifyAll方法后还有需要执行的代码,就像这样:

synchronized (对象) {

完成条件

对象.notifyAll();

... 通知后的处理逻辑

}

需要把通知后的处理逻辑执行完成后,把锁释放掉,其他线程才可以从wait状态恢复过来,重新竞争锁来执行代码。比方说在维修工修好厕所并通知了等待上厕所的人们之后,他还没有从厕所出来,而是在厕所的墙上写了 "XXX到此一游"之类的话之后才从厕所出来,从厕所出来才代表着释放了锁,狗哥、猫爷、王尼妹才开始争抢进入厕所的机会。

notify方法只会将等待队列中的一个线程移出,而notifyAll方法会将等待队列中的所有线程移出。

大家可以把上边代码中的notifyAll方法替换称notify方法,看看执行结果~

wait和sleep的区别

眼尖的小伙伴肯定发现,wait和sleep这两个方法都可以让线程暂停执行,而且都有InterruptedException的异常说明,那么它们的区别是啥呢?

wait是Object的成员方法,而sleep是Thread的静态方法。

只要是作为锁的对象都可以在同步代码块中调用自己的wait方法,sleep是Thread的静态方法,表示的是让当前线程休眠指定的时间。

调用wait方法需要先获得锁,而调用sleep方法是不需要的。

在一次强调,一定要在同步代码块中调用锁对象的wait方法,前提是要获得锁!前提是要获得锁!前提是要获得锁!而sleep方法随时调用~

调用wait方法的线程需要用notify来唤醒,而sleep必须设置超时值。

线程在调用wait方法之后会先释放锁,而sleep不会释放锁。

这一点可能是最重要的一点不同点了吧,狗哥、猫爷、王尼妹这些线程一开始是获取到厕所的锁了,但是调用了wait方法之后主动把锁让出,从而让维修工得以进入厕所维修。如果狗哥在发现厕所是不可用的条件时选择调用sleep方法的话,线程是不会释放锁的,也就是说维修工无法获得厕所的锁,也就修不了厕所了~ 大家一定要谨记这一点啊!

总结

线程间需要通过通信才能协作解决某个复杂的问题。

可变共享变量是天然的通信媒介,但是使用的时候一定要保证线程安全性,通常使用volatile变量或synchronized来保证线程安全性。

一个线程在获取到锁之后,如果指定条件不满足的话,应该主动让出锁,然后到专门的等待区等待,直到某个线程完成了指定的条件,再通知一下在等待这个条件完成的线程,让它们继续执行。这个机制就是wait/notify机制。

等待线程的通用模式:

synchronized (对象) {

处理逻辑(可选)

while(条件不满足) {

对象.wait();

}

处理逻辑(可选)

}

可以分为下边几个步骤:

获取对象锁。

如果某个条件不满足的话,调用锁对象的wait方法,被通知后仍要检查条件是否满足。

条件满足则继续执行代码。

通知线程的通用模式:

synchronized (对象) {

完成条件

对象.notifyAll();、

}

可以分为下边几个步骤:

获得对象的锁。

完成条件。

通知在等待队列中的等待线程。

wait和sleep的区别

wait是Object的成员方法,而sleep是Thread的静态方法。

调用wait方法需要先获得锁,而调用sleep方法是不需要的。

调用wait方法的线程需要用notify来唤醒,而sleep必须设置超时值。

线程在调用wait方法之后会先释放锁,而sleep不会释放锁。

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

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

相关文章

opensips mysql 认证_基于ubuntu中使用mysql实现opensips用户认证的解决方法

1 MySQL支持1.1 MySQL模块支持默认安装时,不包含MySQL模块。opensipsctlrc文件中包含了数据库配置的信息,要想让opensips支持mysql数据库,在编译安装的时候就要支持mysql模块。方法如下:(1)修改opensips中的Makefile.conf&#xf…

java 注解与反射_Java注解与反射直接上手

什么是注解(Annotation)注解顾名思义为注释、讲解,可以理解为一种标签或标记,可以用在类、方法或者域上。向类、方法等添加注解,相当于给他们贴上了一层标签。注解使用在使用注解时,在类名、方法名、域上直接用注解名即可。比如一…

java按钮退出_java – 如何在此程序中添加退出按钮?怎么样“清楚”?

我需要添加一个“清除计算器”的按钮,以及一个退出butPanel上的程序的按钮.它也需要是非常基本的Java代码,因为我是初学者,并且有一个糟糕的comp.sci.老师.我有一个带退出按钮的代码示例,但我不确定如何将它放入我的程序中.我已经尝试了这么多.此外,如果有一个更好的“错误检查…

python土味情话_Python 将土味情话语录设置为桌面壁纸

本文编写于 128 天前,最后修改于 128 天前,其中某些信息可能已经过时。41041-3yfokd0irbe.png38220-tlrmwji3zwo.pngimport osimport tempfileimport timeimport requestsimport win32apiimport win32conimport win32guifrom PIL import Image, ImageDra…

Day70力扣打卡

打卡记录 收集足够苹果的最小花园周长&#xff08;找规律 二分&#xff09; 链接 class Solution:def minimumPerimeter(self, neededApples: int) -> int:l, r 1, 10 ** 5while l < r:mid (l r) >> 1if 2 * (2 * (mid ** 3) 3 * (mid ** 2) mid) > nee…

bootstrap加载mysql数据库_bootstrap后台管理系统前后台实现(含数据库)

撰写本文档目的是让后续开发者在理解该系统架构的基础上遵循一定规范保持系统架构的合理性&#xff1b;同时也能够达到允许没有开发经验仅有web基础的入门开发者能够通过复制粘贴的方式仿照demo示例进行开发的目的。目 录1 案例调研与选取... 21.1 案例调研... 21.…

幻兽天下修复版java_宠物天下四-幻兽天下

快速搜索机型:诺基亚 N70系列(176208)7610 3230 6600 6260 6620 6630 6670 6680 6681 6682 N70 N72 ;松下: X700 X800 ;联想: P930诺基亚 N73系列(240320)N73 5320 5320XM 5320di_XM 5630XM 5700 5700XM 5710XM 5730XM 6110 6110N 6120 6120C 6120ci 6121 6122C 6124C 6210S 62…

java类spring加载_spring的加载机制?

1,今天面试官问我spring的加载机制有哪些---这么"抽象"的问题作为一个十多年经验的自己写过MVC,IOC,ORM, 等各种中间件小框架的开发人员也回答不出来~确切的说是无从谈起,不知道从哪个角度说这个事情,在我的观点里这个问题本身的问法就有问题,什么叫"加载机制&q…

java计数器策略模式_java设计模式(二十一)--策略模式

对于策略模式,我在很多面试题上看到过考察这一类的问题,这种模式也的确比较好用。 我感觉这种模式就是将不同实现的方法放到一个接口中,然后通过实现这个接口来实现不同的运行结果,这种模式有三部分构成: 策略接口 策略实现类 策略作用类(使用策略的类) 网络上的专业解释:此模式…

java负数十进制转二进制_java中负数的补码转换为十进制

C&plus;&plus;全局和静态变量初始化转自:http://www.cnblogs.com/zhenjing/archive/2010/10/15/1852116.html 对于C语言的全局和静态变量,不管是否被初始化,其内存空间都是全局的 ...eclips常用快捷键本文取自:Eclipse中常用快捷键 - yizhang88的专栏 - 博客频道 - CSD…

linux setuid函数_setuid函数解析

在讨论这个setuid函数之前&#xff0c;我们首先要了解的一个东西就是内核为每个进程维护的三个UID值。这三个UID分别是实际用户ID(real uid)、有效用户ID(effective uid)、保存的设置用户ID(saved set-user-ID)。首先说这个实际用户ID&#xff0c;就是我们当前以哪个用户登录了…

java中asl_带你认识绕不开的ASLR

微软从windows vista/windows server 2008(kernel version 6.0)开始采用ASLR技术&#xff0c;主要目的是为了防止缓冲区溢出ASLR技术会使PE文件每次加载到内存的起始地址随机变化&#xff0c;并且进程的栈和堆的起始地址也会随机改变。ASLR(Address space layout randomization…

java复杂性_java – 计算Big-O复杂性

我最终会给这个程序一个类似60,000个400像素图像的输入文件,所以我试着想一想这个代码如何用大输入运行.为了便于阅读,我用“blah”和所有带有简单字母(nn,mm和kk)的ArrayList名称替换了不重要的东西.for (Perceptron P : nn){//blah}for (Perceptron P : mm) {//blah}for (Per…

java泛型与接口_Java泛型用于方法,类和接口

什么是泛型&#xff1f;型就是数据类型参数化。就是说原本定死的数据类型像方法中的形参一样&#xff0c;数据是不确定的&#xff0c;使用的时候由人传进去&#xff0c;泛型就是这样&#xff0c;数据类型不确定了。使用的时候再确定就可以了。泛型的特点是JDK1.5的新特性泛型可…

unix系统编码 java_JAVA字符编码系列三:Java应用中的编码问题

这两天抽时间又总结/整理了一下各种编码的实际编码方式&#xff0c;和在Java应用中的使用情况&#xff0c;在这里记录下来以便日后参考。为了构成一个完整的对文字编码的认识和深入把握&#xff0c;以便处理在Java开发过程中遇到的各种问题&#xff0c;特别是乱码问题&#xff…

Java 捕获 mybatis异常_3 springboot集成mybatis和全局异常捕获

mybatis有两种方式&#xff0c;一种是基于XML&#xff0c;一种是基于注解springboot集成mybatis首先先创建表&#xff0c;这里都简化了DROP TABLE IF EXISTS user;CREATE TABLE user (id int(11) NOT NULL auto_increment,username varchar(255) default NULL,PRIMARY KEY (id)…

java背景图片加上组件_关于 java swing组件加背景图片的问题

最近自己做了一个小的进销存软件,背景图片加上后不能最大化。尝试了好几种方法 最后终于把问题解决了。下面把自己写的实例 分享一下&#xff1a;一个是在JFrame窗体中加如背景图片一个是在Jpanel 面板中加入背景图片都可以伸缩铺满整个屏幕&#xff0c;算是自己的一个小心得。…

java applet 访问文件_使用JavaApplet访问数据库

使用Java Applet访问数据库学习任何的程序语言&#xff0c;当然都得与数据库&#xff0c;Java刚刚诞生的时候&#xff0c;对数据库的支持并不是很好&#xff0c;经过这几年的发展&#xff0c;它对数据库的支持也已经完全达到了成熟的境地。由于这里主要是介绍Java Applet小程序…

java ip 范围内打卡_定位地理位置PHP判断员工打卡签到经纬度是否在打卡之内

具体代码如下所述&#xff1a;/* 计算两组经纬度坐标之间的距离* param $lat1 纬度1* param $lng1 经度1* param $lat2 纬度2* param $lng2 经度2* param int $len_type 返回值类型(1-m 2-km)* param int $decimal 保留小数位数* return float*/public function getDistance($l…

与java线程有关的,线程多少和什么有关?大神们表示有话要说!

原标题&#xff1a;线程多少和什么有关&#xff1f;大神们表示有话要说&#xff01;来源&#xff1a;importnew.com/10780.htmlEddie的回答:Charlie Martin的回答:benjismith的回答:Neil Coffey的回答:McGovernTheory在StackOverflow提了这样一个问题:Java虚拟机最多支持多少个…