Java多线程笔记(刘意day23、24)

1.JVM运行程序原理

  • 由Java命令会启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个“主线程”,然后主线程去调用某个类的main方法。所以main方法运行在主线程中。在此之前的所有程序都是单线程的。
  • JVM的启动是单线程还是多线程的?
    • 多线程:最低启动了两个线程,用户线程 + 垃圾回收线程(先启动)
    • 垃圾回收线程:先启动,斗则很容易内存溢出。

2.如何实现多线程的程序

  • 线程是依赖进程存在的,应该先创建一个进程出来。
  • 进程是由系统创建的,所以应该去调用系统功能创建一个进程。
  • Java不能直接调用系统功能,所以没有办法直接实现多线程程序。
  • 但是,可以调用C/C++写好的程序来实现多线程程序。由C/C++去调用系统功能创建进程,然后提供一些类供Java去调用。

3.创建多线程的方式(3种)

(1)继承Thread

  • 步骤
    • (1)自定义类MyThread继承Thread类
    • (2)重写run()方法
      • 为什么要重写run()方法?
        • 不是类中多有代码都需要被线程执行。所以将需要被线程执行的代码放在run()中。
      • 调用run()为什么是单线程的?
        • 因为run()直接调用就相当于普通的方法调用,所以看到的是单线程的效果。
      • 如何获取线程对象的名称? getName()
        • 线程名称为什么是Thread-X编号?
          • Thread类构造器中有init()方法,方法中传入参数:同步方法nextThreadNum()。
            • 同步方法nextThreadNum():return threadInitNumber++;int类型,初始化从0开始。
        • 如何设置线程对象的名称呢?
          • t.setName("名称");
        • 如何获取main()方法所在线程对象的名称?
          • System.out.println("Thread.currentThread().getName()");

             

    • (3)启动线程
// MyThread.java
public class MyThread extends Thread {
    @Override
    public void run() {
        // 一般来说,被线程执行的代码都是很好使的。用循环模拟
        for(int i = 0; i < 100; ++i){
            System.out.println(getName() + "------" + i);
        }
    }
}

// MyThreadTest.java
public class MyThreadTest {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.setName("线程1");
        t2.setName("线程2");
        t1.start();
        t2.start();
    }
}

(2)实现Runnable接口

  • 步骤
    • (1)自定义类MyRunnable实现Runnable接口
    • (2)重写run()
    • (3)创建MyRunnable类的对象
    • (4)创建Thread类的对象,把(3)步骤的对象作为构造参数传递
  • 实现接口方式的优点
    • 可以避免由于Java单继承带来的局限性
    • 适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码、数据有效分离,较好的体现了面向对象的设计思想
// MyRunnable.java
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for(int i = 0; i < 10; ++i){
            // 实现接口的方式不能直接使用Thread类的方法,但是可以间接使用
            System.out.println(Thread.currentThread().getName() + "------" + i);
        }
    }
}

// MyRunnableTest.java
public class MyRunnableTest {
    public static void main(String[] args) {
        MyRunnable r = new MyRunnable();

        // 方式1
//        Thread t1 = new Thread(r);
//        Thread t2 = new Thread(r);
//        t1.setName("线程1");
//        t2.setName("线程2");

        // 方式2
        Thread t1 = new Thread(r,"线程1");
        Thread t2 = new Thread(r,"线程2");

        t1.start();
        t2.start();
    }
}

(3)实现Callable接口(带泛型)

  • 这里的泛型指的是call()的返回值类型。
  • 如果需要调用线程后返回结果:用Callable方式
  • 依赖于线程池而存在,故掌握前两种最重要。
/**
 * 线程求和案例
 */
public class MyCallable implements Callable<Integer> {

    private int number;

    public MyCallable(int number){
        this.number = number;
    }

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for(int i = 1; i <= number; ++i){
            sum += i;
        }
        return sum;
    }
}


public class MyCallableDemo {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService pool = Executors.newFixedThreadPool(2);

        Future<Integer> f1 = pool.submit(new MyCallable(100));
        Future<Integer> f2 = pool.submit(new MyCallable(200));
        Integer i1 = f1.get();
        Integer i2 = f2.get();

        System.out.println(i1);
        System.out.println(i2);

        pool.shutdown();
    }
}

 

4.线程调度的两种模型

  • 线程调度的两种模型

    • 分时调度模型:公平,无饥饿

      • 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
    • 抢占式调度模式(Java使用)
      • 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个。优先级高的线程获取CPU时间片相对多一些。
  • 如何设置和获取线程优先级?
    • 线程默认优先级:5.
    • 线程优先级范围:1~10
    • java.lang.IllegalArgumentException非法参数异常:当设置优先级输入参数不在1~10范围内时,抛出此异常。
    • 线程优先级别高仅仅表示线程获取CPU时间片的几率高,但是要在次数比较多时,或者多次运行时才能看到比较好的效果。
// ThreadPriority.java
public class ThreadPriority extends Thread{
    @Override
    public void run() {
        for(int i = 0; i < 100; ++i){
            System.out.println(getName() + "------" + i);
        }
    }
}

// ThreadPriorityTest.java
public class ThreadPriorityTest {
    public static void main(String[] args) {
        ThreadPriority t1 = new ThreadPriority();
        ThreadPriority t2 = new ThreadPriority();
        t1.setName("线程1");
        t2.setName("线程2");
        t1.setPriority(10);
        t2.setPriority(2);
        t1.start();
        t2.start();
    }
}

5.线程控制

  • 线程休眠:静态方法 public static void sleep()
// MyThread.java
public class MyThread extends Thread {
    @Override
    public void run() {
        for(int i = 0; i < 100; ++i){
            System.out.println(getName() + "------" + i);

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


// MyThreadTest.java
public class MyThreadTest {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.setName("线程1");
        t2.setName("线程2");
        t3.setName("线程3");
        t1.start();
        t2.start();
        t3.start();
    }
}
  • 等该线程执行完了,再执行其他的线程:final方法 public final void join()
// MyThreadTest.java
public class MyThreadTest {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        t1.setName("线程1");
        t2.setName("线程2");
        t3.setName("线程3");

        t1.start();
        try {
            // 等线程1全部执行完了,再执行其他的线程。
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        t2.start();
        t3.start();
    }
}
// 运行结果
线程1------0
线程1------1
线程1------2
线程1------3
线程1------4
线程1------5
线程1------6
线程1------7
线程1------8
线程1------9
线程2------0
线程3------0
线程2------1
线程2------2
线程2------3
线程2------4
线程2------5
线程2------6
线程3------1
线程3------2
线程3------3
线程3------4
线程3------5
线程2------7
线程3------6
线程2------8
线程3------7
线程2------9
线程3------8
线程3------9
  • 礼让线程
    • public static void yield()
    • 暂停当前正在执行的线程对象,并执行其他的线程。
    • 让多个线程的执行更加和谐了,但是不能靠他保证一人一次。
// MyThread.java
public class MyThread extends Thread {
    @Override
    public void run() {
        for(int i = 0; i < 10; ++i){
            System.out.println(getName() + "------" + i);
            Thread.yield();
        }
    }
}
  • 守护线程
    • public final void setDaemon()
    • 将该线程标记为守护线程或用户线程
    • 当正在运行的线程都是守护线程时,JVM退出。
    • 该方法必须在被守护线程前调用。
    • 被守护线程死时,守护线程获取到CPU的时间片,会执行完再死掉。
// MyThreadTest.java
public class MyThreadTest {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.setName("关羽");
        t2.setName("张飞");

        // 设置守护线程
        t1.setDaemon(true);
        t2.setDaemon(true);
        t1.start();
        t2.start();

        Thread.currentThread().setName("刘备");
        for(int i = 0; i < 5; ++i){
            System.out.println(Thread.currentThread().getName() + "------" + i);
        }
    }
}


// 运行结果
关羽------0
刘备------0
张飞------0
刘备------1
关羽------1
刘备------2
张飞------1
刘备------3
关羽------2
刘备------4
张飞------2
关羽------3
张飞------3
关羽------4
张飞------4
关羽------5
张飞------5
关羽------6
张飞------6
关羽------7
张飞------7
关羽------8
关羽------9
  • 停止线程
    • public final void stop()
    • 让线程停止,后面的代码都不执行。
    • 该方法已经横线划掉了,但是还可以用。
    • 不建议使用,太暴力了。
// MyThread.java
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("开始执行:" + new Date());

        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            System.out.println("线程被终止了!");
        }

        System.out.println("结束执行:" + new Date());
    }
}


// MyThreadTest.java
public class MyThreadTest {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();

        // 超过3s,就中断线程
        try {
            Thread.sleep(3000);
            t1.stop();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

// 运行结果
开始执行:Wed Apr 28 15:06:51 CST 2021
  • 中断线程
    • public void interrupt()
    • 把线程的状态终止,并抛出一个InterruptedException异常。
// MyThreadTest.java
public class MyThreadTest {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();

        // 超过3s,就中断线程
        try {
            Thread.sleep(3000);
            t1.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

// 运行结果
开始执行:Wed Apr 28 15:07:46 CST 2021
线程被终止了!
结束执行:Wed Apr 28 15:07:49 CST 2021

6.线程生命周期图5个   线程状态转换图7个

  • 新建:创建线程对象
  • 就绪:有执行资格,没有执行权
  • 运行:有执行资格,有执行权
  • 阻塞:没有执行资格,没有执行权。(同步阻塞/等待阻塞/其他阻塞)由于一些操作让线程处于了该状态。另外一些操作可以把它激活,激活后处于就绪状态。
  • 死亡:线程对象变成垃圾,等待被回收

7.多线程练习

卖电影票

  • 某电影院正在上映一部电影,共有100张票,有3个售票窗口,请设计一个程序模拟该电影院售票。
  • 继承Thread类方式:不合理
  • 使用Runnable接口的方式:更好的将数据与代码分离

线程安全问题

  • 出现线程安全的原因
    • 是否是多线程环境
    • 是否有共享数据
    • 是否有多条语句操作共享数据
  • 解决思想:
    • 针对第三条,前两条不能处理
    • 把多条语句操作共享数据的代码给包成一个整体,让某个线程执行时,别的线程不能执行——同步机制
  • 同步机制
    • 特点:多个线程使用的是同一个锁对象。
    • 优点:解决了多线程的安全问题。
    • 缺点:当线程特别多时,每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

同步机制实现1

  • 同步的两种方式
    • 同步代码块
    • 同步方法
  • synchronized(对象){需要同步的代码;}
    • 对象:锁,多个线程必须是同一把锁
    • 同步代码块的锁对象可以是任意的
    • 需要同步的代码:多条语句操作共享数据的代码
// SellTicket.java
public class SellTicket implements Runnable {
    // 共享资源
    private int tickets = 100;

    // 锁对象可以是任意的
    private Object obj = new Object();
    // private Test obj = new Test();

    @Override
    public void run() {
        // 为了模拟一直有票
        while(true){
            if(tickets > 0){
                synchronized (obj){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets-- + "张票");
                }
            }
        }
    }

    class Test{}
}

// SellTicketDemo.java
public class SellTicketDemo {
    public static void main(String[] args) {
        // 创建一个资源:一个数据
        SellTicket st = new SellTicket();
        // 创建三个线程
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

        // 启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}
  •  
    • 同步方法:把synchronized加在方法上
      • 同步方法的锁对象:this
      • // SellTicket.java
        public class SellTicket implements Runnable {
            // 共享资源
            private int tickets = 100;
        
            @Override
            public synchronized void run() {
                // 为了模拟一直有票
                while(true){
                    if(tickets > 0){
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
        
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets-- + "张票");
                    }
                    
                }
            }
        }
        

         

    • 静态方法的锁对象:类的字节码文件对象。
      • synchronized SellTicket.class
  • 线程安全的类
    • StringBuffer
    • Hashtable
    • Vector
      • 即是线程安全,也不用它,效率太低
      • 一般用
List<String> list = Collections.synchronizedList(new ArrayList<String>());

同步机制实现2

  • Lock是一个接口
  • 可以明确的看到什么时候加锁,什么时候放锁
    • void lock()
    • void unlock()
  • 实现类:ReentrantLock
// SellTicket.java
public class SellTicket implements Runnable {
    // 共享资源
    private int tickets = 100;
    // 创建锁对象
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        // 为了模拟一直有票
        while(true){
            try {
                // 加锁
                lock.lock();

                if(tickets > 0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets-- + "张票");
                }
            } finally {
                // 如果发生异常可以不用catch,但是一定要把锁释放掉
                lock.unlock();
            }

        }
    }
}


// SellTicketDemo.java
public class SellTicketDemo {
    public static void main(String[] args) {
        // 创建一个资源:一个数据
        SellTicket st = new SellTicket();
        // 创建三个线程
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

        // 启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

 

  • 同步的弊端
    • 效率低
    • 容易产生死锁
      • 两个或者两个以上的线程在争夺资源的过程中,发生的一种相互等待的现象。
      • 面试题:写一个死锁Demo(圆桌上人吃饭餐具互相持有一个且等待的问题)
      • public class MyLock {
            // 创建两个锁对象,模拟一副筷子的两只
            public static final Object objA = new Object();
            public static final Object objB = new Object();
        }
        
        
        public class DieLock extends Thread{
            private boolean flag;
            public DieLock(boolean flag){
                this.flag = flag;
            }
        
            @Override
            public void run() {
                if(flag){
                    synchronized (MyLock.objA){
                        System.out.println("if objA");
                        synchronized (MyLock.objB){
                            System.out.println("if objB");
                        }
                    }
                } else {
                    synchronized (MyLock.objB){
                        System.out.println("else objB");
                        synchronized (MyLock.objA){
                            System.out.println("else objA");
                        }
                    }
                }
            }
        }
        
        public class DieLockDemo {
            public static void main(String[] args) {
                DieLock dl1 = new DieLock(true);
                DieLock dl2 = new DieLock(false);
                dl1.start();
                dl2.start();
            }
        }

         

生产者消费者模型

  • 线程间通信问题:不同种类的线程(生产者线程、消费者线程)间针对同一个资源的操作。
    • 生产和消费的应该是用一个资源——线程通信
      • 实现:在外界把这个数据生产出来,通过构造方法传递给生产线程和消费线程。
      • 线程安全问题:生产者和消费者加的锁必须是同一把。

public class Student {
    String name;
    int age;
}

public class SetThread implements Runnable{
    private Student s;
    private int x = 0;

    public SetThread(Student s){
        this.s = s;
    }

    @Override
    public void run() {
        while(true){
            synchronized (s){
                if (x % 2 == 0){
                    s.name = "林青霞";
                    s.age = 27;
                } else {
                    s.name = "刘意";
                    s.age = 30;
                }
                x++;
            }
        }
    }
}


public class GetThread implements Runnable{

    private Student s;

    public GetThread(Student s){
        this.s = s;
    }

    @Override
    public void run() {
        while(true){
            synchronized (s){
                System.out.println(s.name + "------" + s.age);
            }
        }
    }
}


/**
 * 分析:
 *      资源类:Student
 *      设置学生数据:SetThread(生产者)
 *      获取学生数据:GetThread(消费者)
 *      测试类:StudentDemo
 */
public class StudentDemo {
    public static void main(String[] args) {
        // 创建资源
        Student s = new Student();

        // 生产和消费的类
        SetThread st = new SetThread(s);
        GetThread gt = new GetThread(s);

        // 创建线程
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);

        // 启动线程
        t1.start();
        t2.start();
    }
}
  • 线程的等待唤醒机制:生产者有生产出来的东西,才能让消费者去消费。(没有东西可以消费时,消费者必须等待)
    • Object类中的:
      • 等待:wait()
      • 唤醒:
        • notify():唤醒单个线程
        • notifyAll():唤醒所有线程
    • 为什么这些方法不定义在Thread类中呢?
      • 这些方法的调用必须通过锁对象(可以是任意的)调用,所以必须在Object类中。
public class Student {
    String name;
    int age;
    boolean flag;
}

public class SetThread implements Runnable{
    private Student s;
    private int x = 0;

    public SetThread(Student s){
        this.s = s;
    }

    @Override
    public void run() {
        while(true){
            synchronized (s){
                // 判断有没有生产好的
                if(s.flag){
                    try {
                        s.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                if (x % 2 == 0){
                    s.name = "林青霞";
                    s.age = 27;
                } else {
                    s.name = "刘意";
                    s.age = 30;
                }
                x++;

                // 修改标记,并唤醒线程
                s.flag = true;
                s.notify();
            }
        }
    }
}


public class GetThread implements Runnable{

    private Student s;

    public GetThread(Student s){
        this.s = s;
    }

    @Override
    public void run() {
        while(true){
            synchronized (s){
                if(!s.flag){
                    try {
                        s.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println(s.name + "------" + s.age);

                // 修改标记,并唤醒线程
                s.flag = false;
                s.notify();
            }
        }
    }
}


public class StudentDemo {
    public static void main(String[] args) {
        // 创建资源
        Student s = new Student();

        // 生产和消费的类
        SetThread st = new SetThread(s);
        GetThread gt = new GetThread(s);

        // 创建线程
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);

        // 启动线程
        t1.start();
        t2.start();
    }
}
  • 改进版
    • 将Student的成员变量私有化
    • 把设置和获取的操作封装成了功能,并同步
    • 设置和获取的线程中只需要调用方法即可
public class Student {
    private String name;
    private int age;
    private boolean flag;

    public synchronized void set(String name, int age){
        // 生产者:有数据就等待
        if (this.flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 设置数据
        this.name = name;
        this.age = age;

        // 修改标记
        this.flag = true;
        this.notify();
    }

    public synchronized void get(){
        // 消费者:没有数据就等待
        if(!this.flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 获取资源
        System.out.println(this.name + "------" + this.age);

        // 修改标记
        this.flag = false;
        this.notify();
    }
}



public class SetThread implements Runnable{
    private Student s;
    private int x = 0;

    public SetThread(Student s){
        this.s = s;
    }

    @Override
    public void run() {
        while(true){
                if (x % 2 == 0){
                    s.set("林青霞", 27);
                } else {
                    s.set("刘意", 30);
                }
                x++;
        }
    }
}


public class GetThread implements Runnable{

    private Student s;

    public GetThread(Student s){
        this.s = s;
    }

    @Override
    public void run() {
        while(true){
            s.get();
        }
    }
}


public class StudentDemo {
    public static void main(String[] args) {
        // 创建资源
        Student s = new Student();

        // 生产和消费的类
        SetThread st = new SetThread(s);
        GetThread gt = new GetThread(s);

        // 创建线程
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);

        // 启动线程
        t1.start();
        t2.start();
    }
}

8.线程池

  • 最麻烦的:线程池的大小
    • 压力测试
    • 并发访问测试
  • Executors工厂类:产生线程池
    • newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。(线程最大并发数不可控制);线程池为无限大,当执行第二个任务时若第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

    • newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

      • 可以指定线程池数量

      • 可以执行Runnable或者Callable对象代表的线程

      • 调用如下方法即可

      • Future<?> submit(Runnable task)
        <T> Future<T> submit(Callable<T> task)
        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;
            }
        }
        
        
        
        public class ExcutorsDemo {
            public static void main(String[] args) {
                // 创建线程池
                ExecutorService pool = Executors.newFixedThreadPool(2);
        
                // 创建两个匿名的新线程传进去
                pool.submit(new MyRunnable());
                pool.submit(new MyRunnable());
        
                // 结束线程池
                pool.shutdown();
            }
        }
        

         

    • newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行、延迟执行。

    • newSingleThreadExecutor:创建一个线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

public static ExecutorService newCachedThreadPool(){}
public static ExecutorService newFixedThreadPool(){}
public static ExecutorService newScheduledThreadPool(){}
public static ExecutorService newSingleThreadPool(){}
  • 匿名内部类的方式实现多线程程序
    • 创建的线程太多了,化简
    • 匿名内部类的格式
      • 本质:使该类或者接口的子类对象
new 类名或者接口名(){
    重写方法;
};
public class ThreadDemo {
    public static void main(String[] args) {
        // 方式1
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; ++i){
                    System.out.println(Thread.currentThread().getName() + "------" + i);
                }
            }
        }.start();

        // 方式2
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; ++i){
                    System.out.println(Thread.currentThread().getName() + "------" + i);
                }
            }
        }).start();

        // 更有难度的
        // 两个run()执行哪个? 执行Thread的World
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; ++i){
                    System.out.println("Hello" + "------" + i);
                }
            }
        }){
            @Override
            public void run() {
                for (int i = 0; i < 100; ++i){
                    System.out.println("World" + "------" + i);
                }
            }
        }.start();
    }
}
  • 定时器
    • 可用于调度多个不定时任务以后台线程的方式执行
    • 实现
      • java.util.Timer:定时器类
      • java.util.TimerTask:(抽象类)任务类
public class TimerDemo {
    public static void main(String[] args) {
        // 创建定时器对象
        Timer t = new Timer();

        // 3s后执行任务
        t.schedule(new MyTask(t), 3000);

//        // 3s后执行任务第一次,如果不成功,每隔2s再执行一次
//        t.schedule(new MyTask(t), 3000,2000);
    }
}

class MyTask extends TimerTask{

    private Timer t;

    public MyTask(){}

    public MyTask(Timer t){
        this.t = t;
    }
    @Override
    public void run() {
        System.out.println("该任务需要执行的操作");

        // 任务执行完了要结束,不然会一直执行
        t.cancel();
    }
}
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

/**
 * 在指定的时间删除我们指定目录
 */
public class DeleteDir {
    public static void main(String[] args) throws ParseException {
        Timer t = new Timer();

        String s = "2021-04-30 18:00:00";
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");
        Date d = simpleDateFormat.parse(s);

        t.schedule(new DeleteFolder(), d);

    }
}

class DeleteFolder extends TimerTask{
    @Override
    public void run() {
        File file = new File("demo");
        deleteFolder(file);
    }

    /**
     * 递归删除文件夹及其子文件夹和子文件
     * @param file
     */
    public void deleteFolder(File file){
        File[] files = file.listFiles();
        if(files != null){
            for(File i : files){
                if(i.isDirectory()){
                    deleteFolder(i);
                } else {
                    System.out.println(i.getName() + "------" + file.delete());
                }
            }
            System.out.println(file.getName() + "------" + file.delete());
        }
    }
}

9.多线程面试题

  •  sleep() 和 wait() 方法的区别
    • sleep():必须指定时间,不释放锁
    • wait():可以不指定时间,也可以指定时间,释放锁
  • run() 和 start() 的区别?
    • run():仅仅是封装需要被执行的代码,直接调用是普通方法。
    • start():首先启动了线程,然后再由JVM去调用该线程的run()。
  • java.lang.IllegalThreadStateException 非法线程状态异常?
    • 同一个线程启动两次,会报这个错误。
    • 实现多线程,需要创建两个对象,分别调用他们的start()。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值