线程简单概述

1、小知识点

  • 进程:启动一个应用程序就是启动了一个进程
  • 线程:线程是进程中的一个执行单元
  • 进程和线程的关系?
    答:进程相当于一家公司,线程就是这家公司的员工;进程之间的内存是独立的,不共享,比如酷狗和英雄联盟的内存是独立的;线程之间的内存是共享的,其中栈内存是线程独有的,但是方法区内存和堆内存是线程共享的,也就是一个线程一个栈;
    扩展:栈内存存储局部变量,堆内存存储实例变量,方法区内存存储静态变量,由于栈是线程独有的,所以局部变量是安全的,由于方法区内存和堆内存是线程共享的,所以实例变量和静态变量是不安全的
  • 最早的计算机支持多进程吗?
    答:不支持,最多的计算机只支持单进程,CPU的使用率比较低
  • 多进程并发处理,有什么用呢?
    答:提供了CPU的使用率,对于单核的计算机来说,多进程并发并不是真正意义上的多进程,因为CPU同一时间内只能执行一个进程,只是当我们来回切换的时候CPU也能快速的进行进程间的切换
  • 多线程并发的作用?
    答:同时处理多个业务,提高处理速度,其实多线程并发也是假的,对于单核CPU来说,CPU在同一时间内只能做一件事情,并不会同时做多件事情,多个线程在抢夺CPU的执行权(CPU时间片),谁抢到CPU的执行权,谁就可以执行,然后多线程频繁切换执行,所以让我们感觉是多线程同时执行的
  • 如果两台电脑的QQ需要通信,需要什么?
    答:那我首先要知道你电脑的IP地址,这样才能找到你的电脑,IP地址是计算机在网络中的唯一标识,在同一个网络中,IP地址不能重复;然后我要知道你的端口,这样才能找到你的应用,一个应用启动的时候会开启一个进程,这个进程会拥有一个端口,端口是进程的唯一标识,在同一台计算机中,端口不能重复;

2、创建线程的3种方法

方法1、继承Thread,重写run()方法

代码:

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        for (int i=0;i<1000;i++){
            System.out.println("主线程-----------------"+i);
        }
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        for (int j=0;j<1000;j++){
            System.out.println("分支线程》》》》》》》》》"+j);
        }
    }
}

解释:定义MyThread类,继承Thread类,使用Ctrl+O重写run()方法,在main()方法里面定义Thread类对象,其实MyThread实现了Thread之后已经成为了一个线程,所以可以直接调用线程的start()方法开启myThread 线程,然后线程会执行线程中的run()方法,这样就实现了多线程,如下图:
在这里插入图片描述

方法2、直接定义Thread对象,重写run()方法(同方法1,都是继承Thread类)

代码:

public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread("branchThread"){
            @Override
            public void run() {
                for (int j=0;j<1000;j++){
                    System.out.println("分支线程》》》》》》》》》"+j);
                }
            }
        };
        thread.start();
        for (int i=0;i<1000;i++){
            System.out.println("主线程-----------------"+i);
        }
    }
}

方法3、实现Runnable,实现run方法

代码:

public class Test {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
        for (int i=0;i<1000;i++){
            System.out.println("主线程-----------------"+i);
        }
    }
}
class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int j=0;j<1000;j++){
            System.out.println("分支线程》》》》》》》》》"+j);
        }
    }
}

解释:MyRunnable类实现了Runnable接口,然后MyRunnable变成了一个可执行的类,可不是线程类,然后使用Ctrl+O实现run()方法,Thread线程对象定义的时候,里面是一个可执行的类,所以可以定义MyRunnable类对象,然后在Thread定义的时候把这个类对象放入()里面,使用start()启动线程,线程调用可执行的类中的run()方法
注意:线程调用的是start()方法,目的是开启一个线程,然后让线程去调用run()方法,不能在main方法里面直接调用run()方法,那样就是普通的方法调用,而不是多线程,因为就没有执行start()方法,也就没有其他的线程,这两种方法来说,实现Runnable比较好,因为这种方法把继承让了出来,扩展性比较强

方法4、使用Callable接口

尚硅谷JUC笔记第七集:Callable

3、线程生命周期

包括:新建状态(new)、就绪状态(runnable)、运行状态(running)、阻塞状态(blocked)、死亡状态(dead)
在这里插入图片描述

  • JVM对线程的调度模型:
    答:JVM采用抢占式调度模型,优先级较高的,可能获得的CPU时间片较多一点
  • 获取当前线程、设置当前线程名称、获取当前线程名称的几个方法:
		// 获取当前线程,注意这个是静态方法,无论是谁调用它,获取的都是d当前方法所在的线程
        Thread thread = Thread.currentThread();
        // 设置当前线程名称
        thread.setName("主线程");
        // 获取当前线程名称
        String name =thread.getName();

4、线程中的sleep方法

例1:

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        for (int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"--->"+i);
            // 由于父类中的run()方法都没有抛出异常,所以子类重写的run()方法也不能抛出异常,这也就是为什么线程睡眠不能向上抛出异常的原因
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

结果:
执行1次,睡眠1秒
例2:

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        try {
            myThread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("hello world!");
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        for (int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}

结果:
分支线程执行完毕后,等待了不到1s,输出了hello world!,可能你就有疑虑了,为什么我是使用分支线程调用的sleep方法,为什么受影响的倒是主线程,这是因为sleep()方法是一个静态方法,无论哪个名称调用它,其实编译的时候都编译成了Thread.sleep(1000);,所以这相当于是主线程睡眠了1s,简单来说就是由于sleep()方法是静态方法,所以在哪个线程中调用sleep()方法,就会让哪个线程睡眠,无论是使用什么调的sleep()方法
拓展:
其实上面的静态方法调用的问题,我还可以在举一个例子:

public class Test03 {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        MyClass.myFangFa();
        myClass = null;
        myClass.myFangFa();// 静态方法和.前面的东西没有半毛钱关系,其实真正编译之后还是MyClass.myFangFa();
    }
}
class MyClass{
    public static void myFangFa(){
        System.out.println("方法执行");
    }
}

结果:

方法执行
方法执行

解释:
可能你会认为会产生空指针异常,但是不会的,因为myFangFa是静态方法,所以无论谁调用它,编译的时候都会变成类调用它,所以即使前面调用的值为空,都是没有任何问题的,因为静态方法都是被静态方法所属的类直接调用的

5、如何中断一个线程的睡眠?

答:使用interrupt()方法,这种方法可以让处于休眠状态的线程抛出异常,从而中断睡眠,例子如下:

public class Test05 {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
        thread.interrupt();
    }
}
class MyRunnable implements Runnable{

    @Override
    public void run() {
        try {
            Thread.sleep(1000*60*60*24*365);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("起来干活了");
    }
}

结果:

java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.bjpowernode.test.MyRunnable.run(Test05.java:20)
	at java.lang.Thread.run(Thread.java:745)
起来干活了

6、如何强制终止线程的执行?

答:使用stop()方法,可以强行终止线程的执行,例子如下:

public class Test05 {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i =0;i<10;i++){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"--->"+i);
                }
            }
        });
        thread.setName("分支线程");
        thread.start();
        for (int i = 0;i<5;i++){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        thread.stop();
    }
}

结果:

分支线程--->0
分支线程--->1
分支线程--->2
分支线程--->3
分支线程--->4

7、如何合理终止线程的执行?

答:在分支线程里面设置一个布尔标记值,外部可以对布尔标记值进行修改,布尔标记值默认是true,把布尔标记值放在if里面,下面设置一个else,else里面设置break;,然而if函数呢放在一个while循环里面,当我不想在执行线程中循环的时候,通过外部把布尔标记值变成false,然后执行else里面的break,退出循环就可以了,例子如下:

public class Test05 {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
        for (int i=0;i<5;i++){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        myRunnable.setRun(false);
    }
}
class MyRunnable implements Runnable{
    private boolean isRun = true;
    public void setRun(boolean isRun){
        this.isRun = isRun;
    }
    int i=0;
    @Override
    public void run() {
        while (true){
            if (isRun){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"--->"+i++);
            }else {
                break;
            }
        }
    }
}

结果:

Thread-0--->0
Thread-0--->1
Thread-0--->2
Thread-0--->3
Thread-0--->4

8、线程优先级

代码:

public class Test {
    public static void main(String[] args) {
        System.out.println("线程最大优先级:"+Thread.MAX_PRIORITY);
        System.out.println("线程最小优先级:"+Thread.MIN_PRIORITY);
        System.out.println("线程正常优先级:"+Thread.NORM_PRIORITY);
        System.out.println("线程默认优先级:"+Thread.currentThread().getPriority());
        // 设置当前线程优先级是1,这两个方法属于实例方法
        Thread.currentThread().setPriority(1);
    }
}

结果:

线程最大优先级:10
线程最小优先级:1
线程正常优先级:5
线程默认优先级:5

总结:
线程优先级较高的,获取的CPU时间片就多一点,不过优先级较低的线程也可以抢夺CPU时间片,不过抢到的比较少而已

9、线程让位

使用yield()方法进行让位,让位的意思就是在这个方法执行之后短时间不去抢夺CPU执行权,如果已经抢到CPU执行权,那就放弃CPU执行权,这个方法是一个静态方法,在哪一个线程里面就让哪一个线程让位,线程让位之后可以重新抢夺CPU执行权,让位的时间是很短暂的,例子如下:

public class Test {
    public static void main(String[] args) {
        MyRunnable1 m1 = new MyRunnable1();
        MyRunnable2 m2 =new MyRunnable2();
        Thread thread1 = new Thread(m1);
        Thread thread2 = new Thread(m2);
        thread1.setName("m1");
        thread2.setName("m2");
        thread1.start();
        thread2.start();
    }
}
class MyRunnable1 implements Runnable{
    @Override
    public void run() {
        for (int i =0;i<10000;i++){
            if (i%100==0){
                Thread.yield();
            }
            System.out.println(Thread.currentThread().getName()+"》》》》》》》》》》"+i);
        }
    }
}
class MyRunnable2 implements Runnable{
    @Override
    public void run() {
        for (int i =0;i<10000;i++){
            System.out.println(Thread.currentThread().getName()+"=================="+i);
        }
    }
}

结果:

当执行第700次的时候发现取余100是0,所以线程让位,这个时候m2线程执行

10、线程合并

使用的是join()方法,如果把其他的线程合并到某个线程上,那么就先执行合并过来的其他线程,当其他线程执行完毕之后,在执行本线程,代码如下:

public class Test {
    public static void main(String[] args) {
        MyRunnable1 m1 = new MyRunnable1();
        Thread thread1 = new Thread(m1);
        thread1.setName("m1");
        thread1.start();
        for (int i =0;i<100;i++){
            if (i==50){
                try {
                    thread1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"================="+i);
        }
    }
}
class MyRunnable1 implements Runnable{
    @Override
    public void run() {
        for (int i =0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+"》》》》》》》》》》"+i);
        }
    }
}

结果:
在这里插入图片描述
在这里插入图片描述
解释:
可以看到,当i=50的时候,main线程停止运行,m1线程开始运行,当m1线程执行完毕,main()线程继续运行

11、线程同步机制概述

1、什么条件下需要考虑线程的安全性呢?
答:一、多线程并发环境下;二、多线程共享数据;三、所共享的数据涉及到修改的操作;
2、在多线程并发的环境下,如何保证数据的安全性?
答:使用线程同步机制,也就是说程序需要符合同步编程模型
3、什么是线程同步机制?
答:线程之间出现了等待关系,线程排队执行,这就是线程同步机制
4、什么是同步编程模型,什么是异步编程模型?
答:线程之间出现了等待关系,线程排队执行,这就是同步编程模型;线程之间没有出现等待关系,多线程同时并发执行,这就是异步编程模型;简单来说同步就是线程等待线程,异步就是多线程并发执行

  • 实现线程同步机制
    1、为什么要考虑线程同步机制呢?
    假设有这么一个例子,夫妇二人分别使用银行卡和存折去对一张卡取钱,如果丈夫取完了钱,发生了网络延迟,然后妻子同时也在取钱,这个时候妻子也在取钱,如果两个线程不是同步的,那么妻子也可以取钱,当妻子取完了钱,更新了账户余额,丈夫那边的网络延迟解除了,这个时候也会更新账户余额,那么这个时候银行就亏了,所以说需要线程同步机制,对于上面的例子我使用两个线程去实现,使用sleep()方法去完成网络延迟的功能,代码如下:
public class Test {
    public static void main(String[] args) {
        Account account = new Account();
        account.setActno("银行卡");
        account.setBlance(2500);
        MyRunnable m1 = new MyRunnable(account);
        MyRunnable m2 = new MyRunnable(account);
        Thread thread1 = new Thread(m1);
        Thread thread2 = new Thread(m2);
        thread1.setName("m1");
        thread2.setName("m2");
        thread1.start();
        thread2.start();
    }
}
class MyRunnable implements Runnable{
    private Account account;

    public MyRunnable() {
    }

    public MyRunnable(Account account) {
        this.account = account;
    }

    @Override
    public void run() {
        double money = 1000;
        account.withdraw(money);
        System.out.println(Thread.currentThread().getName()+"取款"+money+"元,取后剩余"+account.getBlance()+"元");
    }
}
class Account{
    private String actno;
    private double blance;

    public Account() {
    }

    public Account(String actno, double blance) {
        this.actno = actno;
        this.blance = blance;
    }

    public String getActno() {
        return actno;
    }

    public Account setActno(String actno) {
        this.actno = actno;
        return this;
    }

    public double getBlance() {
        return blance;
    }

    public Account setBlance(double blance) {
        this.blance = blance;
        return this;
    }

    /**
     * 取出钱,然后更新余额
     * @param money 取出的钱
     */
    public void withdraw(double money){
        double afterBlance = this.blance-money;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setBlance(afterBlance);
    }
}

结果:

m2取款1000.0元,取后剩余1500.0元
m1取款1000.0元,取后剩余1500.0

现在使用同步代码块去修改上面的代码:那就是withdraw()方法中的代码使用synchronized(){}括起来,其他的程序不变,如下面的代码:

	public void withdraw(double money){
        synchronized (this){
            double afterBlance = this.blance-money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBlance(afterBlance);
        }
    }

如果在执行,结果如下:

m1取款1000.0元,取后剩余1500.0元
m2取款1000.0元,取后剩余500.0

所以说通过synchronized 就实现了线程同步机制,括号里面的是共享对象,注意要是引用数据类型,只要这个对象是线程共享的对象,代码中的this指代的是账户对象,这个账户对象是两个线程共享的,或者可以换成actno也可以,actno是实例变量,存储在堆中,也是两个线程共享的,即使是在账户类中随便定义一个实例变量,也是可以的,因为实例变量存储在堆中,所以是两个线程共享的,也就是说()里面的对象是两个线程共享的就可以了,但是blance是不可以的,因为这个不是引用数据类型,如果你在withdraw()方法中定义一个String类型的变量也是不可以的,因为局部变量在栈中存储,由于一个线程一个栈,局部变量不是线程共享的,所以局部变量不能作为共享对象;
synchronized 不仅可以这样使用,还可以把synchronized 放在实例方法里面,例如可以把withdraw()方法更改一下,代码如下:

	public synchronized void withdraw(double money){
	    double afterBlance = this.blance-money;
	    try {
	        Thread.sleep(1000);
	    } catch (InterruptedException e) {
	        e.printStackTrace();
	    }
	    this.setBlance(afterBlance);
    }

这样产生的结果也是一样的,都是实现了线程同步机制,但是这个共享对象已经被确定了,就是this,this指代谁调用这个方法,那么this就是谁,那么共享对象就是谁,但是这个有一个弊端就是有的时候共享对象不是this,就不能使用这种方法了,并且同步代码块的范围是方法体中全部的代码,这样可能会拖慢程序执行的效率,其实同步代码块应该是在保证数据安全的前提下,尽可能的少,这样效率就会高
至于synchronized同步代码块的执行原理:假设m1线程和m2线程同时并发执行,当m1线程遇到了synchronized关键字,就会获取共享对象的对象锁,其实对象锁就是一个方便理解的名字,其实底层并不是一把锁,而是一个类似于0和1的标记,当m1线程获取到对象锁之后,就会把对象锁锁住,然后执行同步代码块里面的代码,当m1线程把里面的代码执行完了,然后就会打开对象锁,如果m1线程还没有把同步代码块里面的代码执行完,m2也遇到了synchronized关键字,它也会获取共享对象的对象锁,但是发现对象锁被锁住了,m2线程就会等待对象锁打开才会执行同步代码块的代码
说起来对象锁了,还有一个是类锁,就是把synchronized放在静态方法上,因为对象可以有多个,但是类只有一个,所以对象锁可以有多把,但是类锁只有一把,只要锁了其他的所有线程都不能使用了,例子代码如下:

	public static synchronized void hello(){
        System.out.println("hello");
    }

12、线程同步的几个经典面试题

面试题1:

public class Test01 {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        MyRunnable myRunnable1 = new MyRunnable(myClass);
        MyRunnable myRunnable2 = new MyRunnable(myClass);
        Thread t1 = new Thread(myRunnable1);
        Thread t2 = new Thread(myRunnable2);
        t1.setName("m1");
        t2.setName("m2");
        t1.start();
        t2.start();
    }
}
class MyRunnable implements Runnable{
    private MyClass myClass;

    public MyRunnable(MyClass myClass) {
        this.myClass = myClass;
    }

    @Override
    public void run() {
        if ("m1".equals(Thread.currentThread().getName())){
            myClass.m1();
        } else {
            myClass.m2();
        }
    }
}
class MyClass{
    public synchronized void m1(){
        System.out.println("m1 begin");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("m1 end");
    }
    public void m2(){
        System.out.println("m2 begin");
        System.out.println("m2 end");
    }
}

结果:

m1 begin
m1 end
m2 begin
m2 end

解释:
如果m1线程先获得CPU执行权,那就会执行m1()方法,但是m1()方法上面有synchronized关键字,所以执行到m1()方法的时候,会去锁池中找对象锁,然后遇到了sleep()方法,就进入了阻塞状态,这个时候m2线程就有机会去获得CPU执行权了,因为m2()方法上面没有synchronized关键字,所以不会去找对象锁,直接执行m2()中的代码;

面试题2:

把m2()方法上面也加上synchronized关键字,其他的不变
解释:
这个时候如果执行m1()方法,然后睡眠,在执行m2()方法,当看到synchronized关键字的时候,就会去找对象锁,这个时候发现锁被m1()方法占用了,这个时候m2()方法只有等待m1()方法执行完毕了,才会执行

面试题3:

在面试题2的基础上,给m1和m2方法加一个关键字static
解释:
这个考察的是类锁,无论一个类的对象锁有多少把,但是一个类只有一个类锁,静态方法是类的方法,假设m1线程先执行,把类锁占用了,然后睡眠,当m2线程获得CPU时间片的时候,也会去找类锁,发现类锁被m1线程占用了,所以只有等待m1()方法执行完毕,才能执行m2线程中的m2()方法

13、死锁

代码:

public class Test {
    public static void main(String[] args) {
        MyThread1 myThread1 = new MyThread1();
        MyThread2 myThread2 = new MyThread2();
        Object o1 = 1;
        Object o2 = 2;
        myThread1.setObject(o1, o2);
        myThread2.setObject(o1, o2);
        myThread1.start();
        myThread2.start();
    }
}
class MyThread1 extends Thread{
    private Object o1;
    private Object o2;
    public void setObject(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){
            
            }
        }
    }
}
class MyThread2 extends Thread{
    private Object o1;
    private Object o2;
    public void setObject(Object o1,Object o2){
        this.o1=o1;
        this.o2=o2;
    }
    @Override
    public void run() {
        synchronized (o2){
        	// 使用sleep(),让死锁一定发生
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1){

            }
        }
    }
}

解释:
同步代码块嵌套,很容易造成死锁,死锁在实际开发中不容易排查,所以尽可能减少同步代码块嵌套的使用

  • 守护线程
public class Test {
    public static void main(String[] args) {
        MyThread1 myThread1 = new MyThread1();
        myThread1.setName("守护线程");
        // 设置成主线程的守护线程
        myThread1.setDaemon(true);
        myThread1.start();
        for(int i = 1;i<=5;i++){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"====执行了"+i+"秒");
        }
    }
}
class MyThread1 extends Thread{
    @Override
    public void run() {
        int i = 1;
        while (true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"》》》执行了"+(i++)+"秒");
        }
    }
}

结果:

守护线程》》》执行了1秒
main====执行了1秒
main====执行了2秒
守护线程》》》执行了2秒
main====执行了3秒
守护线程》》》执行了3秒
main====执行了4秒
守护线程》》》执行了4秒
main====执行了5秒
守护线程》》》执行了5

解释:
只要把setDaemon()里面的参数设置成true,那就是把这个线程设置成守护线程了,守护的是你设置setDaemon()的代码所在的那个线程,比如这个就是分支线程守护主线程,守护线程一般都是死循环,但是当用户线程结束的时候,守护线程会自动结束

14、定时器

代码:

public class Test {
    public static void main(String[] args) {
        Timer timer = new Timer();
        Date date = null;
        try {
            date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2019-12-8 12:35:00");
        } catch (ParseException e) {
            e.printStackTrace();
        }
        
        // 第一种方法,从data开始,经过10s执行一次任务
        timer.schedule(new Task(),date,1000*10);
        
        // 第二种方法
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("主任务执行了");
            }
        }, date, 1000*5);
    }
}
class Task extends TimerTask {
    @Override
    public void run() {
        System.out.println("任务执行了");
    }
}

结果:

任务执行了
主任务执行了
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值