单线程
/*
什么是单线程:
单线程在程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行
单线程处理的优点:
同步应用程序的开发比较容易,但由于需要在上一个任务完成后才能开始新的任务,所以其效率通常比多线程应用程序低。
*/
public class Demo {
public static void main(final String[] args) {
// 代码1
show();
}
public static void show(){
// 代码11
fun1();
fun2();
//代码22
}
public static void fun1() {
//代码111
}
public static void fun2() {
//代码222
}
}
进程、线程;串行、并行、并发;Java程序的运行原理
/*
进程:
正在运行的程序,是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。
线程:
是存在于进程中的,一个进程可以包含多个线程的。
单个线程是进程中的单个顺序控制流,或者说就是一个单独执行路径
一个进程如果只有一条执行路径,称之为单线程程序
一个进程如果包含多条执行路径,称之为多线程程序。
多线程举例:
网盘下载,扫雷,杀毒软件。
了解三个名词:
1、串行:指的是所有的任务都是按照先后顺序执行的,在前一个任务没处理完的情况下
是不会处理下一个任务的。
比如理发店只有一个理发师,每个人剪头发的时候都需要等待前面的人剪完。
2、并行:指的是将任务分给不同的处理器去处理,每一个处理器中的处理是串行。
比如火车站售票会有多个窗口。
3、并发:实质上是一种现象,并发需要处理器的支持,比如在处理一个任务的时候,操作系统
可以进行调度其他的任务,不论串行还是并行,都需要操作系统的支持并发。
假设喝水是一个任务,每个火车售票员在卖票的同时也能喝水,就说明支持并发。
java程序的运行原理:
java命令会启动一个java虚拟机。启动JVM,相当于启动了一个应用程序
也就是启动了一个进程。
该进程会自动启动一个“主线程”,然后由这个主线程去调用某个类的main方法
main方法运行在主线程中的。我们之前写的程序都是单线程程序。
思考题:
JVM启动的时候是单线程还是多线程的呢?
多线程。
主线程
垃圾回收线程
JVM在启动的时候,最低要求要有两个线程,JVM启动的时候是多线程。
*/
public class MyThreadDemo {
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person();
Person p3 = new Person();
Person p4 = new Person();
Person p5 = new Person();
Person p6 = new Person();
Person p7 = new Person();
Person p8 = new Person();
Person p9 = new Person();
//...pn
//用for循环创建100000个对象,那么在栈中创建那么多对象,在堆中开辟了那么多空间
//为什么没有报错呢?或者说为什么没有发生内存溢出呢?
//那是因为JVM启动至少启动了垃圾回收线程和主线程,
//所以JVM是多线程的。
for(int i=0;i<100000;i++){
Person p = new Person();
}
}
}
创建线程的第一种方式:继承Thread类
/*
为什么要重写run方法呢?
1、每一个线程实现的功能都不一样,所以要重写
2、要启动一个线程就会调用run方法,只有当代码需要被线程执行的时候
再将被需要的代码写到run方法里
*/
public class MyThread1 extends Thread {
@Override
public void run() {
//写我们自己的逻辑代码
//System.out.println("数加科技,yyds");
//一般来说,被执行线程的代码都是比较耗时的,为了模拟耗时
//我这边就用循环模拟
for(int i=0;i<100;i++){
System.out.println(i);
}
}
}
/*
创建线程的第一种方式:继承Thread类(java.lang下的),重写run()方法
步骤:
1、自定义一个类继承Thread类
2、自定义类重写Thread类中的run()方法
3、创建子类对象
4、启动线程
*/
public class MyThreadDemo2 {
public static void main(String[] args) {
//创建子类对象
//每new一次相当于创建了一个线程(但是仅仅只是创建,并没有启动)
//MyThread1 t1 = new MyThread1();
//t1.run();
//t1.run();
//单纯调用run方法仅仅表示的是普通的方法调用,所以这里是单线程执行的
//要想看到多线程的效果,就必须调用指定的启动线程的方法:start()
//面试题:调用run方法和调用start方法的区别?
//run()方法的调用仅仅是封装了线程执行的代码,但是直接调用的话与普通方法调用没有任何区别
//start()方法的调用,首先启动一个线程,然后由JVM去调用该线程的run方法
//t1.start();
//IllegalThreadStateException:非法的线程状态异常
//因为调用了两次start()方法,相当于t1这个线程被调用了两次,而不是启动了两个线程的意思
//t1.start();
System.out.println("===================================");
//模拟多线程环境
//至少创建2个或者2个以上的线程
MyThread1 t1 = new MyThread1();
MyThread1 t2 = new MyThread1();
t1.start();
t2.start();
}
}
如何获取和设置线程名称
//String getName()
//返回此线程的名称。
//假如我不给线程起名字通过getName()方法获取到的线程的名字会是什么呢?
//通过Thread无参构造方法源码发现:
//返回的是:Thread-threadInitNumber++(threadInitNumber初始默认值为0)
public class MyThread2 extends Thread {
public MyThread2() {
super();
}
public MyThread2(String name) {
//调用父类的有参构造方法
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
/*
如果获取和设置线程的名字呢?
1、通过构造方法设置名字
Thread(String name) 分配一个新的 Thread对象。
2、通过Thread类中
void setName(String name) 将此线程的名称更改为等于参数 name 。
*/
public class MyThreadDemo3 {
public static void main(String[] args) {
//创建线程对象
//通过构造方法给线程起名字
//由于是多线程,创建的个位为两个或者两个以上
// MyThread2 t1 = new MyThread2("朱佳乐");
// MyThread2 t2 = new MyThread2("小虎");
// t1.start();
// t2.start();
// 通过setName给线程起名字
// MyThread2 t1 = new MyThread2();
// MyThread2 t2 = new MyThread2();
// MyThread2 t3 = new MyThread2();
//
// 给线程起名字
// t1.setName("张咪");
// t2.setName("张梦云");
// t3.setName("刘梦云");
//
// //启动线程
// //线程的执行具有随机性
// t1.start();
// t2.start();
// t3.start();
//public static Thread currentThread()
//获取当前方法所在的线程名称
//现在获取的是main方法所在的线程名称
String name = Thread.currentThread().getName();
System.out.println("main方法所在的线程名称为:" + name);//main
}
}
线程调度
public class MyPriorityThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
/*
线程调度:
假如我们的计算机只有一个 CPU(一核),那么CPU在某一个时刻只能执行一条指令,
线程只有得到CPU时间片,也就是使用权,才可以执行指令。
那么Java是如何对线程进行调用的呢?
线程有两种调度模型:
分时调度模型
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
抢占式调度模型
优先让优先级高的线程使用CPU,如果线程的优先级相同,
那么会随机选择一个占用CPU的时间片,
优先级高的线程获取的CPU时间片相对多一些。(注意是相对而不是绝对)
Java使用的是抢占式调度模型。
我们在前几个代码中都没有设置优先级,我们猜测应该有一个默认的优先级
默认的优先级是多少呢?
默认的优先级是: 5
获取线程的优先级
public final int getPriority()
设置线程的优先级
public final void setPriority(int newPriority)
观察源码发现:线程的优先级范围是1-10
注意事项:
1、线程的默认优先级为5
2、线程的优先级范围是1-10
3、线程的优先级高仅仅表示的是获取CPU时间片的机率会高一些,
并不代表一定是最先获取的。
*/
public class ThreadPriorityDemo {
public static void main(String[] args) {
//创建2个或者2个以上的线程对象
MyPriorityThread t1 = new MyPriorityThread();
MyPriorityThread t2 = new MyPriorityThread();
t1.setName("朱佳乐");
t2.setName("张咪");
//获取线程的优先级
// int p1 = t1.getPriority();
// System.out.println(p1);//5
// int p2 = t2.getPriority();
// System.out.println(p2);//5
//设置优先级
//非法的优先级设置异常
//观察源码发现:线程的优先级范围是1-10
//IllegalArgumentException
// t1.setPriority(1000);
// t2.setPriority(0);
t1.setPriority(10);
t2.setPriority(1);
//启动线程
t1.start();
t2.start();
}
}
线程控制
public class MySleepThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
//加入一个休眠方法
//睡1秒钟,1秒=1000毫秒
//此时会发生编译时期异常
//InterruptedException:被打断异常
//try...catch...处理
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/*
线程休眠
public static void sleep(long millis)
以毫秒为单位的睡眠时间长度
那么sleep()方法在哪里加呢?
在重写的run()方法里面加
*/
public class ThreadSleepDemo {
public static void main(String[] args) {
MySleepThread t1 = new MySleepThread();
MySleepThread t2 = new MySleepThread();
MySleepThread t3 = new MySleepThread();
t1.setName("张咪");
t2.setName("张梦云");
t3.setName("刘梦云");
t1.start();
t2.start();
t3.start();
}
}
public class MyJoinThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
/*
加入线程:其他线程等待调用该方法的线程死亡,再执行
调用join()方法在该线程调用start方法之后调用。
其他线程要为实现线程加入的线程让步。(让其先执行)
*/
public class ThreadJoinDemo {
public static void main(String[] args) {
MyJoinThread t1 = new MyJoinThread();
MyJoinThread t2 = new MyJoinThread();
MyJoinThread t3 = new MyJoinThread();
t1.setName("张咪");
t2.setName("朱佳乐");
t3.setName("小虎");
//让t3先执行
//此时会出现编译异常
t3.start();
try {
//调用join()方法在start()方法之后调用
t3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//此时t1和t2会等t3先执行完之后
//再相互抢占CPU的时间片
t1.start();
t2.start();
}
}
public class MyYieldThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
Thread.yield();
}
}
}
/*
礼让线程:public static void yield()
它的作用是让线程执行起来之间看起来更加和谐一点(没什么作用)
该方法在哪里调用呢?
在重写的run()方法中调用
*/
public class ThreadYieldDemo {
public static void main(String[] args) {
MyYieldThread t1 = new MyYieldThread();
MyYieldThread t2 = new MyYieldThread();
t1.setName("小虎");
t2.setName("冯提莫");
t1.start();
t2.start();
}
}
public class MyDaemonThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(getName() + ":" + i);
}
}
}
/*
后台线程 public final void setDaemon(boolean on)
(守护线程)
java中有两类线程:用户线程(非守护线程),守护线程
用户线程:我们在学习多线程之前的所有代码,运行起来的时候都是一个个的用户线程
守护线程:所谓的守护线程,指的就是程序运行的时候在后台提供一个通用的服务线程
比如说垃圾回收线程,它就是一个称职的守护线程,并且这个线程是程序不可或缺一部分
或者说只要程序存在非守护线程,程序就不会停止
如果一个程序都是守护程序,程序就停止了。
注意:
设置守护线程必须在启动之前设置
*/
public class ThreadDaemonDemo {
public static void main(String[] args) {
MyDaemonThread t1 = new MyDaemonThread();
MyDaemonThread t2 = new MyDaemonThread();
MyDaemonThread t3 = new MyDaemonThread();
//刘备是非守护线程(用户线程)
t1.setName("刘备");
t2.setName("关羽");
//设置关羽为刘备的守护线程
t2.setDaemon(true);
t3.setName("张飞");
//设置张飞为刘备的守护线程
t3.setDaemon(true);
t1.start();
t2.start();
t3.start();
}
}
import java.util.Date;
public class MyStopThread extends Thread {
@Override
public void run() {
System.out.println("开始执行时间:" + new Date());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束时间:" + new Date());
System.out.println("hello");
}
}
/*
中断线程:
public final void stop():
让正在运行的线程停止,run方法剩下的代码不会执行,这个方法过时了,被弃用了。
public void interrupt():
中断正在运行的线程,被中断的线程将run方法执行完毕,
并且抛出异常InterruptedException: sleep interrupted
*/
public class ThreadStopDemo {
public static void main(String[] args) {
MyStopThread myStopThread = new MyStopThread();
myStopThread.start();
try {
//让当前主线程main休眠3秒
Thread.sleep(3000);
//此时停止线程
// myStopThread.stop();
//此时打断线程
myStopThread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程的生命周期
创建线程的第二种方式:实现Runnable接口
public class MyRunnable1 implements Runnable {
@Override
public void run() {
for(int i=1;i<=100;i++){
//这里报错是因为实现的Runnable接口中并没有getName()方法(只有一个抽象的run()方法),
//所以这里无法使用Thread类中的方法
//System.out.println(getName()+":"+i);
//间接使用,使用currentThread()获取当前执行的线程对象
//间接调用getName()方法。
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
/*
创建线程的第二种方式:实现Runnable接口
1、创建自定义类实现Runnable接口
2、实现run方法
3、创建自定义类的对象
4、创建Thread类的对象,将第三步创建的自定义对象作为参数
传递到构造方法中。
此时需要Thread类中的构造方法:
public Thread(Runnable target)分配一个新的Thread对象。
需要传一个实现Runnable接口的类的对象
public Thread(Runnable target,String name)分配一个新的Thread对象
需要传一个实现Runnable接口的类的对象和线程的名字
实现接口方式的好处
1、可以避免由于Java单继承带来的局限性。
2、适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,
数据有效分离,较好的体现了面向对象的设计思想。
*/
public class MyRunnableDemo1 {
public static void main(String[] args) {
//创建自定义类的对象
MyRunnable1 myRunnable1 = new MyRunnable1();
//借助Thread类创建线程对象
Thread t1 = new Thread(myRunnable1);
Thread t2 = new Thread(myRunnable1);
t1.start();
t2.start();
}
}
电影售票案例
public class TicketWindow1 extends Thread {
//每new一次都会有一个100张票
//private int tickets = 100;
//所以应该让多个线程窗口共享这100张票,所以我们应该定义为静态的
private static int tickets = 100;
@Override
public void run() {
//在run方法中定义100张票是有问题的
//每个线程都会走到这里来,这样的话,每个线程相当于卖的是自己的那100张票
//所以,我们将票定义在这里不太合理
//int tickets = 100;
while (true){
if(tickets>0){
System.out.println(getName()+"正在出售第"+(tickets--)+"张票");
}
}
}
}
/*
某电影院目前正在上映贺岁大片,共有100张票,
而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
第一种方式:
继承Thread类
*/
public class SellTicketDemo1 {
public static void main(String[] args) {
//创建三个线程对象,模拟三个窗口
TicketWindow1 window1 = new TicketWindow1();
TicketWindow1 window2 = new TicketWindow1();
TicketWindow1 window3 = new TicketWindow1();
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
window1.start();
window2.start();
window3.start();
}
}
public class TicketWindow2 implements Runnable {
//使用Runnable接口,创建线程的时候,只new了一次
//所以不需要加static关键字
private int tickets = 100;
@Override
public void run() {
while (true){
if(tickets>0){
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
}
}
}
}
/*
某电影院目前正在上映贺岁大片,共有100张票,
而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
第二种方式:
实现Runnable接口
*/
public class SellTicketDemo2 {
public static void main(String[] args) {
TicketWindow2 ticketWindow2 = new TicketWindow2();
//创建三个线程对象,模拟三个窗口售票
Thread window1 = new Thread(ticketWindow2);
Thread window2 = new Thread(ticketWindow2);
Thread window3 = new Thread(ticketWindow2);
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
window1.start();
window2.start();
window3.start();
}
}
public class TicketWindow3 implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (true){
//产生问题的分析:
//A:相同的票卖了很多次
//B:出现了第0张票,负数的票
//A1:一开始window1,window2,window3都进来了
//A2:一开始的票数是100
//B1:假设这里tickets是1的时候,三个窗口都进来了
if(tickets>0){
//A3:假设window2先抢到CPU的时间片
//A4:然后window2先休眠100毫秒
//A5:window1,window3
try {
Thread.sleep(100); //A6:window1开始休息,window2,window3都在休息
} catch (InterruptedException e) {
e.printStackTrace();
}
//A7:三个线程休息完后,都变成就绪状态
//A8:假设window1抢到了CPU执行权
//A13:当window1正在输出的时候,window2抢到了CPU执行权,此刻tickets值还没有发生变化,依旧是100
//A14:所以输出:窗口1正在出售第100张票
//A15:所以输出:窗口2正在出售第100张票
//A16:当它两个输出完之后,tickets的值减1了以后window3抢到了CPU执行权
//A17:所以轮到window3输出的时候:窗口3正在出售第99张票
//A18:这就是为什么会出现重复的原因
//B2:三个窗口都进来了之后,都会执行这一步
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
//A9:理想状态下:
//A10:窗口1正在出售第100张票
//A11:窗口2正在出售第99张票
//A12:..
//A19:出现重复的情况解释:
//A20:由于CPU的操作都是原子性的
//A21:由因为tickets--不是原子性操作,因为它是两步操作,先赋值,输出完之后再--
//A22:在窗口1输出这句话的时候,tickets完成了赋值操作,但是还没有完成--操作,这时候
//A23:窗口2也执行到这一步,而这时候tickets并没有发生变化,所以会看到有重复的票出现
//B3:出现0和负数解释:
//B4:假设window1先抢到CPU执行权
//B5:窗口1正在出售第1张票
//B6:window1输出完时候tickets--,tickets=0
//B7:这时候window2抢到了CPU执行权
//B8:窗口2正在出售第0张票
//B9:window2输出完时候tickets--,tickets=-1
//B10:最后window3才抢到了CPU执行权
//B11:窗口3正在出售第-1张票
}
}
}
}
/*
为了模拟更加真实的售票情况,我们加入延迟(让线程休眠一会)
加入延迟之后。产生了两个问题:
1、相同的票卖了很多次
是由CPU的操作是原子性导致的
2、出现了第0张票,负数的票。 运行结果:(窗口2正在出售第0张票、窗口3正在出售第-1张票)
是由于线程的执行具有随机性和延迟性导致的,加入了sleep()方法后线程变成了阻塞状态,
让其他线程执行。
如何解决这个两个问题呢?
*/
public class SellTicketDemo3 {
public static void main(String[] args) {
TicketWindow3 ticketWindow3 = new TicketWindow3();
//创建三个线程对象,模拟三个窗口售票
Thread window1 = new Thread(ticketWindow3);
Thread window2 = new Thread(ticketWindow3);
Thread window3 = new Thread(ticketWindow3);
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
window1.start();
window2.start();
window3.start();
}
}
解决线程安全问题的基本思想及解决同步安全问题的方式1
public class TicketWindow4 implements Runnable {
private int tickets = 1000;
Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
if (tickets > 0) {
try {
Thread.sleep(50); //window1开始休息,window2,window3都在休息
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
}
}
}
/*
我们刚刚加入延迟的卖票程序后,发现出现问题了,
这样的问题在多线程中称之为,线程安全问题。
要向解决这个问题,就应该去判断哪些原因导致的问题所在:
(三点,也是今后我们去判断一个程序是否存在线程安全的标准,缺一不可)
1、是否存在多线程环境
2、是否存在共享数据/共享变量
3、是否有多条语句操作着共享数据/共享变量
回想一下,刚刚的案例是否满足以上条件
1、是否存在多线程环境,是。有三个线程模拟三个窗口
2、是否存在共享数据/共享变量,是。共享数据是100张票
3、是否有多条语句操作着共享数据/共享变量,是。tickets--等等
由此可见,我们上一个案例出现问题是一个正常的现象,因为它同时满足以上三个条件
如何解决这些问题呢?
第一,二两个原因条件我们是改变不了的,我们只能想办法改变第三个原因条件,因为只要有一个条件
不满足,就不是线程安全问题了。
解决问题的思想:
要是有一个办法,在其中一个线程执行的时候,其他线程进不来不就好了吗。
如果可以把多条语句操作共享数据的代码包起来,包成一个整体,在某个线程执行的时候,
别的线程进不来,直到这个线程执行完毕一次run方法之后,别的线程再进来。
java提供了一个机制给我们使用,叫做:同步安全机制
解决同步安全问题的方式1:
同步代码块:
格式:
synchronized(对象){
需要同步的代码(这里放多条操作共享数据的代码);
}
1、这里的对象是什么呢?
随便创建一个对象试试
2、需要同步的代码是什么?
这里放多条操作共享数据的代码
*/
public class SellTicketDemo4 {
public static void main(String[] args) {
TicketWindow4 ticketWindow4 = new TicketWindow4();
//创建三个线程对象,模拟三个窗口售票
Thread window1 = new Thread(ticketWindow4);
Thread window2 = new Thread(ticketWindow4);
Thread window3 = new Thread(ticketWindow4);
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
window1.start();
window2.start();
window3.start();
}
}
同步的特点
public class TicketWindow1 implements Runnable{
//定义100张票
private int tickets = 100;
private Object obj = new Object();
@Override
public void run() {
//模拟一直卖票
while (true){
//为了线程的安全,加上同步代码块
//synchronized关键字后面的小括号中到底放什么呢?
synchronized (obj) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
}
}
}
/*
同步的好处:
解决了多线程的安全问题
同步的弊端:
加了一个线程之后,就相当于加了一把锁,每次进入同步代码块的时候都会判断一下
无形中,降低了执行效率
*/
public class SellTicketDemo1 {
public static void main(String[] args) {
//创建实现Runnable接口的自定义对象
TicketWindow1 ticketWindow1 = new TicketWindow1();
//创建三个线程对象模拟三个窗口
//Thread类中的第二种构造方法:
//Thread(Runnable target, String name)
//分配一个新的 Thread对象。
//在创建线程对象的同时,给线程起个名字
Thread t1 = new Thread(ticketWindow1,"窗口1");
Thread t2 = new Thread(ticketWindow1,"窗口2");
Thread t3 = new Thread(ticketWindow1,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
同步代码块中的锁对象
public class TicketWindow2 implements Runnable {
//定义100张票
private int tickets = 1000;
private Object obj = new Object();
//创建一个自定义类的对象
private Demo demo = new Demo();
@Override
public void run() {
while (true){
//将demo放到synchronized关键字后面的小括号中
//发现也能解决线程同步安全的问题
synchronized (demo) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
}
}
}
//我们先自定义一个类
class Demo {
}
/*
1、同步代码块的锁对象是谁呢?
任意对象,但是多个线程之间锁对象要一样
*/
public class SellTicketDemo2 {
public static void main(String[] args) {
TicketWindow2 ticketWindow2 = new TicketWindow2();
//创建三个线程对象模拟三个窗口
//Thread(Runnable target, String name)
//分配一个新的 Thread对象。
//在创建线程对象的同时,给线程起个名字
Thread t1 = new Thread(ticketWindow2,"窗口1");
Thread t2 = new Thread(ticketWindow2,"窗口2");
Thread t3 = new Thread(ticketWindow2,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
同步方法
public class TicketWindow2 implements Runnable {
//定义100张票
private int tickets = 1000;
//定义一个变量
int i = 1;
@Override
public void run() {
while (true) {
//加上判断,让线程一会执行上面的代码,一会执行下面的代码
if (i % 2 == 0) {//当i为2的倍数的时候,执行下方代码
//为了防止同步方法和同步代码块锁对象不一致
//将synchronized关键字后面的锁对象换成this
//运行后发现:线程是同步安全的
synchronized (this) {
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
} else {//否则调用sellTicket()方法
sellTicket();
}
//要让i发生变化
i++;
}
}
//同步方法:将synchronized关键字放在修饰符和返回值之间
//那么为了防止同步方法和同步代码块锁对象不一致,而无法保证线程同步安全的问题
//我们需要知道同步方法的锁对象到底是谁?
//因为方法是由当前对象调用的,那么我就大胆猜测用this指针指代当前调用方法的对象
public synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
/*
同步方法:
将synchronized关键字放到方法上
同步方法的锁对象是this
*/
public class SellTicketDemo2 {
public static void main(String[] args) {
TicketWindow2 ticketWindow2 = new TicketWindow2();
//创建三个线程对象模拟三个窗口
//Thread(Runnable target, String name)
//分配一个新的 Thread对象。
//在创建线程对象的同时,给线程起个名字
Thread t1 = new Thread(ticketWindow2,"窗口1");
Thread t2 = new Thread(ticketWindow2,"窗口2");
Thread t3 = new Thread(ticketWindow2,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
当同步方法是静态的时
public class TicketWindow2 implements Runnable {
//定义100张票
//private int tickets = 1000;
//将同步方法改为静态之后,因为静态的只能访问静态的
//所以将成员变量也变成静态的
private static int tickets = 1000;
int i = 1;
@Override
public void run() {
while (true) {
if (i % 2 == 0) {
//为了防止同步方法和静态同步代码块锁对象不一致,
//而无法保证线程同步安全的问题
//将同步方法的锁对象换成run方法所在类的字节码文件
//运行后发现:线程是同步安全的
synchronized (TicketWindow2.class) {
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
} else {
sellTicket();
}
i++;
}
}
//静态方法的锁对象是run方法所在类的字节码文件
public synchronized static void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
}
/*
静态方法的锁对象是谁呢?
class文件,字节码文件对象也是属于一个Object类下面的对象(反射的时候说)
这个class文件不能是随便一个类的字节码文件
应该是run方法所在类的字节码文件
*/
public class SellTicketDemo2 {
public static void main(String[] args) {
TicketWindow2 ticketWindow2 = new TicketWindow2();
//创建三个线程对象模拟三个窗口
//Thread(Runnable target, String name)
//分配一个新的 Thread对象。
//在创建线程对象的同时,给线程起个名字
Thread t1 = new Thread(ticketWindow2,"窗口1");
Thread t2 = new Thread(ticketWindow2,"窗口2");
Thread t3 = new Thread(ticketWindow2,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
解决线程同步安全问题的方式2
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TicketWindow3 implements Runnable {
//定义100张票
private int tickets = 100;
//创建锁对象
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
//加锁
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
//释放锁
lock.unlock();
}
}
}
/*
解决线程同步安全问题的第二种解法:加锁Lock
在此之前我们解决线程同步安全问题的时候,使用的是synchronized关键字,通过分析
然后将哪些代码块给包起来,但是,我们并没有直接看到在哪里上了锁,或者说
在哪里释放了锁让其他线程获取到
为了更加清晰的表达如何加锁以及如何释放锁,JDK1.5之后提供了一个新的锁对象
Lock。
Lock(接口)
具体的子类:Class ReentrantLock
void lock() 加锁
void unlock() 释放锁
*/
public class SellTicketDemo3 {
public static void main(String[] args) {
TicketWindow3 ticketWindow3 = new TicketWindow3();
//创建三个线程对象模拟三个窗口
//Thread(Runnable target, String name)
//分配一个新的 Thread对象。
//在创建线程对象的同时,给线程起个名字
Thread t1 = new Thread(ticketWindow3,"窗口1");
Thread t2 = new Thread(ticketWindow3,"窗口2");
Thread t3 = new Thread(ticketWindow3,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
死锁的问题
//自定义一个类创建两把锁
public class MyLock {
//定义静态的成员变量,方便通过类名调用锁
//并且被final修饰,锁不可改变
public static final Object lock1 = new Object();
public static final Object lock2 = new Object();
}
//定义一个类继承Thread类
public class DieLock extends Thread {
//定义一个flag判断是哪国人
private boolean flag;
//通过构造方法对flag赋值
public DieLock(boolean flag){
this.flag = flag;
}
/*
运行结果:
现象1:
if lock1
else lock2
现象2:
else lock2
if lock1
现象3:
理想情况下,不产生死锁
*/
@Override
public void run() {
if(flag){
//1、假设d1先进来拿到了lock1这把锁
synchronized (MyLock.lock1){
//2、然后d1输出"if lock1"
System.out.println("if lock1");
//5、然后d1等待d2释放锁lock2
synchronized (MyLock.lock2){
System.out.println("if lock2");
}
}
}else {
//3、假设此时d2进来拿到了lock2这把锁
synchronized (MyLock.lock2){
//4、然后d2输出"else lock2"
System.out.println("else lock2");
//6、同时d2等待d1释放锁lock1,这样就出现了死锁现象
synchronized (MyLock.lock1){
System.out.println("else lock1");
}
}
}
}
}
/*
死锁:
是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
举例(餐桌问题):
中国人,美国人吃饭的问题。
假设中国人必须要有两根筷子才能吃饭
美国人必须要有一把刀,一把叉才能吃饭
正常情况下:
中国人:两支筷子
美国人:一把刀,一把叉
现在:
中国人:一支筷子,一把刀
美国人:一支筷子,一把叉
如果出现了同步嵌套,就容易产生死锁问题
*/
public class DieLockDemo {
public static void main(String[] args) {
DieLock d1 = new DieLock(true);
DieLock d2 = new DieLock(false);
d1.start();
d2.start();
}
}
线程间通信
线程间通信图解
案例
//创建共享数据
public class Student {
String name;
int age;
}
//生产者
public class SetThread implements Runnable {
private Student s;
public SetThread(Student student){
this.s = student;
}
@Override
public void run() {
// Student s = new Student();
s.name = "张梦云";
s.age = 18;
}
}
//消费者
public class GetThread implements Runnable {
private Student s;
public GetThread(Student student){
this.s = student;
}
@Override
public void run() {
// Student s = new Student();
System.out.println(s.name + "---" + s.age);
}
}
/*
分析:
共享数据:Student
生产者:SetThread 给学生的成员变量进行赋值操作
消费者:GetThread 获取学生的成员变量的信息
测试类:StudentDemo 创建线程并测试
问题1:按照我们的思路去写代码,运行后发现每一次的结果都是null---0,这是必然的
原因:我们再每个线程类中都创建了新的学生对象,而我们实际上要求的是设置成员变量值与
获取成员变量值的对象应该是同一个。
如何解决呢?
在外界将学生对象创建出来,然后通过构造方法传给每个线程
*/
public class StudentDemo {
public static void main(String[] args) {
//创建共享的学生对象
Student student = new Student();
//创建自定义类对象
SetThread setThread = new SetThread(student);
GetThread getThread = new GetThread(student);
//根据Runnable的对象创建生产者和消费者的线程对象
Thread proThread = new Thread(setThread);
Thread cusThread = new Thread(getThread);
proThread.start();
cusThread.start();
}
}
案例plus
//创建共享数据
public class Student {
String name;
int age;
}
import java.util.concurrent.locks.Lock;
//生产者
public class SetThread implements Runnable {
private Student s;
private int i = 0;
private Lock lock;
public SetThread(Student student) {
this.s = student;
}
public SetThread(Student student, Lock lock) {
this.s = student;
this.lock = lock;
}
//解决方案1、关键字加锁
// @Override
// public void run() {
// while (true){
// synchronized (s){
// if(i%2==0){
// s.name = "张梦云";
// s.age = 18;
// }else {
// s.name = "咪啊";
// s.age = 20;
// }
// i++;
// }
// }
// }
//解决方案2:Lock锁
@Override
public void run() {
while (true) {
lock.lock();
if (i % 2 == 0) {
s.name = "张梦云";
s.age = 18;
} else {
s.name = "咪啊";
s.age = 20;
}
i++;
lock.unlock();
}
}
}
import java.util.concurrent.locks.Lock;
//消费者
public class GetThread implements Runnable {
private Student s;
private Lock lock;
public GetThread(Student student) {
this.s = student;
}
public GetThread(Student student, Lock lock) {
this.s = student;
this.lock = lock;
}
//解决方案1、关键字加锁
// @Override
// public void run() {
// while (true){
// synchronized (s){
// System.out.println(s.name + "---" + s.age);
// }
// }
// }
//解决方案2:Lock锁
@Override
public void run() {
while (true) {
lock.lock();
System.out.println(s.name + "---" + s.age);
lock.unlock();
}
}
}
/*
分析:
共享数据:Student
生产者:SetThread 给学生的成员变量进行赋值操作
消费者:GetThread 获取学生的成员变量的信息
测试类:StudentDemo 创建线程并测试
问题2:我们为了数据出来的效果好一点,我们加入了循环和判断,不同的判断给不同的值
但是在运行的时候,出现了新的问题
1、同一条数据出现了很多次
2、姓名和年龄不匹配
原因:
1、同一条数据出现了很多次
CPU一点点的时间篇就足矣执行很多次。
2、姓名和年龄不匹配
这是由于线程执行的时候具有随机性导致的。
产生了线程安全的问题:
1、是否存在多线程环境 是
2、是否存在共享数据 是
3、是否有多条语句操作共享数据 是
既然都满足条件,说明线程不安全
解决方案:加锁
1、关键字加锁
2、Lock锁
注意事项:
1、不同种类的线程类都要加锁
2、不用种类的线程类中的锁对象要一样
*/
public class StudentDemo {
public static void main(String[] args) {
//创建共享的学生对象
Student student = new Student();
//解决方案2:
//为了保证锁对象的唯一,在测试类中创建锁对象
Lock lock = new ReentrantLock();
//创建自定义类对象
SetThread setThread = new SetThread(student,lock);
GetThread getThread = new GetThread(student,lock);
//根据Runnable的对象创建生产者和消费者的线程对象
Thread proThread = new Thread(setThread);
Thread cusThread = new Thread(getThread);
proThread.start();
cusThread.start();
}
}
等待唤醒机制
案例plus+
//共享数据
public class Student {
String name;
int age;
//定义一个flag判断成员变量有没有值
boolean flag;
}
import java.util.concurrent.locks.Lock;
//生产者
public class SetThread implements Runnable {
private Student s;
private int i = 0;
private Lock lock;
public SetThread(Student student) {
this.s = student;
}
public SetThread(Student student, Lock lock) {
this.s = student;
this.lock = lock;
}
@Override
public void run() {
while (true){
synchronized (s){
//判断学生对象有没有值
//flag初始的时候是flase,表示没有值,如果是true表示学生对象有值
//有值对于生产者来说,等待消费者消费
if(s.flag){
try {
//调用wait(),线程阻塞
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(i%2==0){
s.name = "张梦云";
s.age = 18;
}else {
s.name = "咪啊";
s.age = 20;
}
i++;
//生产者生产完数据之后通知消费者消费数据
s.notify();
//并修改flag为true
s.flag = true;
}
}
}
}
import java.util.concurrent.locks.Lock;
//消费者
public class GetThread implements Runnable {
private Student s;
private Lock lock;
public GetThread(Student student) {
this.s = student;
}
public GetThread(Student student, Lock lock) {
this.s = student;
this.lock = lock;
}
@Override
public void run() {
while (true){
synchronized (s){
//判断学生对象有没有值
//如果flag的值是false,说明没有数据,消费者进if判断等待
if(!s.flag){
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name + "---" + s.age);
//消费者消费完数据后通知生产者生产数据
s.notify();
//并且修改flag为false
s.flag = false;
}
}
}
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
分析:
共享数据:Student
生产者:SetThread 给学生的成员变量进行赋值操作
消费者:GetThread 获取学生的成员变量的信息
测试类:StudentDemo 创建线程并测试
问题3:虽然我们解决线程安全问题,但是经过分析,程序还存在着等待唤醒机制的问题。
加入等待唤醒机制。
如何添加等待唤醒机制呢?
Object类中有三个方法:
void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。
void notify() 唤醒正在等待对象监视器的单个线程。
void notifyAll() 唤醒正在等待对象监视器的所有线程。
这三个方法为什么不定义在Thread类中呢?
这些方法想要调用,必须通过锁的对象调用,而我们知道 同步代码块的锁对象可以是任意对象
所以这些方法都在Object类中,因为java中所有类的父类都是Object
*/
public class StudentDemo {
public static void main(String[] args) {
//创建共享的学生对象
Student student = new Student(); //一开始 null---0
//创建自定义类对象
SetThread setThread = new SetThread(student);
GetThread getThread = new GetThread(student);
//根据Runnable的对象创建生产者和消费者的线程对象
Thread proThread = new Thread(setThread);
Thread cusThread = new Thread(getThread);
cusThread.start();
proThread.start();
}
}
线程的状态转换图
线程组
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
/*
线程组:
Java中使用ThreadGroup来表示线程组,
它可以对一批线程进行分类管理,
Java允许程序直接对线程组进行控制。
*/
public class ThreadGroupDemo {
public static void main(String[] args) {
//创建自定义类对象
MyRunnable myRunnable = new MyRunnable();
//创建线程对象
Thread t1 = new Thread(myRunnable, "朱佳乐");
Thread t2 = new Thread(myRunnable, "刘生发");
//我们之前都没有提过线程组,以及分组的概念,想着应该有一个默认的分组
//Thread类中有个方法可以获取分组
//ThreadGroup getThreadGroup()
//返回此线程所属的线程组。
ThreadGroup tg1 = t1.getThreadGroup();
System.out.println(tg1);
ThreadGroup tg2 = t2.getThreadGroup();
System.out.println(tg2);
//获取线程组的名字
//String getName()
//返回此线程组的名称。
System.out.println(tg1.getName());//main
System.out.println(tg2.getName());//main
System.out.println(Thread.currentThread().getName());//main
//需求:给线程分组
//Thread类中有个构造方法
//Thread(ThreadGroup group, Runnable target, String name)
//分配一个新的 Thread对象,使其具有 target作为其运行对象,
//具有指定的 name作为其名称,属于 group引用的线程组。
//创建一个新的线程组
//ThreadGroup类中的构造方法
//ThreadGroup(String name)
//构造一个新的线程组。
ThreadGroup tg3 = new ThreadGroup("美女组");
//创建线程对象
Thread t3 = new Thread(tg3, myRunnable, "杨旭");
Thread t4 = new Thread(tg3, myRunnable, "朱佳乐");
Thread t5 = new Thread(tg3, myRunnable, "刘生发");
//获取线程组的名字
System.out.println(t3.getThreadGroup().getName());
System.out.println(t4.getThreadGroup().getName());
//Java允许程序直接对线程组进行控制
//直接通过组名设置这一组都是守护线程,组里面的线程都是守护线程
tg3.setDaemon(true);
}
}
线程池
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。
而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,
更应该考虑使用线程池。
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,
等待下一个对象来使用。
在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
如何实现线程池的代码呢?
1、创建线程池对象,Executors工厂类下的静态方法
public static ExecutorService newCachedThreadPool()
创建一个具有缓存功能的线程池
缓存:百度浏览过的信息再次访问
public static ExecutorService newFixedThreadPool(int nThreads)
创建一个可重用的,具有固定线程数的线程池
public static ExecutorService newSingleThreadExecutor()
创建一个只有单线程的线程池,相当于上个方法的参数是1
我们只学习:(其他两种线程池用的少)
newFixedThreadPool是其中一种线程池
public static ExecutorService newFixedThreadPool(int nThreads)
2、如何往线程池中存放线程?(可以存放哪些线程呢?)
3、在线程池中的线程如何运行呢?
4、我想结束线程的运行,可不可以手动结束呢?如果可以,怎么结束?
*/
public class ThreadPoolDemo {
public static void main(String[] args) {
//创建线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
//ExecutorService类中
//Future<?> submit(Runnable task)
//提交一个可运行的任务执行,并返回一个表示该任务的未来。
MyRunnable myRunnable = new MyRunnable();
pool.submit(myRunnable);
pool.submit(myRunnable);
//提交数超过线程池的数量的时候也会执行,只不过是当有空闲线程位置的时候再去执行
//newFixedThreadPool最大一次性可执行线程数量为初始设置的数量
pool.submit(myRunnable);
//我想结束线程的运行,可不可以手动结束呢?如果可以,怎么结束?
//void shutdown()
//启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。
//先提交的先关闭
pool.shutdown();
System.out.println("======================================");
//RejectedExecutionException
//线程池已经被关闭了,不能再提交任务执行
pool.submit(myRunnable);
}
}
创建线程的第三种方式实现Callable接口
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
import java.util.concurrent.Callable;
public class MyCallable implements Callable {
@Override
public Object call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
return null;
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
创建线程的第三种方式:
自定义类实现Callable接口,实现call()方法
<T> Future<T> submit(Callable<T> task)
提交值返回任务以执行,并返回代表任务待处理结果的Future。
*/
public class ThreadPoolDemo2 {
public static void main(String[] args) {
//创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2);
MyRunnable myRunnable = new MyRunnable();
MyCallable myCallable = new MyCallable();
//可以执行Runnable对象或者Callable对象代表的线程池
pool.submit(myRunnable);
pool.submit(myCallable);
pool.shutdown();
}
}
我们学过哪些线程安全的类
import java.util.*;
/*
到目前为止,我们学过哪些线程安全的类
*/
public class ThreadDemo {
public static void main(String[] args) {
//线程安全的类,字符串缓冲
StringBuffer sb = new StringBuffer();
//Vector
Vector<String> strings = new Vector<>();
//Hashtable
Hashtable<String, String> stringStringHashtable = new Hashtable<>();
//Vector虽然是线程安全的,但是我们今后开发也不用他,那用谁呢?
//我们说过一个工具类Collections
ArrayList<String> strings1 = new ArrayList<>();
List<String> strings2 = Collections.synchronizedList(strings);
}
}
匿名内部类方式使用多线程
/*
匿名内部类方式使用多线程
*/
public class ThreadDemo2 {
public static void main(String[] args) {
//使用匿名内部类的形式创建Thread对象1
//通过构造方法给线程起名字
Thread t1 = new Thread("朱佳乐") {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
};
//通过setName方法给线程起名字
// t1.setName("张梦云");
t1.start();
//使用匿名内部类的形式创建Thread对象2
//通过构造方法给线程起名字
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
},"刘生发");
t2.start();
}
}
定时器
import java.util.Timer;
import java.util.TimerTask;
/*
定时器是一个应用十分广泛的线程工具,
可用于调度多个定时任务以后台线程的方式执行。
在Java中,可以通过Timer和TimerTask类来实现定义调度的功能
TimerTask类(java.util)
观察API发现:TimerTask类的构造方法被protected修饰
所以以自定义子类的形式创建任务并重写抽象的run()方法
如何创建定时器呢?
java提供了一个类给我们使用实现定时器Timer(java.util)
无参构造方法:
Timer()
创建一个新的计时器。
成员方法:
public void schedule(TimerTask task, long delay)
在指定的延迟之后安排指定的任务执行。 定时在未来的某一时刻执行任务
task--要安排的任务
delay--执行任务之前以delay为单位的延迟
(delay这个类型是毫秒)
void schedule(TimerTask task, long delay, long period)
在指定的延迟之后开始 ,重新执行 固定延迟执行的指定任务。
public void cancel()
终止此计时器,丢弃任何当前计划的任务
*/
public class TimerDemo {
public static void main(String[] args) {
//创建定时器对象
//Timer()
//创建一个新的计时器。
Timer timer = new Timer();
//调度任务执行
//在指定的延迟之后安排指定的任务执行。 定时在未来的某一时刻执行任务
//public void schedule(TimerTask task, long delay)
//自定义子类的形式创建任务
//delay这个类型是毫秒
timer.schedule(new MyTask(timer), 3000);
//public void cancel()终止此计时器,丢弃任何当前计划的任务
//不应该在这里写,因为上面的延迟是三秒,但是主线程不会去等
//还没到三秒的时候计时器就被终止了
//所以应该写在run()方法里
//timer.cancel();
//因为run()方法里的timer.cancel();已经终止了此计时器
//所以根本就读不到
//timer.schedule(new MyTask(timer), 3000);
}
}
class MyTask extends TimerTask{
//为了在该类中使用cancel()方法
//定义Timer类型的成员变量
private Timer timer;
//通过构造方法进行传值
public MyTask(Timer timer){
this.timer = timer;
}
@Override
public void run() {
System.out.println("beng!!爆炸了!!");
timer.cancel();
}
}
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo2 {
public static void main(String[] args) {
//创建定时器对象
Timer timer = new Timer();
//void schedule(TimerTask task, long delay, long period)
//在指定的延迟之后开始 ,重新执行 固定延迟执行的指定任务。
//3秒后执行任务,并且每个2秒执行一次任务
timer.schedule(new MyTask2(), 3000, 2000);
}
}
class MyTask2 extends TimerTask {
@Override
public void run() {
System.out.println("beng!!爆炸了!!!");
}
}