当前位置:网站首页>多线程的使用
多线程的使用
2022-07-28 16:35:00 【night_du】
概述
定义
进程与线程
- 进程:是程序执行的一条路径。指⼀个内存中运⾏的应⽤程序,⼀个应⽤程序可以同时运⾏多个进程;进程也是程序的⼀次执⾏过程,是系统运⾏程序的基本单位;系统运⾏⼀个程序即是⼀个进程从创建、运⾏到消亡的过程。
- 线程:⼀个进程中可以有多个线程多个线程并发执⾏可以提⾼程序的效率。线程是进程中的⼀个执⾏单元,负责当前进程中程序的执⾏,⼀个进程中⾄少有⼀个线程。⼀个进程中是可以有多个线程的,这个应⽤程序也可以称之为多线程程序
并发与并行
- 并发:两个或多个事件在同⼀个时间段内发⽣
- 并行:两个或多个事件在同⼀时刻发⽣(同时发⽣)
线程调度
- 分时调度:所有线程轮流使⽤ CPU 的使⽤权,平均分配每个线程占⽤ CPU 的时间
- 抢占式调度:优先让优先级⾼的线程使⽤ CPU,如果线程的优先级相同,那么会随机选择⼀个(线程随机性),Java使⽤的为抢占式调度
线程的组成
CPU时间片:OS为每一个线程分配执行时间
运行数据:
堆空间:存储线程需要使用的对象,多个线程可以共享堆中的对象
栈空间:存储线程需要使用的局部变量,每个线程都拥有独立的栈
逻辑代码
java虚拟机运行流程
- java命令会启动java虚拟机,启动JVM,等于启动了⼀个应⽤程序,也就是启动了⼀个进程。该进程会⾃动启动⼀个 “主线程” ,然后主线程去调⽤某个类的 main ⽅法
- JVM启动后⾄少会创建垃圾回收线程和主线程
线程的创建
Thread
步骤
- 继承Thread方法
- 定义类继承Thread
- 重写run()
- 实现run()的业务
- 创建线程对象
- 调用start()方法开启新线程,内部自动执行run()

public class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread方式创建的线程");
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
原理解析
- 创建多线程肯定要跟系统平台的底层打交道, 我们程序猿根本就不知道如何去做, 所有,我们仅
仅是提供运⾏代码,⾄于如何创建多线程全靠java来实现 - 继承Thread的形式,每个Thread的⼦类对象只能创建⼀个线程
Runnable
步骤
定义类实现Runnable接⼝
实现run⽅法
把新线程要做的事写在run⽅法中
创建⾃定义的Runnable的⼦类对象
创建Thread对象, 传⼊Runnable
调⽤start()开启新线程, 内部会⾃动调⽤Runnable的run()⽅法
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iGfvlmmh-1645536458297)(F:\笔记文件\JavaSE\14、多线程\多线程.assets\image-20210927104114892.png)]](/img/ac/1d1c7712ec255d606b3565a594ecf2.png)
public class MyRunnable implements Runnable { @Override public void run() { System.out.println("Runnable创建的线程"); } } public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); Runnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start(); }实现原理
- Thread类中定义了⼀个Runnable类型的成员变量target⽤来接收Runnable的⼦类对象
- 当调⽤Thread对象的start()⽅法的时候, ⽅法内⾸先会判断target是否为null. 如果不为null就调⽤Runnable⼦类对象的run⽅法
- 多个Thread对象可以共享⼀个Runnable⼦类对象
匿名Thread与Runnable
public class ThreadDemo {
public static void main(String[] args) {
Thread thread1 = new Thread() {
@Override
public void run() {
System.out.println("匿名Thread");
}
};
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名Runnable");
}
});
thread1.start();
thread2.start();
}
}
Thread与Runnable区别
- 执行原理
- 继承Thread类,是直接调用子类的run()
- 实现Runnable接口,实际上是通过接口多态的方式调用实现类的run(),调用的是Runnable接口的run(),真实执行的是实现类的run
- 复用性
- 继承Thread类:单个线程只能使用一个,互相之间独立
- 实现Runnable接口:是一个参数传入Thread类中,所以可以多个对象共同调用一个Runnnable对象
- 创建方式
- 继承Thread类:直接创建线程对象
- 实现Runnable接口:实现Runnable接口,必须通过thread才能创建线程对象,自己不能创建
- 适用场景
- 继承Thread类:对于子类来说,只能继承Thread,如果一个类已经有父类了,就没有办法通过这个方式创建线程
- 实现Runnable接口:实现Runnable接口几乎没有限制,接口是可以多实现的
- 适用的方法
- 继承Thread类:直接使用Thread类中定义的方法
- 实现Runnable接口:没有办法直接使用,只能通过Thread提供的静态方法间接使用Thread的方法
线程状态
基本

等待

阻塞

常用API
getName()
- 通过getName()⽅法获取线程对象的名字
- 此⽅法只适⽤于Thread的形式
- Runnable的形式必须通过获取线程对象来获取名字:Thread.currentThread().getName()
public static void main(String[] args) {
new Thread() {
@Override
public void run() {
System.out.println(this.getName());
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
}
setName()
通过构造⽅法传⼊String类型的名字
new Thread("线程1") {
@Override
public void run() {
System.out.println(this.getName());
}
}.start();
通过setName⽅法可以设置线程对象的名字
Thread thread1 = new Thread("线程⼀"){
public void run(){
System.out.println(this.getName());
}
};
thread1.setName("线程⼀");
thread1.start();
通过获取对象的形式在Runnable运⾏代码中查看当前线程的名称
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
//获取线程的名称
System.out.println(Thread.currentThread().getName());
}
}).start();
}
通过获取线程对象的形式设置线程的名称
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
Thread.currentThread().setName("线程⼆");//设置线程的名称
System.out.println(Thread.currentThread().getName());
}
}).start();
}
通过Thread的构造⽅法设置线程的名称
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
//通过构造⽅法设置线程的名称
},"线程⼆").start();
}
sleep()
将线程休眠若⼲毫秒
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
long time1 = System.currentTimeMillis();
System.out.println(time1);
try {
Thread.sleep(10000);//设置线程等待10000毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
long time2 = System.currentTimeMillis();
System.out.println(time2);
}
//通过构造⽅法设置线程的名称
}).start();
}
setDaemon()
- 围绕着其他⾮守护线程运⾏, 该线程不会单独运⾏,当其他⾮守护线程都执⾏结束后,⾃动退出
- 调⽤Thread的setDaemon()⽅法设置⼀个线程为守护线程
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("线程⼀运⾏中");
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("守护线程运⾏..........");
}
}
});
t2.setDaemon(true);
t1.start();
t2.start();
}
join()
当前线程暂停, 等待指定的线程执⾏结束后, 当前线程再继续
只有调⽤其他线程加⼊⽅法的线程才会停⽌运⾏,其他线程不受影响
- join() 优先执⾏指定线程
- join(毫秒) 优先执⾏指定线程若⼲毫秒
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("线程⼀运⾏中");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("线程⼆运⾏中..........");
try {
t1.join(); //让线程⼀先执⾏
t1.join(100);//让线程⼀先执⾏100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
//只有调⽤了加⼊线程的线程才会停⽌运⾏,其他线程不受影响
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("线程三运⾏中..........");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
t2.start();
t3.start();
}
yield()
让出当前线程的执⾏权
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("线程⼆运⾏中..........");
//让出当前线程的执⾏权
Thread.yield();
}
}
});
isAlive()
判断当前线程是否处于活动状态:已经启动且尚未停止
System.out.println("线程的活动状态是:"+Thread.currentThread().isAlive());
getId()
获取线程的唯一标示
System.out.println(myTreadTest1.getId());
setPriority()
- 每个线程都有优先级 默认是5 , 范围是1-10 ,1表示优先级最低
- 优先级⾼的线程在争夺cpu的执⾏权上有⼀定的优势,但不是绝对的
myTreadTest1.setPriority(10);
interrupt()
线程的停止
interript 也不是想象中的那样,只要调用了这个方法,线程就会停止,其实,调用了interrupt 只相当于给当前线程上了一个停止的标记,而此时,线程其实并没有真正的停止,而这其中很明显,缺少一些步骤。
public class A {
public static void main(String[] args) {
B b = new B();
b.start();
b.interrupt();
}
}
class B extends Thread {
@Override
public void run() {
System.out.println("正在执行线程B。。。" + Thread.currentThread().getName());
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
需要加上一个判断
class B extends Thread {
@Override
public void run() {
System.out.println("正在执行线程B。。。" + Thread.currentThread().getName());
for (int i = 0; i < 10; i++) {
if (this.isInterrupted()){
System.out.println("线程停止");
System.out.println(this.isInterrupted());
break;
}
}
}
}
this.isInterrupted()代表着获取线程的中断标志,如果在此之前你调用了interrupt 的话它就返回true,否则就是 false,所以就可以通过这种方式来达到停止线程的目的
如果你让线程进行休眠,就会抛出一个中断异常,因为你之前打上了中断标志,所以调用sleep 就会抛出一个中断异常,而且还会将中断标志设置成false
class B extends Thread {
@Override
public void run() {
System.out.println("正在执行线程B。。。" + Thread.currentThread().getName());
try {
for (int i = 0; i < 10; i++) {
if (this.isInterrupted()) {
System.out.println("线程停止");
System.out.println(this.isInterrupted());
throw new InterruptedException();
}
System.out.println(i);
}
System.out.println("线程继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
isInterrupted()
isInterrupted()返回线程是否中断
System.out.println(this.isInterrupted());
安全问题

- 多个线程操作同⼀个数据时, 因为线程执⾏的随机性, 就有可能出现线程安全问题
- 使⽤同步可以解决这个问题, 把操作数据的代码进⾏同步,同⼀时间只能有⼀个线程只操作数据
就可以保证数据的安全性
当我们使⽤多个线程访问同⼀资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题,要解决多线程并发访问⼀个资源的安全性问题,Java中提供了同步机制 (synchronized)来解决
同步代码块
synchronized 关键字可以⽤于⽅法中的某个区块中,表示只对这个区块的资源实⾏互斥访问。
格式
synchronized (同步锁){}
同步锁:对象同步锁只是一个概念,可以想象为在对象上标记一个锁
- 锁对象 可以任意类型
- 多个线程对象,要使用同一把锁
案例:售票
使用Thread类创建线程,使用synchronized 锁住票数,使用多个线程调用同一个线程
public class Trcket extends Thread {
static int count = 1;
final static Object object = new Object();
@Override
public void run() {
while (true) {
synchronized (object) {
if (count < 101) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "卖出了第" + count + "张票");
count++;
} else {
break;
}
}
}
}
public Trcket(String name) {
super(name);
}
}
public class TrcketDemo {
public static void main(String[] args) {
Trcket trcket1 = new Trcket("第一窗口");
Trcket trcket2 = new Trcket("第二窗口");
Trcket trcket3 = new Trcket("第三窗口");
Trcket trcket4 = new Trcket("第四窗口");
trcket1.start();
trcket2.start();
trcket3.start();
trcket4.start();
}
}
使用Runnable接口实现run(),创建线程,使用synchronized 锁住票数,使用多个线程调用同一个线程
public class Station implements Runnable {
static int num = 1;
@Override
public void run() {
while (true) {
synchronized (this) {
if (num < 101) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + num);
num++;
} else {
break;
}
}
}
}
}
public class TrcketDemo {
public static void main(String[] args) {
Runnable runnable = new Station();
Thread thread1 = new Thread(runnable,"第一窗口");
Thread thread2 = new Thread(runnable,"第二窗口");
Thread thread3 = new Thread(runnable,"第三窗口");
Thread thread4 = new Thread(runnable,"第四窗口");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
同步方法
使⽤synchronized修饰的⽅法,就叫做同步⽅法,保证A线程执⾏该⽅法的时候,其他线程只能在⽅法外等着
格式
public synchronized void method(){
可能会产⽣线程安全问题的代码}
同步锁是谁? 对于⾮static⽅法,同步锁就是this。 对于static⽅法,我们使⽤当前⽅法所在
类的字节码对象(类名.class)。
Thread使用同步方法实现
public class Ticket extends Thread {
static int count = 1;
private static boolean flag = true;
@Override
public void run() {
while (flag) {
salaTicket();
}
}
public static synchronized void salaTicket() {
if (count < 101) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出了第" + count + "张票");
count++;
} else {
flag = false;
}
}
public Ticket(String name) {
super(name);
}
}
Ticket trcket1 = new Ticket("第一窗口");
Ticket trcket2 = new Ticket("第二窗口");
Ticket trcket3 = new Ticket("第三窗口");
Ticket trcket4 = new Ticket("第四窗口");
trcket1.start();
trcket2.start();
trcket3.start();
trcket4.start();
Runnable使用同步方法实现
public class Station implements Runnable {
static int num = 1;
private static boolean flag = true;
@Override
public void run() {
while (flag) {
salaTicket();
}
}
public static synchronized void salaTicket() {
if (num < 101) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + num);
num++;
} else {
flag = false;
}
}
}
Runnable runnable = new Station();
Thread thread1 = new Thread(runnable,"第一窗口");
Thread thread2 = new Thread(runnable,"第二窗口");
Thread thread3 = new Thread(runnable,"第三窗口");
Thread thread4 = new Thread(runnable,"第四窗口");
thread1.start();
thread2.start();
thread4.start();
注意事项
使用同一把锁的代码才能实现同步
- 没有获取到锁的线程即使得到了cpu的执⾏权,也不能运⾏
- 尽量减少锁的范围,避免效率低下
- 锁可以加在任意类的代码中或⽅法上
死锁
定义
- 使⽤同步的多个线程同时持有对⽅运⾏时所需要的资源
- 多线程同步时, 多个同步代码块嵌套,很容易就会出现死锁
- 锁的嵌套越多,越容易造成死锁的情况
案例
线程⼀需要先获取左边的筷⼦然后获取右边的筷⼦才能吃饭,线程⼆需要先获取右边的筷⼦然后获取左边的筷⼦才能吃饭
当线程⼀持有左边的筷⼦,线程⼆持有右边的筷⼦时,相互等待对⽅释放锁,但⼜同时持有对⽅释放锁的条件,互不相让,就会造成⽆限等待
private static String s1 = "筷⼦左";
private static String s2 = "筷⼦右";
public static void main(String[] args) {
new Thread() {
public void run() {
while (true) {
synchronized (s1) {
System.out.println(getName() + "...拿到" + s1 + "等待" + s2);
synchronized (s2) {
System.out.println(getName() + "...拿到" + s2 + "开吃");
}
}
}
}
}.start();
new Thread() {
public void run() {
while (true) {
synchronized (s2) {
System.out.println(getName() + "...拿到" + s2 + "等待" + s1);
synchronized (s1) {
System.out.println(getName() + "...拿到" + s1 + "开吃");
}
}
}
}
}.start();
}
通信、唤醒机制
wait
- 让当前线程处于等待状态,并释放锁。
- 线程不再活动,不再参与调度,进⼊ wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执⾏⼀个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait set 中释放出来,重新进⼊到调度队列ready queue)中
notify
- 唤醒某个等待中的线程
- 选取所通知对象的 wait set 中的⼀个线程释放;
notifyAll
- 唤醒所有等待中的线程
- 释放所通知对象的 wait set 上的全部线程
注意
- 哪怕只通知了⼀个等待的线程,被通知线程也不能⽴即恢复执⾏,因为它当初中断的地⽅是在同步块内,⽽此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能⾯临其它线程的竞争),成功后才能在当初调⽤ wait ⽅法之后的地⽅恢复执⾏。
总结如下
- 如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
- 否则,从 wait set 出来,⼜进⼊ entry set,线程就从 WAITING 状态⼜变成 BLOCKED状态
public class WaitDemo {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
while (true) {
synchronized (this) {
//唤醒
this.notify();
System.out.println(Thread.currentThread().getName() + "发生信号结束");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
Thread thread = new Thread(runnable,"1");
Thread thread1 = new Thread(runnable,"2");
thread.start();
thread1.start();
}
}

执行流程:如果thread先拿到锁,执行唤醒,但是这个没有线程被唤醒,继续执行,发出信号,thread沉睡,交出锁。thread1拿到锁,执行唤醒thread,但是thread没有锁,只能等,thread1继续执行,发出信号,沉睡,交出锁。
wait、notify细节
- wait⽅法与notify⽅法必须要由同⼀个锁对象调⽤。因为:对应的锁对象可以通过notify唤醒
使⽤同⼀个锁对象调⽤的wait⽅法后的线程。 - wait⽅法与notify⽅法是属于Object类的⽅法的。因为:锁对象可以是任意对象,⽽任意对象
的所属类都是继承了Object类的。 - wait⽅法与notify⽅法必须要在同步代码块或者是同步函数中使⽤。因为:必须要通过锁对象
调⽤这2个⽅法。
两个线程之间的通信
public static void main(String[] args) {
new Thread() {
private String str;
public void run() {
synchronized (Class.class) {
while (true) {
Class.class.notify();//唤醒等待中的线程 如果有的话
System.out.println("线程⼀........");
try {
Class.class.wait();//当前线程陷⼊⽆限期的等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}.start();
new Thread() {
public void run() {
synchronized (Class.class) {
while (true) {
Class.class.notify(); //唤醒等待中的线程
System.out.println("线程⼆.....................");
try {
Class.class.wait(); // 让当前线程陷⼊等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}.start();
}
多个线程间的通信
public static void main(String[] args) {
new Thread() {
public void run() {
synchronized (Class.class) {
while (true) {
Class.class.notifyAll();//唤醒等待中的线程 如果有的话
System.out.println("线程⼀........");
try {
Class.class.wait();//当前线程陷⼊⽆限期的等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}.start();
new Thread() {
public void run() {
synchronized (Class.class) {
while (true) {
Class.class.notifyAll(); //唤醒所有等待中的线程
System.out.println("线程⼆.....................");
try {
Class.class.wait(); // 让当前线程陷⼊等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}.start();
new Thread() {
public void run() {
synchronized (Class.class) {
while (true) {
Class.class.notifyAll(); //唤醒所有等待中的线程
System.out.println("线程333333333...........");
try {
Class.class.wait(); // 让当前线程陷⼊等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}.start();
}
注意事项
- 线程间的所有通信⾏为都必须在同步代码块中执⾏,这些⾏为都是锁调⽤的。
- 当⼀个线程陷⼊等待, 线程会释放掉锁, 并且⽆法动弹, 即使被唤醒了, 也仅仅表示有了获取锁的机会, 只有当真正获取到锁的时候才能继续运⾏
- wait⽅法还有重载的⽅法,可以传⼊毫秒值,表示多少毫秒之后当前线程⾃动唤醒
- ⼀个锁只能唤醒被⾃⼰锁定的线程,⽆法在当前同步代码块内操作别的锁
案例
需求: ⼩明同学上了⼤学, ⽗亲每次给1000块, ⼩明每次花100元, 当钱花完了, 就打电话给⽗亲,通知他去银⾏存钱, 编程模拟
public class WaitDemo {
private static int money = 1000;
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
//同步锁,锁住money
synchronized (WaitDemo.class) {
//当money大于0,执行消费
if (money >= 0) {
System.out.println("消费" + money);
money = money - 100;
//当money=0,唤醒充值线程
} else {
//唤醒充值线程
WaitDemo.class.notify();
try {
//消费线程等待,释放锁
WaitDemo.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (WaitDemo.class) {
//当money=0,开始充值
if (money <= 0) {
money = 1000;
System.out.println("充值" + money);
//充值完成,唤醒消费线程
WaitDemo.class.notify();
try {
//充值线程等待,释放锁
WaitDemo.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}).start();
}
}
案例:写两个线程,其中一个线程打印1-52,另一个线程打印A-Z,打印顺序应该是12A34B56C…5152Z
private static int i = 1;//切换线程,需要锁住
static int num = 1;//数字
static int n = 65;//字母
private static void three() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (Task927.class) {
//当数字大于52,退出系统
if (num > 52) {
System.exit(0);
//当i取余不等于0,进入数字线程
} else if (i % 2 != 0) {
//输出两个数字
System.out.print(num + "\t");
num++;
System.out.print(num + "\t");
num++;
i++;
//唤醒字母线程
Task927.class.notify();
try {
//数字线程进入等待,释放锁
Task927.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else if (n > 52) {
return;
}
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (Task927.class) {
//当i取余等于0,进入数字线程
if (i % 2 == 0) {
System.out.print((char) n + "\t");
n++;
i++;
//唤醒数字线程,字母线程进入等待,释放锁
Task927.class.notify();
Task927.class.wait();
}
}
}
}
}).start();
}
生产者和消费者问题
概述
包⼦铺线程⽣产包⼦,吃货线程消费包⼦。当包⼦没有时(包⼦状态为false),吃货线程等待,包⼦铺线程⽣产包⼦(即包⼦状态为true),并通知吃货线程(解除吃货的等待状态),因为已经有包⼦了,那么包⼦铺线程进⼊等待状态。接下来,吃货线程能否进⼀步执⾏则取决于锁的获取情况。如果吃货获取到锁,那么就执⾏吃包⼦动作,包⼦吃完(包⼦状态为false),并通知包⼦铺线程(解除包⼦铺的等待状态),吃货线程进⼊等待。包⼦铺线程能否进⼀步执⾏则取决于锁的获取情况。
处理
public class A {
public static void main(String[] args) {
//等待唤醒案例
BaoZi bz = new BaoZi();
ChiHuo ch = new ChiHuo("吃货", bz);
BaoZiPu bzp = new BaoZiPu("包⼦铺", bz);
ch.start();
bzp.start();
}
}
class BaoZi {
String pier;
String xianer;
boolean flag = false;//包⼦资源 是否存在 包⼦资源状态
}
class ChiHuo extends Thread {
private BaoZi bz;
public ChiHuo(String name, BaoZi bz) {
super(name);
this.bz = bz;
}
@Override
public void run() {
while (true) {
synchronized (bz) {
if (bz.flag == false) {
//没包⼦
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("吃货正在吃" + bz.pier + bz.xianer + "包⼦");
bz.flag = false;
bz.notify();
}
}
}
}
class BaoZiPu extends Thread {
private BaoZi bz;
public BaoZiPu(String name, BaoZi bz) {
super(name);
this.bz = bz;
}
@Override
public void run() {
int count = 0;
//造包⼦
while (true) {
//同步
synchronized (bz) {
if (bz.flag == true) {
//包⼦资源 存在
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 没有包⼦ 造包⼦
System.out.println("包⼦铺开始做包⼦");
if (count % 2 == 0) {
// 冰⽪ 五仁
bz.pier = "冰⽪";
bz.xianer = "五仁";
} else {
// 薄⽪ ⽜⾁⼤葱
bz.pier = "薄⽪";
bz.xianer = "⽜⾁⼤葱";
}
count++;
bz.flag = true;
System.out.println("包⼦造好了:" + bz.pier + bz.xianer);
System.out.println("吃货来吃吧");
//唤醒等待线程 (吃货)
bz.notify();
}
}
}
}
执行原理:创建包子类,创建吃货类和包子铺类,将包子类作为对象,锁住包子类的属性。根据flag不断切换线程
案例
需求:李四、王五向张三转账,每次转500,转五次(不考虑余额不足问题)
import lombok.Data;
public class Money {
public static void main(String[] args) {
ZhangSan z = new ZhangSan(0);
LiShi liShi = new LiShi(8000, z);
WangWu wangWu = new WangWu(6000, z);
liShi.start();
wangWu.start();
}
}
@Data
class ZhangSan {
private int money;
boolean aBoolean = false;
public ZhangSan(int money) {
this.money = money;
}
}
@Data
class LiShi extends Thread {
int num = 0;
private int money;
private ZhangSan zhangSan;
public LiShi(int money, ZhangSan zhangSan) {
this.money = money;
this.zhangSan = zhangSan;
}
@Override
public void run() {
while (num < 5) {
synchronized (zhangSan) {
if (zhangSan.aBoolean == false) {
zhangSan.setMoney(zhangSan.getMoney() + 500);
money = money - 500;
System.out.println("李四线程:" + Thread.currentThread().getName() + "\t张三金额:"
+ zhangSan.getMoney() + "\t李四剩余金额" + money);
num++;
zhangSan.aBoolean = true;
}
}
}
}
}
class WangWu extends Thread {
private int money;
private ZhangSan zhangSan;
int num = 0;
public WangWu(int money, ZhangSan zhangSan) {
this.money = money;
this.zhangSan = zhangSan;
}
@Override
public void run() {
while (num < 6) {
synchronized (zhangSan) {
if (num == 5) {
System.out.println("张三最后金额:" + zhangSan.getMoney());
System.exit(0);
} else if (zhangSan.aBoolean == true) {
zhangSan.setMoney(zhangSan.getMoney() + 500);
money = money - 500;
System.out.println("王五线程:" + Thread.currentThread().getName() + "\t张三金额:"
+ zhangSan.getMoney() + "\t王五剩余金额" + money);
num++;
zhangSan.aBoolean = false;
}
}
}
}
}
ThreadLocal
定义
- 保存线程的独立变量,是存储线程变量的容器,里面的数据可以在线程中任意位置取出
- ThreadLocal的变量是线程分离的,别的线程无法使用,保证变量的安全性
- 每个Thread都持有一个TreadLocalMap类型的变量(该类是一个轻量级的Map,功能与map一样,区别是桶里放的是entry而不是entry的链表。功能还是一个map。)以本身为key,以目标为value
常用API
- get():返回当前线程的此线程局部变量的副本中的值
- remove():删除此线程局部变量的值
- set(Value):将当前线程的局部变量的副本设置为指定的值
- withInitial( supplier <? extends S> supplier):创建线程的局部变量
static ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
local.set(1);
local.get();
local.remove();
return 2;
}
};
案例
public class ThreadLocalDemo {
public static void main(String[] args) {
Lock1 lock1 = new Lock1(6);
Lock1 lock2 = new Lock1(6);
Lock1 lock3 = new Lock1(6);
Thread thread1 = new Thread(lock1, " 线程一:");
Thread thread2 = new Thread(lock2, " 线程二:");
Thread thread3 = new Thread(lock3, " 线程三:");
thread1.start();
thread2.start();
thread3.start();
}
}
class Lock1 extends Thread {
private int num;
public Lock1(int num) {
this.num = num;
}
static ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
local.set(1);
local.get();
local.remove();
return 0;
}
};
private Integer getNextValue() {
local.set(local.get() + 1);
return local.get();
}
@Override
public void run() {
for (int i = 0; i < num; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + getNextValue());
}
}
}
方案
public class Example {
static ThreadLocal<Integer> local = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
local.set(1);
System.out.println(local.get());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
local.set(2);
System.out.println(local.get());
}
}).start();
}
public static int method() {
return local.get();
}
}
Lock接口
定义
和Synchronized相比,结构上更加灵活,提供了更多实⽤性的⽅法,功能更加强⼤, 性能更加优越
常用方法
- void lock() //获取锁,如果锁被占⽤,则等待
- boolean tryLock(); // 尝试获取锁 ,成功则返回true,失败则返回false,不阻塞
- void unlock()// 释放锁
public class TicketDemo {
public static void main(String[] args) {
Runnable runnable = new TicketDemoService();
new Thread(runnable).start();
new Thread(runnable).start();
}
}
class TicketDemoService implements Runnable {
private int ticket = 100;
Lock lock = new ReentrantLock();//创建锁
@Override
public void run() {
while (ticket > 0) {
lock.lock();//加锁
System.out.println(Thread.currentThread().getName() + "\t" + ticket--);
lock.unlock();//解锁
}
}
}
互斥锁
定义
- 使⽤ReentrantLock类代替synchronized关键字, 提供了锁定和解锁的⽅法,更多的操作所得⽅法
常用方法
- lock() :锁定当前线程
- unlock(): 解锁
- newCondition() :获取可以操作线程等待和唤醒的Condition对象
- await() :让当前线程陷⼊等待
- signal() :唤醒某个被锁定的线程
public class ReetrantLockDemo {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();//创建锁
Condition condition = lock.newCondition();//创建可以操作线程等待唤醒的对象
new Thread() {
@Override
public void run() {
lock.lock();//加锁
for (int i = 0; i < 10; i++) {
condition.signal();//唤醒其他线程
System.out.println("线程1");
try {
condition.await();//本线程沉睡
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lock.unlock();//解锁
}
}.start();
new Thread() {
@Override
public void run() {
lock.lock();
for (int i = 0; i < 10; i++) {
condition.signal();
System.out.println("线程2");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lock.unlock();
}
}.start();
}
}

注意事项
互斥锁提供了更多操作线程的方法,但是必须使用同一把锁
读写锁
定义
- ⼀种⽀持⼀写多读的同步锁,读写分离,课分别分配读锁、写锁
- ⽀持多次分配读写锁,使多个读写操作可以并发执⾏
互斥规则
- 写写、读写:互斥、阻塞
- 读读:不互斥
- 在读的操作远⾼于写的操作的环境中,可以在保障线程安全的情况下,提⾼运⾏的效率
import lombok.Data;
import lombok.Value;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
/** * @Author:night_du * @Date:2021/9/29 8:11 */
public class ReentrantReadWriteLockDemo {
public static void main(String[] args) {
MyClass myClass = new MyClass();
//设置value的线程
Runnable runnable = new Runnable() {
@Override
public void run() {
myClass.setValue(10);
}
};
//获取value的线程
Runnable runnable1 = new Runnable() {
@Override
public void run() {
myClass.getValue();
}
};
//线程池:创建可以提供管理终端和方法的对象
ExecutorService service = Executors.newFixedThreadPool(20);
long millis = System.currentTimeMillis(); //获取时间戳
for (int i = 0; i < 2; i++) {
//运行两次设置value的线程
service.submit(runnable);
}
for (int i = 0; i < 18; i++) {
service.submit(runnable1); //运行18次获取value的线程
}
service.shutdown(); //关闭线程
while (!service.isTerminated()) {
//如果线程没有关闭,空转等待
System.out.println(System.currentTimeMillis() - millis+"\t"+ myClass.getValue()); //获取时间戳的差额
}
}
}
@Data
class MyClass {
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();//创建读写锁
ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();//获取读的锁
ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();//获取写的锁
private int value;//读取或写入的值
/** * @return 获取value */
public int getValue() {
readLock.lock();//加读的锁
try {
return value;
} finally {
readLock.unlock();//解读的锁
}
}
/** * @param value 设置value值 */
public void setValue(int value) {
writeLock.lock();//加写的锁
try {
this.value = value;//设置值
} finally {
writeLock.unlock();//解写的锁
}
}
}
线程组
定义
- ThreadGroup来表示线程组,它可以对⼀批线程进⾏分类管理,Java允许程序直接对线程组进⾏控制
- 提供了⼀些操作整体的⽅法, ⽐如设置组中线程的权限,销毁所有所有线程等等
案例
public class ThreadGroupDemo {
public static void main(String[] args) {
Runnable runnable1 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
if (i >= 6) {
System.out.println("------------");
Thread.currentThread().getThreadGroup().interrupt();//添加中断标记
break;
}
}
}
};
Runnable runnable2 = new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("***");
//检测到当前线程存在中断标记,终止代码
if (Thread.interrupted()) {
break;
}
}
}
};
//创建新的线程组
ThreadGroup threadGroup = new ThreadGroup("new ThreadGroup");
//将线程添加到线程组:线程组、线程、线程名称
Thread thread3 = new Thread(threadGroup, runnable1, "3333");
Thread thread4 = new Thread(threadGroup, runnable2, "4444");
System.out.println(thread3.getName() + "\t" + thread3.getThreadGroup().getName()
+ "\t" + thread3.getThreadGroup().getMaxPriority());
System.out.println(thread4.getName() + "\t" + thread4.getThreadGroup().getName()
+ "\t" + thread4.getThreadGroup().getMaxPriority());
thread3.start();
thread4.start();
}
}
线程池
定义
⼀个容纳多个线程的容器,其中的线程可以反复使⽤,省去了频繁创建线程对象的操作,⽆需反复创建线程⽽消耗过多资源
好处
- 降低资源消耗。减少了创建和销毁线程的次数,可以反复利用,执行多个任务
- 提高响应速度
- 方便管理。根据系统性能调配线程数量,防止服务器崩溃
创建
线程池的顶级接口是 java.util.concurrent.Executor,但是它不是线程池,是执行线程的工具,真正的接口是 java.util.concurrent.ExecutorService
Executors
public static ExecutorService newFixedThreadPool(int nThreads) :返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最⼤数量)
public Future<?> submit(Runnable task) :获取线程池中的某⼀个线程对象,并执⾏
Future接⼝:⽤来记录线程任务执⾏完毕后产⽣的结果。线程池创建与使⽤
Executors创建的四种线程池
- newCachedThreadPool(); //可缓冲的线程池 – 更加灵活的回收空置线程,没有可以回收的创建新的线程
- newFixedThreadPool(count); // 定长线程池,可以控制线程最大数量的,超出则等待
- newScheduledThreadPool(count); //定长的线程池,支持定时任务或者周期任务
- newSingleThreadExecutor(); //单线程的线程池,只有一个线程
newFixedThreadPool
定长线程池,可以控制线程最大数量的,超出则等待
步骤
- 创建线程池对象:
ExecutorService service = Executors.newFixedThreadPool(10); - 创建线程Runnable:
Runnable runnable = new Runnable() {} - 提交线程:
Future<?> submit = service.submit(runnable); - 关闭线程:
service.shutdown();
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ThreadPoolDemo {
public static void main(String[] args) {
//创建有界线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//创建线程
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("线程池任务");
}
};
//提交线程到线程池
Future<?> submit = service.submit(runnable);
System.out.println(submit);
//关闭线程池
service.shutdown();
}
}
ScheduledExecutorService
带有定时任务的线程池
步骤
- 创建线程池对象:
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5); - 创建线程:
Runnable runnable1 = new Runnable() {} - 提交线程:
executorService.schedule(runnable1, 3, TimeUnit.SECONDS); - 关闭线程:
executorService.shutdown();
import java.util.concurrent.*;
public class ThreadPoolDemo {
public static void main(String[] args) {
//带有定时任务的线程池 5=核心线程数
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
//创建runnable对象
Runnable runnable1 = new Runnable() {
@Override
public void run() {
System.out.println("延迟3秒运行");
}
};
//重复提交任务
for (int i = 0; i < 10; i++) {
executorService.schedule(runnable1, 3, TimeUnit.SECONDS);
}
//关闭
executorService.shutdown();
}
}
Callable接口
概念
Callable和Runnable接口类似,但是具有泛型返回值,可以声明异常
特点
- 类似与Runnable接口
- 有返回值
- 可以抛出异常
- 调用Call()计算数据
- 支持泛型
实现原理


import java.util.concurrent.*;
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
//创建Callable对象,可以理解成带有计算的Runnable线程,因为带返回值
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "-------");
return 0;
}
};
//创建异步计算对象,也充当Callable和Thread之间的中间件
FutureTask<Integer> task = new FutureTask<>(callable);
//创建Thread线程
Thread thread = new Thread(task);
thread.start();
System.out.println(task.get());
//创建管理线程池的工具,核心线程数=5
ExecutorService service = Executors.newFixedThreadPool(5);
//提交线程
Future<Integer> submit = service.submit(callable);
System.out.println(submit.get());
//延迟20毫秒提交
System.out.println(submit.get(20, TimeUnit.SECONDS));
}
}
注意事项
- FutureTask.get()方法可能会阻塞,因为数据在call后会return,在此之前,get阻塞
- 一般get()放在最后,并通过异步操作执行调用
FutureTask是什么?
就是实现异步调⽤,传⼊callable任务,他会单开⼀个线程去执⾏callable⾥⾯的call⽅法; 在主线程中需要执⾏⽐较耗时的操作时,但⼜不想阻塞主线程时,可以把这些作业交给Future对象在后台完成, 当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执⾏状态。 ⼀般FutureTask多⽤于耗时的计算,主线程可以在完成⾃⼰的任务后,再去获取结果。 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get ⽅法。⼀旦计算完成, 就不能再重新开始或取消计算。get⽅法⽽获取结果只有在计算完成时获取,否则会⼀直阻塞直到任务转⼊完成状态, 然后会返回结果或者抛出异常。
package com.qf.javase.day.day23;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/** * @Author:night_du * @Date:2021/9/29 19:02 */
public class FutureTest {
public static void main(String[] args) throws
ExecutionException, InterruptedException {
MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask(myThread); // 适配器类
new Thread(futureTask, "A").start();
new Thread(futureTask, "B").start(); //结果会被缓存
Object o = futureTask.get(); // 会阻塞,⼀般通过异步操作
System.out.println(o);
}
}
class MyThread implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("call()");
return "123";
}
}
当使⽤两个线程调⽤的时候,发现只输出⼀次,因为结果被缓存了,提⾼了效率
Future Task可用于异步获取执行结果或取消执行任务的场景。通过传入Runnable或者Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过Future Task的get方法异步获取执行结果,因此,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。另外,Future Task还可以确保即使调用了多次run方法,它都只会执行一次Runnable或者Callable任务,或者通过cancel取消Future Task的执行等。
Future接口
Future接⼝提供⽅法来检测任务是否被执⾏完,等待任务执⾏完获得结果,也可以设置任务执⾏的超时时间。这个设置超时的⽅法就是实现Java程序执⾏超时的关键。
常用方法
Future接⼝是⼀个泛型接⼝,严格的格式应该是Future,其中V代表了Future执⾏的任务返回值的类型。
- boolean cancel (boolean mayInterruptIfRunning) 取消任务的执⾏。参数指定是否⽴即中断任务执⾏,或者等等任务结束
- boolean isCancelled () 任务是否已经取消,任务正常完成前将其取消,则返回 true
- boolean isDone () 任务是否已经完成。需要注意的是如果任务正常终⽌、异常或取消,都将返回true
- V get () throws InterruptedException, ExecutionException 等待任务执⾏结束,然后获得V类型的结果。InterruptedException 线程被中断异常, ExecutionException任务执⾏异常,如果任务被取消,还会抛出CancellationException
- V get (long timeout, TimeUnit unit) throws InterruptedException, ExecutionException,TimeoutException 同上⾯的get功能⼀样,多了设置超时时间。参数timeout指定超时时间,uint指定时间的单位,在枚举类TimeUnit中有相关的定义。如果计算超时,将抛出
TimeoutException
使用原理
Futrue有两个子类:FatureTask和SwingWroker<T,V>。FutureTask类实现了Runnable接口,所以可以直接交给Executor执行。
Queue
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
public class QuereDemo {
public static void main(String[] args) {
//创建Queue对象
Queue<Integer> queue = new ArrayBlockingQueue<>(3);
//如果数据为空,不报异常,返回null
Integer poll = queue.poll();//删除
System.out.println(poll);
Integer peek = queue.peek();//查询
System.out.println(peek);
queue.offer(145);//添加,即使超出最长容量,也不会报异常
queue.offer(17);
queue.offer(21);
queue.offer(681);
for (Integer integer : queue) {
//遍历
System.out.println(integer);
}
/*//如果数据为空,报异常:java.util.NoSuchElementException Integer remove = queue.remove();//删除 Integer element = queue.element();//查询 queue.add(11); queue.add(21); queue.add(35); java.lang.IllegalStateException: Queue full queue.add(4); for (Integer integer : queue) { System.out.println(integer); }*/
}
}
ArrayBlockingQueue
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class ArrayBlockingQueueDemo<S> {
public static void main(String[] args) {
BlockingQueue<String> queue = new ArrayBlockingQueue<String>(30);
Thread producer = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
queue.put("放入一个苹果");
System.out.println("进货一个苹果");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread customer = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
String take = queue.take();
System.out.println("消费了一个苹果---" + take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
producer.start();
customer.start();
}
}
Runtime
定义
使用系统命令
使用
public static void main(String[] args) throws IOException {
Runtime runtime = Runtime.getRuntime();
runtime.exec("shutdown -s -t 300");//300秒后关机
runtime.exec("shutdown -a"); //取消关机
}
Timer
定义
定时器
import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo {
private static Timer timer;
public static void main(String[] args) {
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println(1);
//timer.cancel(); //取消定时任务,终结定时器对象
}
};
Timer timer = new Timer();//创建定时器对象
//timer.schedule(task, 2000);
timer.schedule(task, 5000, 1000);//5秒后执行,每一秒后执行一次
}
}
ckingQueue<String>(30);
Thread producer = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
queue.put("放入一个苹果");
System.out.println("进货一个苹果");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread customer = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
String take = queue.take();
System.out.println("消费了一个苹果---" + take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
producer.start();
customer.start();
}
}
Runtime
定义
使用系统命令
使用
public static void main(String[] args) throws IOException {
Runtime runtime = Runtime.getRuntime();
runtime.exec("shutdown -s -t 300");//300秒后关机
runtime.exec("shutdown -a"); //取消关机
}
Timer
定义
定时器
import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo {
private static Timer timer;
public static void main(String[] args) {
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println(1);
//timer.cancel(); //取消定时任务,终结定时器对象
}
};
Timer timer = new Timer();//创建定时器对象
//timer.schedule(task, 2000);
timer.schedule(task, 5000, 1000);//5秒后执行,每一秒后执行一次
}
}
边栏推荐
- 如何看软件测试未来的发展?
- 【p5.js学习笔记】局部变量(let)与全局变量(var)声明
- 【C语言笔记分享】字符函数和字符串函数(建议收藏)
- 【机器学习笔记】Regularization : Ridge Regression(岭回归)
- 软件测试零基础小白学习需要掌握哪些技能?
- 软件测试培训两个月靠谱吗?
- Visual object class introduces Pascal VOC dataset
- 域名解析问题记录
- 100+医学影像数据集集锦
- Interviewer: the actual record of algorithm question brushing.pdf I can't even answer it
猜你喜欢

小白必看的软件测试发展路线

【C语言笔记分享】——动态内存管理malloc、free、calloc、realloc、柔性数组
@RequestMapping详解

【Unity】Timeline学习笔记(七):自定义片段(Clip)

都说软件测试是IT行业最差的,是这样的吗?

Talk about what you know about publishing online (I)

【p5.js学习笔记】鼠标交互事件

C#中virtual(虚方法)的理解以及和abstract(抽象方法)的区别

Can‘t use an undefined value as an ARRAY reference at probe2symbol

R language drawing / drawing / drawing 2
随机推荐
Technical aspects passed easily, HR: those with only three years of experience in large factories are not worth 20K
【C语言笔记分享】字符函数和字符串函数(建议收藏)
JS synchronizes the local time with the server time
leetcode系统性刷题(四)-----哈希表与字符串
Talk about what you know about publishing online (I)
软件测试前景如何?该如何进行学习呢?
7-8 浪漫侧影(25分)建树+新解题思路
都说软件测试是IT行业最差的,是这样的吗?
零基础转行软件测试到底能不能行?
es6 Promise
leetcode系统性刷题(五)-----动态规划
hgu95av2.在线安装失败
[阅读笔记] For Paper:R-CNN系列的三篇论文总结
【p5.js学习笔记】鼠标交互事件
.net MVC的理解
Alibaba P8 architect talk: seven knowledge points (including interview questions) that must be learned well to become an architect
Ggplot2 map
R language drawing / drawing / drawing 2
软件测试真有网上说的那么好吗?
Map R language