🌟在 Java 中,可以通过三种方式实现多线程
run()
方法run()
方法call()
方法,并使用 Future 来获取 call() 方法的返回结果java.lang
包下的一个线程类,用来实现 Java 多线程创建一个 Thread 线程类的子类(子线程),同时重写 Thread 类的 run()
方法
class MyThread1 extends Thread {}
run()
方法public void run() {}
创建该子类的实例对象,并通过调用 start()
方法启动多线程
MyThread1 thread1 = new MyThread1("thread1");
start()
方法启动多线程thread1.start();
创建一个 Runnable 接口的实现类,同时重写接口中的 run()
方法
class MyThread2 implements Runnable {}
run()
方法public void run(){ }
创建 Runnable 接口的实现类对象
MyThread2 myThread2 = new MyThread2();
使用 Thread 有参构造方法创建线程实例,并将 Runnable 接口的实现类的实例对象作为参数传入
Thread (Runnable target,String name)
构造方法创建线程对象Thread thread1 = new Thread(myThread2,"thread1");
调用线程实例的 start()
方法启动线程
start()
方法启动线程thread1.start();
创建一个 Callable 接口的实现类,同时重写 Callable 接口的call()
方法
class MyThread3 implements Callable<Object>{ }
call()
方法public Object call() throws Exception {}
创建 Callable 接口的实现类对象
MyThread3 myThread3 = new MyThread3();
通过 FutureTask 线程结果处理类的有参构造方法来封装 Callable 接口实现类
FutureTask<Object> ft1 = new FutureTask<> (myThread3);
使用参数为 FutureTask 类对象的 Thread 有参构造方法创建 Thread 线程实例
Thread(Runnable target ,String name)
构造方法创建线程对象Thread thread1 = new Thread(ft1,"thread1");
调用线程实例的 start()
方法启动线程
start()
方法启动线程thread1.start();
可以通过 FutureTask 对象的方法管理返回值
System.out.println("thread1 返回结果:" + ft1.get());
方法名 | 说明 |
---|---|
boolean cancel(boolean mayInterruptIFRunning) | 用于取消任务参数 ,mayInterruptIfRunning 表示是否允许取消正在执行却没有执行完毕的任务,如果设置 true,则表示可以取消正在执行的任务。 |
boolean isCancelled() | 判断任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true |
boolean isDone() | 判断任务是否已经完成,若任务完成,则返回 true |
V get() | 用于获取执行结果,这个方法会发生阻塞,一直等到任务执行完毕才返回执行结果。 |
V get(long timeout, TimeUnit unit) | 用于在指定时间内获取执行结果,如果在指定时间内,还没获取到结果,就直接返回 null。 |
在创建多线程时如果没有通过构造方法指定线程名称,则系统默认生成线程名称
由于一个类不能同时有两个父类,所以在当前类已经有一个父类的基础上,那么就只能采用实现 Runnable 接口或者 Callable 接口的方式来实现多线程。
前台线程和后台线程是一种相对的概念,新创建的线程默认都是前台线程,如果某个对象在启动之前调用了setDaemon(true)
语句,这个线程就变成一个后台线程。
System.out.println("main 线程是后台线程吗?" + Thread.currentThread().isDaemon());
thread.setDameon(true);
setDaemon()
方法必须在start()
方法之前调用,否则后台线程设置无效。start()
方法,此时就会从新建状态进入可运行状态start()
方法之后,等待 JVM 的调度,此时线程并没有运行。wait()
、 join()
等方法,就
会将当前运行中的线程转换为等待状态。wait()
方法而处于等待状态中的线程,必须等待其他线程调用 notify()
或者
notifyAll()
方法唤醒当前等待中的线程;调用 join()
方法而处于等待状态中的线程,必须等待其他
加入的线程终止。sleep(long millis)
、 wait(long timeout)
、
join(long millis)
等方法。wait(long timeout)
方法而处于等待状态中的线程,需要通过其他线程
调用 notify()
或者 notifyAll()
方法唤醒当前等待中的线程,或者等待限时时间结束后
也可以进行状态转换。run()
方法、call()
方法正常执行完毕或者线程抛出一个未捕获的异常(Exception)、
错误(Error),线程就进入终止状态。常量名 | 说明 |
---|---|
static int MAX_PRIORITY | 表示线程的最高优先级,相当于 10 |
static int MIN_PRIORITY | 表示线程的最低优先级,相当于 1 |
static int NORM_PRIORITY | 表示线程的普通优先级,相当于值 5 |
数字
thread1.setPriority(10);
优先级常量
thread2.setPriority(Thread.MIN_PRIORITY);
sleep(long millis)
。sleep()
方法可以让正在执行的线程暂停一段时间,进入休眠等待状态,这样其他的线程就可以
得到执行的机会。sleep(long millis)
方法会声明抛出 InterruptedException 异常,因此在调用该方法时应该捕获异常,或者声明抛出该异常。Thread.sleep(500);
sleep(long millis)
和 sleep(long millis,int nanos)
,这两种方法都带有休眠时间参数,当其他线程都终止后并不代表当前休眠的线程会立即执行,而是必须当休眠时间结束后,线程才会转换到就绪状态。yield()
方法来实现;yield()
该方法和 sleep(long millis)
方法有点类似,都可以让当前正在运行的线程暂停,区别在于 yield()
方法不会阻塞该线程,它只是将线程转换成就绪状态,让系统的调度器重新调度一次。yield()
方法之后,与当前线程优先级相同或者更高的线程可以获得执行的机会。Thread.yield()
join()
方法时,调用的线程将被阻塞,直到被 join()
方法加入的线程执行完成后它才会继续运行public static void main(String [] args) throws InterruptedException {}
(2) thread1.join()
线程的并发执行可以提高程序的效率,但是,当多个线程去访问同一个资源时,也会引发一些安全问题。
synchronized
关键字来修饰的代码块,这段代码块被称作同步代码块。
synchronized(lock){ // 操作共享资源代码块 }
run()
方法中,否则每个线程运行到run()
方法都会创建一个新对象,这样每个线程都会有一个不同的锁,每个锁都有自己的标志位,线程之间便不能产生同步的效果。
synchronized
定义的区域内时,便为这些操作加了同步锁。synchronized
关键字来修饰,被修饰的方法为同步方法,它能实现和同步代码块同样的功能。
🌟🌟具体语法格式: [修饰符] synchronized 返回值类型 方法名 ([参数1, ···]){ } ;
🌟🌟示例代码: private synchronized void saleTicket () { }
synchronized
修饰的方法在某一时刻只允许一个线程访问,访问该方法的其他线程都会发生阻塞,直到当前线程访问完毕后,其他线程才有机会执行。synchronized
同步代码块和同步方法使用一种封闭式的锁机制,使用起来非常简单,也能够解决线程同步过程中出现的线程安全问题;但也有一些限制,例如它无法中断一个正在等候获得锁的线程,也无法通过轮询得到锁,如果不想等下去,也就没法得到锁。private final Lock lock = new ReentrantLock() ;
(2)对代码块进行加锁: lock.lock() ;
(3)执行玩代码块后释放锁: lock.unlock() ;
lock()
方法和 unlock()
方法外,还提供了一些其他同步锁操作的方法,例如 tryLock()
方法可以判断某个线程锁是否可用。finally {}
代码块中调用unlock()
方法来解锁两个线程在运行时都在等待对方的锁,这样便造成了程序的停滞,这种现象称为死锁。
在多线程的程序中,上下工序可以看作两个线程,这两个线程之间需要协同完成工作,就需要线程之间进行通信。
wait()
、 notify()
、 notifyAll()
等方法用于解决线程间的通信问题,由于 Java 中所有类都是 Object 类的子类或间接子类,因此任何类的实例对象都可以直接使用这些方法。
🌟(1) void wait()
:使当前线程放弃同步锁并进入等待,直到其他线程进入此同步锁,并调用 notify()
或 notifyAll()
方法唤醒该线程为止。
🌟(2) void notify()
:唤醒此同步锁上等待的第一个调用 wait()
方法的线程。
🌟(3) void notifyAll()
:唤醒此同步锁上调用 wait()
方法的所有线程。wait()
、 notify()
、 notifyAll()
这三个方法的调用者都应该是同步锁对象,如果这三个方法的调用者不是同步锁对象,Java 虚拟机就会抛出 IllegalMonitorStateException 异常。wait()
提供了多个重载方法,包括无参 wait()
方法、有等待时间的wait(long timeout)
方法和 wait(long timeout, int nanos)
方法。其中,带有等待时间参数的 wait()
方法,除了会在其他线程对象调用 notify()
和notifyAll()
方法来唤醒当前处于等待状态的线程,还会在等待时间过后自动唤醒处于等待状态的线程。创建一个实现 Runnable 接口或者 Callable 接口的实现类,同时重写run()
或者 call()
方法;
class MyThread4 implements Callable<Object> {}
call()
方法public Object call() throws Exception {}
创建 Runnable 接口或者 Callable 接口的实现类对象;
MyThread4 myThread4 = new MyThread4();
使用 Executors 线程执行器类创建线程池;
ExecutorService executor = Executors.newCachedThreadPool();
使用 ExecutorService 执行器服务类的 submit()
方法将 Runnable 接口或者 Callable 接口的实现类对象提交到线程池进行管理;
Future<Object> result1 = executor.submit(myThread4);
线程任务执行完成后,可以使用 shutdown()
方法关闭线程池。
executor.shutdown();
方法名 | 说明 |
---|---|
ExecutorService newCachedThreadPool() | 创建一个可扩展线程池的执行器。这个线程池执行器适用于启动许多短期任务的应用程序。 |
ExecutorService newFixedThreadPool(int nThreads) | 创建一个固定线程数量线程池的执行器。这种线程池执行器可以很好地控制多线程任务,也不会导致由于相应过多导致的程序崩溃。 |
ExecutorService newSingleThreadExecutor() | 在特殊需求下创建一个只执行一个任务的单个线程。 |
ScheduledExecutorService newScheduledThreadPool(int corePoolSize) | 创建一个定长线程池,支持定时及周期性任务执行。 |
Callable
接口实现多线程时,会用到 FutureTask
类对线程执行结果进行管理和获取,由于该类在获取结果时是通过阻塞或者轮询的方式,违背多线程编程的初衷且耗费过多资源。FutureTask
存在的不足进行了改进,增加了一个强大的函数式异步编程辅助类 CompletableFuture
,该类同时实现了 Future 接口
和 CompletionStage 接口
,并对 Furure 进行了强大的扩展,简化异步编程的复杂性。runAsync()
和 supplyAsync()
方法的本质区别就是获取的 CompletableFuture 对象是否带有计算结果。ForkJoinPool.commonPool()
作为它的线程池进行多线程管理方法名 | 说明 |
---|---|
static CompletableFuture<Void> runAsync (Runnable runnable) | 以 Runnable 函数式接口类型为参数,并使用 ForkJoinPool.commomPool() 作为它的线程池执行异步代码获取 CompletableFuture 计算结果为空的对象。 |
static CompletableFuture<Void> runAsync (Runnable runnable,Executor executor) | 以 Runnable 函数式接口类型为参数,并传入指定的线程池执行器 executor 来获取CompletableFuture 计算结果为空的对象。 |
static <U> CompletableFuture<U> supplyAsync(Supplier<U>supplier) | 以 Supplier 函数式接口类型为参数,并使用 ForkJoinPool.commonPool() 作为它的线程池执行异步代码获取 CompletableFuture 计算结果非空的对象 |
static <U> CompletableFuture<U> supplyAsync (Supplier<U>supplier, Executor executor) | 以 Supplier 函数式接口类型为参数,并传入指定的线程池执行器 executor 来获取CompletableFuture 计算结果非空的对象。 |