e^nx

导航

多线程基础

多线程

背景知识:

cpu:做运算功能,一开始是单核的

多进程:(单核cpu)多个进程,采用交替执行的方式执行。但是进程之间的交替付出了很大代价

多cpu和多核cpu:对于操作系统和软件来说都一样。

多线程:随着科技的发展,多个进程可以分别并行在多个cpu上,进程之间变得更加独立。但是过多的进程,导致的交替执行,任然付出了很大代价,所以进步将原本进程中单线程(满足通知执行的需求),改为多线程。多个线程来并发执行,进程主要负责资源分配,线程承当进程的调度功能。

多线程的Java程序

  1. 其实java命令,它启动了一个jvm进程
  2. jvm进程会创建一个main线程
  3. 在main线程中,运行主类中的main方法
  4. 同时有一个后台进程, 也就是垃圾回收线程。

概念(全文背诵)

进程和线程:
操作系统中所有运行中的任务通常对应一个进程,当程序进入内存运行时,即变成进程。进程时处于运行过程中的程序,是系统进行资源分配和调度的一个独立单位。即使在多核处理器中,宏观上并行的进程微观上也是并发,由于进程之间交替执行需要很大的开销,故进程中扩展多了多线程来帮助进程实现调度,在进程初始化后,主线程就被创建了,线程拥有的是父进程的全部资源,且多个进程共享资源,但相互保持独立性。

多线程有啥用,优势在哪儿?

不是提高执行速度,而是提高CPU的使用率,提高程序的运行效率.

并发,并行,串行?

  • 串行:一个任务接一个任务

  • 并发:一个时间短内,多个程序快速的交替执行

  • 并行: 同一个时间点,多个程序同时运行(其实没有同一个时间点,只是理想的状态,微观上还是并发)

run()方法有啥作用?

run方法就是线程执行体,如果直接调用run就会当作普通方法执行。就作为线程去调用了run,就不受系统的调度去控制而简洁执行。

start()方法的作用?

让线程进入就绪态

如何创建线程和启动?

继承Thread类创建线程类:

public class FirstThread extends Thread{

    private  int i;

    // 线程执行体
    @Override
    public void run() {
        for (; i < 100; i++) {
            System.out.println(this.getName() + " " + i);

        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName());


                FirstThread thread1 = new FirstThread();
                thread1.setName("线程1");
                thread1.start();
                FirstThread thread2 = new FirstThread();
                thread2.setName("线程2");
                thread2.start();

        }

    }
}
  • 为什么i 不是static 的时候,线程1,2都执行了100次,而static的时候就线程1,2,两个线程加起来的次数约等于100次,为什么是约等于?

    当i非静态变量时候,i没有在两个线程之间共享,主线程的main方法让每个线程都执行了100*100次,当为静态变量的时候,两个线程共享i,所以i的变化对于两个线程是可见的,但注意!由于线程它们使用资源是抢占型的,在都是默认优先级的情况下,会出现一个线程没有执行完,而另一个线程抢占了,导致某个i被重复输出的情况。所以导致输出的数字个数是约等于100。

  • 主线程main不会调用run线程体吗??

    是的,因为主线程的线程体不是由run()方法确定的,而是由main()方法确定的--main() 方法的方法体代表主线程的执行体。

实现Runnable接口创建线程类(也可通过匿名内部类创建)

public class SecondThread implements Runnable{

    private int i;

    @Override
    public void run() {
        for (; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "--" + i);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "--" + i);
            if (i == 20){
                // 创建类
                SecondThread st = new SecondThread();
                // 通过new Thread(target, name)方法创建新线程
                new Thread(st, "新线程1").start();
                new Thread(st, "新线程2").start();
            }
        }
    }
}

两种创建方式有啥不一样?

继承Thread类创建线程类

创建步骤:

  1. 写一个类继承Thread类

    子类并不是线程类,只是对线程的描述。用子类创建的对象倒是线程。

  2. 重写run方法

    1. 创建子类让子类start,运行子线程

    如果不start直接就run,就相当于对象普通调用了。

实现Runnable接口创建线程类(好用)

创建步骤:

  1. 实现Runnable接口

  2. 重写run方法

  3. 主线程中创建实现类对象(这个对象只是作为target)

  4. 新建Thread对象,并打上标记。

  5. start执行Thread对象

    共享变量的问题:

  • 继承Thread类创建的线程类,相互线程对象之间独立,不共享非静态变量,静态变量共享
  • 实现Runnable接口创建的线程类,线程对象之间由于是同一个实现类的标记,变量共享

优先级问题:

优先级包括:动态优先级和静态优先级

  • 优先级高得线程,随着运行得时间会降低优先级。

  • 就绪态得线程随着等待时间得提高优先级。

  • 优先级高并不代表一定先run,看操作系统心情。

  • 通常不设置具体的数值,只需要设置位normal or MAX_PRIORITY or MIN_PRIORITY 这样设置程序有最好的可移植性

    因为不同操作系统的优先级不一样,有的有7个。

  • 子线程与父线程拥有相同的优先级

如何控制线程?

  1. 线程睡眠 sleep

    • sleep方法会让当前线程进入阻塞态,完全给别人机会,不管其他线程的优先级。
    • 声明抛出了InterruptedException异常,要么捕捉,要么抛出。
  2. 线程加塞 join

    当前执行的线程同学先等等,让调用join方法的先执行完。

    通俗一点说:谁他妈让我(某个线程)调用了join方法,谁就等我(某个线程)执行完。比如你让我调用了join的方法,你就得等我执行完。你再执行,但是我在执行的时候,”他“(除了咱俩以外的其他线程)不受影响。

    补充理解:

    join方法:线程老弟说 ”谁他妈让爷调用了自己的join方法,谁就要等爷执行完。“

    通俗点:你修了一个单人间厕所,请我进去方便一下(join()),这肯定得我先弄完。然后你再进去嘛。

  3. 线程礼让 yield(尽量不要使用这个玩意)

    • 没有声明抛出任何异常
    • 当前执行的线程1同学先暂停,但是不是进入阻塞态,而是直接进入就绪态。也就是小小的暂停一下。
    • 下次系统的线程调度,也很有可能还是线程1又抢到了时间片,开始运行。
  4. 后台线程(守护线程)setDaemon

    线程start之前就要调用守护方法

    守护线程到底是被保护的,还是保护别的线程的?

    我认为:就垃圾回收线程来说,就是来保护jvm中堆空间不要溢出。保护程序正常执行的。

  5. 中断线程stop, 直接中断不抛出异常(不建议用) 建议用interrupt(),抛出中断异常,但线程体里面的语句依旧会执行。

线程的五种状态:

新建态,就绪态,运行态,阻塞态,死亡

五种状态出现的标志,在什么情况出现什么状态?

  • 新建:新建一个线程

  • 就绪:start调用线程就绪,运行到就绪可以通过yield方法

    丢失执行权

  • 运行:就绪到运行态的转换由系统线程调度所决定

  • 阻塞:

    除了缺少执行权,还缺失资源

    线程阻塞的时候,操作系统会将线程的执行情况中的信息保存在内存中。等待被唤醒的时候再读出来恢复至线程的运行态。

    • 运行的进程调用sleep()
    • join()会进入阻塞,
    • IO中read方法等堵塞方法
    • 等待阻塞:wait()也会进入阻塞
    • 同步阻塞:试图取访问一个同步监视器,但是人家正在被其他线程使用,并还没解锁呢
    • 线程正在等待某个通知 notify
    • suspend方法将线程挂起。(避免使用这玩意,容易死锁)

    解除阻塞:

    • sleep()时间已到
    • IO的阻塞式方法已经返回
    • 成功获得了试图取得的同步监视器
    • 等到了通知
    • 线程被调用了resume()恢复方法
  • 死亡:

    1. run() 或者call方法执行完 线程正常结束
    2. 线程抛出了一个异常
    3. stop()方法结束(不推荐常导致死锁)
    • 死了就是死了,不可再start()复活。再诈尸就出异常

    流程图简单总结:

    ![2021-1-21 17-40-15](C:\Users\Public\Documents\极域课堂管理系统软件V6.0 2016 豪华版\Snapshots\2021-1-21 17-40-15.JPG)

注意:

  • 主线程结束时候,子线程该跑的继续跑,只要子线程开启动,就不受主线程的影响了。
  • isAlive方法测试线程是否死亡,就绪,运行,阻塞都算还活着,新建和死亡就是死了。false
  • sleep()方法该方法有三种重载形式。

后台(守护)进程:

两个相关方法:

  • setDaemon(true)方法指定线程设置成后台线程
  • isDaemon() 方法 判断是否是后台进程

特点:

  • 前台的进程都死了,爷还守护个屁,也死了算了。

疑问:为什么出现多个线程共享的变量的值出现重复输出的问题?(比如多线程买票的demo中出现的重票问题)

出现安全问题的环境:

  • 程序实现了多线程

  • 多线程共享了数据

  • 线程体中有非原子操作

    以上三个条件同时满足时:应该警惕安全问题。

线程的安全性问题来源?为什么有安全问题?(核心)

同步延迟性:
不同的线程在交替访问一个内存中的一个变量的时候,先访问的线程在自己所处的cpu缓存中修改了该变量的值,但由于内存中变量偶尔跟不上cpu缓存修改的速度,无法立即同步,导致第二个线程再访问内存中的那个变量时,还是之前的值。 所以出现了安全问题。

同步随机性:
可能突然就内存中的变量和各个cpu缓存区中的变量同步了,导致出问题

如何解决呢?

预备知识:

原子性操作的含义?

一个目标操作要么一次执行完,要么不执行。

同步与异步:

  • 同步:若资源被占用,哪个这个进程就会等待释放,按顺序执行。同步是一种阻塞模式
  • 异步:就是大家各干各个的,某个线程或者进程,因为其他原因暂停,其他的照旧执行,暂停的任务等待条件重新具备以后再继续跑。多线程就是异步的

为什么要有同步的一些操作?

因为操作系统同步机制具有随机性和延迟性,还是自己解决靠谱。

线程同步机制:

synchronized:

同步代码块

如何实现逻辑上的同步?

同步方法或者同步块中的区域,里面的变量的改变,同步的操作都交给线程,一个线程执行完锁释放,其他线程才可进来。而不是操作系统区随机性同步了。

锁对象:锁对象时java的任意对象

同步方法:

普通同步方法:

锁对象时 this

静态同步方法:

锁对象:当前类的.class对象

监视器

何为监视器? 什么应该作为监视器(锁)

  • 可以是任意对象,一般声明在继承了Thread的子类,或者实现了Runnable接口的子类 的成员变量位置。
  • 共享变量在哪个对象里面。那个对象就作为锁(最好这么做),
  • 注意: 多个线程使用同一把锁才能达到同步,达到前者没有用完解锁,后者不能用的效果,如果有每个线程都有一把锁,那么先调用的那个线程不用解锁,后面的线程照样执行。

Lock同步锁

使用步骤:

  • 新建一个锁对象 new ReentrantLock();
  • lock()方法 上锁
  • unlook 解锁

同步synchronized vs Lock

  • Lock锁具有比synchronized 更加广泛的锁定操作,允许实现更灵活的结构。
  • Lock显式上锁,解锁。且锁对象明确。
  • synchronized 简单 同步方法不用创建对象。隐式锁

死锁:

两个线程相互等待都不愿意释放自己的锁。

死锁解决方法:

  1. 更改锁的顺序

  2. 在加上一把锁,把操作变成了原子操作

补充 :每个类都有一个字节码文件对象·。

线程之间如何通信?

  1. 通过Object对象的synchronized + wait 和 notify notifyAll方法
  2. 通过Loke + Condition 的await 和 signal() 和 signalAll()方法

有哪几种通信方式:

  1. 传统方法 隐式调用wait 和signal,signalAll方法
  2. 使用Lock+Conditionn控制进程同学,原理和上面一样。只是Condition作为同步监视器,显式得去控制
  3. 使用阻塞队列BlockingQueue控制,主要是用put和take方法

传统通信手段:

  • wait():此方法一旦执行,当前线程就进入阻塞状态,并释放同步监视器(锁资源)。
  • notify():此方法一旦执行,就会唤醒被 wait 的一个线程。如果有多个线程被 wait,就唤醒优先级最高的那个。
  • notifyAll():此方法一旦执行,就会唤醒所有被 wait 的线程。
  • 3、wait()、notify()、notifyAll()三个方法必须使用在同步代码块或者同步方法中。
  • 4、wait()、notify()、notifyAll()三个方法是定义在java.lang.Object类中

wait()方法

阻塞功能:

  • 在某个线程中:对象.wait() 在哪个线程中被调用,哪个线程就进入阻塞态
  • 它的阻塞态,得被别人通知才醒来。
  • 和sleep最大的区别,sleep睡醒以后,锁没有释放,线程一wait 会释放锁

生产者消费者模型:

生成者线程干啥: 生产产品

消费者线程干啥:消费产品

生产者消费者模型实例:

开一个水果店,有完备的供应链,有水果就卖出,生产者线程和消费者线程,通过水果店通信。

ReadMe:

生产者和消费者模型
生产者和消费者都是线程,中间的商店作为共享变量

1.新建生产者和消费者线程,以那个商店作为共享变量
2.以商店商品的消耗情况,是否为null作为两个线程切换的点
3.两个线程的线程题run方法应该while不停生产和消费。
4.能够接收两个线程的生产和消费的功能交给,商店去做判断。


Producer类:实现Runnable
构造方法:
以商店对象作为有参构造
成员变量:
Shop shop 变量 // 要给他供货
成员方法:
production  // 生产商品
重写run方法

Consumer类:实现Runnable
以商店对象作为有参构造

成员变量
Shop shop 变量 // 要从商店拿货
成员方法
buyFruit
重写run方法


Shop类 实现Runnable接口
成员变量:
Fruits fruits 商品
成员方法,(线程的通信交给它来做判断)
要放置商品, placeFruitsthis.fruits = fruits;
要卖货,sellFruits 将成员变量设置为空


Fruits 类
成员变量:
String name
int price

构造方法
无参构造
有参构造

FruitsShop类:

package Day22.producerandconsumer;

import java.util.EmptyStackException;

/**
 * @author :XiongZhikang
 * @date :Created in 2021/1/22 21:40
 * @description:想吃水果了
 * @modified By:
 */
public class FruitsShop {
    // 我的水果专供一种水果
    Fruits fruits;

    // 接收生产者 生产出来的的水果, 在实现的过程中,由于没有加锁,出现了不合法的监视器异常,为什么呢?
    public synchronized void setFruits(Fruits fruits) {
//
        // 如果过水果店里还有水果,生产者生产的水果,商店就先不上架
        if (this.fruits != null){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 如果水果店里面没有水果了,就上架,并且通知其他线程,比如消费者线程来消费拉
        else{
            System.out.println("从工厂进货:" + fruits);
            this.fruits = fruits;
            this.notifyAll();
        }
    }

    // 将水果卖出的方法
    public synchronized void sellFruits(){
        // synchronized
        // 如果商店没了水果,就先别卖了,消费者进程先wait
        if (fruits == null){
                    try {
                        this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }   // 如果还有水果, 就卖出,并通知生产者线程等其他线程干活了,生产者快来上货!
        else {
            System.out.println("水果点卖出:" + fruits);
            this.fruits = null;
            this.notifyAll();
        }

    }
}


class Fruits{
    String name;
    int price;

    public Fruits(String name, int price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Fruits{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}

Producer类:

package Day22.producerandconsumer;

import java.util.Random;

/**
 * @author :XiongZhikang
 * @date :Created in 2021/1/22 21:37
 * @description:生产者
 * @modified By:
 */
public class Producer implements Runnable{
    // 生产者要为商户提供水果,这里我们让水果品种随机
    FruitsShop fruitsShop;
    Random random = new Random();

    // 这是生产者能够提供的水果类型
    Fruits[] fruitsList = {new Fruits("西瓜", 30), new Fruits("榴莲", 50)
    ,new Fruits("香蕉", 5)};

    public Producer(FruitsShop fruitsShop) {
        this.fruitsShop = fruitsShop;

    }

    // 生产水果:
    public void production(){
        fruitsShop.setFruits(fruitsList[random.nextInt(3)]);
    }

    @Override
    public void run() {
        // 24小时无休止生产
        while (true){
            production();
        }
    }
}

Consumer类:

package Day22.producerandconsumer;

/**
 * @author :XiongZhikang
 * @date :Created in 2021/1/22 21:38
 * @description:消费者
 * @modified By:
 */
public class Consumer implements Runnable{
    // 消费者 要去那个商店里面买水果
    FruitsShop fruitsShop;

    public Consumer(FruitsShop fruitsShop) {
        this.fruitsShop = fruitsShop;
    }

    // 要调用
    public void buyFruit(){
        fruitsShop.sellFruits();
    }

    @Override
    public void run() {
//        一直买入
        while(true){
            buyFruit();
        }
    }
}

RunPACPattern类:

package Day22.producerandconsumer;

/**
 * @author :XiongZhikang
 * @date :Created in 2021/1/22 22:35
 * @description:运行生产者消费者模式
 * @modified By:
 */
public class RunPACPattern {
    public static void main(String[] args) {
        // 小店,工厂,买家全部实例化。   准备干大事
        FruitsShop fruitsShop = new FruitsShop();
        // 小店作为中间最重要的一环,参与其他两个线程的构造。通过这个小店联系工厂和买家
        Producer producer = new Producer(fruitsShop);
        Consumer consumer = new Consumer(fruitsShop);

        // 然后联系厂家 和 卖家  建立线程。准备开工
        Thread producerThread = new Thread(producer);
        Thread consumerThread = new Thread(consumer);

        System.out.println("熊氏集团开业大吉!!!");
        // 开始动工
        producerThread.start();
        consumerThread.start();
    }
}

这种设计的优点:

我们将功能,具体的操作放入到中间容器,一个类中处理。
而让生产者,和消费者只执行简单的生产和买入的操作。

实现过程中有个疑问:

为什么wait外面一定要有同步锁?

参考说法:

https://stackoverflow.com/questions/2779484/why-must-wait-always-be-in-synchronized-block

https://www.jianshu.com/p/4dc88dff8f86

分析:

​ 一开始没有看明白,原因在于低估了cpu调度两个线程的速度,对多线程出现安全问题的根本原因不敏感。从我的代码上看,两个线程调用的商店的不同方法 ,表面上是很独立,各自运行各自的,商店有水果就不生产者就wait,消费者就买+notify,商店没有水果,生产者就生产,消费者买不到就wait。但是重点在于他们有一个共享的变量,Fruits fruits,如果不加锁,可能出现生产者在给商店添加商品,执行到this.fruits = fruits,并this.notifyAll();通知消费者线程来买,重点来了:一般都是多核cpu,进程中的多线程并行执行.各自把内存中的fruits拿到自己所在的cpu缓存,由于cpu缓存的对fruits修改很快,内存中的fruits还是null,或者另一个线程所在的核心中的缓存区还没来得及同步,导致消费者线程fruits == null,线程wait, 生产者线程继续生产,发现自己线程运行在的cpu缓存中的fruits不是null,也wait。导致两个线程都进入了阻塞,所以wait必须上锁防止操作系统的同步问题。

根本原因:

  1. 多核cpu 多个cpu,cpu缓存与内存的同步延迟性。
  2. 两个线程并不独立,共享了变量
  3. 对变量的操作也非原子性操作。受线程调度机制影响,导致变量来不及同步。

多线程会出现安全问题的信号

  • 程序实现了多线程

  • 多线程共享了数据

  • 线程体中有非原子操作

    (原子操作:不会被线程调度机制打断的操作)

一个线程进入阻塞态后,再次醒来,是否会从阻塞之前还没执行的地方运行?

是的

面试题:Thread.sleep vs Object.wait()

1.所属不同:
sleep定义在Thread类,静态方法

wait定义在 Object类中,非静态方法

2.唤醒条件不同
a. sleep:休眠时间到
b. wait:在其他线程中,在同一个锁对象上,调用了notify或notifyAll方法

3.使用条件不同:
a.sleep没有任何前提条件
b. wait(),必须当前线程,持有锁对象,锁对象上调用wait()

4.休眠时,对锁对象的持有,不同:(最最核心的区别)
a.线程因为sleep方法而处于阻塞状态的时候,在阻塞的时候不会放弃对锁的持有

b.但是wait()方法,会在阻塞的时候,放弃锁对象持有

线程池:

真正开发中用的都是线程池,线程池类似于公交车,用完了还可以继续用。

优点:

  1. 提高响应速度,减少线程的创建时间
  2. 降低资源消耗,重复利用线程池中线程,不需要每次都创建
  3. 便于线程管理。

两种线程池的创建方法以及特点:

  1. ExecutorService newCashedThreadPool()

特点:

  • 线程数量不固定

  • 60s没有被使用,立即销毁

  1. ExecutorService newFixedThreadPool(线程池的大小)

特点:

  • 线程数量固定

  • 维护一个无界队列,暂存来不及处理的任务

  • 按照任务的提交顺序,将任务执行。

一般线程池的创建和执行任务的步骤:

  1. 创建提供指定线程数量的线程池,
  2. 需要提供实现Runnable接口,创建实现类对象,执行指定线程的操作
  3. execute 提交,或者submit提交任务和线程池。
  4. shutdown关闭
public class ThreadPool {
    public static void main(String[] args) {
        // 建立线程池 指定数量
        ExecutorService service = Executors.newFixedThreadPool(10);

        // 创建Runnable的实现类对象,提供实现Runnable 接口实现类的对象
        NumberThread target = new NumberThread();



        service.execute(target);  // 适用于runnable
//      service.submit()
        // 关闭链接
        service.shutdown();

    }
}

例子:利用线程池创建线程买100张票

步骤:

  1. 写一个类来实现Runnable接口
  2. 重写run方法
  3. main方法中,Executors.newFixedThreadPool(10);创建一个线程池,可以用接口ExecutorService引用指向,或者用实现类ThreadPoolExecutor引用指向。子类可以改池的参数。
  4. 创建Runnable接口的实现类对象 让其作为一个target
  5. 批量用线程池创建线程。
  6. 任务完成,线程池shutdown()
package Day21;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

class NumberThread implements Runnable{
    int i;
    @Override
    public void run() {

        for (i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()  + "在执行,已经买了: " + i + "票");
            try {
                Thread.currentThread().join(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


public class ThreadPool {
    public static void main(String[] args) {
        // 建立线程池 指定数量  多态方式 Executors是工厂类  ExecutorService代表尽快执行线程的线程池,是一个接口
        ExecutorService service = Executors.newFixedThreadPool(10);

        // 看看这个线程池是哪个类创建的
        System.out.println(service.getClass());

        // ThreadPoolExecutor转化为实现子类
        ThreadPoolExecutor service1 = ((ThreadPoolExecutor) service);

        // 实现类对象可以修改线程池的参宿
        service1.setCorePoolSize(11);


        // 创建Runnable的实现类对象,提供实现Runnable 接口实现类的对象
        NumberThread target = new NumberThread();

        // 以for循环,线程池的形式 批量创建线程
        for (int i = 0; i < 8; i++) {
            service1.execute(target);
        }
//        service.submit()
        // 关闭链接
        service.shutdown();

    }
}

实现Callable接口创建线程任务并交给线程池做处理:

  1. 创建Callable接口实现类
  2. 重写call方法
  3. 创建线程池
  4. 创建Callable接口实现类对象
  5. 以Callable接口实现类对象作为任务,提交给线程池

有啥用啊:

可能就是 返回值,作为线程结束的标志吧,或许可以实现多线程分解大问题,计算完各个小问题的结果,通过返回值拼凑得到大问题的结果吧。

定时任务:

Timer

数据结构:

其中维护了一个任务集合(小顶堆)有一个后台进程负责:每次从小顶堆中取出堆任务。执行完的进程,会到堆中重新排序。

怎么写一个定时任务:

  1. 写个子类继承TimerTask 作用:定时任务
  2. 重写run方法 具体任务内容
  3. main 方法新建要给定时器 new Timer()
  4. 用定时器给定时任务安排执行计划 schedule(任务,第一次执行时间,然后间隔多久执行一次)
  5. 不用了就cancel

例子: 将一个字符定时写入一个名为日志文件中去。

package Day23;

import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

/**
 * @author :XiongZhikang
 * @date :Created in 2021/1/23 14:11
 * @description:重写日志文件定时器
 * @modified By:
 */
public class RewriteLogFileTimer extends TimerTask {
    public static void main(String[] args) {
       
        // 创建一个定时器
        Timer timer = new Timer();
        
        // 启动定时器,一次执行+周期执行
        timer.schedule(new RewriteLogFileTimer(), 3000, 5000);
        
        // 不用了 就cancel  有用的时候 还是让它继续跑
//        timer.cancel();

    }

    @Override
    public void run() {
        // 线程体内要重写日志文件
        // 每次执行都新建一个时间戳 时间戳转换为指定 格式时间
        String currentTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date().getTime());

        // 就输入一个字符串。“xxx日期 日志写入成功”
        try(
                FileWriter fileWriter = new FileWriter("E:\\JavaProjectStation\\JavaBaseHomeworkFile\\LogFile.txt", true);
        )
        {
            fileWriter.write(currentTime + "\t日志文件备份成功\n");
        } catch (IOException e) {
            e.printStackTrace();
        }

    }


}

posted on 2021-01-23 22:22  e^nx  阅读(68)  评论(0编辑  收藏  举报