Java基础——多线程

1 概述

线程:程序运行时资源分配的最小单位,一个程序内部的一条执行路径。
现在的程序通常都是多线程的程序,这样能够提高程序的运行速度。比如说,以一个cpu为例,假设该cpu只能运行一个程序,当一个程序需要做耗时的处理时,可能使用到的是其他的硬件资源去进行处理,而cpu却已经空闲下来了,那么cpu的这个空闲时间是完全浪费的,如果这个程序是多线程的程序,那么就可以在这个空闲的时间去处理另一个任务,好像两个任务是同时执行一样。

2 线程的创建方式

2.1 继承Thread类

Thread类代表着线程,可以直接创建Thread类的对象来创建线程,但通常都是通过继承Thread类来创建线程类。

public class ThreadDemo1 {
    public static void main(String[] args) {
        new MyThread().start();
        for (int i = 0; i < 5; i++) {
            System.out.println("MainThread:" + i);
        }
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("MyThread:" + i);
        }
    }
}

定义线程类,并重写Thread类中的run方法。创建线程类对象,然后调用start方法启动线程,线程执行完成后自动退出。
输出结果

MainThread:0
MainThread:1
MyThread:0
MyThread:1
MainThread:2
MyThread:2
MainThread:3
MyThread:3
MainThread:4
MyThread:4

可以看到我们创建的子线程与主线程的结果是交替输出的,两个线程均在运行中,而不是运行完一个的代码后再运行另一个。

2.2 实现Runnable接口

第二种创建线程的方式是实现Runnable接口,并重写其中的run方法。这个方法比继承Thread类使用更加方便,因为在Java中只能继承一个类,而可以实现多个接口,如果使用继承Thread类的方式会存在局限性。

public class ThreadDemo2 {
    public static void main(String[] args) {
        new Thread(new MyThread2()).start();
        for (int i = 0; i < 5; i++) {
            System.out.println("MainThread:" + i);
        }
    }
}

class MyThread2 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("MyThread2:" + i);
        }
    }
}

实现Runanble接口并重写run方法,并创建自建线程类的对象将其传进Thread类的构造方法中创建Thread对象,并调用start方法启动线程。

public Thread(Runnable target) {
    this(null, target, "Thread-" + nextThreadNum(), 0);
}

可以看到这种方法在启动的时候还是使用Thread类对象来开启线程的,Runnable类型的对象作为一个target交给Thread类来执行。
输出结果

MainThread:0
MainThread:1
MyThread2:0
MyThread2:1
MainThread:2
MainThread:3
MyThread2:2
MyThread2:3
MainThread:4
MyThread2:4

通常使用Runnable接口这种方式的时候使用的是匿名类对象的方式,并不创建实际的类来实现Runnable接口。

new Thread(new Runnable() {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("MainThread:" + i);
        }
    }
}).start();

Runnable是一个函数式接口,接口中只有一个run方法,对于函数式接口,可以使用lambda表达式进行简化

new Thread(() -> {
    for (int i = 0; i < 5; i++) {
        System.out.println("MainThread:" + i);
    }
}).start();

2.3 实现Callable接口

前面两种方式存在的问题是只能执行线程,并不能返回线程执行的结果。在某些场景下,需要线程去执行一个耗时的操作,执行完成之后我们要去获取执行的结果。
在jdk5.0之后提供了Callable和FutureTask结合的方式获取线程执行的结果。

public class ThreadDemo3 {
    public static void main(String[] args) {
        Callable<String> call = new MyCallable(100);
        FutureTask<String> futureTask = new FutureTask<>(call);
        new Thread(futureTask).start();
        try {
            String result = futureTask.get();
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class MyCallable implements Callable<String> {
    private int n;

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

    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= n; i++) {
            sum += i;
        }
        return "子线程计算结果:" + sum;
    }
}

自建类实现Callable接口,重写call方法并写入线程执行逻辑。Callable是一个泛型类,可以自定义call方法执行完成后返回的结果类型。创建类对象后需要将对象交给FutureTask对象,FutureTask最终实现了Runnable接口,所以其对象可以传给Thread类来创建Thread类对象来启动线程。线程执行完成后可以使用FutureTask的get方法获取执行后的结果。
如果线程没有执行完毕,会阻塞在get方法处,直到线程执行完毕后读取结果。

3 线程常用的方法

3.1 线程属性相关

线程属性相关API

public final String getName()
public final synchronized void setName()
public static native Thread currentThread();

示例:

public class ThreadDemo1 {
    public static void main(String[] args) {
        Thread thread1 = new MyThread();
//        thread1.setName("thread1");
        thread1.start();

        Thread thread2 = new MyThread();
//        thread2.setName("thread2");
        thread2.start();

        String mainThreadName = Thread.currentThread().getName();
        for (int i = 0; i < 5; i++) {
            System.out.println(mainThreadName + ":" + i);
        }
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

输出

main:0
Thread-0:0
Thread-1:0
Thread-0:1
main:1
Thread-0:2
Thread-1:1
Thread-0:3
main:2
Thread-0:4
Thread-1:2
main:3
Thread-1:3
main:4
Thread-1:4

可以看到可以给线程设置名字,也可以使用默认的线程名,默认线程名是主线程为main,子线程为Thread-索引。
可以将线程名设置放到线程类的构造器中。

class MyThread extends Thread {

    public MyThread(String name) {
       super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

一般不给线程取名字。

3.2 线程睡眠

API

public static native void sleep(long millis) throws InterruptedException;

使当前线程睡眠多少毫秒。

4 线程安全问题

当多个线程同时操作同一个共享资源的时候,会存在线程安全问题。

public class ThreadDemo4 {
    public static int i = 0;

    public static void main(String[] args) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int j = 0; j < 5; j++) {
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                    i++;
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int j = 0; j < 5; j++) {
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                    i++;
                }
            }
        }).start();
    }
}

输出

Thread-1:0
Thread-1:1
Thread-0:0
Thread-1:2
Thread-0:3
Thread-1:4
Thread-0:5
Thread-1:6
Thread-0:7
Thread-0:9

看输出结果,里面出现了两个0,且没有8这个数,这就是由于多个线程同时去操作一个共享资源导致的问题。++操作并不是一个不可分割的原子操作,可以分为三步,获取i的值,i的值加一,将加一后的结果赋值给i。那么就会导致这个问题,最开始读取的时候,两个线程同时读取,读到的都是0,然后加1后返回。没有8的原因也可见了,Thread-0读到7,然后执行加1,此时Thread-1刚执行完读取6然后在Thread-1中加1,导致短时间了内加了两次。
线程安全问题导致的原因就是各个线程对同一个数据的修改不是原子性的,导致其他线程读到了脏数据。

5 线程同步

上一小节中发现,当多个线程同时访问及修改共享资源的时候,会导致线程安全问题,其实质就是对共享资源的修改不是原子性的,导致其他线程访问的数据是线程修改后没有写回的脏数据。要解决这个问题,就需要操作多个线程有序的访问共享资源。

5.1 使用synchronized关键字

synchronized关键字可以用于修改代码块或者方法。被synchronized关键字修改的代码块称为同步代码块,修饰的方法称为同步方法。在其中包含的代码再同一时间内只能由一个线程访问,其他线程只能等现在访问的线程执行结束后,才能访问。
synchronized关键字后面跟一个锁,当一个线程访问该范围内代码时,会获取到锁,其他线程则无法获取锁,也无法执行范围内的代码,只有等正在执行的线程执行结束后释放锁,等待的线程才会获取锁进行执行。

public class ThreadDemo4 {
    public static int i = 0;

    public static void main(String[] args) {
        new Thread(ThreadDemo4::printNumber).start();
        new Thread(ThreadDemo4::printNumber).start();
    }

    public static void printNumber() {
        for (int j = 0; j < 5; j++) {
            synchronized (ThreadDemo4.class) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
                i++;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

输出:

Thread-0:0
Thread-1:1
Thread-1:2
Thread-0:3
Thread-1:4
Thread-0:5
Thread-1:6
Thread-0:7
Thread-1:8
Thread-0:9

线程交替访问同步代码块,只有一个线程访问结束后,释放synchronized定义的锁ThreadDemo4.class,其他线程才能够获取该锁来访问t同步代码块。
上面是使用synchronized关键字修饰代码段,称为同步代码段,更多的是使用synchronized关键字修饰方法的同步方法。

public class ThreadDemo4 {
    public static int i = 0;

    public static void main(String[] args) {
        new Thread(ThreadDemo4::printNumber).start();
        new Thread(ThreadDemo4::printNumber).start();
    }

    public static synchronized void printNumber() {
        for (int j = 0; j < 5; j++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
            i++;
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

输出:

Thread-0:0
Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4
Thread-1:5
Thread-1:6
Thread-1:7
Thread-1:8
Thread-1:9

从上面同步方法可以看出,同一时间,只有一个线程能够访问同步方法,当当前线程执行结束之后,释放锁,其他线程才能够获取锁执行同步方法。

锁对象要求:理论上是需要一个对于当前执行的线程来说是同一个对象。
锁对象使用任意唯一对象:不好,因为会影响其他无关的线程的执行。
规范要求:建议使用共享资源作为锁对象

  • 实例方法:使用this作为锁对象

因为实例方法,多个线程访问的都是同一个对象的该方法,所以传入this表示该实例对象,使用的是同一个把锁

  • 静态方法:使用字节码对象作为锁对象

对于静态方法,多个线程访问的是一个静态方法,将字节码对象作为锁,也是持有的同一把锁

同步方法底层原理:

  • 同步方法底层其实也是加了锁的,锁定的是整个方法
  • 对于实例方法:同步方法默认使用this作为锁对象
  • 对于静态方法:同步方法默认使用字节码对象作为锁对象

同步代码块和同步方法怎么选择?
同步代码块锁定的代码更加具体,但同步方法使用更加广泛,更加方便。

5.2 Lock锁

为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,使得加锁和释放锁变得更加的具体和灵活。
Lock是接口,一般使用实现类ReentrantLock。

方法名说明
public ReentrantLock()获得Lock锁实现类对象
void lock()获得锁
void unlock()释放锁
public class ThreadDemo4 {
    public static int i = 0;
    private static final Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        new Thread(ThreadDemo4::printNumber).start();
        new Thread(ThreadDemo4::printNumber).start();
    }

    public static void printNumber() {
        for (int j = 0; j < 5; j++) {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + ":" + i);
            i++;
            lock.unlock();
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

6 线程池

线程池是一个线程复用技术。当不使用线程池时,每次运行创建一个线程,会导致线程越来越多,线程是系统的一个重要的资源,如果频繁的创建线程,会大大的消耗系统的性能。线程池就是创建了一些线程在池子中,有任务来的时候从池子中拿出一个线程,执行任务,当任务执行结束后将线程返回给线程池,以实现线程复用的技术。

JDK5开始提供了线程池的接口:ExecutorService
创建线程池对象有两种方式:

  • 1.使用ExecutorService的实现类ThreadPoolExecutor创建一个线程池对象
  • 2.使用Executors工具类调用方法返回不同特点的线程池对象

6.1 ThreadPoolExecutor构造方法

ThreadPoolExecutor构造器参数说明

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

参数一:指定线程池的线程数量(核心线程): corePoolSize ->不能小于0
参数二:指定线程池可支持的最大线程数: maximumPoolSize ->最大数量>=核心线程数量
参数三:指定临时线程的最大存活时间: keepAliveTime ->不能小于0
参数四:指定存活时间的单位(秒、分、时、天): unit ->时间单位
参数五:指定任务队列: workQueue ->不能为null
参数六:指定用哪个线程工厂创建线程: threadFactory ->不能为null
参数七:指定线程忙,任务满的时候,新任务来了怎么办: handler ->不能为null

ExecutorService pool = new ThreadPoolExecutor(3,5,6, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

临时线程:临时线程只有在核心线程和任务队列都满了之后才会创建。
如上面的线程池初始化,拥有核心3个,最大线程数5个,任务队列可以缓存5个排队的处理任务。只有当任务大于3(核心线程数)+5(任务队列长度)=8个时,才会创建临时线程。由于最大线程数量为5个,所以能够支持的最大线程是3(核心线程数)+5(任务队列长度)+2(临时线程数)=10个。当没有线程执行结束,且有超过10个线程任务想要从线程池中获取线程执行时,就会触发拒绝策略。

6.2 拒绝策略

拒绝策略是ThreadPoolExecutor构造方法中最后传入的一个参数,用于指定当任务请求超出线程池数量加缓冲区长度时怎么处理新的线程任务请求。

策略说明
ThreadPoolExecutor.AbortPolicy丢弃新任务并抛出RejectedExecutionException异常。默认策略
ThreadPoolExecutor.DiscardPolicy丢弃新任务,但是不抛异常。不推荐做法
ThreadPoolExecutor.DiscardOldestPolicy抛弃队列中等待最久的任务,并将新任务加入
ThreadPoolExecutor.CallerRunsPolicy由主线程调用任务的run方法执行,不依赖线程池

6.3 使用线程池处理Runnable任务

public class ThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 6,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(5),
                new ThreadPoolExecutor.AbortPolicy());

        pool.execute(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        });
    }
}

输出:

pool-1-thread-1:0
pool-1-thread-1:1
pool-1-thread-1:2
pool-1-thread-1:3
pool-1-thread-1:4

6.4 使用线程池处理Callable任务

public class ThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 6,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(5),
                new ThreadPoolExecutor.AbortPolicy());

        Future<Integer> f1 = pool.submit(() -> {
            int count = 0;
            for (int i = 1; i <= 5; i++) {
                count += i;
                System.out.println(Thread.currentThread().getName() + ":" + count);
            }
            return count;
        });
        try {
            System.out.println(Thread.currentThread().getName() + ":" + f1.get());
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }
    }
}

输出:

pool-1-thread-1:1
pool-1-thread-1:3
pool-1-thread-1:6
pool-1-thread-1:10
pool-1-thread-1:15
main:15

6.5 Executors创建线程池

Executors工具类通过调用不同方法创建不同类型的线程池

方法名说明
newCachedThreadPool()线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了一段时间则会被回收掉
newFixedThreadPool()创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它
newSingleThreadExecutor()创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程
newScheduledThreadPool​(int corePoolSize)创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务
Executors底层实际也是调用了ThreadPoolExecutor创建线程池对象的,只是很多参数已经被配置封装好了。
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue());
}

在这里插入图片描述
大型并发系统环境中使用Executors如果不注意可能会出现系统风险。在阿里巴巴的Java开发手册中规定,不允许使用Executors创建线程池,建议使用ThreadPoolExecutor的方式来指定线程池参数。

7 线程通信

线程通信就是线程间相互发送数据,线程共享一个资源即实现了线程通信。
线程通信实际应用场景是生产者模式与消费者模式,生产者生产完数据后唤醒消费者,消费者消费完数据后通知消费者生产数据。
相关api

方法名说明
void wait​()让当前线程等待并释放所占锁,直到另一个线程调用notify()方法或 notifyAll()方法
void notify​()唤醒正在等待的单个线程
void notifyAll​()唤醒正在等待的所有线程
public class TestDemo {
    public static void main(String[] args) {
        CallThread callThread = new CallThread();
        callThread.start();

        ReceiveThread receiveThread = new ReceiveThread();
        receiveThread.start();
    }

    class CallSystem {
        public static int number = 0;

        public synchronized static void call() {
            try {
                number++;
                System.out.println("成功接入一个用户,等待分发~~");

                CallSystem.class.notify();
                CallSystem.class.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        public synchronized static void receive() {
            try {
                String name = Thread.currentThread().getName();
                if (number == 1) {
                    Thread.sleep(1000);
                    System.out.println(name + "此电话已经接收完毕了~~");
                    number--;
                }
                CallSystem.class.notify();
                CallSystem.class.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    static class CallThread extends Thread {
        @Override
        public void run() {
            while (true) {
                CallSystem.call();
            }
        }
    }

    static class ReceiveThread extends Thread{
        @Override
        public void run() {
            while (true) {
                CallSystem.receive();
            }
        }
    }
}

输出

成功接入一个用户,等待分发~~
Thread-1此电话已经接收完毕了~~
成功接入一个用户,等待分发~~
Thread-1此电话已经接收完毕了~~
成功接入一个用户,等待分发~~
Thread-1此电话已经接收完毕了~~
成功接入一个用户,等待分发~~

生产者生产一个消息后消费者消费,如此反复。

8 定时器

8.1 Timer创建与执行

定时器是一种控制任务延时调用,或周期调用的技术。作用有:闹钟或者定时邮件发送。
实现方式有两种:1、Timer 2、ScheduleExecutorService

public class TimerDemo1 {
    public static void main(String[] args) {
        Timer timer = new Timer();

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "aaa" + new Date());
            }
        }, 0, 2000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "bbb" + new Date());
            }
        }, 0, 2000);
    }
}

输出:

Timer-0 aaa Mon Jan 02 22:11:52 CST 2023
Timer-0 bbb Mon Jan 02 22:11:52 CST 2023
Timer-0 aaa Mon Jan 02 22:11:54 CST 2023
Timer-0 bbb Mon Jan 02 22:11:54 CST 2023
Timer-0 aaa Mon Jan 02 22:11:56 CST 2023
Timer-0 bbb Mon Jan 02 22:11:56 CST 2023
...

可以看到Timer实际会创建子线程执行任务代码,多个任务可以用一个Timer对象执行。但是需要注意的是当一个Timer对象执行多个任务时,多个任务执行的执行结果会影响其他线程的执行。
如执行时间影响:

public class TimerDemo1 {
    public static void main(String[] args) {
        Timer timer = new Timer();

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " aaa " + new Date());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, 0, 2000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " bbb " + new Date());
            }
        }, 0, 2000);
    }
}

输出:

Timer-0 aaa Mon Jan 02 22:14:09 CST 2023
Timer-0 bbb Mon Jan 02 22:14:10 CST 2023
Timer-0 aaa Mon Jan 02 22:14:11 CST 2023
Timer-0 bbb Mon Jan 02 22:14:12 CST 2023
Timer-0 aaa Mon Jan 02 22:14:13 CST 2023
Timer-0 bbb Mon Jan 02 22:14:14 CST 2023
Timer-0 aaa Mon Jan 02 22:14:15 CST 2023
Timer-0 bbb Mon Jan 02 22:14:16 CST 2023
...

任务aaa执行时间长,会导致Timer线程先执行完任务aaa,然后才会执行bbb,说明两个任务在同一Timer线程中执行,一个任务执行时间长会导致另一个线程不能及时的执行。
异常影响:

public class TimerDemo1 {
    public static void main(String[] args) {
        Timer timer = new Timer();

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " aaa " + new Date());
                System.out.println(10 / 0);
            }
        }, 0, 2000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " bbb " + new Date());
            }
        }, 0, 2000);
    }
}

输出:

Timer-0 aaa Mon Jan 02 22:17:29 CST 2023
Exception in thread "Timer-0" java.lang.ArithmeticException: / by zero
	at com.xy.demo8_timer.TimerDemo1$1.run(TimerDemo1.java:15)
	at java.base/java.util.TimerThread.mainLoop(Timer.java:566)
	at java.base/java.util.TimerThread.run(Timer.java:516)

一个任务抛出了异常,会导致整个Timer线程崩溃,其他的任务也就不会执行了。

8.2 使用ScheduleExecutorService

ScheduleExecutorService是一个线程池,可以通过Executors工具类创建,创建一个Timer的线程池,每次执行的任务通过线程池处理。

public class TimerDemo2 {
    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
        pool.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " aaa " + new Date());
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, 0, 2, TimeUnit.SECONDS);

        pool.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " aaa " + new Date());
                System.out.println(10 / 0);
            }
        }, 0, 2, TimeUnit.SECONDS);

        pool.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " aaa " + new Date());
            }
        }, 0, 2, TimeUnit.SECONDS);
    }
}

输出:

pool-1-thread-3 aaa Mon Jan 02 22:24:22 CST 2023
pool-1-thread-2 aaa Mon Jan 02 22:24:22 CST 2023
pool-1-thread-1 aaa Mon Jan 02 22:24:22 CST 2023
pool-1-thread-3 aaa Mon Jan 02 22:24:24 CST 2023
pool-1-thread-3 aaa Mon Jan 02 22:24:26 CST 2023
pool-1-thread-1 aaa Mon Jan 02 22:24:28 CST 2023
pool-1-thread-3 aaa Mon Jan 02 22:24:28 CST 2023
pool-1-thread-3 aaa Mon Jan 02 22:24:30 CST 2023
...

如输出所示,有一个定时任务后,会从线程池中分配一个线程进行处理。即使其中一个线程出现了异常或者延时,也不会影响其他线程的执行。

9 线程的状态

线程的状态就是线程从创建到死亡的整个过程,中间经历了各种状态转换。
Java中总共定义了6种状态。

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

State是Thread类中的一个枚举类,总共定义了6种状态,下面是6种状态之间的转换:
在这里插入图片描述
下面为6种状态的总结
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值