Java多线程

多线程的创建

方式一:继承Thread类后重写run()方法,调用线程对象的start()方法启动线程

  • 优点:编码简单

  • 缺点:线程类已经继承Thread,无法继承其他类,不利于扩展

  • 注意:直接调用run方法会当成普通方法执行,此时相当于还是单线程执行

    把主线程任务放在子线程之前,这样主线程一直是先跑完的,相当于是一个单线程的效果了

方式二:实现Runnable接口

方案一:

  1. 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
  2. 创建MyRunnable任务对象
  3. 把MyRunnable任务对象交给Thread处理
  4. 调用线程对象的start()方法启动线程

Thread构造器:

构造器 说明
public Thread(String name) 可以为当前线程指定名称
public Thread(Runnable target) 封装Runnable对象成为线程对象
public Thread(Runnable target ,String name ) 封装Runnable对象成为线程对象,并指定线程名称

​ 优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强

​ 缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的

方案二:实现Runnable接口(匿名内部类形式)

new Thread(new Runnable() {
    @Override
    public void run() {

    }
}).start();
=====================================================
new Thread(() -> 方法体).start();

方式三:实现Callable接口

  • 得到任务对象
    1. 定义类实现Callable接口,重写call方法,封装要做的事
    2. 用FutureTask(继承Runnable接口)把Callable对象封装成线程任务对象
  • 把线程任务对象交给Thread并调用start()方法启动线程,执行任务
  • 线程执行完毕后,通过FutureTask的get()方法获取任务执行的结果
FutureTask<String> ft = new FutureTask<>(new Callable<String>() {
    @Override
    public String call() throws Exception {
        return "123";
    }
});
new Thread(ft).start();
System.out.println(ft.get());

​ 优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强

​ 可以在线程执行完毕后去获取线程执行的结果

​ 缺点:编码相对复杂

Thread的构造器常用方法

方法名称 说明
public Thread(String name) 可以为当前线程指定名称
public Thread(Runnable target) 封装Runnable对象成为线程对象
public Thread(Runnable target ,String name ) 封装Runnable对象成为线程对象,并指定线程名称
方法名称 说明
String getName() 获取当前线程的名称,默认线程名称是Thread-索引
void setName(String name) 将此线程的名称更改为指定的名称,通过构造器也可以设置线程名称
public static Thread currentThread(): 返回对当前正在执行的线程对象的引用
public static void sleep(long time) 让当前线程休眠指定的时间后再继续执行,单位为毫秒。

注意:

  1. 此方法是Thread类的静态方法,可以直接使用Thread类调用
  2. 这个方法是在哪个线程执行中调用的,就会得到哪个线程对象

解决线程安全问题

线程安全问题发生的原因是多个线程同时访问同一个共享资源且存在修改该资源

解决方法是加锁,把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来

方式一:同步代码块

synchronized(同步锁对象) {
    操作共享资源的代码(核心代码)
}
//对于实例方法建议使用this作为锁对象。
//对于静态方法建议使用字节码(类名.class)对象作为锁对象

方式二:同步方法

修饰符 synchronized 返回值类型 方法名称(形参列表) {
    操作共享资源的代码
}
//同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码

方式三:Lock锁

  • 为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,更加灵活、方便
  • Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
  • Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来构建Lock锁对象
方法名称 说明
public ReentrantLock() 获得Lock锁的实现类对象
void lock() 获得锁
void unlock() 释放锁

线程通信

所谓线程通信就是线程间相互发送数据,线程间共享一个资源即可实现线程通信

常见形式:

  • 通过共享一个数据的方式实现。
  • 根据共享数据的情况决定自己该怎么做,以及通知其他线程怎么做

实际应用场景:

  • 生产者与消费者模型:生产者线程负责生产数据,消费者线程负责消费生产者产生的数据。

  • 要求:生产者线程生产完数据后唤醒消费者,然后等待自己,消费者消费完该数据后唤醒生产者,然后等待自己

线程通信的前提:线程通信通常是在多个线程操作同一个共享资源的时候需要进行通信,且要保证线程安全

Object类的等待和唤醒方法:

方法名称 说明
void wait() 让当前线程等待并释放所占锁,直到另一个线程调用notify()方法或 notifyAll()方法
void notify() 随机唤醒一个正在等待的单个线程
void notifyAll() 唤醒正在等待的所有线程
  • 注意:上述方法应该使用当前同步锁对象进行调用,否则会引发IllegalMonitorStateException异常

线程池

线程池就是一个可以复用线程的技术

不使用线程池的问题:如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能

得到线程池对象:

  • 方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象
  • 使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象

ThreadPoolExecutor构造器的参数说明:

public ThreadPoolExecutor(int corePoolSize,	// 指定线程池的线程数量(核心线程),不能小于0
                          int maximumPoolSize,	// 指定线程池可支持的最大线程数,最大数量>=核心线程数量
                          long keepAliveTime,	// 指定临时线程的最大存活时间,不能小于0
                          TimeUnit unit,	// 指定存活时间的单位(秒、分、时、天),时间单位
                          BlockingQueue<Runnable> workQueue,	// 指定任务队列,不能为null
                          ThreadFactory threadFactory,	// 指定用哪个线程工厂创建线程,不能为null
                          RejectedExecutionHandler handler)	// 指定线程忙,任务满的时候,新任务的处理策略,不能为null

创建线程池示例:

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

临时线程什么时候创建?

  • 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。

什么时候会开始拒绝任务?

  • 核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝

ExecutorService的常用方法

方法名称 说明
void execute(Runnable command) 执行任务/命令,没有返回值,一般用来执行 Runnable 任务
Future submit(Callable task) 执行任务,返回未来任务对象获取线程结果,一般拿来执行 Callable 任务
void shutdown() 等任务执行完毕后关闭线程池
List shutdownNow() 立刻关闭,停止正在执行的任务,并返回队列中未执行的任务

新任务拒绝策略

策略 详解
ThreadPoolExecutor.AbortPolicy 丢弃任务并抛出RejectedExecutionException异常。是默认的策略
ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但是不抛出异常 这是不推荐的做法
ThreadPoolExecutor.DiscardOldestPolicy 抛弃队列中等待最久的任务 然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy 由主线程负责调用任务的run()方法从而绕过线程池直接执行

定时器

ScheduledExecutorService,目的是为了弥补Timer的缺陷(单线程,任一任务出异常会中断,影响其他任务执行),ScheduledExecutorService内部为线程池

Executors的方法 说明
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 得到线程池对象
ScheduledExecutorService的方法 说明
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) 周期调度方法

优点:基于线程池,某个任务出异常不会影响其他定时任务的执行

线程状态

线程状态 描述 对应流程
NEW(新建) 线程刚被创建,但是并未启动。 创建线程对象
Runnable(可运行) 线程已经调用了start()等待CPU调度 start方法
Blocked(锁阻塞) 线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态;。 无法获得锁对象
Waiting(无限等待) 一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能够唤醒 wait方法
Timed Waiting(计时等待) 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。带有超时参数的常用方法有Thread.sleep 、Object.wait。 sleep方法
Teminated(被终止) 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 全部代码执行完毕
posted @ 2022-09-17 16:52  TimQiu  阅读(47)  评论(0)    收藏  举报