多线程

程序和进程的概念------------------------------
程序 - 数据结构 + 算法,主要指存放在硬盘上的可执行文件
进程 - 主要指运行在内存中的可执行文件
目前主流的操作系统都支持多进程,为了让操作系统同时可以执行多个任务
但进程是重量级的,也就是新建一个进程会消耗CPU和内存空间等系统资源,因此进程的数量比较局限
线程的概念------------------------------
为了解决上述问题就提出线程的概念,线程就是进程内部的程序流
也就是说操作系统内部支持多进程的,而每个进程的内部又是支持多线程的
线程是轻量的,新建线程会共享所在进程的系统资源(如CPU和内存空间),因此目前主流的开发都是采用多线程
多线程是采用时间片轮转法来保证多个线程的并发执行,所谓并发就是指宏观并行微观串行的机制(单核来说)
对于单核CUP来说:多个线程就是看起来是一起运行的,但是实际上没有一起运行
因为运行速度太快,看不出来(仔细一看不是一起运行的,粗略一看是一起运行的)
对于多核CUP来说:多个线程若分配与两个或者两个以上的CPU同时运行,那么就有可能真正的同时运行
如两个线程分别由各自的CPU来操作,只是占资源,操作由CPU操作
对于进程来说,我们通常在进程里运行多个线程,且线程占用的CPU和内存空间资源都是由进程的CPU和内存空间资源分配的
而进程的CPU和内存资源都是由系统资源分配,如一些电脑的内存空间大小有8G或者16G内存
而CPU资源也要看型号或者主频,外频,总线频率
线程的创建------------------------------
public class Thread
extends Object
implements Runnable
java.lang.Thread类代表线程,任何线程对象都是Thread类(子类)的实例
Thread类是线程的模板,封装了复杂的线程开启等操作,封装了操作系统的差异性
创建方式------------------------------
自定义类继承Thread类并重写run方法,然后创建该类的对象调用start方法
自定义类实现Runnable接口并重写run方法,创建该类的对象作为实参来构造Thread类型的对象
然后使用Thread类型的对象调用start方法
相关的方法------------------------------
Thread(),使用无参的方式构造对象
Thread(String name),根据参数指定的名称来构造对象
Thread(Runnable target),根据参数指定的引用来构造对象,其中Runnable是个接口类型
Thread(Runnable target,String name),根据参数指定引用和名称来构造对象
void run(),若使用Runnable引用构造了线程对象,调用该方法时最终调用接口中的版本
若没有使用Runnable引用构造线程对象,调用该方法时则啥也不做
void start(),用于启动线程,Java虚拟机会自动调用该线程的run方法
package com.lagou.task18;public class ThreadTest {public static void main(String[] args) {// 1.使用无参方式构造Thread类型的对象// 由源码可知:Thread类中的成员变量target的数值为null。//Thread(),使用无参的方式构造对象Thread t1 = new Thread();Thread t2 = new Thread("1"); //最后run也是不运行,target还是null// 2.调用run方法进行测试// 由源码可知:由于成员变量target的数值为null,因此条件if (target != null)不成立//跳过{}中的代码不执行//  而run方法中除了上述代码再无代码,因此证明run方法确实啥也不干//void run(),若使用Runnable引用构造了线程对象,调用该方法时最终调用接口中的版本//若没有使用Runnable引用构造线程对象,调用该方法时则啥也不做t1.run();// 3.打印一句话System.out.println("我想看看你到底是否真的啥也不干!");}
}
执行流程------------------------------
执行main方法的线程叫做主线程,执行run方法的线程叫做新线程/子线程
main方法是程序的入口,对于start方法之前的代码来说,由主线程执行一次,当start方法调用成功后线程的个数由1个变成了2个
新启动的线程去执行run方法的代码,主线程继续向下执行,两个线程各自独立运行互不影响。当run方法执行完毕后子线程结束
当main方法执行完毕后主线程结束。两个线程执行没有明确的先后执行次序,由操作系统调度算法来决定
可以看作谁先用CPU执行
即对于单核来说:虽然在main里面创建的线程,但还会与main主线程抢占CPU资源,即谁先抢占(操作系统调度算法),谁先执行
不会因为该线程是主线程的子线程而不进行抢夺
如:它先分时间片给第一个调用,然后时间到了,把第一个踢出去
再把时间片分给第二个调用,时间到了又把第二个踢出去
又让第一个来,就好像2个人排队玩游戏,一人5分钟
即时间片是先分配好的,然后再继续分配,通常叫做抢占,即它通常会依次运行,若你运行时,程序有输出多条的
基本上是多核或者程序运行时间的差异,最有可能是优先级的关系(优先级大分配的时间多,即可以运行的时间久)
若还是会有一个输出两次的,则应该是内部运行时间的差异
那么就有可能会依次运行(程序时间不相差过大,而多核在相差不多时,会出现不同结果,而单核不会)
分配到同一个CPU里面,微观上是依次执行
之所以是有可能,是因为程序运行时间的不同,而导致某个程序比其他程序多运行几次,然后他才运行完,但时间是一样的
但对于多核来说:就会同时运行了,但还是会先占CPU的先运行,只不过不会再依次运行,由不同CPU操作
package com.lagou.task18;public class SubThreadRun extends Thread {@Overridepublic void run() {// 打印1 ~ 20之间的所有整数for (int i = 1; i <= 20; i++) {System.out.println("run方法中:i = " + i); // 1 2 ... 20}}
}
package com.lagou.task18;public class SubThreadRunTest {public static void main(String[] args) {// 1.声明Thread类型的引用指向子类类型的对象Thread t1 = new SubThreadRun();// 2.调用run方法测试,本质上就是相当于对普通成员方法的调用因//此执行流程就是run方法的代码执行完毕后才能继续向下执行//t1.run();// 用于启动线程,Java虚拟机会自动调用该线程类中的run方法// 相当于又启动了一个线程,加上执行main方法的线程是两个线程t1.start();// 打印1 ~ 20之间的所有整数for (int i = 1; i <= 20; i++) {System.out.println("-----------------main方法中:i = " + i); // 1 2 ... 20}}
}
package com.lagou.task18;public class SubRunnableRun implements Runnable {@Overridepublic void run() {// 打印1 ~ 20之间的所有整数for (int i = 1; i <= 20; i++) {System.out.println("run方法中:i = " + i); // 1 2 ... 20}}
}
package com.lagou.task18;public class SubRunnableRunTest {public static void main(String[] args) {// 1.创建自定义类型的对象,也就是实现Runnable接口类的对象SubRunnableRun srr = new SubRunnableRun();// 2.使用该对象作为实参构造Thread类型的对象// 由源码可知:经过构造方法的调用之后,Thread类中的成员变量target的数值为srr//Thread(Runnable target),根据参数指定的引用来构造对象,其中Runnable是个接口类型Thread t1 = new Thread(srr);// 3.使用Thread类型的对象调用start方法// 若使用Runnable引用构造了线程对象,调用该方法(run)时最终调用接口中的版本// 由run方法的源码可知:if (target != null) {//                         target.run();//                    }// 此时target的数值不为空这个条件成立,执行target.run()的代码,也就是srr.run()的代码t1.start();//srr.start();  Error// 打印1 ~ 20之间的所有整数for (int i = 1; i <= 20; i++) {System.out.println("-----------------main方法中:i = " + i); // 1 2 ... 20}}
}
方式的比较------------------------------
继承Thread类的方式代码简单,但是若该类继承Thread类后则无法继承其它类
而实现Runnable接口的方式代码复杂,但不影响该类继承其它类以及实现其它接口,因此以后的开发中推荐使用第二种方式
匿名内部类的方式------------------------------
使用匿名内部类的方式来创建和启动线程
package com.lagou.task18;public class ThreadNoNameTest {public static void main(String[] args) {//匿名内部类可以看成没有名字的内部类//是内部类的简化写法,它的本质是一个带具体实现的父类或者父接口的匿名的子类对象//即相当于指向子类// 匿名内部类的语法格式:父类/接口类型 引用变量名 = new 父类/接口类型() { 方法的重写 };// 1.使用继承加匿名内部类的方式创建并启动线程/*Thread t1 = new Thread() {@Overridepublic void run() {System.out.println("张三说:在吗?");}};t1.start();*/new Thread() {@Overridepublic void run() {System.out.println("张三说:在吗?");}}.start();// 2.使用实现接口加匿名内部类的方式创建并启动线程Runnable ra = new Runnable() {@Overridepublic void run() {System.out.println("李四说:不在。");}};Thread t2 = new Thread(ra);t2.start();/*new Thread(new Runnable() {@Overridepublic void run() {System.out.println("李四说:不在。");}}).start();*/// Java8开始支持lambda表达式: (形参列表)->{方法体;}/*Runnable ra = ()-> System.out.println("李四说:不在。");new Thread(ra).start();*/new Thread(()-> System.out.println("李四说:不在。")).start();}
}
线程的生命周期------------------------------

在这里插入图片描述

新建状态 - 使用new关键字创建之后进入的状态,此时线程并没有开始执行
就绪状态 - 调用start方法后进入的状态,此时线程还是没有开始执行
运行状态 - 使用线程调度器调用该线程后进入的状态,此时线程开始执行
当线程的时间片执行完毕后任务没有完成时回到就绪状态
消亡状态 - 当线程的任务执行完成后进入的状态,此时线程已经终止
阻塞状态 - 当线程执行的过程中发生了阻塞事件进入的状态,如:sleep方法。阻塞状态解除后进入就绪状态
即不管怎样,无论你是阻塞还是没有在运行完,最后都要重新在运行的地方开始,即就绪状态,但并没有清除你以前的行为
且创建时,只要是关于Thread类的,即调用了他的构造方法,基本上就是创建一个线程
注意:若直接输出直接量,且放在开启线程(start方法)后面,基本上就会先运行该输出的值,因为你在运行run方法时
需要输出的时间远远大于直接输出直接量,所以通常会发现你run在运行时,还没输出数值时,他就输出了(多核来说,单核也会)
实际上是他快
但对于单核,则有可能会依次运行(程序时间不相差过大,而多核在相差不多时,会出现不同结果,而单核不会)
之所以是有可能,是因为程序运行时间的不同,而导致某个程序比其他程序多运行几次,然后他才运行完,但时间是一样的
线程的编号和名称------------------------------
long getId(),获取调用对象所表示线程的编号
String getName(),获取调用对象所表示线程的名称
void setName(String name),设置/修改线程的名称为参数指定的数值
static Thread currentThread(),获取当前正在执行线程的引用
案例题目
自定义类继承Thread类并重写run方法,在run方法中先打印当前线程的编号和名称
然后将线程的名称修改为"zhangfei"后再次打印编号和名称。要求在main方法中也要打印主线程的编号和名称
package com.lagou.task18;public class ThreadIdNameTest extends Thread {public ThreadIdNameTest(String name) {super(name); // 表示调用父类的构造方法}@Overridepublic void run() {//long getId(),获取调用对象所表示线程的编号//String getName(),获取调用对象所表示线程的名称System.out.println("子线程的编号是:" + getId() + ",名称是:" + getName()); // 14  Thread-0 guanyu// 修改名称为"zhangfei"//void setName(String name),设置/修改线程的名称为参数指定的数值setName("zhangfei");System.out.println("修改后子线程的编号是:" + getId() + ",名称是:" + getName()); // 14  zhangfei}public static void main(String[] args) {ThreadIdNameTest tint = new ThreadIdNameTest("guanyu");tint.start();// 获取当前正在执行线程的引用,当前正在执行的线程是主线程,也就是获取主线程的引用//static Thread currentThread(),获取当前正在执行线程的引用Thread t1 = Thread.currentThread();//获取该线程,但并没有创建,即可以说没有调用构造方法System.out.println("主线程的编号是:" + t1.getId() + ", 名称是:" + t1.getName());System.out.println(System.currentTimeMillis());}
}
package com.lagou.task18;public class RunnableIdNameTest implements Runnable  {@Overridepublic void run() {// 获取当前正在执行线程的引用,也就是子线程的引用//必须是start方法调用才可算是真正的执行线程,否则就只有创建线程//通过t2执行Thread t1 = Thread.currentThread();System.out.println("子线程的编号是:" + t1.getId() + ", 名称是:" + t1.getName()); // 14 guanyu//Thread t2 = new Thread(rint, "guanyu") 源码里,最后还是RunnableIdNameTest来调用run方法//若RunnableIdNameTest继承Thread的话,用this.getName()就不是guanyu了//因为继承了Thread就是创建了线程,即不是guanyu,但是还没有去执行,则必须要start方法执行//否则Thread.currentThread()方法,获得的不是你的引用,而是其他线程到该代码的引用t1.setName("zhangfei");System.out.println("修改后子线程的编号是:" + t1.getId() + ", 名称是:" + t1.getName());// 14 zhangfei}public static void main(String[] args) {RunnableIdNameTest rint = new RunnableIdNameTest();//Thread t2 = new Thread(rint);Thread t2 = new Thread(rint, "guanyu");t2.start();// 获取当前正在执行线程的引用,当前正在执行的线程是主线程,也就是获取主线程的引用Thread t1 = Thread.currentThread();System.out.println("主线程的编号是:" + t1.getId() + ", 名称是:" + t1.getName());}
}
常用的方法------------------------------
static void yield(),当前线程让出处理器(离开Running状态),使当前线程进入Runnable状态等待,不占用CPU资源
直接等待了
static void sleep(times),使当前线程从 Running 放弃处理器进入Block状态, 休眠times毫秒
再返回到Runnable如果其他线程打断当前线程的Block(sleep), 就会发生InterruptedException
因为有阻塞状态,即休眠的毫秒数,所以占用CPU资源,因为休眠不结束的话,就会一直运行
但会有他里面代码的内存占用,但占用内存是必然的,其他程序也都需要内存,所以这里不用考虑内存
int getPriority(),获取线程的优先级
void setPriority(int newPriority),修改线程的优先级
优先级越高的线程不一定先执行,但该线程获取到时间片的机会会更多一些(时间更长)
除了程序也有运行时间,过大,则会后出结果
最主要的就是设置的优先级,不会真正的优先,就如概率一样,99%正确也会有1%的错误,可以将优先级看成概率
优先级默认为5 
void join(),等待该线程终止,即主线程会等待,但是主线程里的其他子线程有启动的,那么那个子线程会继续运行
而不会等待
void join(long millis),等待参数指定的毫秒数
boolean isDaemon(),用于判断是否为守护线程
void setDaemon(boolean on),用于设置线程为守护线程
其他程序运行时间,也会导致sleep的操作
如我要一个数值输出8次,每停顿一秒输出一次,而主线程停顿8秒,打印主线程等待结束,通常情况下,都会输出8次
但是我在第一次的时候,就写一个很大的循环,可以循环8秒的循环,那么就会出现,只输出一次的结果
即可以对于一般程序来说,基本不会影响,因为他所占的时间对于一秒来说可以忽略不计
如主线程停5秒,子线程停1秒,主线程的5秒会导致子线程的判断条件为false,如下
//子线程
//run方法里:
white(a == true){
System.out.println(1);
Thread.sleep(1000); // 停一秒
}
//主线程
Thread.sleep(5000);
a = false;
//可以知道如下,我将--------八个-表示一秒钟,那么就会出现以下情况
1-------- 1-------- 1-------- 1-------- 1-------- a == ture(不成立)
-------- -------- -------- -------- -------- a =false
//可以发现子线程里的--------在等待时,主线程就结束了,而实际上,输出的1的时间远远小于一个"-",即忽略不计
//但是若这个1所占的"-"够长,
//----------------------------------------------------------------
//若将子线程的输出位置改变,如下
//子线程
//run方法里:
white(a == true){
Thread.sleep(1000); // 停一秒
System.out.println(1);
}
//主线程
Thread.sleep(5000);
a = false;
//可以知道如下,我将--------八个-表示一秒钟,那么就会出现以下情况
--------1 --------1 --------1 --------1 --------1 a == ture(不成立)
-------- -------- -------- -------- -------- a =false
//可以知道当a的值改变时,由于子线程还在等待,即还没运行完,所以输出1后,在退出循环
//且实际上1所占的时间远远小于一个"-",即也是忽略不计
//但是若有一个大的循环,则会只输出第一个1,然后判断a为false,结束循环
//所以得出一个结论,但等待的时间够长,且程序够少,那么就可以直接看成等待秒数的计算
//如主线程等待5秒,子线程等待1秒,输出的值都是5/1次
//但也有一个解释,就是,只要加起来的程序所占时间(即5秒钟内的程序运行时间,其中有相等的1秒钟)
//大于子线程的等待秒数
//则不可以直接用5/1来算次数(根据大多少具体判断)
//就如大的循环一样
//而对于加起来程序所占时间(即5秒钟内的程序运行时间,其中有相等的1秒钟),与等待秒数是否相等问题:
//若正好占一秒,而你等待一秒,则形成无缝连接
//对于第一种情况,就是子线程等待时间会与主线程等待时间相交,那么就会少输出一个1
//除非主线程在子线程判断结束后,等待时间才到,那么就不会
//对于第二种情况,就是输出的程序时间会与主线程等待时间相交,那么就会少输出一个1
//除非主线程在子线程判断结束后,等待时间才到,那么就不会
//即一个将等待时间交接,一个将程序时间交接
//等待时间交接可以数据先处理,然后再直接让出CPU资源,但最后让出CPU资源
//程序时间交接会将数据后处理,最后操作,可能会对数据重新赋值,但会先让出CPU资源
//这些代码认为是单核,但运行时可能是双核,你的电脑是双核
package com.lagou.task18;public class ThreadPriorityTest extends Thread {@Overridepublic void run() {//int getPriority(),获取线程的优先级//System.out.println("子线程的优先级是:" + getPriority()); // 5  10  优先级越高的线程不一定先执行。for (int i = 0; i < 20; i++) {System.out.println("子线程中:i = " + i);}}public static void main(String[] args) {ThreadPriorityTest tpt = new ThreadPriorityTest();// 设置子线程的优先级//void setPriority(int newPriority),修改线程的优先级//优先级越高的线程不一定先执行,但该线程获取到时间片的机会会更多一些tpt.setPriority(Thread.MAX_PRIORITY);tpt.start();Thread t1 = Thread.currentThread();//System.out.println("主线程的优先级是:" + t1.getPriority()); // 5 普通的优先级for (int i = 0; i < 20; i++) {System.out.println("--主线程中:i = " + i);}}}
package com.lagou.task18;public class ThreadJoinTest extends Thread {@Overridepublic void run() {// 模拟倒数10个数的效果System.out.println("倒计时开始...");for (int i = 10; i > 0; i--) {System.out.println(i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("新年快乐!");}public static void main(String[] args) {ThreadJoinTest tjt = new ThreadJoinTest();tjt.start();// 主线程开始等待System.out.println("主线程开始等待...");try {// 表示当前正在执行的线程对象等待调用线程对象,也就是主线程等待子线程终止//void join(),等待该线程终止//tjt.join();//oid join(long millis),等待参数指定的毫秒数tjt.join(5000); // 最多等待5秒} catch (InterruptedException e) {e.printStackTrace();}//System.out.println("终于等到你,还好没放弃!");System.out.println("可惜不是你,陪我到最后!");}
}
package com.lagou.task18;public class ThreadDaemonTest extends Thread {@Overridepublic void run() {//boolean isDaemon(),用于判断是否为守护线程//System.out.println(isDaemon()? "该线程是守护线程": "该线程不是守护线程"); // 默认不是守护线程// 当子线程不是守护线程时,虽然主线程先结束了,但是子线程依然会继续执行,直到打印完毕所有数据为止// 当子线程是守护线程时,当主线程结束后,则子线程随之结束for (int i = 0; i < 50; i++) {System.out.println("子线程中:i = " + i);}}public static void main(String[] args) {ThreadDaemonTest tdt = new ThreadDaemonTest();// 必须在线程启动之前设置子线程为守护线程//void setDaemon(boolean on),用于设置线程为守护线程tdt.setDaemon(true);tdt.start();for (int i = 0; i < 20; i++) {System.out.println("-------主线程中:i = " + i);}}
}
主线程:是产生其他子线程的线程,通常它必须最后完成执行比如执行各种关闭动作
即可以说守护线程是给主线程操作子线程的一种设置
案例题目------------------------------
编程创建两个线程,线程一负责打印1 ~ 100之间的所有奇数,其中线程二负责打印1 ~ 100之间的所有偶数
在main方法启动上述两个线程同时执行,主线程等待两个线程终止
package com.lagou.task18;public class SubThread1 extends Thread {@Overridepublic void run() {// 打印1 ~ 100之间的所有奇数for (int i = 1; i <= 100; i += 2) {System.out.println("子线程一中: i = " + i);}}
}
package com.lagou.task18;public class SubThread2 extends Thread {@Overridepublic void run() {// 打印1 ~ 100之间的所有偶数for (int i = 2; i <= 100; i += 2) {System.out.println("------子线程二中: i = " + i);}}
}
package com.lagou.task18;public class SubThreadTest {public static void main(String[] args) {SubThread1 st1 = new SubThread1();SubThread2 st2 = new SubThread2();st1.start();st2.start();System.out.println("主线程开始等待...");try {st1.join();st2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("主线程等待结束!");}
}
package com.lagou.task18;public class SubRunnable1 implements Runnable {@Overridepublic void run() {// 打印1 ~ 100之间的所有奇数for (int i = 1; i <= 100; i += 2) {System.out.println("子线程一中: i = " + i);}}
}
package com.lagou.task18;public class SubRunnable2 implements Runnable {@Overridepublic void run() {// 打印1 ~ 100之间的所有偶数for (int i = 2; i <= 100; i += 2) {System.out.println("------子线程二中: i = " + i);}}
}
package com.lagou.task18;public class SubRunnableTest {public static void main(String[] args) {SubRunnable1 sr1 = new SubRunnable1();SubRunnable2 sr2 = new SubRunnable2();Thread t1 = new Thread(sr1);Thread t2 = new Thread(sr2);t1.start();t2.start();System.out.println("主线程开始等待...");try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("主线程等待结束!");}
}
线程同步机制------------------------------
当多个线程同时访问同一种共享资源时,可能会造成数据的覆盖等不一致性问题
即当多个线程需要访问同一个资源时
它们需要以某种顺序来确保该资源在某时刻只能被一个线程使用,否则,程序的运行结果将会是不可预料的
此时就需要对线程之间进行通信和协调,该机制就叫做线程的同步机制
多个线程并发读写同一个临界资源时会发生线程并发安全问题
异步操作:多线程并发(一起进发)的操作,各自独立运行,可以理解位不同的步骤,即不用排队
同步操作:多线程串行(跟着进行)的操作,先后执行的顺序,可以理解为同样的步骤,即排队
对于同步机制,主要针对于多核,因为单核实际上还是依次运行的
对于多个start方法,先调用的一般先执行,只不过现在的电脑处理很快的,你几乎都感觉不到在并发
由于速度快,但CPU先运算的还是会先执行
解决方案------------------------------
由程序结果可知:当两个线程同时对同一个账户进行取款时,导致最终的账户余额不合理
引发原因:线程一执行取款时还没来得及将取款后的余额写入后台,线程二就已经开始取款
解决方案:让线程一执行完毕取款操作后,再让线程二执行即可,将线程的并发操作改为串行操作
经验分享:在以后的开发尽量减少串行操作的范围,从而提高效率
实现方式------------------------------
在Java语言中使用synchronized关键字来实现同步/对象锁机制从而保证线程执行的原子性
即要么我不执行,要么等我执行完,且执行过程不可打断
具体方式如下:
使用同步代码块的方式实现部分代码的锁定,格式如下:
synchronized(类类型的引用) { //这里的"类类型的引用"的专业术语:同步监视器
编写所有需要锁定的代码
} 
使用同步方法的方式实现所有代码的锁定,直接使用synchronized关键字来修饰整个方法即可该方式等价于: 
synchronized(this) { 
整个方法体的代码
} 
即直接用synchronized对方法修饰时,就说明这个方法是谁调用谁来当同步监听器,即等价于this
换言之,就是这个方法被锁了,只对于同一引用,因为其他引用调用的this是不同的
即这就是为什么需要一个同步监听器的原因
由于synchronized(类类型的引用)"类类型的引用"是多变的,即不同的引用也会参与同步,如静态的引用
但基本上都会在类里面新建该引用,防止同一引用的调用,会出现不同步,如synchronized(new 类名())
但是正是因为有可能会没有新建,则出现了直接修饰整个方法,即相当于this,这样就保证了是同一引用的会同步
而对于静态来说,由于没有this,但是静态的基本上都是一个类的,即使用了该静态,就一定是使用了同一个类的方法
那么这时使用synchronized修饰方法时等价于使用了synchronized(类名.class),这个类名无论怎么变化
他们在调用这个静态方法时都会同步(串行)
最后可以理解为无论是什么方法或者代码,在有synchronized锁面前,只要()里面的同步监听器是相同的
相同监听器所修饰的锁都必须等待前一个的锁打开(运行完,而Lock必须解锁)
否则,无论你是不同的方法,还是不同的类的方法,都必须等一等
回忆:继承类时相同方法都有重写,一个类的方法是在方法区里,且独一份,并且类里面最好不要创建本类的对象
因为有嵌套,如main方法里创建对象时,运行时有问题,或者静态对象调用时,也会出现问题
当然静态变量在本类方法里,可以直接写出,或者加上类名写出都可以
package com.lagou.task18;public class AccountThreadTest extends Thread {private int balance; // 用于描述账户的余额private static Demo dm = new Demo(); // 隶属于类层级,所有对象共享同一个public AccountThreadTest() {}public AccountThreadTest(int balance) {this.balance = balance;}public int getBalance() {return balance;}public void setBalance(int balance) {this.balance = balance;}@Overridepublic /*static*/ /*synchronized*/ void run() {/*System.out.println("线程" + Thread.currentThread().getName() + "已启动...");//synchronized (dm) { // ok//synchronized (new Demo()) { // 锁不住  要求必须是同一个对象// 1.模拟从后台查询账户余额的过程int temp = getBalance(); // temp = 1000  temp = 1000// 2.模拟取款200元的过程if (temp >= 200) {System.out.println("正在出钞,请稍后...");temp -= 200;  // temp = 800   temp = 800try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("请取走您的钞票!");} else {System.out.println("余额不足,请核对您的账户余额!");}// 3.模拟将最新的账户余额写入到后台setBalance(temp); // balance = 800  balance = 800//}*/test();}public /*synchronized*/ static void test() {synchronized (AccountThreadTest.class) { // 该类型对应的Class对象,由于类型是固定的,因此Class对象也是唯一的,因此可以实现同步System.out.println("线程" + Thread.currentThread().getName() + "已启动...");//synchronized (dm) { // ok//synchronized (new Demo()) { // 锁不住  要求必须是同一个对象//因为这样每来一个都会创建对象,相当于每个人都拿一把锁// 1.模拟从后台查询账户余额的过程int temp = 1000; //getBalance(); // temp = 1000  temp = 1000// 2.模拟取款200元的过程if (temp >= 200) {System.out.println("正在出钞,请稍后...");temp -= 200;  // temp = 800   temp = 800try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("请取走您的钞票!");} else {System.out.println("余额不足,请核对您的账户余额!");}// 3.模拟将最新的账户余额写入到后台//setBalance(temp); // balance = 800  balance = 800}}public static void main(String[] args) {AccountThreadTest att1 = new AccountThreadTest(1000);att1.start();AccountThreadTest att2 = new AccountThreadTest(1000);att2.start();System.out.println("主线程开始等待...");try {att1.join();//t2.start(); // 也就是等待线程一取款操作结束后再启动线程二//不符合多项程的意义,因为没有同时启动了att2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("最终的账户余额为:" + att1.getBalance()); // 800}}
package com.lagou.task18;import java.util.concurrent.locks.ReentrantLock;public class AccountRunnableTest implements Runnable {private int balance; // 用于描述账户的余额private Demo dm = new Demo();private ReentrantLock lock = new ReentrantLock();  // 准备了一把锁public AccountRunnableTest() {}public AccountRunnableTest(int balance) {this.balance = balance;}public int getBalance() {return balance;}public void setBalance(int balance) {this.balance = balance;}@Overridepublic /*synchronized*/ void run() {// 开始加锁lock.lock();// 由源码可知:最终是account对象来调用run方法,因此当前正在调用的对象就是account//也就是说this就是account//synchronized (this) { // okSystem.out.println("线程" + Thread.currentThread().getName() + "已启动...");//synchronized (dm) { // ok//synchronized (new Demo()) { // 锁不住  要求必须是同一个对象// 1.模拟从后台查询账户余额的过程int temp = getBalance(); // temp = 1000  temp = 1000// 2.模拟取款200元的过程if (temp >= 200) {System.out.println("正在出钞,请稍后...");temp -= 200;  // temp = 800   temp = 800try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("请取走您的钞票!");} else {System.out.println("余额不足,请核对您的账户余额!");}// 3.模拟将最新的账户余额写入到后台setBalance(temp); // balance = 800  balance = 800//}lock.unlock(); // 实现解锁}public static void main(String[] args) {AccountRunnableTest account = new AccountRunnableTest(1000);//AccountRunnableTest account2 = new AccountRunnableTest(1000);Thread t1 = new Thread(account);Thread t2 = new Thread(account);//Thread t2 = new Thread(account2);t1.start();t2.start();System.out.println("主线程开始等待...");try {t1.join();//t2.start(); // 也就是等待线程一取款操作结束后再启动线程二t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("最终的账户余额为:" + account.getBalance()); // 600  800}
}class Demo{}
注意:一个类里面可以写多个类(不是内部类),而文件名通常必须要与有public修饰的类名一样,否则编译时会报错
之所以是通常,是因为若都没有public那么就会编译通过,会出现多个类的字节码文件
即使用java xxx(那些字节码文件的名字,不修改的话即类名),会使用相应的类的main方法
但是若直接用java xxx.java则会执行,他跳过了该错误
而他没有直接的看到出现的字节码文件,那么在运行时,会根据类的执行顺序而运行main方法
且不管你是否为public修饰的类,即只运行放在前面的类的main方法
静态方法的锁定------------------------------
当我们对一个静态方法加锁,如:
public synchronized static void xxx(){.}
那么该方法锁的对象是类对象
每个类都有唯一的一个类对象
获取类对象的方式:类名.class
静态方法与非静态方法同时使用了synchronized后它们之间是非互斥关系的
原因在于:静态方法锁的是类对象而非静态方法锁的是当前方法所属对象
静态变量是有引用的连接的,即也可以用引用来调用
所有的修饰都基本上写在返回值前面,而返回值前面的修饰基本上是可以改变位置的
注意事项------------------------------
使用synchronized保证线程同步应当注意:多个需要同步的线程在访问同步块时,看到的应该是同一个锁对象引用
即当使用类实现接口时,由于Thread都使用该类,即调用该类的run方法,最后创建多个Thread时
调用的都是同一个对象的run方法
即synchronized里的引用若使用本类创建的引用时,使用的是同一个
若使用类继承Thread的话,那么调用的是不同的对象的run方法,那么synchronized里的引用若使用本类创建的引用时
使用的不是同一个
在使用同步块时应当尽量减少同步范围以提高并发的执行效率
线程安全类和不安全类------------------------------
StringBuffer类是线程安全的类,但StringBuilder类不是线程安全的类
Vector类和 Hashtable类是线程安全的类,但ArrayList类和HashMap类不是线程安全的类
Collections.synchronizedList() 和 Collections.synchronizedMap()等方法实现安全
死锁的概念------------------------------
线程一执行的代码:public void run(){synchronized(a){//持有对象锁a,等待对象锁b synchronized(b){ 编写锁定的代码; } }
}
线程二执行的代码:public void run(){ synchronized(b){ //持有对象锁b,等待对象锁a synchronized(a){ 编写锁定的代码; } }
}
注意:在以后的开发中尽量减少同步的资源,减少同步代码块的嵌套结构的使用
使用Lock(锁)实现线程同步------------------------------
从Java5开始提供了更强大的线程同步机制—使用显式定义的同步锁对象来实现
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具
该接口的主要实现类是ReentrantLock类,该类拥有与synchronized相同的并发性
在以后的线程安全控制中,经常使用ReentrantLock类显式加锁和释放锁
常用的方法------------------------------
ReentrantLock(),使用无参方式构造对象
void lock(),获取锁
void unlock(),释放锁
与synchronized方式的比较------------------------------
Lock是显式锁,需要手动实现开启和关闭操作
而synchronized是隐式锁,执行锁定代码后自动释放
Lock只有同步代码块方式的锁,即锁与解锁之间的代码
与synchronized不一样,synchronized是运行完后,才可让其他代码进入,而Lock必须要解锁后才可进入
而synchronized有同步代码块方式和同步方法两种锁
使用Lock锁方式时,Java虚拟机将花费较少的时间来调度线程,因此性能更好
由于Lock可以实现手动操作,即可以有更多的操作空间
Object类常用的方法------------------------------
void wait(),用于使得线程进入等待状态(阻塞状态,类似于sleep方法),直到其它线程调用notify()notifyAll()方法
且占用CPU资源,并会解除对象锁,而sleep不会
wait()方法会强迫线程先进行释放锁操作,即必须要在锁里面进行,否则报错
即可以知道,他可以随时释放锁,与unlock方法类似(只是类似,因为他释放的是synchronized)
但他也可以让该线程进行等待,且必须唤醒或者等待他时间结束,这里是必要的
也可以看成sleep与unlock的结合体,即可以有更多的操作空间
如互相输出,即线程的来回操作
也可以让两个线程同时操作,如当唤醒后或者等待时间结束后,一个操作wait方法前面的,另一个操作wait方法后面的
但必须要在synchronized锁里面,Lock锁里面也不行,因为底层代码的缘故,wait()方法会强迫线程先进行释放锁操作
即必须要在synchronized锁里面进行,否则报错,因为wait就是用来操作锁的,即在锁里面调用wait方法时
可以隐式的看成监听器.wait(),所以当syschronized的参数为this时,可以直接写wait(),否则需要加参数名.wait()
除非他有隐式的this,如引用调用时,就有this,要不然就报错
即虽然是服务与锁,但实际上服务于监听器,即锁可以称为监听器的锁
那么就会有,当调用方法时,若引用相同(设为引用为c),且该方法里的synchronized的监听器为this
那么该锁里面写上wait或者notify,也就是c.wait或者c.notify,后者会将拥有同样监听器的锁唤醒,而前者不会
因为wait方法作用于本身线程,与其他线程无关,而notify方法可以让其他线程调用,来唤醒同样监听器的线程
其中notify是c.notify可以让该类型锁里面的wait唤醒,而wait虽然也是c.wait
但实际上他并没有让其他该类型锁的线程等待,前者可以通过找到wait方法,而进行唤醒
后者却找不到让其他线程等待的东西,即只可以使得当前线程等待
这里"监听器"实际叫做"同步监听器"
当你认为输出结果与预期不对时,有可能是输出的那个语句没看到了,因为太慢了,即会很少
void wait(long timeout),用于进入等待状态,直到其它线程调用方法或参数指定的毫秒数已经过去为止
即当等待时间到了时,就会自动解除等待,当然,也可以在他等待时直接唤醒
void notify(),用于唤醒等待的单个线程,与wait方法一样,有this可以不加,有参数,加参数
void notifyAll(),用于唤醒等待的所有线程,与wait方法一样,有this可以不加,有参数,加参数
解决wait方法和sleep方法是否会释放锁的问题
//当两个线程进入时
public synchronized void run(){notify(); //放前面,是防止又调用wait方法System.out.println(1);try {wait(); //Thread.sleep(1000); //wait(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(2);}
//输出112
//当wait()变为Thread.sleep(1000)时
//输出1212
//即可以知道sleep没有释放锁
//而wait释放了锁
//最后当wait()变成wait(1000)时
//输出1122,最后一个2基本等待1秒出现,除非有其他情况,如卡了等等
package com.lagou.task18;public class ConsumerThread extends Thread {// 声明一个仓库类型的引用作为成员变量,是为了能调用调用仓库类中的生产方法   合成复用原则private StoreHouse storeHouse;// 为了确保两个线程共用同一个仓库public ConsumerThread(StoreHouse storeHouse) {this.storeHouse = storeHouse;}public ConsumerThread() {}@Overridepublic void run() {while (true) {storeHouse.consumerProduct();try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}
}
package com.lagou.task18;/*** 编程实现生产者线程,不断地生产产品*/
public class ProduceThread extends Thread {// 声明一个仓库类型的引用作为成员变量,是为了能调用调用仓库类中的生产方法   合成复用原则private StoreHouse storeHouse;// 为了确保两个线程共用同一个仓库public ProduceThread(StoreHouse storeHouse) {this.storeHouse = storeHouse;}@Overridepublic void run() {while (true) {storeHouse.produceProduct();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
package com.lagou.task18;/*** 编程实现仓库类*/
public class StoreHouse {private int cnt = 0; // 用于记录产品的数量public synchronized void produceProduct() {notify();if (cnt < 10) {System.out.println("线程" + Thread.currentThread().getName() + "正在生产第" + (cnt+1) + "个产品...");cnt++;} else {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}}public synchronized void consumerProduct() {notify();if (cnt > 0) {System.out.println("线程" + Thread.currentThread().getName() + "消费第" + cnt + "个产品");cnt--;} else {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}}
}
package com.lagou.task18;public class StoreHouseTest {public static void main(String[] args) {// 创建仓库类的对象StoreHouse storeHouse = new StoreHouse();// 创建线程类对象并启动ProduceThread t1 = new ProduceThread(storeHouse);ConsumerThread t2 = new ConsumerThread(storeHouse);t1.start();t2.start();}
}
线程池------------------------------
从Java5开始新增加创建线程的第三种方式为实现java.util.concurrent.Callable接口
常用的方法------------------------------
V call(),计算结果并返回
FutureTask类------------------------------
public class FutureTask<V>
extends Object
implements RunnableFuture<V> //RunnableFuture实现了Runnable接口
java.util.concurrent.FutureTask类用于描述可取消的异步计算
该类提供了Future接口的基本实现,包括启动和取消计算
查询计算是否完成以及检索计算结果的方法,也可以用于获取方法调用后的返回结果
常用的方法------------------------------
FutureTask(Callable callable),根据参数指定的引用来创建一个未来任务
V get(),获取call方法计算的结果,并等待线程执行完毕
package com.lagou.task18;import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class ThreadCallableTest implements Callable {@Override//V call(),计算结果并返回public Object call() throws Exception {// 计算1 ~ 10000之间的累加和并打印返回int sum = 0;for (int i = 1; i <= 10000; i++) {sum +=i;}System.out.println("计算的累加和是:" + sum); // 50005000return sum;}public static void main(String[] args) {ThreadCallableTest tct = new ThreadCallableTest();//FutureTask(Callable callable),根据参数指定的引用来创建一个未来任务FutureTask ft = new FutureTask(tct);Thread t1 = new Thread(ft);t1.start(); //会执行call方法,与执行run方法类型Object obj = null;try {//V get(),获取call方法计算的结果,并等待线程执行完毕obj = ft.get();} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}System.out.println("线程处理方法的返回值是:" + obj); // 50005000}
}
线程池的由来------------------------------
在服务器编程模型的原理,每一个客户端连接用一个单独的线程为之服务,当与客户端的会话结束时,线程也就结束了
即每来一个客户端连接,服务器端就要创建一个新线程
如果访问服务器的客户端很多,那么服务器要不断地创建和销毁线程,这将严重影响服务器的性能
概念和原理------------------------------
线程池的概念:首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后
就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中
在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程
线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程
任务是提交给整个线程池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务
相关类和方法------------------------------
public class Executors
extends Object
从Java5开始提供了线程池的相关类和接口:java.util.concurrent.Executors类和java.util.concurrent.ExecutorService接口
其中Executors是个工具类和线程池的工厂类,可以创建并返回不同类型的线程池
常用的方法------------------------------
static ExecutorService newCachedThreadPool(),创建一个可根据需要创建新线程的线程池
static ExecutorService newFixedThreadPool(int nThreads),创建一个可重用固定线程数的线程池
static ExecutorService newSingleThreadExecutor(),创建一个只有一个线程的线程池
其中ExecutorService接口是真正的线程池接口,主要实现类是ThreadPoolExecutor
常用的方法------------------------------
void execute(Runnable command),执行任务和命令,通常用于执行
Runnable Future<V> submit(Callable<T> task),执行任务和命令,通常用于执行
Callablevoid shutdown(),启动有序关闭
package com.lagou.task18;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadPoolTest {public static void main(String[] args) {// 1.创建一个线程池ExecutorService executorService = Executors.newFixedThreadPool(10);// 2.向线程池中布置任务executorService.submit(new ThreadCallableTest());// 3.关闭线程池executorService.shutdown();}
}# 在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程###### 线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程###### 任务是提交给整个线程池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务###### 相关类和方法------------------------------```java
public class Executors
extends Object
从Java5开始提供了线程池的相关类和接口:java.util.concurrent.Executors类和java.util.concurrent.ExecutorService接口
其中Executors是个工具类和线程池的工厂类,可以创建并返回不同类型的线程池
常用的方法------------------------------
static ExecutorService newCachedThreadPool(),创建一个可根据需要创建新线程的线程池
static ExecutorService newFixedThreadPool(int nThreads),创建一个可重用固定线程数的线程池
static ExecutorService newSingleThreadExecutor(),创建一个只有一个线程的线程池
其中ExecutorService接口是真正的线程池接口,主要实现类是ThreadPoolExecutor
常用的方法------------------------------
void execute(Runnable command),执行任务和命令,通常用于执行
Runnable Future<V> submit(Callable<T> task),执行任务和命令,通常用于执行
Callablevoid shutdown(),启动有序关闭
package com.lagou.task18;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadPoolTest {public static void main(String[] args) {// 1.创建一个线程池ExecutorService executorService = Executors.newFixedThreadPool(10);// 2.向线程池中布置任务executorService.submit(new ThreadCallableTest());// 3.关闭线程池executorService.shutdown();}
}
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 学生信息管理系统

    程序设计要求&#xff1a; 1&#xff0c;功能要求&#xff1a;增加&#xff0c;删除&#xff0c;查找&#xff0c;修改&#xff0c;排序。 2&#xff0c;要求有管理员和普通用户两个管理系统&#xff0c;且二者都要有登录界面。 3&#xff0c;对于登录界面有要有用户名和密码…...

    2024/4/15 7:18:52
  2. spring cloud

    理论、概念 微服务架构是一种架构模式&#xff0c;它提倡将单一的应用程序划分成一组小的服务&#xff0c;服务之间互相协调、互相配合&#xff0c;为用户提供最终价值。每个服务运行在其独立的进程中&#xff0c;服务与服务间采用轻量级的通信机制互相协作&#xff08;通常是…...

    2024/4/15 16:32:21
  3. 达朴汇联CEO张焱:从隐私计算出发,共建Web3.0

    “当你某一天突然发现这个世界怎么变成这样了&#xff1f;这个时候你要明白&#xff0c;背后有人在默默的努力去改变这个世界。我们正处在这个时代。欢迎大家在2022年加入这个不卷的赛道&#xff0c;让我们从隐私计算出发&#xff0c;共建Web3.0。” 2021年&#xff0c;隐私计…...

    2024/5/2 8:14:45
  4. Dimensional Space(次元空间)卡牌链游——AWAKEN

    Dimensional space 是一家于 2020 年初成立的专注于游戏技术的游戏工作室。团队由 30 名全职员工组成&#xff0c;目前旗下首款区块链游戏&#xff1a;AWAKEN已经开发完成。而这次AWAKEN只是Dimensional Space次元空间生态的其中一个&#xff0c;也就意味着上述游戏往后将全部采…...

    2024/4/24 11:24:06
  5. 全球通信基础设施面临 APT、机器人、DDoS;移动网络最弱

    根据 Arbor Networks 的第 8 次年度全球基础设施安全报告(WISR)&#xff0c;高级持续性威胁(APT) 是首要考虑因素&#xff1b;分布式拒绝服务 (DDoS) 攻击的规模已趋于稳定&#xff0c;但变得更加复杂&#xff1b;数据中心和云服务是特别有吸引力的目标&#xff1b;移动运营商在…...

    2024/4/20 9:04:22
  6. react之renderProps

    react之renderProps 类似于vue中的插槽 <A render {(name) > <C name {name}/>}/> {this.props.render(name)}代码片段&#xff1a; import React, { PureComponent } from react; import Child from ../Child; import ./index.cssexport default class Pa…...

    2024/4/14 4:25:44
  7. vue基础知识2

    4、剩余参数 大家可能遇到过这种问题&#xff0c;一个函数&#xff0c;传入参数的个数是不确定的&#xff0c;这就可以用ES6的剩余参数 function fn (name, ...params) {console.log(name)console.log(params) } fn (林三心, 1, 2) // 林三心 [ 1, 2 ] fn (林三心, 1, 2, 3, …...

    2024/4/14 4:26:19
  8. i.mx6ull移植mosquitto gateway-bridge

    官网获取openssl mosquitto源码 Eclipse Mosquitto 参考资料编译openssl库移植到开发板相应目录下 参考 imx6ull 移植mqtt_bbssuiji2的博客-CSDN博客_imx6 mqtt Linux环境下交叉编译mosquitto及其依赖_xxxx-CSDN博客_mqtt依赖openssl ​​​​​​[ARM入门] 迅为i.MX6开发…...

    2024/4/24 14:03:11
  9. RocketMQ4.9.1源码解析-(HA模块)主从Slave上报与消息处理

    接着上一篇 RocketMQv4.9.1源码分析-HA主从 Master读写处理 解答了Master相关的问题&#xff0c;这篇文章围绕Slave相关的一些问题继续看代码。 对于slave&#xff0c;我们有如下一些疑惑&#xff1a; slave如何获取master的路由信息slave如何向master报告offset的slave如何处…...

    2024/4/14 19:32:31
  10. vue中用$ref和$refs写出像js一样的this遍历样式

    这里我们用ref和refs 先看ref和refs的用法 单个在vue中访问DOM元素节点&#xff0c;使用ref 如&#xff1a; <p ref"one" click ”Mounted()“></p> 通过this.$refs.ref名 在合适的时机&#xff08;事件&#xff09;,获取该ref名对应的DOM元素 类…...

    2024/4/14 4:25:54
  11. 迭代法求平方根

    主要是由于浮点型数在计算机中存储的原因&#xff0c;浮点数比较相等不能用&#xff0c;而要这样。 #include<stdio.h> #include<math.h> int main() {double a,x;scanf("%lf",&a);x1;while(fabs(1.0/2*(xa/x)-x)>0.00001){x1.0/2*(xa/x);}x1.0/…...

    2024/4/14 4:26:09
  12. 使用Elasticsearch创建表的时候报错:【NoNodeAvailableException: None of the configured nodes are available】

    报错信息如下&#xff1a; org.elasticsearch.client.transport.NoNodeAvailableException: None of the configured nodes are available: [{#transport#-1}{MXkg7HdyTo6tkKSHVTcAUg}{10.0.0.43}{10.0.0.43:9200}] at org.elasticsearch.client.transport.TransportClientNode…...

    2024/4/20 8:17:26
  13. Mybatis获取插入数据后的自增主键的值

    mapper.xml中的useGeneratedKeys和keyProperty属性 利用mybatis提供的useGeneratedKeys和keyProperty属性可以在插入一条数据后&#xff0c;获取插入数据自增主键的值返回给前台。 keyProperty属性绑定java实体类中的属性&#xff0c;useGeneratedKeys设置为true即可。 LAST…...

    2024/4/14 4:25:44
  14. ModelAndView传递数据

    文件结构 实现代码 usercontroller.java RestController RequestMapping("/user") public class UserController {GetMapping("/login.do")public ModelAndView login(){System.out.println("显示登录界面");return new ModelAndView("log…...

    2024/4/14 4:26:04
  15. 浅尝JVM

    原文&#xff1a;https://nuyoah-xlh.github.io/2022/01/07/%E6%B5%85%E5%B0%9DJVM/ JVM的位置 JVM体系结构 类加载器 **作用&#xff1a;**加载class文件。new Student()时&#xff0c;引用放在栈中&#xff0c;而实例放在堆中。 过程&#xff08;双亲委派机制 &#xff09;…...

    2024/4/20 13:21:05
  16. P5705 【深基2.例7】数字反转(python3实现)方法一

    https://www.luogu.com.cn/problem/P5705 """P5705 【深基2.例7】数字反转&#xff08;python3实现&#xff09;方法一 https://www.luogu.com.cn/problem/P5705""" pfloat(input())qint(p*10)aq//1000bq//100%10cq//10%10dq%10print("%d&…...

    2024/4/14 4:25:44
  17. PostgreSQL 访问外部数据库之 postgres_fdw

    文章目录 postgres_fdw 简介 postgres_fdw 安装 创建外部服务器对象 创建用户映射 创建外部表 访问远程数据表 postgres_fdw 选项 连接选项 对象名称选项 成本评估选项 远程…...

    2024/4/15 8:25:38
  18. 正序输出一个数的各数位数字

    首先用一个函数算出数的位数&#xff0c;然后一边除一边输出。 #include<stdio.h> int f(int n) {int i0;while(n!0){n/10;i;}return i; } int main() { int n;int j;int power1;scanf("%d",&n);for(j1;j<f(n);j){power*10;}while(n!0){printf("…...

    2024/4/28 6:45:48
  19. db2 in 多目

    SELECT * FROM schema.table O WHERE ( O.column1, O.column2 ) in (values (11,12), (21,22), (31,32), (41,42), (51,52), (61,62), (71,72), (81,82), (91,92))...

    2024/4/19 10:31:28
  20. 命令行中md转pdf

    安装pandoc安装pdflatex&#xff1a;无法直接下载到该程序&#xff0c;可以下载miktex&#xff0c;其中会集成该程序更新miktex宏包&#xff1a;打开miktex console&#xff0c;检查更新并安装&#xff08;该步骤未验证是否必需&#xff09;开始转换&#xff1a;pandoc.exe REA…...

    2024/4/14 4:27:00

最新文章

  1. 过渡与动画

    单元素/组件过渡 Vue在插入、更新或者移除 DOM 时&#xff0c;提供多种不同方式的过渡效果&#xff08;一个淡入淡出的效果&#xff09; 在条件渲染&#xff08;使用v-if&#xff09;、条件展示&#xff08;使用v-show&#xff09;、动态组件、组件根节点等情形中&#xff0c;可…...

    2024/5/2 16:11:14
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. uniapp原生下拉刷新在手机上不起作用

    开启原生下拉刷新时&#xff0c;页面里使用了全屏高的scroll-view&#xff0c;向下拖动内容时&#xff0c;会优先触发scroll-view滚动而不是下拉刷新。 "enablePullDownRefresh": true, 这就可能会导致下拉刷新不起作用&#xff0c;这时候就需要做到取舍&#xff0c…...

    2024/4/30 5:54:31
  4. 北航2023年考研机试题

    【问题描述】一共6个手机基站&#xff0c;具有记录手机连接基站的能力&#xff0c;6个手机基站分别为ABCDEF&#xff0c;他们具有自己的覆盖范围且任何两个基站的覆盖范围不想交&#xff0c;基站保存的手机登陆日志包括手机号&#xff08;11位&#xff0c;用字符串保存&#xf…...

    2024/4/30 3:37:54
  5. Vue ts 如何给 props 中的变量指定特定类型,比如 Interface 类的

    Vue ts 如何给 props 中的变量指定特定类型&#xff0c;比如 Interface 类的 我有一个这样的变量值类型 一、在没用 ts 之前的 props 类型指定方式 我们都知道之前在没用 ts 之前的 props 变量值类型指定方式&#xff1a; 如下图&#xff0c;billFood 定义方式是这样的&…...

    2024/5/1 4:30:48
  6. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/1 17:30:59
  7. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/30 18:14:14
  8. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/29 2:29:43
  9. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/2 9:28:15
  10. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  11. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  12. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  13. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/30 9:43:09
  14. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  15. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/2 15:04:34
  16. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  17. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  18. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/29 20:46:55
  19. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/30 22:21:04
  20. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/1 4:32:01
  21. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  22. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  23. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/30 9:42:22
  24. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/2 9:07:46
  25. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/30 9:42:49
  26. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  27. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  28. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  29. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  30. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  31. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  32. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  33. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  34. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  35. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  36. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  37. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  38. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  39. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  40. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  41. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  42. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  43. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  44. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  45. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57