java多线程(常用方法、实现方式、线程安全问题、生命周期、线程池)
多线程相关的三组概念
程序和进程
-
程序(program):一个固定的运行逻辑和数据的集合,是一个静态的状态,一般存储在硬盘中。简单来说就是我们编写的代码
-
进程(process):一个正在运行的程序,一个程序的一次运行,是一个动态的概念般存储在内存中。 例如:command + option + esc,打开任务管理器可以看到所有进程
-
进程是正在运行的一个程序:
程序是静态的:QQ这个程序,如果不运行就是存在磁盘中,不占内存,所以是静态的。
进程是动态的:QQ这个程序,如果在运行就运行在内存中,占用内存,所以是动态的。
进程和线程
-
进程(process):一个正在运行的程序,一个程序的一次运行,是一个动态的概念存储在内存中。
-
比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间,比如我们再启动迅雷,就会又启动一个进程,操作系统会给迅雷分配新的内存空间
-
进程是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生,存在和消亡的过程
-
-
线程(thread):一条独立的执行路径。多线程,在执行某个程序的时候,该程序可以有多个子任务,每个线程都可以独立的完成其中一个任务。在各个子任务之间,没有什么依赖关系,可以单独执行。
-
线程是由进程创建的,是进程的一个实体
-
一个进程可以拥有多个线程
-
-
进程和线程的关系: 进程是用于分配资源的单位 一个进程中,可以有多条线程;但是一个进程中,至少有一条线程
比如当我们启动迅雷时,就开启一个进程,使用迅雷时我们可以同时下载5个电影资源,每一个下载任务就是一个线程
线程不会独立的分配资源,一个进程中的所有线程,共享同一个进程中的资源
并行和并发
-
并行(parallel):多个任务(进程、线程)同时运行。在某个确定的时刻,有多个任务在执行,多个cpu可以实现并行。 条件:有多个cpu,多核编程
-
并发(concurrent):多个任务(进程、线程)同时发起。不能同时执行的(只有一个cpu),只能是同时要求执行。就只能在某个时间片内,将多个任务都有过执行。一个cpu在不同的任务之间,来回切换,只不过每个任务耗费的时间比较短,cpu的切换速度比较快,所以可以让用户感觉就像多个任务在同时执行。并发就是同一时刻,多个任务交替执行,造成一种,“貌似同时”的错觉,简单来说,单核cpu实现的多任务就是并发
-
举例:
-
并发
2.并行
-
一个人可以同时做多件事儿,就是并发
多个人可以同时做多件事儿,就是并行
并发是多个任务之间互相抢占资源
并行是多个任务之间不互相抢占资源
并发的关键是你有处理多个任务的能力,不一定要同时。 并行的关键是你有同时处理多个任务的能力。
最关键的点就是:是否是『同时』。
但是我们的电脑也会存在并发与并行同时存在的情况
并行和并发的图示
并发的本质:不同任务来回切换:
并行和并发的比较:
问&答:
问:既然只有一个cpu,还需要在不同的任务之间来回切换,那么效率到底是提升了还是降低了?
答:
1、提升了:整个系统的效率提升了(cpu的使用率提升了),对于某个具体的任务而言,效率是降低了
2、并发技术,解决的是不同的设备之间速率不同的问题 cpu: 10^-9秒 大概是千分之一毫秒 执行一次. 0.000000001 内存:10^-6秒 0.000001 磁盘:10^-3秒 0.001 人:10^0秒.
3、cpu 的效率非常高,给磁盘一个指令,让磁盘寻找某个字节,cpu就赶紧切换到其它的任务,执行其它的
计算、存储的操作。这样子就可以让计算机系统中的所有设备都运转起来,提升了cpu的使用率。绝不能出现
cpu这种昂贵的设备等待磁盘这种廉价设备的情况。
多线程的实现方式
多线程实现方式一:继承方式
-
继承Thread
-
步骤:
1、定义一个类,继承Thread类
2、重写自定义类中的run方法,用于定义新线程要运行的内容
3、创建自定义类型的对象
4、调用线程启动的方法:start()方法
代码示例:
/*** 多线程实现方式一:实现方式*/
public class Simple01 {public static void main(String[] args) {// 创建自定义类型的对象MyThread mt = new MyThread();// 调用对象的start方法,启动线程mt.start(); // 用主线程 调用自定义的线程
// 让主线程(主方法本来就所在的线程)继续运行for (int i = 1; i <= 100; i++) {System.out.println(i);}// 现象是主线程内容和新线程内容交替执行,两条线程相互没有依赖关系}
}
// 定义一个类,继承Thread类
class MyThread extends Thread {//重写run方法,定义线程的运行内容@Overridepublic void run() {for (int i = 0; i <=100; i++) {System.err.println(i);}}
}
多线程实现方式二:实现方式
-
实现Runnable接口:
Runnable接口的实现类对象,表示一个具体的任务,将来创建一个线程对象之后,让线程执行这个任务
-
步骤: 1、定义一个任务类,实现Runnable接口 2、重写任务类中的run方法,用于定义任务的内容 3、创建任务类对象,表示任务 4、创建一个Thread类型的对象,用于执行任务类对象 5、调用线程对象的start方法,开启新线程
代码示例:
/*** 多线程实现方式二:实现方式*/
public class Simple02 {public static void main(String[] args) {// 创建任务类对象MyTask myTask = new MyTask();// 创建线程对象,绑定要执行的任务Thread thread = new Thread(myTask);// 启动新线程thread.start();
for (int i = 1; i <= 10000; i++) {System.out.println(i);}}
}
// 定义一个任务类
class MyTask implements Runnable{@Override// 重写run方法,定义任务的内容public void run() {for (int i = 1; i <= 10000; i++) {System.err.println(i);}}
}
两种方式的比较
-
代码复杂程度:
继承Thread方式简单
实现Runnable接口的方式比较复杂
-
设计:java中只支持单继承、不支持多继承
-
继承方式:
某个类继承了Thread类,那么就无法继承其他业务中需要的类型,就限制了我们的设计。所以扩展性较差。
-
实现方式:
某个类通过实现Runnable的方式完成了多线程的设计,仍然可以继承当前业务中的其它类型,扩展性较强。
-
-
灵活性:
-
继承方式:
将线程对象和任务内容绑定在了一起,耦合性较强、灵活性较差
-
实现方式:
将线程对象和任务对象分离,耦合性就降低,灵活性增强:同一个任务可以被多个线程对象执行,某个线程对象也可以执行其它的任务对象。并且将来还可以将任务类对象,提交到线程池中运行;任务类对象可以被不同线程运行,方便进行线程之间的数据交互。
-
使用匿名内部类创建线程对象
代码示例:
/*** 使用匿名内部类创建线程对象*/
public class Simple03 {public static void main(String[] args) {
//第一条线程打印1-100,使用继承的方式new Thread() {@Overridepublic void run() {for(int i = 1; i <= 100; i++) {System.out.println(i + "...extends");}}}.start();
//第二条线程打印1-100,使用实现的方式new Thread(new Runnable() {@Overridepublic void run() {for(int i = 1; i <= 100; i++) {System.out.println(i + "...implements" );}}}).start();}
}
Thread类中的常用方法
获取线程名称
-
getName():获取线程的名称
-
注意事项: 1、如果没有给线程命名,那么线程的默认名字就是Thread-x,x是序号,从0开始
代码示例:
/*** 获取线程名称*/
public class Simple04 {public static void main(String[] args) {Thread thread = new Thread() {@Override// 继承方式 可以加this关键字找到getNamepublic void run() {System.out.println("name:"+this.getName() );}};thread.setName("线程0");thread.start();Thread thread1 = new Thread() {@Overridepublic void run() {System.out.println("name1:"+this.getName() );}};thread1.setName("线程1");thread1.start();}
}
设置线程名称
-
setName(String name) 使用线程对象的引用,调用该方法给线程起名字
-
构造方法: Thread(String name):给线程通过构造方法起名字 Thread(Runnable target, String name):在给线程target对象赋值的同时,也给线程起名
-
注意事项: 线程设置名字,既可以在启动之前设置,也可以在启动之后设置
代码名称:
/*** 设置 线程名称*/
public class Simple05 {public static void main(String[] args) {Thread thread = new Thread("线程0") {@Override// 继承方式 可以加this关键字找到getNamepublic void run() {System.out.println("name:"+this.getName() );}};thread.start();
new Thread(new Task(),"线程1") {@Overridepublic void run() {System.out.println("name:"+this.getName() );}}.start();}
}
class Task implements Runnable{@Overridepublic void run() {}
}
获取当前线程对象
1、作用: 某段代码只要是在执行,就一定是在某个方法中执行的,只要在某个方法中,代码就一定是被某个线程执行的。所以任意一句代码,都有执行这句代码的线程对象。 可以在任意位置,获取当前正在执行这段代码的线程对象 2、方法: Thread Thread.currentThread(); 返回当前正在执行这段代码的线程对象的引用 哪条线程在执行这段代码,返回的就是哪条线程
代码示例:
/*** 获取当前线程对象*/
public class Simple06 {public static void main(String[] args) {Thread thread = new Thread("线程0") {@Override// 继承方式 可以加this关键字找到getNamepublic void run() {System.out.println("name:"+this.getName() );}};thread.start();
// System.out.println(thread.getName());
new Thread(new Task(),"线程1").start();}
}
class Task implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread());}
}
线程休眠
-
Thread.sleep(long time) 让当前线程休眠 time:休眠的时间,单位是毫秒
-
作用: 当某段代码在某个位置需要休息的时候,就使用线程的休眠 无论哪个线程在运行这段代码,碰到sleep方法都需要停下休息
-
注意事项: 有一个异常,中断异常:InterruptedException 在普通方法中,可以声明抛出 在重写的run方法中,必须只能处理,不能声明
代码示例:
/*** 线程休眠*/ public class Simple07 {public static void main(String[] args) throws InterruptedException { // InterruptedException 中断异常 // System.out.println(1); // Thread.sleep(1000); // System.out.println(2); MyCountDown myCountDown = new MyCountDown();Thread thread = new Thread(myCountDown);thread.start();} } class MyCountDown implements Runnable {@Overridepublic void run() {// 在run方法中,异常只能处理,不能抛出for (int i = 5; i >= 1; i--) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(i);}} }
守护线程
-
setDaemon(boolean flag):每条线程默认不是守护线程,只有设定了flag为true之后,该线程才变成守护线程
-
isDaemon():判断某条线程是否是守护线程
-
守护线程的特点: 守护线程就是用于守护其它线程可以正常运行的线程,在为其它的核心线程准备良好的运行环境。
如果非守护线程全部死亡,守护线程就没有存在的意义了,一段时间之后,虚拟机也一同结束。
虚拟机结束了,所有线程都结束了。
-
别名: 后台线程
好比前台的表演都是需要后台工作人员的准备支持,前台没有人表演了,那么后台工作人员就没有存在的意义了。
在jvm中,gc就是守护线程,作用就是当用户自定义的线程以及主线程执行完毕后,gc才停止工作,
不然的话,当主线程没有执行结束,gc就结束了,这样就会造成gc垃圾还没有清理完,主线程早就挂了。
(注意:主线程不是守护线程,gc才是守护线程)。
我们只需要了解什么是守护线程的概念,即可。
代码示例:
/*** 线程守护*/
public class Simple08 {public static void main(String[] args) {
Thread thread = new Thread(() -> {for (int i = 0; i < 100; i++) {System.out.println(i+"...守护线程");}});// 设置为守护线程thread.setDaemon(true);System.out.println(thread.isDaemon());// 启动之后,系统中就有两条线程:主线程和守护线程thread.start();
for (int i = 1; i <= 50; i++) {System.out.println(i + "...main");}//主线程运行完成之后,系统中就只剩下了守护线程//如果非守护线程全部死亡,守护线程就没有存在的意义了,//一段时间后,虚拟机也一同结束。//当主线程执行结束,jvm还没有立即结束,所以守护线程还会执行一段时间}
}
在实际开发中,守护线程(Daemon Thread)有以下主要作用:
-
后台任务执行:守护线程通常用于执行后台任务,这些任务不需要对用户可见,且不影响主线程的执行。比如,在 Web 服务器中,可以使用守护线程处理一些定时任务、日志记录等后台操作,而不会影响用户请求的处理。
-
资源回收:守护线程在 JVM 关闭时会自动终止,并且它们的执行不会阻止 JVM 退出。因此,守护线程通常用于执行资源回收工作,如垃圾回收器(Garbage Collector)线程就是守护线程,负责回收不再使用的对象。
-
定时任务:守护线程可以用于执行定时任务,如定时更新缓存、定时发送心跳包等。这样可以在后台自动执行这些任务,而不需要手动触发。
-
后台服务:某些后台服务可能需要一直运行,并随着应用的启动而启动。这些服务可以作为守护线程运行,以确保在应用退出时能够自动终止。
多线程中的线程安全问题
问题描述
某段代码在没有执行完成的时候,cpu就可能被其它线程抢走,结果导致当前代码中的一些数据发生错误
原因:没有保证某段代码的执行的完整性、原子性
希望:这段代码要么全都执行,要么全都没有执行
代码示例:
/*** 多线程中的线程安全问题* 模拟出票系统*/
public class Simple01 {public static void main(String[] args) {Ticket ticket = new Ticket(); // 堆中只有一个对象Thread thread1 = new Thread(ticket, "窗口1");Thread thread2 = new Thread(ticket, "窗口2");Thread thread3 = new Thread(ticket, "窗口3");thread1.start();thread2.start();thread3.start();}
}
class Ticket implements Runnable {
private int ticktNum = 0;//票号
// 线程售票public void run() {while (ticktNum < 10) { // 模拟售10张票// 1.模拟出票时间try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}// 2.打印进程号和票号String name = Thread.currentThread().getName();ticktNum = ++ticktNum;System.out.println("线程" + name + "售票:票号" + ticktNum);}// 出现的问题,某票号有可能被卖了多次// 不存在的票10号也有可能出现出现了// 出现不存在的票号原因:线程1,2在tickNum++ 时候被其他线程抢走了。倒置被累加多次
}
}
多个线程对全局变量只读,不写,那么一般这个变量是安全的,
多个线程同时执行写的操作,那么就需要考虑线程同步的问题,否则会影响线程安全问题
综上所述,线程安全问题根本原因:
-
多个线程在操作共享的数据;
-
多个线程对共享数据有写的操作;
问题解决:
要解决以上线程问题,只要在某个线程修改共享资源的时候,其它线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。
为了保证每个线程都能正常执行共享资源操作,Java引入了线程同步机制:
构造代码块
1) 同步代码块(synchronized)
2) 同步方法 (synchronized)
同步代码块
-
同步代码块: 使用一种格式,达到让某段代码执行的时候,cpu不要切换到影响当前代码的代码上去 这种格式,可以确保cpu在执行A线程的时候,不会切换到其它线程上去
-
使用格式 synchronized (锁对象) { 需要保证完整性、原子性的一段代码(需要同步的代码) }
-
使用同步代码块之后的效果: 当cpu想去执行同步代码块的时候,需要先获取到锁对象,获取之后就可以运行代码块中的内容;
当cpu正在执行当前代码块内容时,cpu可以切换到其它代码,但是不能切换到具有相同锁对象上;
当cpu执行完当前代码块中代码之后, 会释放锁对象,cpu就可以运行其它具有当前锁对象的同步代码块了;
代码示例:
/*** 同步代码块*/
public class Simple02 {public static void main(String[] args) {Printer p = new Printer();
Thread t1 = new Thread() {@Overridepublic void run() {while (true) {p.print1();}}};
Thread t2 = new Thread() {@Overridepublic void run() {while (true) {p.print2();}}};
t1.start();t2.start();}
}
class Printer {Object object = new Object();public void print1() {// 线程1在获取了obj锁之后,线程2就无法获取obj锁,// 就只能等待线程1运行完这个方法,两条线程才能再次共同争夺锁对象synchronized (object) {System.out.print("码");System.out.print("上");System.out.print("未");System.out.println("来");}
}
public void print2() {synchronized (object) {System.err.print("线");System.err.print("程");System.err.println("锁");}
}
}
// 不加锁的话,会出现:码上未来码 线程锁码 等数据不准确现象
// A线程执行print1完毕 释放obj对象,然后cpu被B线程抢去执行print2方法,
// B线程执行print2完毕,释放obj对象,然后cpu又被A或者B 线程抢去执行对应方法,
同步方法
1、同步代码块:在某段代码执行的时候,不希望cpu切换到其他影响当前线程的线程上去,就在这段代码上加上同步代码块,至于该方法中一些只读的成员变量就不需要放在同步代码块中了。
所以同步代码块中,只存放需要保证完整性、原子性的一段代码。
2、如果某个方法中,所有的代码都需要加上同步代码块,使用同步方法这种【简写格式】来替代同步代码块
3、同步方法的格式:
权限修饰符 [静态修饰符] synchronized 返回值类型 方法名称(参数列表) {
需要同步的方法体
}
4、同步方法的锁对象
如果是非静态的方法,同步方法的锁对象就是this,当前对象,哪个对象调用这个同步方法,这个同步方法使用的锁就是哪个对象
5、如果是静态的方法,同步方法的锁对象就是当前类的字节码对象,类名.class(在方法区的一个对象),哪个类在调用这个同步方法,这个同步方法使用的锁就是哪个类的字节码对象
代码示例:
/*** 同步方法*/
public class Simple03 {public static void main(String[] args) {Printer p = new Printer();Thread t1 = new Thread() {@Overridepublic void run() {while (true) {p.print1();}}};
Thread t2 = new Thread() {@Overridepublic void run() {while (true) {p.print2();}}};
t1.start();t2.start();}
}
class Printer {
public static synchronized void print1() {System.out.print("码");System.out.print("上");System.out.print("未");System.out.println("来");}
public static synchronized void print2() {System.err.print("线");System.err.print("程");System.err.println("锁");}
}
6、特别注意,如果是静态方法,在静态方法内部使用同步代码块,同步代码块锁对象也是当前类的字节码对象
/*** 静态方法中使用同步代码块*/
public class Simple04 {public static void main(String[] args) {Printer p = new Printer();Thread t1 = new Thread() {@Overridepublic void run() {while (true) {p.print1();}}};
Thread t2 = new Thread() {@Overridepublic void run() {while (true) {p.print2();}}};
t1.start();t2.start();}
}
class Printer {public static void print1() {synchronized (Printer.class) {System.out.print("码");System.out.print("上");System.out.print("未");System.out.println("来");}}public static void print2() {synchronized (Printer.class) {System.err.print("线");System.err.print("程");System.err.println("锁");}}
}
锁对象的说明
-
线程加上同步是为了让两条线程相互不要影响,如果相互影响,影响的是数据的正确性
-
使用锁对象,其实主要是为了锁数据。当某条线程在操作这个数据时,其它线程不能操作当前的数据。
-
如果两条线程要操作相同的数据,那么这两条线程就需要在操作数据的部分都加上同步代码块,并且使用相同的锁对象。
死锁
-
A线程需要甲资源,同时拥有乙资源;B线程需要乙资源,同时拥有甲资源,两条线程都不肯释放自己拥有的资源,同时也需要其它的资源时,就都无法进行运行。形成“死锁”现象。
-
代码表现: 有了同步代码块的嵌套,就可能发生死锁。某条线程获取了外层锁对象A,需要内层锁对象B,等待;
因为另一条线程此时已经获取了外层的锁对象B,需要内层的锁对象A,等待。两条线程就会形成死锁。
代码示例:
/*** 死锁*/
public class Simple05 {public static void main(String[] args) {
Thread t1 = new Thread("苏格拉底") {public void run() {while (true) {synchronized ("筷子A") {System.out.println("苏格拉底争取到了筷子A,等待筷子B");
synchronized ("筷子B") {System.out.println("苏格拉底获取了所有的筷子,狂吃狂吃");}}}}};
Thread t2 = new Thread("柏拉图") {public void run() {while (true) {synchronized ("筷子B") {System.out.println("柏拉图争取到了筷子B,等待筷子A");synchronized ("筷子A") {System.out.println("柏拉图获取了所有的筷子,狂吃狂吃");}}}}};t1.start();t2.start();}
}
// 开发尽量避免锁的嵌套以此 来避免死锁现象发生。
// 公平锁 非公平锁 表锁 行锁 自旋锁
火车票案例
/*** 火车票案例*/
public class Simple06 {/*** 三个窗口,同时售卖100张火车票* 打印某个窗口卖出了1张票,还剩x张* 窗口是随机的** @param args*/public static void main(String[] args) {Tickets t1 = new Tickets();Tickets t2 = new Tickets();Tickets t3 = new Tickets();t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}
class Tickets extends Thread {
//为了让多个线程多个对象 共享同一个数据 我们加上静态private static int tickets = 100;
public void run() {while (true) {// 多个线程 使用同一个锁对象 使用xx.classsynchronized (Tickets.class) {if (tickets <= 0) {break;}try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//线程1等待 线程2等待//线程3 把ticket-- 变成0 这时候cpu切换到线程1 线程1执行--操作变为-1tickets--;System.out.println(getName() + "卖出了1张票, 还剩" + tickets + "张票");}}}
}
线程生命周期
概述
-
线程是一个动态的概念,有创建的时候,也有运行和变化的时候,必须也就有消亡的时候,所以从生到死就是一个生命周期。在生命周期中,有各种各样的状态,这些状态可以相互转换。
-
状态罗列: 新建态:刚创建好对象的时候,刚new出来的时候 就绪态:线程准备好了所有运行的资源,只差cpu来临 运行态:cpu正在执行的线程的状态 阻塞态:线程主动休息、或者缺少一些运行的IO资源,即使cpu来临,也无法运行 死亡态:线程运行完成、出现异常、调用方法结束
状态转换图
Java中线程状态的描述
-
线程状态只是理论上的状态,在计算机系统中有更加精确的描述,可以将各种不同的状态,封装成对象,这些对象可能和理论状态不完全相同。
-
Java语言中,获取线程状态的方法: getState():返回当前线程对象的状态对象;返回值类型是Thread.State,Thread的内部类
-
Thread.State:Java语言中,描述线程状态的类型 说明:由于状态时有限个的,所以该类的对象,不需要手动创建,直接在类中创建好了,获取即可,
这种类型就是枚举类型。每个对象,称为枚举项。
Thread.State中,枚举项:6个 NEW:新建态,没有开启线程 RUNNABLE:就绪态和运行态 BLOCKED:阻塞态 (等待锁、I\O,等待锁对象释放了,IO资源准备完毕了,就不阻塞了) WAITING:阻塞态 (调用了wait方法,等待其它线程唤醒的状态,没人喊它,它就一直阻塞) TIMED_WAITING:阻塞态(调用了有时间限制的wait方法、sleep方法) TERMINATED:死亡态
代码示例:
public class Simple01 {
public static void main(String[] args) throws InterruptedException {//test1_new_timedWaiting_terminated_runnable();test2_blocked_TERMINATED();
}
//演示blocked阻塞状态public static void test2_blocked_TERMINATED() throws InterruptedException {
Thread t1 = new Thread("t1线程") {public void run() {synchronized ("abc") {for (int i = 0; i <= 3; i++) {//打印数字,和线程名称System.out.println(i + "..." + getName());try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}}};Thread t2 = new Thread("t2线程") {public void run() {//没有获取到锁的线程对象的状态是:BLOCKEDsynchronized ("abc") {for (int i = 0; i <= 3; i++) {System.out.println(i + "..." + getName());try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}}};t1.start();t2.start();
while (true) {System.out.println("t1...." + t1.getState());System.out.println("t2...." + t2.getState());Thread.sleep(50);//t1 和 t2 判断线程是否结束if (t1.getState().equals(Thread.State.TERMINATED) &&t2.getState().equals(Thread.State.TERMINATED)) {System.out.println("两条线程都结束了");break;}}}
private static void test1_new_timedWaiting_runnable() throws InterruptedException {Thread t1 = new Thread() {public void run() {for (int i = 1; i < 100; i++) {System.out.println(i);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}
}};System.out.println("A:" + t1.getState());//NEWt1.start();System.out.println("B:" + t1.getState());//RUNNABLEThread.sleep(10);//0.01System.out.println("C:" + t1.getState());//TIMED_WAITING}
}
线程池
概述
-
没有线程池的状态:
当使用一条线程的时候,先将线程对象创建出来,启动线程,在运行过程中,正常完成任务之后,线程对象就结束了,就变成了垃圾对象,需要被垃圾回收器回收如果在系统中,大量的任务都是小任务,任务消耗时间较短、线程对象的创建和消亡耗费的时间比较多,
结果:大部分的时间都浪费在了线程对象的创建和死亡上。
-
有线程池的状态 在没有任务的时候,先把线程对象准备好,存储到一个容器中,一旦有任务来的时候,就不需要创建
对象,而是直接将对象获取出来执行任务如果任务破坏力较小,任务可以直接完成,这个线程对象不
会进入死亡状态,而是被容器回收,继续活跃。如果任务破坏力较大,把线程任务搞死了,那么线程池
也会继续提供下一个线程,继续完成这个任务。
线程池使用
-
步骤:获取线程池对象;创建任务类对象;将任务类对象提交到线程池中
-
获取线程池对象:
工具类:
//Executors:生成线程池的工具类,根据需求生成指定大小的线程池 //ExecutorService Executors.newSingleThreadExecutor():创建一个有单个线程的线程池 //ExecutorService Executors.newFixedThreadPool(int nThreads):创建一个指定线程数量的线程池
-
创建任务类对象:Runnable的实现类对象,用于定义任务内容
-
将任务类对象提交到线程池中 ExecutorService:是一个接口,不需要手动创建这个接口的实现类对象,使用方法获取到的就是这个接口的实现类对象,就可以调用这个接口中的方法
-
submit(Runnable r):可以将一个任务类对象,提交到线程池中,如果有空闲的线程,就可以马上运行这个任务,如果没有空闲线程,那么这个任务就需要等待。
-
shutDown():结束线程池,已经提交的全部保证完成,在此代码后就不准继续提交任务了
-
shutDownNow():结束线程池,已经开始运行的,保证完成,等待的任务不保证完成
但是已经提交的,还没有运行的,就不给运行了,作为返回值范围;对于没有提交的,不准提交。
代码示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Simple02 {public static void main(String[] args) {// 准备一个线程池对象 1个// ExecutorService es = Executors.newSingleThreadExecutor();// 准备一个线程池对象 2个ExecutorService es = Executors.newFixedThreadPool(2);Object obj = new Object();Runnable r1 = new Runnable() {@Overridepublic void run() {synchronized (obj) {for (int i = 1; i <= 10; i++) {System.out.println(i + "..." + Thread.currentThread().getName());try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}}}}};Runnable r2 = new Runnable() {@Overridepublic void run() {synchronized (obj) {for (int i = 11; i <= 20; i++) {System.out.println(i + "..." + Thread.currentThread().getName());try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}}}}};Runnable r3 = new Runnable() {@Overridepublic void run() {synchronized (obj) {for (int i = 21; i <= 30; i++) {System.out.println(i + "..." + Thread.currentThread().getName());try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}}}}};
es.submit(r1);es.submit(r2);es.submit(r3);// 已经提交的全部保证完成,不准继续提交了
// es.shutdown();// 已经提交的 前三个任务 保证完成,在shutdown之后就没法添加任务了// es.submit(r3);// es.shutdownNow();// shutDownNow():结束线程池,已经开始运行的,保证完成,等待的任务不保证完成// 另外结束线程池这句话遇到任务中有sleep执行的时候就会有问题,会抛异常// 可以把任务sleep的代码注释上 就不报错了}
}
相关文章:
![](https://img-blog.csdnimg.cn/img_convert/623fae1102ede4fde8e0c6880b4bfc30.png)
java多线程(常用方法、实现方式、线程安全问题、生命周期、线程池)
多线程相关的三组概念 程序和进程 程序(program):一个固定的运行逻辑和数据的集合,是一个静态的状态,一般存储在硬盘中。简单来说就是我们编写的代码 进程(process):一个正在运行的…...
![](https://img-blog.csdnimg.cn/direct/04d09561cda54ae08b7cb1a8b13d579c.png)
Day05 linux高级系统设计 - 管道
复制文件描述符 dup函数 作用: 文件描述符复制 语法: #include <unistd.h> int dup (int oldfd); 参数: 所需复制得文件描述符 返回值: 复制到的文件描述符 功能: 从文件描述符表中,找一个最小…...
![](https://www.ngui.cc/images/no-images.jpg)
低代码:美味膳食或垃圾食品?
一、什么是低代码 低代码是一种开发方法,通过可视化界面和少量的编码,使开发者能够快速构建应用程序。它的目标是提高开发效率、降低开发成本,并支持快速迭代和敏捷开发。 二、低代码的优缺点 低代码开发具有以下优点: 快速开…...
![](https://img-blog.csdnimg.cn/img_convert/4ad482ae386c4b8eddc935efeab0b83d.webp?x-oss-process=image/format,png)
免费网页抓取工具大全【附下载和工具使用教程】
在当今信息爆炸的时代,获取准确而丰富的数据对于企业决策和个人研究至关重要。而网页抓取工具作为一种高效获取互联网数据的方式,正逐渐成为大家解决数据需求的得力助手。本文将深入探讨网页抓取工具的种类,并为大家提供简单实用的页面采集教…...
Leetcode 39 组合总和
题意理解: 一个 无重复元素 的整数数组 candidates 和一个目标整数 target 从candidates 取数字,使其和 target ,有多少种组合(candidates 中的 同一个 数字可以 无限制重复被选取) 这道题和之前一道组合的区别&am…...
![](https://img-blog.csdnimg.cn/direct/deecd0e7c973427a9f34d77ca523dc28.png)
Windows下使用AndroidStudio及CMake编译Android可执行程序或静态库动态库
Windows下使用AndroidStudio及CMake编译Android可执行程序或静态库动态库 文章目录 Windows下使用AndroidStudio及CMake编译Android可执行程序或静态库动态库一、前言二、编译环境三、示例C/CPP程序1、总体工程结构2、示例代码3、CMakeLists.txt(重要)4、…...
![](https://www.ngui.cc/images/no-images.jpg)
MySQL七 | 存储引擎
目录 存储引擎 存储引擎特点 存储引擎选择 Innodb与MyISAM区别 存储引擎 默认存储引擎:InnoDB show engines;#展示当前数据库支持的存储引擎 存储引擎特点 特点InnoDBMyISAMMemory存储限制64TB有有事务安全支持--锁机制行锁表锁表锁Btree锁支持支持 支持 Hash索引--支…...
![](https://img-blog.csdnimg.cn/direct/1fa6f4a5ca4a47a19c8feca2595f02fa.png)
网上下载的pdf文件,为什么不能复制文字?
不知道大家有没有到过这种情况?在网上下载的PDF文件打开之后,发现选中文字之后无法复制。甚至其他功能也都无法使用,这是怎么回事?该怎么办? 当我们发现文件打开之后,编辑功能无法使用,很可能是…...
![](https://img-blog.csdnimg.cn/img_convert/567ba7dcec3bd697c6d27af4e3d46fba.png)
Linux下apisix离线安装教程
Linux下apisix离线安装教程 一、首先需要安装etcd:二、通过rpm离线安装apisix三、启动apisix四、安装apisix-dashboard1、安装2、更改dashboard登录账号名和密码3、运行 一、首先需要安装etcd: 解压缩etcd后执行以下命令: tar -xvf etcd-v3.…...
![](https://img-blog.csdnimg.cn/direct/3a78e9d212814bdea8acc9db549b6e15.png)
基于STM32 + DMA介绍,应用和步骤详解(ADC多通道)
前言 本篇博客主要学习了解DMA的工作原理和部分寄存器解析,针对ADC多通道来对代码部分,应用部分作详细讲解,掌握代码编程原理。本篇博客大部分是自己收集和整理,如有侵权请联系我删除。 本次博客开发板使用的是正点原子精英版&am…...
![](https://img-blog.csdnimg.cn/img_convert/ad968f2cb8a9fe0f597f895407716634.png)
openGauss学习笔记-144 openGauss 数据库运维-例行维护-慢sql诊断
文章目录 openGauss学习笔记-144 openGauss 数据库运维-例行维护-慢sql诊断144.1 背景信息144.2 前提条件 openGauss学习笔记-144 openGauss 数据库运维-例行维护-慢sql诊断 144.1 背景信息 在SQL语句执行性能不符合预期时,可以查看SQL语句执行信息,便…...
![](https://img-blog.csdnimg.cn/direct/99cd4503dd364e8d83bf3cdf63d75fd6.jpeg)
计算机毕业设计springboot+ssm停车场车位预约系统java
管理员不可以注册账号 停车位包括车位所在楼层、车位编号、车位类型(全时间开放/高峰期开放)、预定状态等 用户预约时要求支付预约时间段的停车费用 违规行为:1.停车超过预约时间段 2.预约未使用 于系统的基本要求 (1)功能要求&am…...
![](https://img-blog.csdnimg.cn/img_convert/719f11f2054c480bb2855f1479cdf161.png)
打破常规思维:Scrapy处理豆瓣视频下载的方式
概述 Scrapy是一个强大的Python爬虫框架,它可以帮助我们快速地开发和部署各种类型的爬虫项目。Scrapy提供了许多方便的功能,例如请求调度、数据提取、数据存储、中间件、管道、信号等,让我们可以专注于业务逻辑,而不用担心底层的…...
![](https://img-blog.csdnimg.cn/direct/8b1697b8ce7f48cc976d7c435c55244b.png)
系列学习前端之第 2 章:一文精通 HTML
全套学习 HTMLCSSJavaScript 代码和笔记请下载网盘的资料: 链接: https://pan.baidu.com/s/1-vY2anBdrsBSwDZfALZ6FQ 提取码: 6666 HTML 全称:HyperText Markup Language(超文本标记语言) 1、 HTML 标签 1. 标签又称元素&#…...
![](https://www.ngui.cc/images/no-images.jpg)
SCSS Module 这样处理配置和使用太赞了
SCSS Module 只是Scss和Css Module结合,可以利用SCSS对代码静态处理的能力,使得样式处理更强大一些,并不是什么新的东西,对比css-in-js和scoped,个人偏向喜欢Scss Module做样式隔离,先说一下优点࿱…...
![](https://img-blog.csdnimg.cn/direct/adedc1192e7f4c87ad4ea80f400469d0.png)
【Unity动画】Unity 2D动画创建流程
本文以2D为案例,讲解Unity 播放动画的流程 准备和导入2D动画资源 外部导入序列帧生成的 Unity内部制作的 外部导入的3D动画 2.创建动画过程 打开时间轴Ctrl6 选中场景中的一个未来需要播放动画的物体 回到时间轴点击Create一个新动画片段 拖动2D动画资源放入…...
![](https://img-blog.csdnimg.cn/f68d05a84ef541f595c0886ca4d6a348.png)
【算法每日一练]-图论(保姆级教程篇12 tarjan篇)#POJ3352道路建设 #POJ2553图的底部 #POJ1236校园网络 #缩点
目录: 今天知识点 加边使得无向图图变成双连通图 找出度为0的强连通分量 加边使得有向图变成强连通图 将有向图转成DAG图进行dp POJ3352:道路建设 思路: POJ2553:图的底部 思路: POJ1236校园网络 思路&#x…...
![](https://img-blog.csdnimg.cn/img_convert/1285f4ce98c55042db920957a29f973c.webp?x-oss-process=image/format,png)
Python数据科学视频讲解:数据挖掘与建模的注意事项
1.7 数据挖掘与建模的注意事项 视频为《Python数据科学应用从入门到精通》张甜 杨维忠 清华大学出版社一书的随书赠送视频讲解1.7节内容。本书已正式出版上市,当当、京东、淘宝等平台热销中,搜索书名即可。内容涵盖数据科学应用的全流程,包括…...
![](https://img-blog.csdnimg.cn/direct/15eba8d6765048368e25d6c1c9daa60c.png)
unity | 动画模块之循环滚动选项框
一、作者的话 评论区有人问,有没有竖排循环轮播选项框,我就写了一个 二、效果动画 如果不是你们想要的,就省的你们继续往下看了 三、制作思路 把移动分成里面的方块,还有背景(父物体),方块自…...
![](https://img-blog.csdnimg.cn/direct/d75677bbdad14e05a030446412cc8996.png)
TinyMPC - CMU (卡耐基梅隆大学)开源的机器人 MPC 控制器
系列文章目录 CasADi - 最优控制开源 Python/MATLAB 库 文章目录 系列文章目录前言一、机器人硬件对比1.1 Teensy 上的微控制器基准测试1.2 机器人硬件1.3 BibTeX 二、求解器三、功能(预期)3.1 高效3.2 鲁棒3.3 可嵌入式3.4 最小依赖性3.5 高效热启动3.…...
![](https://img-blog.csdnimg.cn/direct/39947d06061047cfb6b48e1a98afc61d.png)
C++ 对象的初始化和清理:构造函数和析构函数
目录 构造函数和析构函数 构造函数 析构函数 构造函数的分类及调用 括号法 显示法 隐式转换法 拷贝构造函数的调用时机 使用一个已经创建完毕的对象来初始化一个新对象 值传递的方式给函数参数传值 以值方式返回局部对象 构造函数调用规则 初始化列表 类对象作…...
![](https://www.ngui.cc/images/no-images.jpg)
Tmux中使用Docker报错 - 解决方案
问题 进入Tmux会话后,在其中使用Docker可能会出现如下报错: Got permission denied while trying to connect to the Docker ……解决方案 退出tmux会话: tmux detach在tmux会话外部杀掉tmux进程: pkill -f tmux重新进入tmux:…...
![](https://img-blog.csdnimg.cn/img_convert/5d5bb159cad2711a7abeedda52d30d72.webp?x-oss-process=image/format,png)
如何在WordPress中批量替换图片路径?
很多站长在使用WordPress博客或者搬家时,需要把WordPress文章中的图片路径进行替换来解决图片不显示的问题。总结一下WordPress图片路径批量替换的过程,方便有此类需求的站长们学习。 什么情况下批量替换图片路径 1、更换了网站域名 有许多网站建设初期…...
![](https://img-blog.csdnimg.cn/direct/2706584a820948e59420fa119e1ed4cf.png)
el-pagination 纯前端分页
需求:后端把所有数据都返给前端,前端进行分页渲染。 实现思路:先把数据存储到一个大数组中,然后调用方法进行切割。主要使用数组的slice方法 所有代码: html <template><div style"padding: 20px&qu…...
![](https://img-blog.csdnimg.cn/direct/084de73562a24de6bc237d1cf8e6fe18.png)
基于springboot的校园二手市场
博主主页:猫头鹰源码 博主简介:Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容:毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…...
![](https://img-blog.csdnimg.cn/3a01dc60e62c415b812015851343f1c0.png#pic_center)
【开源】基于Vue和SpringBoot的在线课程教学系统
项目编号: S 014 ,文末获取源码。 \color{red}{项目编号:S014,文末获取源码。} 项目编号:S014,文末获取源码。 目录 一、摘要1.1 系统介绍1.2 项目录屏 二、研究内容2.1 课程类型管理模块2.2 课程管理模块2…...
![](https://img-blog.csdnimg.cn/direct/7b804c0c619c49e7a93fc1845a0fd6aa.png)
Mysql分布式集群部署---MySQL集群Cluster将数据分成多个片段,每个片段存储在不同的服务器上
1.1 目的 部署MysqlCluster集群环境 1.2 MySQL集群Cluster原理 1 数据分片 MySQL集群Cluster将数据分成多个片段,每个片段存储在不同的服务器上。这样可以将数据负载分散到多个服务器上,提高系统的性能和可扩展性。 2. 数据同步 MySQL集群Cluster使…...
![](https://www.ngui.cc/images/no-images.jpg)
身份认证技术
身份认证是对系统的用户进行有效性、真实性验证。 1.口令认证方式 使用口令认证方式,用户必须具有一个唯一的系统标识,并且保证口令在系统的使用和存储过程中是安全的,同时口令在传输过程中不能被窃取、替换。另外特别要注意的是在…...
![](https://img-blog.csdnimg.cn/direct/2cae65ff2cb84406921c663887c6eed1.png)
Centos7、Mysql8.0 load_file函数返回为空的终极解决方法--暨selinux的深入理解
零、问题背景 最近想换房,为了方便自己对比感兴趣的房子,因此决定将目标房源的基本信息放在表里,特别是要一目了然的看到众多房子的各种图纸和照片,因此决定要在Mysql8.0.34数据库中以二进制形式保存图片(抛开合理性和…...
![](https://www.ngui.cc/images/no-images.jpg)
基于Spring Boot的水产养殖管理系统
文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于Spring Boot的水产养殖管理系统,jav…...
![](https://img-blog.csdnimg.cn/direct/8d145442827e4660b25a5b4f74486cf7.png)
LCR 090. 打家劫舍 II(leetcode)动态规划
文章目录 前言一、题目分析二、算法原理1.状态表示2.状态转移方程3.初始化4.填表顺序5.返回值是什么 三、代码实现总结 前言 在本文章中,我们将要详细介绍一下LeetcodeLCR 090. 打家劫舍 II。采用动态规划解决,这是一道经典的多状态dp问题 一、题目分析…...
![](https://img-blog.csdnimg.cn/direct/ebdc6973839c48519dc704a1adb91ec1.png)
【小沐学Python】Python实现语音识别(Whisper)
文章目录 1、简介1.1 whisper简介1.2 whisper模型 2、安装2.1 whisper2.2 pytorch2.3 ffmpeg 3、测试3.1 命令测试3.2 代码测试:识别声音文件3.3 代码测试:实时录音识别 结语 1、简介 https://github.com/openai/whisper 1.1 whisper简介 Whisper 是…...
![](https://img-blog.csdnimg.cn/img_convert/ebb9bedd54789df6c5c1f7ab8c1b3a56.png)
Nginx负载均衡实战
🎵负载均衡组件 ngx_http_upstream_module https://nginx.org/en/docs/http/ngx_http_upstream_module.html upstream模块允许Nginx定义一组或多组节点服务器组,使用时可以通过多种方式去定义服务器组 样例: upstream backend {server back…...
![](https://www.ngui.cc/images/no-images.jpg)
Redis skiplist源码解析(支持范围查询)
跳表是一个多层的有序链表,在跳表中进行查询操作时,查询代码可以从最高层开始查询。层数越高,结点数越少,同时高层结点的跨度会比较大。因此,在高层查询结点时,查询一个结点可能就已经查到了链表的中间位置…...
![](https://img-blog.csdnimg.cn/direct/1f40d37f91a04349ad577f37650b8e46.png)
MVSNeRF:多视图立体视觉的快速推广辐射场重建(2021年)
MVSNeRF:多视图立体视觉的快速推广辐射场重建(2021年) 摘要1 引言2 相关工作3 MVSNeRF实现方法3.1 构建代价体3.2 辐射场的重建3.3 体渲染和端到端训练 3.4 优化神经编码体 Anpei Chen and Zexiang Xu and Fuqiang Zhao et al. MVSNeRF: Fast…...
![](https://www.ngui.cc/images/no-images.jpg)
华为OD机试真题-CPU算力分配-2023年OD统一考试(C卷)
题目描述: 现有两组服务器A和B,每组有多个算力不同的CPU,其中A[i]是A组第i个CPU的运算能力,B[i]是B组第i个CPU的运算能力。一组服务器的总算力是各CPU的算力之和。为了让两组服务器的算力相等,允许从每组各选出一个CPU进行一次交换,求两组服务器中,用于交换的CPU的算力,…...
![](https://www.ngui.cc/images/no-images.jpg)
校验数据是否重叠(各种操作符>,<,>=,<=,or,and)
最近接到一个需求,其中部分功能涉及到数据的重叠校验,并且录入的数据需要包含各种操作符。如果只通过java代码来查询并进行循环判断的话,判断情况会很复杂,幸好有同事的帮忙提供了一个用sql查询重叠部分的方法,现在分享…...
![](https://www.ngui.cc/images/no-images.jpg)
大一C语言作业 12.8
1.C 对一维数组初始化时,如果全部元素都赋了初值,可以省略数组长度。 这里没有指定数组长度,编译器会根据初始化列表的元素个数来确定数组长度。 2.C 在C语言中,字符数组是不能用赋值运算符直接赋值的。 3.C 在二维数组a中&#x…...
![](https://img-blog.csdnimg.cn/direct/4a3e5d851d5d4598a9978c577a9e8f05.png)
ELasticsearch:什么是语义搜索?
语义搜索定义 语义搜索是一种解释单词和短语含义的搜索引擎技术。 语义搜索的结果将返回与查询含义匹配的内容,而不是与查询中的单词字面匹配的内容。 语义搜索是一组搜索引擎功能,其中包括根据搜索者的意图及其搜索上下文理解单词。 此类搜索旨在通过…...
![](https://img-blog.csdnimg.cn/img_convert/ed3ce44023e56ec70804a596f979629c.jpeg)
ooTD I 女儿是自己的,尽情打扮尽情可爱
分享女宝的时尚穿搭 奶乎乎的黄色也太好看了 超足充绒量+优质面料 柔软蓬松上身体验感超赞 怎么穿都好看系列 轻轻松松打造时尚造型!!...
![](https://www.ngui.cc/images/no-images.jpg)
第62天:django学习(十一)
cookie和session 发展史 一开始,只有一个页面,没有登录功能,大家看到东西都一样。 时代发展,出现了需要登录注册的网站,要有一门技术存储我们的登录信息,于是cookie诞生了。 cookie: - 存储形式:k:v键值对…...
![](https://img-blog.csdnimg.cn/direct/bfe7fff7a5b843acb053c4de11de02cd.png)
Rust测试字符串的移动,Move
代码创建了一个结构体,结构体有test1 字符串,还有指向字符串的指针。一共创建了两个。 然后我们使用swap 函数 交换两个结构体内存的内容。 最后如上图。相同的地址,变成了另外结构体的内容。注意看指针部分,还是指向原来的地址…...
![](https://img-blog.csdnimg.cn/direct/91e86b399eea481e9d9db91dd215bb9a.png)
vue+electron问题汇总
1. Vue_Bug Failed to fetch extension, trying 4 more times 描述:项目启动时报错 解决:注释图片中内容 2. Module not found: Error: Can’t resolve ‘fs’ in 描述:项目启动报错 解决:vue.config.js中添加图中数据 3.导入…...
![](https://img-blog.csdnimg.cn/direct/941c16d664974fa293e77b0d6ac06280.png)
Linux中的网络时间服务器
本章主要介绍网络时间的服务器 使用chrony配置时间服务器配置chrony客户端服务器同步时间 1.1 时间同步的重要性 一些服务对时间要求非常严格,例如如图所示的由三台服务器搭建的ceph集群 这三台服务器的时间必须保持一致,如果不一致,就会显…...
![](https://img-blog.csdnimg.cn/direct/b2528f63a34b4e51b5a788071a073765.png)
fastadmin打印页面
如下图选中订单号进行打印 html中增加代码 <div id"toolbar" class"toolbar"><a href"javascript:;" class"btn btn-primary btn-refresh" title"{:__(Refresh)}" ><i class"fa fa-refresh">&l…...
![](https://img-blog.csdnimg.cn/direct/300e995470ff451f8d6adf6bee972b27.png)
Java 将word转为PDF的三种方式和处理在服务器上下载后乱码的格式
我这边是因为业务需要将之前导出的word文档转换为PDF文件,然后页面预览下载这样的情况。之前导出word文档又不是我做的,所以为了不影响业务,只是将最后在输出流时转换成了PDF,当时本地调用没什么问题,一切正常…...
![](https://www.ngui.cc/images/no-images.jpg)
C\C++ 获取最值
C C 语言的不同类型的最值可以在 limits.h 头文件里找到定义 #include <limits.h>int main() {printf("%d", INT_MAX); // 整数最大值printf("%d", INT_MIN); // 整数最小值 } C C 有模板,可以通过替换下面的 int 和 doubleÿ…...
![](https://img-blog.csdnimg.cn/img_convert/aa38d260bf42cebc54e6a6b0b02b0b6f.png)
机器学习之无监督学习:九大聚类算法
今天,和大家分享一下机器学习之无监督学习中的常见的聚类方法。 今天,和大家分享一下机器学习之无监督学习中的常见的聚类方法。 在无监督学习中,我们的数据并不带有任何标签,因此在无监督学习中要做的就是将这一系列无标签的数…...
![](https://img-blog.csdnimg.cn/direct/4ff8f55a25a14f078984096bde55e696.png)
Linux高级管理-搭建网站服务
在Ihternet 网络环境中,Web 服务无疑是最为流行的应用系统。有了Web站点,企业可以充分 展示自己的产品,宣传企业形象。Web站点还为企业提供了与客户交流、电子商务交易平台等丰富 的网络应用。部署与维护Web 服务是运维工程师必须掌握的一个技…...
![](https://img-blog.csdnimg.cn/direct/6f38d23174434fbaa1ee75afe2786432.png)
Windows 系统,TortoiseSVN 无法修改 Log 信息解决方法
使用SVN提交版本信息时,注释内容写的不全。通过右键TortoiseSVN的Show log看到提交的的注释,右键看到Edit log message的选项,然而提交后却给出错误提示: Repository has not been enabled to accept revision propchanges; ask …...