文章目录
创建线程的方式
继承Thread类
- 创建一个类继承Thread类
- 重写Thread中的run方法
- 实例化该类并
- 调用start方法即可开启该线程
package com.wcy.code01;
/**
* 继承的方式创建线程
*/
public class ThreadTest01 {
public static void main(String[] args) {
//实例化线程
MyThread t = new MyThread();
t.start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程->" + i);
}
}
}
/**
* 继承Thread类
* 并且重写run方法
* run方法体中的代码就是该线程需要执行的代码
*/
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("子线程->" + i);
}
}
}
实现Runnable接口
- 创建一个类实现Runnable接口
- 实现run方法
- 实例化一个类
- 将该实例传入Thread构造函数
package com.wcy.code01;
public class ThreadTest02 {
public static void main(String[] args) {
RunnableClass r = new RunnableClass();
//将Runnable对象传入Thread中实例化一个线程
Thread t = new Thread(r);
t.start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程->" + i);
}
}
}
/**
* 实现Runnable接口
* 实现run方法
* 方法体中的语句是该线程需要执行的语句
*/
class RunnableClass implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("子线程->" + i);
}
}
}
- 匿名内部类方式
- 使用匿名内部类的方式实例化一个线程
package com.wcy.code01;
/**
* 匿名内部类方式
*/
public class ThreadTest03 {
public static void main(String[] args) {
//实例化一个线程
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("子线程->" + i);
}
}
});
//开辟线程
t.start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程->" + i);
}
}
}
实现Callable接口
- jdk8后可以使用
- 该方法可以获取线程的返回值
- 前面的方法不可获取线程的返回值
- 获取线程的返回值时,效率会降低
package com.wcy.code04;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest01 {
public static void main(String[] args) {
FutureTask futureTask = new FutureTask<>(new CallableClass());
Thread thread = new Thread(futureTask);
thread.start();
try {
//获取线程的返回值
//该方法会阻塞
//会一直等到该线程结束 获取到返回值后才能继续往下执行
Object o = futureTask.get();
System.out.println(o);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
System.out.println("main 结束");
}
}
class CallableClass implements Callable{
@Override
public Object call() throws Exception {
System.out.println("Callable begin");
Thread.sleep(1000*5);
System.out.println("Callable end");
return 300;
}
}
package com.wcy.code04;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest01 {
public static void main(String[] args) {
FutureTask futureTask = new FutureTask<>(new Callable(){
@Override
public Object call() throws Exception {
System.out.println("Callable begin");
Thread.sleep(1000*5);
System.out.println("Callable end");
return 300;
}
});
Thread thread = new Thread(futureTask);
thread.start();
try {
//获取线程的返回值
//该方法会阻塞
//会一直等到该线程结束 获取到返回值后才能继续往下执行
Object o = futureTask.get();
System.out.println(o);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
System.out.println("main 结束");
}
}
线程的生命周期
线程的五种状态
-
新建状态
当用new操作符创建一个线程时。此时程序还没有开始运行线程中的代码。 -
就绪状态
一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。
处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序来调度的。 -
运行状态(running)
当线程获得CPU时间片(执行权)后,它才进入运行状态,真正开始执行run()方法。 -
阻塞状态(blocked)
线程运行过程中,可能由于各种原因进入阻塞状态:
①线程通过调用sleep方法进入睡眠状态;
②线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
③线程试图得到一个锁,而该锁正被其他线程持有;
④线程在等待某个触发条件;
所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。
线程被堵塞可能是由下述五方面的原因造成的:
(1) 调用sleep(毫秒数),使线程进入"睡眠"状态。在规定的时间内,这个线程是不会运行的。
(2) 用suspend()暂停了线程的执行。除非线程收到resume()消息,否则不会返回"可运行"状态。
(3) 用wait()暂停了线程的执行。除非线程收到nofify()或者notifyAll()消息,否则不会变成"可运行"(是的,这看起来同原因2非常相象,但有一个明显的区别是我们马上要揭示的)。
(4) 线程正在等候一些IO(输入输出)操作完成。
(5) 线程试图调用另一个对象的"同步"方法,但那个对象处于锁定状态,暂时无法使用。 -
死亡状态(dead)
有两个原因会导致线程死亡:
①run方法正常退出而自然死亡;
②一个未捕获的异常终止了run方法而使线程猝死;
为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法,如果是可运行或被阻塞,这个方法返回true;如果线程仍旧是new状态且不是可运行的,或者线程死亡了,则返回false。
相关方法
方法 | 作用 |
---|---|
void setName(String name) | 设置该线程的名字 不设置默认的名字为Thread-x |
String getName() | 获取该线程的名字 |
static Thread currentThread() | 返回对当前正在执行的线程对象的引用。 这是一个静态方法,该方法在哪个位置引用就返回当前的线程对象 |
package com.wcy.code01;
public class ThreadTest04 {
public static void main(String[] args) {
MyThread01 t1 = new MyThread01();
//设置名字
t1.setName("t1");
System.out.println(t1.getName());
t1.start();
MyThread01 t2 = new MyThread01();
t2.setName("t2");
System.out.println(t2.getName());
t2.start();
MyThread01 t3 = new MyThread01();
//Thread-2
System.out.println(t3.getName());
t3.start();
//获取当前线程对象
Thread currentThread = Thread.currentThread();
//main方法线程对象的名字就是main
System.out.println(currentThread.getName());
}
}
class MyThread01 extends Thread {
@Override
public void run() {
Thread currentThread = Thread.currentThread();
System.out.println("当前线程为->" + currentThread.getName());
}
}
线程的中断
- static void sleep(long millis)
使当前正在执行的线程以指定的毫秒数暂停,进入睡眠状态
放弃当前线程的执行权让其他线程使用
这行代码出现在哪个进程中,哪个进程就会休眠!!!
package com.wcy.code01;
public class ThreadTest05 {
public static void main(String[] args) {
long t1Start = System.currentTimeMillis();
MyThread02 t1 = new MyThread02();
t1.setName("t1");
t1.start();
//虽然是t1调用的sleep
//但是sleep是一个静态方法
//最终t1.sleep()-->Thread.sleep()
//所以这里会让main线程睡眠 并不会让t1线程睡眠
//所以sleep方法在哪个线程出现就让哪个线程睡眠
try {
t1.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//10秒之后才会打印该信息
System.out.println("hello");
long t1End = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "执行时长" + (t1End - t1Start) / 1000 + "s");
}
}
class MyThread02 extends Thread {
@Override
public void run() {
long t1Start = System.currentTimeMillis();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
try {
//睡眠1秒 每秒打印一次
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long t1End = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "执行时长" + (t1End - t1Start) / 1000 + "s");
}
}
只需记得sleep在哪调用,该线程就睡眠!!!
- void interrupt() 中断线程睡眠
触发InterruptedException异常达到中断睡眠的目的
package com.wcy.code01;
/**
* 终止线程的睡眠
*/
public class ThreadTest06 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"->begin");
//这里的异常只能用try catch捕捉 不能用throw抛出
//因为子类抛出的异常不能比父类的多
try {
Thread.sleep(1000*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"->end");
}
});
t1.setName("t1");
t1.start();
// 五秒后中断t1的睡眠
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//采用异常机制来中断睡眠
//调用该方法后会触发t1中的InterruptedException异常达到中断的目的
t1.interrupt();
}
}
线程的终止
终止线程的方式
1 stop()方法,不过该方法已经弃用,不推荐使用,该方法会直接杀死该线程,可能会造成数据丢失
2 设置运行标志位 这是通用的方法
package com.wcy.code02;
/**
* 线程终止
*/
public class ThreadTest07 {
public static void main(String[] args) {
RunnableClass r1 = new RunnableClass();
Thread t1 = new Thread(r1);
t1.setName("t1");
t1.start();
//五秒后终止t1线程
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//终止线程的方式
//1 stop()方法,不过该方法已经弃用,不推荐使用,该方法会直接杀死该线程,可能会造成数据丢失
//2 设置运行标志位 这是通用的方法
//t1.stop();
r1.isRun = false;
}
}
class RunnableClass implements Runnable {
//运行标志位
boolean isRun = true;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (isRun) {
//每秒打印一次
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->" + i);
} else {
//可以在这里执行保存数据等操作
System.out.println("数据保存完毕!");
//直接结束
return;
}
}
}
}
线程调度
线程调度模型
- 均分式调度模型
是指让所有的线程轮流获得cpu的使用权,并且平均分配每个线程占用的CPU的时间片 - 抢占式调度模型
是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃CPU。
java中使用的是抢占式调度模型
java中线程的最高优先级10,默认优先级5,和最低优先级1
package com.wcy.code02;
/**
* 线程调度
* 线程调度模型
* 1.抢占式调度模型:优先级不同
* 2.均分式调度模型:优先级相同
*/
public class ThreadTest08 {
public static void main(String[] args) {
//java中线程的最高优先级10,默认优先级5,和最低优先级1
//优先级高的线程会有更多的机会抢占cpu时间片
//并不是优先级高就先完全执行完毕
//优先级高的线程会大部分先执行完 cpu会分配更多的执行资源用于执行该线程
System.out.println("最高优先级:" + Thread.MAX_PRIORITY);
System.out.println("默认优先级:" + Thread.MIN_PRIORITY);
System.out.println("最低优先级:" + Thread.NORM_PRIORITY);
Thread t1 = new Thread(new RunnableClass01());
t1.setName("t1");
t1.setPriority(Thread.MAX_PRIORITY);
//main 5
System.out.println(Thread.currentThread().getName() + "线程的优先级为" + Thread.currentThread().getPriority());
//t1 10
System.out.println(t1.getName() + "线程的优先级为" + t1.getPriority());
t1.start();
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
class RunnableClass01 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
线程让位
- static void yield()
- 这是一个非阻塞的方法,放弃当前线程的时间片给其他线程使用
- 该方法只是让当前线程的状态从运行状态回到就绪状态
- 回到就绪状态后可以重新抢占时间片
package com.wcy.code02;
/**
* 线程让位 yield
* static void yield()
* 这是一个非阻塞的方法,放弃当前线程的时间片给其他线程使用
* 该方法只是让当前线程的状态从运行状态回到就绪状态
* 回到就绪状态后可以重新抢占时间片
*/
public class ThreadTest09 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
//每运行100次进行一次线性让位
if (i % 100 == 0) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
});
t1.setName("t1");
t1.start();
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
线程的合并
- void join()
- 调用这个方法的就会将该线程合并到当前运行的线程中
- 直到这个线程运行完毕后才会继续向下执行
package com.wcy.code02;
/**
* 线程合并 join
* 调用这个方法的就会将该线程合并到当前运行的线程中
* 直到这个线程运行完毕后才会继续向下执行
*/
public class ThreadTest10 {
public static void main(String[] args) {
System.out.println("main start");
MyThread t1 = new MyThread();
t1.setName("t1");
t1.start();
try {
//合并t1线程到当前线程中
//直到t1线程执行完毕才会继续往下执行
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
System.out.println("main end");
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
线程安全
出现线程安全问题的条件
- 多线程并发
- 有共享数据
- 共享数据有修改的行为
满足以上的条件就会存在线程安全的问题
java的三大变量的线程安全问题
- 实例变量
实例变量会有线程安全的问题,实例变量存储在堆内存中,堆内存只有一个,有可能多个线程会同时引用实例变量 - 静态变量
- 实例变量会有线程安全的问题,实例变量存储在方法区内存中,方法区内存只有一个,有可能多个线程会同时引用静态变量
- 局部变量
局部变量不会存在线程安全问题,因为局部变量存储在栈中,每个线程都有自己的栈,所以局部变量不共享
java线程安全的解决方法
- 线程同步机制
让线程排队执行,不能并发,牺牲效率保护数据的安全
异步编程模型和同步编程模型
- 异步编程模型
线程之间互不干扰,线程并发执行 - 同步编程模型
线程之间会发生等待关系,线程需要排队执行
异步并发,同步排队
线程同步机制
-
语法
synchronized(共享对象){
同步代码块
}
共享对象:多个线程共享的对象,如果多个线程都会操作这个对象,此时的这个对象就是共享对象
如果说想要t1,t2,t3线程同步,并且这三个线程都是操作对象o1,那么就需要j将o1放入共享对象位置上 -
synchronized的执行原理
synchronized(o1){ //执行的代码 }
在java的每个对象中都会有一把锁,锁是一个标记,有多少个对象就会有多少把锁
每个类会有一个类锁,不论怎么样都只有一个类锁
线程执行synchronized中的代码需要有共享对象的对象锁同步机制执行过程:
1.t1和t2线程并发执行,开始执行代码到synchronized处的代码
2.假设t1首先执行,遇到了synchronized关键字,这时会t1寻找共享对象的对象锁,并且占有它,然后执行同步代码块,在程序执行中一直都会占有共享对象的对象锁。知道t1执行结束,才会释放对象锁。
3.当t2后来执行遇到了synchronized关键字,此时也会也寻找共享对象的对象锁,但是此时t1正在执行并且占有了对象锁,所以t2此时找不到对象锁,只有等t1执行完毕后,t2才会找到对象锁,执行同步代码。
简而言之:当线程并发操作共享对象,遇到synchronized关键字,下一个线程只有等上一个线程执行完毕之后才会执行。
-
synchronized可以出现在实例方法上
public synchronized void doSome(){
//同步代码块
}
这时共享对象一定是当前对象this,并且不可改变
- synchronized可以出现在静态方法上
public synchronized static void doSome(){
//同步代码块
}
此时共享的对象是静态方法所在的类,线程找的是类锁,因为类锁只有一个,所以当其他线程调用的是该类中的其他synchronized修饰静态方法时,也要等到该静态方法结束。
模拟账户取钱案例
账户类
class Account {
//用户名
private String name;
//余额
private int money;
public Account(String name, int money) {
this.name = name;
this.money = money;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
/**
* 模拟取钱机制
*
* @param num:取出的金额
*/
public void drawMoney(int num) {
//取出后更改余额
int currentMoney = this.getMoney() - num;
this.setMoney(currentMoney);
//打印取款信息
System.out.println(
Thread.currentThread().getName()+"-->"+this.getName() + "取出" + num + ",余额" + this.getMoney());
}
}
执行取钱操作的线程类
class MyThread extends Thread {
Account account;
public MyThread(Account account) {
this.account = account;
}
@Override
public void run() {
//取钱5000
this.account.drawMoney(5000);
}
}
- 首先不考虑线程安全的问题,执行一个账户在多个线程中取钱的操作
public class TheadSafeTest01 {
public static void main(String[] args) {
Account zs = new Account("张三", 10000);
MyThread t1 = new MyThread(zs);
MyThread t2 = new MyThread(zs);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
如果不考虑线程安全,这里执行后会出现几种情况
- t1先取出了5000,然后还没有更新余额,此时t2进来了,在没有t1取出5000还更改余额的基础上,取出了5000元,然后t1才更改余额,接着t2也在10000的基础上更改余额,此时取出来了10000,但是余额还有5000.
- 正常情况,t1取完了之后,t2才接着取,余额为零
3.取钱的流程正常,打印结果不正常。在t1取完钱并设置余额位5000后,还没有显示取款信息,然后此时t2也执行了上面的操作将余额设置为0,然后t1才去查询余额打印,所以两次余额都是0.
其中第一种情况是最要命的,这是完全不正确的逻辑,而且当在取完钱还没有更改余额之前发生了网络延迟,那么一定会发生这种情况,模拟就是这样
public void drawMoney(int num) {
//取出
int currentMoney = this.getMoney() - num;
//模拟延迟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更改余额
this.setMoney(currentMoney);
System.out.println(
Thread.currentThread().getName()+"-->"+this.getName() + "取出" + num + ",余额" + this.getMoney());
}
所以说这是一种完全不正确的逻辑,必须杜绝,所以接下来我们使用java解决线程安全的线程同步机制来解决这种问题。
将取钱的方法改为如下形式,线程共享对象是this,所以是当前的账户对象,两个线程同时使用,当一个线程先到来之后,会占有对象锁,后面来的线程就必须要等到前面的线程执行完毕才能执行,从而避免了上面的问题。
public void drawMoney(int num) {
//线程同步机制
synchronized (this) {
//取出
int currentMoney = this.getMoney() - num;
//模拟延迟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更改余额
this.setMoney(currentMoney);
System.out.println(
Thread.currentThread().getName() + "-->" + this.getName() + "取出" + num + ",余额" + this.getMoney()
);
}
}
也可以是如下形式
public synchronized void drawMoney(int num) {
//线程同步机制
//取出
int currentMoney = this.getMoney() - num;
//模拟延迟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更改余额
this.setMoney(currentMoney);
System.out.println(
Thread.currentThread().getName() + "-->" + this.getName() + "取出" + num + ",余额" + this.getMoney()
);
}
死锁
synchnized嵌套使用不注意会引发死锁,难以调试,尽量避免使用。
- 死锁实现
代码中由于t1先线程占用了对象o1的对象锁然后睡眠,此时t2也占用了对象o2的对象锁,当睡眠醒来后,t1需要占用o2的对象锁,t2需要占用o1的对象锁,但是两个对象的对象锁已经被互相占用,此时两个线程都会一直无休止等待,程序并不会报错。这就是死锁。
package com.wcy.code03;
public class DeadLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
MyThread01 t1 = new MyThread01(o1, o2);
MyThread02 t2 = new MyThread02(o1, o2);
t1.start();
t2.start();
}
}
class MyThread01 extends Thread{
Object o1, o2;
public MyThread01(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o2){
//确保死锁发生
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
}
}
}
}
class MyThread02 extends Thread{
Object o1, o2;
public MyThread02(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
}
}
}
}
实际中解决线程安全的方案
- 尽量使用局部变量代替实例变量
- 必须要使用实例变量,就创建多个对象
- 如果必须只创建一个对象,再使用synchronized
守护线程
java中线程的分类
- 用户线程
- 守护线程(后台线程)
守护线程的特点:守护线程是一个无限循环,当用户线程结束之后,守护线程也会结束,java中的垃圾回收机制就是一个守护线程。
- 实现守护线程
package com.wcy.code04;
public class DaemonThread {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.setName("t1");
//将线程设置为守护线程
//用户进程结束后会自动结束守护线程
t1.setDaemon(true);
t1.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + (i));
}
}
}
class MyThread extends Thread {
@Override
public void run() {
int i = 0;
while (true) {
System.out.println(Thread.currentThread().getName() + "-->" + (++i));
}
}
}
定时器
- 每间隔一段时间执行一件特定的事情
- 定时器的实现
- 使用sleep()实现,这种方式比较麻烦,需要自己手写
- 使用util工具包中定义的Timer类实现
定时器Timer的用法
- 构造方法
Timer()
创建一个新的计时器。
Timer(boolean isDaemon)
创建一个新的定时器,其相关线程可以指定为守护线程 。
Timer(String name)
创建一个新的定时器,其相关线程具有指定的名称。
Timer(String name, boolean isDaemon)
创建一个新的定时器,其相关线程具有指定的名称,可以指定为守护线程 。 - 相关方法
void cancel()
终止此计时器,丢弃任何当前计划的任务。
int purge()
从该计时器的任务队列中删除所有取消的任务。
void schedule(TimerTask task, Date time)
在指定的时间安排指定的任务执行。
void schedule(TimerTask task, Date firstTime, long period)
从指定 的时间开始 ,对指定的任务执行重复的 固定延迟执行 。
void schedule(TimerTask task, long delay)
在指定的延迟之后安排指定的任务执行。
void schedule(TimerTask task, long delay, long period)
在指定 的延迟之后开始 ,重新执行 固定延迟执行的指定任务。
void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
从指定的时间 开始 ,对指定的任务执行重复的 固定速率执行 。
void scheduleAtFixedRate(TimerTask task, long delay, long period)
在指定的延迟之后 开始 ,重新执行 固定速率的指定任务。
方法主要了解void schedule(TimerTask task, Date firstTime, long period)
TimerTask task
:一个TimerTask任务
Date firstTime
:第一次执行时间
long period
:间隔时间
Timer实现定时器任务
package com.wcy.code04;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TimerTest {
public static void main(String[] args) {
Timer timer = new Timer();
//设置一个开始时间
Date firstTime = new Date();
//日期模板
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
try {
//将该时间解析为Date类型
firstTime = sdf.parse("2021-06-28 22:22:50");
} catch (ParseException e) {
e.printStackTrace();
}
//设置定时器任务 间隔10s
timer.schedule(new MyTimeTask(), firstTime, 1000*10);
}
}
/**
* 定时任务类
*/
class MyTimeTask extends TimerTask{
@Override
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String date = sdf.format(new Date());
System.out.println(date+"执行了一次任务!");
}
}
wait和notify方法
Object类中的wait和notify方法,所以继承Object的类中都有这两个方法,这两个方法都不是通过线程来调用的,是通过对象调用
- wait()
Object o = new Object();
o.wait()
该方法的作用是让在o对象上活动的线程进入无限期等待状态,直到被唤醒为止
- notify()
Object o = new Object();
o.notify()
该方法的作用是唤醒o对象上处于等待状态的线程
- notifyAll()
Object o = new Object();
o.notifyAll()
该方法的作用是唤醒o对象上所有处于等待的线程
重点:
wait和notify方法都是建立在synchronized线程同步的基础之上的
o.wait()方法调用后会然正在o上活动的线程进入等待的状态,并且放弃o的对象锁
o.notify()方法调用后只会通知等待的线程醒过来,并不会放弃o的对象锁
消费者和生产者模式
- 生产者模式和消费者模式是专门为了解决某个问题而产生的
- 最终达到的目的是要使消费和生产达到平衡状态
举个例子来说明:
有一个生产者和一个消费者,共同使用一个仓库。
生产者只负责生产产品放入仓库中
消费者只负责从仓库中取出产品
如果消费者和生产者是不同的两个线程,仓库是这两个线程共同操作的对象
那么根据线程安全的条件
- 多线程并发
- 有共享数据
- 共享数据有修改的行为
这两个线程必须要使用synchroized线程同步机制,来保证数据的安全。
当仓库里没有产品了,生产者必须生产,而消费者就必须要停止消费
此时生产者线程必须工作,而消费者线程就必须等待,直到仓库中有产品了,才会唤醒消费者线程。
停止消费线程的过程:当消费者线程抢到执行权后,占据仓库的对象锁,然后判读仓库此时的容量,如果容量为空,就用仓库调用wait方法,此时消费者线程就释放对象锁,进入等待状态。当生产者线程生产完毕后会调用notify方法来唤醒沉睡的消费者线程,继续抢夺执行权
如果仓库存满了,那么生产者就必须停止生产,消费者必须消费产品
此时生产者线程必须停止工作,而消费者线程就必须消费产品,直到仓库中有剩余容量了,才会唤醒生产者线程。
停止生产线程的过程:当生产者线程抢到执行权后,占据仓库的对象锁,然后判读仓库此时的容量,如果容量满了,就用仓库调用wait方法,此时生产者线程就释放对象锁,进入等待状态。当消费者线程消费完毕后会调用notify方法来唤醒沉睡的生产者线程,继续抢夺执行权。
实现生产者和消费者模式的两个例子
- 上述仓库的例子
package com.wcy.code05;
import java.util.ArrayList;
/**
* 生产者和消费者模式
* 仓库实例
* 仓库空了不能消费
* 仓库满了不能生产
*/
public class ThreadTest02 {
public static void main(String[] args) {
//使用数组来模拟仓库
//仓库的容量设定为1, 所以生产一次就消费一次
ArrayList<Object> list = new ArrayList<>();
//消费者和生产者线程
Production production = new Production(list);
Consumer consumer = new Consumer(list);
production.setName("生产者");
consumer.setName("消费者");
production.start();
consumer.start();
}
}
/**
* 生产者
*/
class Production extends Thread {
ArrayList<Object> list;
public Production(ArrayList<Object> list) {
this.list = list;
}
@Override
public void run() {
//记录一下产品的编号
int count = 1;
while (true) {
synchronized (list) {
//如果仓库满了 生产者停止生产 进入等待状态
if (list.size() != 0) {
try {
//等待
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果仓库没有满 就生产产品 添加到仓库中
String s = "产品" + count;
list.add(s);
System.out.println(getName() + "生产了-->" + s);
count += 1;
//唤醒沉睡的消费者者线程
list.notifyAll();
}
}
}
}
/**
* 消费者
*/
class Consumer extends Thread {
ArrayList<Object> list;
public Consumer(ArrayList<Object> list) {
this.list = list;
}
@Override
public void run() {
while (true) {
synchronized (list) {
//仓库容量为0 消费者停止 生产者开始
if (list.size() == 0) {
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(getName()+"消费了-->"+ list.get(0));
list.remove(0);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒生产者线程
list.notifyAll();
}
}
}
}
- 交替打印奇数和偶数
package com.wcy.code05;
import java.util.ArrayList;
/**
* 生产者和消费者模式实现
* 要求:
* 用两个线程来实现交替输出奇数和偶数
* 一个线程输出奇数
* 另一个线程输出偶数
* 必须交替输出奇数和偶数
*/
public class ThreadTest01 {
public static void main(String[] args) {
ArrayList<Object> num = new ArrayList<>();
num.add(1);
PrintOddNumber odd = new PrintOddNumber(num);
PrintEvenNumber even = new PrintEvenNumber(num);
odd.setName("奇数线程");
even.setName("偶数线程");
odd.start();
even.start();
}
}
/**
* 奇数线程
*/
class PrintOddNumber extends Thread {
ArrayList<Object> num;
public PrintOddNumber(ArrayList<Object> num) {
this.num = num;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//使用同步机制锁住num对象
synchronized (num) {
//判断当前的是不是奇数
int currentNum = (int) num.get(0);
if (currentNum % 2 == 0) {
try {
//不是奇数 就放弃时间片 进入等待状态
num.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//输出并加1
System.out.println(currentThread().getName() + "-->" + num.get(0));
int newNum = (int) num.get(0) + 1;
num.set(0, newNum);
//执行结束后唤醒等待的线程
num.notifyAll();
}
}
}
}
/**
* 偶数线程
*/
class PrintEvenNumber extends Thread {
ArrayList<Object> num;
public PrintEvenNumber(ArrayList<Object> num) {
this.num = num;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//使用同步机制
synchronized (num) {
//判断是否为偶数
int currentNum = (int) num.get(0);
if (currentNum % 2 != 0) {
try {
//不是偶数 就放弃时间片 进入等待
num.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(currentThread().getName() + "-->" + num.get(0));
int newNum = (int) num.get(0) + 1;
num.set(0, newNum);
//结束后唤唤醒等待的线程
num.notifyAll();
}
}
}
}
码子真不容易~