Java中如何创建新线程?
第一种方式:继承Thread类
- 写一个子类继承Thread
- 重写run方法
- 创建该类的对象,代表一个线程
- 调用start方法启动线程,该线程会执行run方法
这种方式的优点在于编码方式简单,但是该类已经继承了Thread类,不能继承其他类。
注意:
- 启动线程时一定调用start方法,而非run方法(直接调用run方法会被当成是普通方法执行,只有调用start方法才会启动一个新线程)
- 不能把主线程的任务放在启动子线程的语句之前(要先启动子线程,否则主线程会先跑完,相当于单线程执行)
第二种方式:实现Runnable接口
- 定义一个实现类实现Runnable
- 重写run方法
- 实例化该实现类任务对象
Runnable target = new MyRunnable();
- 把任务对象交给一个线程对象处理并调用Thread对象的start方法
new Thread(target).start();
这种方式的优点在于任务类只是实现接口,还是可以继续继承其他类、实现其他接口的,扩展性强。
可以使用匿名内部类的写法简化代码的写法。
// 写法一
Runnable target = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; ++i) {
System.out.println("子线程输出"+ i);
}
}
};
new Thread(target).start();
// 写法二(简化一)
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; ++i) {
System.out.println("子线程输出"+ i);
}
}
}).start();
// 写法三(简化二)
new Thread(() -> {
for (int i = 0; i < 5; ++i) {
System.out.println("子线程输出"+ i);
}
}).start();
前述两种方式都存在问题:线程的任务执行完毕后无法直接返回数据,这存在局限性。jdk5.0提供了Callable接口和FutureTask类来解决这个问题。
第三种方式:使用Callable接口和FutureTask类
- 定义一个类实现Callable<>接口,重写call方法,定义要封装的任务以及要返回的数据
- 创建一个Callable对象,封装成FutureTask<>任务对象
- 把任务对象交给Thread线程对象,启动子线程
- 要获取结果时,调用任务对象的get方法
FutureTask对象是一个任务对象,实现了Runnable接口,它的作用在于,在线程执行完毕之后,调用实例的get方法可以获取任务执行结果。
这种方式的优点在于扩展性强,而且能够获得子线程执行的结果,不过编码比较复杂。
Java中的Thread
类提供了针对线程对象的一些常用api,供使用者调用。
方法签名 | 描述 |
---|---|
public String getName() |
当前线程的名字,默认名字是Thread-索引 |
public void setName(String name) |
为线程设置名字 |
public static Thread currentThread() |
获取当前执行的线程对象 |
public static void sleep(long time) |
让当前执行的进程休眠毫秒 |
public final void join() |
当前线程阻塞直到调用join 方法的线程执行完毕 |
线程安全问题
线程安全问题出现的原因是存在同时执行的多个线程同时访问(修改)同一个共享资源。线程同步可以用来解决线程安全问题。线程同步(加锁)的方式有三种。
第一种方式:同步代码块
把访问共享资源的代码上锁,每次只允许一个线程进入,执行完毕后自动解锁。
synchronized (锁) {
// 访问共享资源的代码
}
建议用共享资源作为锁,比如:如果代码块在实例方法中,建议使用this作为锁;如果代码块在类的静态方法中,建议使用类名.class作为锁
第二种方式:同步方法
把访问共享资源的方法整个上锁,这种方式不需要显式指定锁(实例方法默认使用this,静态方法使用类名.class作为锁),锁的范围是整个方法代码
修饰符 synchronized 返回值类型 方法名(形参列表) {
// 访问共享资源的代码
}
这种方式与第一种方式相比,同步代码块的方式加锁范围更小更灵活,性能比较好,但是同步方法的代码可读性更好
第三种方式:Lock锁
Lock锁是jdk5开始提供的操作,可以创建出锁对象进行加解锁,更加灵活方便。注意Lock是接口,可以使用实现类比如ReentrantLock来实例化Lock对象
// 类内声明
privat final Lock lck = new ReentrantLock();
// 使用try-catch-finally加解锁
try {
lck.lock();
// 业务代码,访问共享资源
} catch (Exception e) {
// 处理异常
} finally {
lck.unlock();
}
线程间通信和线程池
线程通信,是指多个线程共同操作共享资源时,不同线程之间通过某种方式互相通信,以相互协调
线程池,是一种可以复用线程的技术。为什么需要使用线程池以及复用线程?web应用中,用户每发起一个请求,后台就需要创建一个新线程来处理。创建新线程的开销很大,同时请求过多时会产生大量线程,会严重影响系统性能。
线程池分为两个部分:工作线程WorkThread(也叫核心线程)和任务队列WorkQueue。线程池可以指定创建一定数量的工作线程,用于处理任务队列中等待处理的任务,一个线程在处理完当前任务之后,可以接着处理队列中正在等待处理的任务。为了避免系统资源耗尽,可以限制核心线程的数量和任务队列的大小。注意,任务队列中的对象一定要实现Runnable和Callable接口,才能称为任务对象。
创建线程池第一种方式:使用ExecutorService接口的一个实现类ThreadPoolExecutor创建线程池对象
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数 | 描述 |
---|---|
corePoolSize | 核心线程的数量 |
maximumPoolSize | 最大线程数量,两个参数之差表示可以创建的临时线程的数量 |
keepAliceTime | 临时线程的存活时间 |
unit | 临时线程存活时间单位(秒分时天) |
workQueue | 线程池的任务队列 |
threadFactory | 线程池的线程工厂,用来创建线程 |
handler | 任务拒绝策略(线程均忙且任务队列已满的情况下如何处理新任务) |
ExecutorService pool =
new ThreadPoolExecutor(3, 5, 8,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
临时线程创建时机:新任务提交时,核心线程都忙,任务队列已满,且此仍可以创建临时线程。拒绝新任务的时机:核心线程和临时线程都忙,且任务队列已满,新任务再提交时。
线程池处理Runnable对象/任务:
// 先定义类实现Runnable接口,重写run方法
Runnable target = new MyRunnable();
pool.execute(target); // 线程池自动创建新线程,自动处理任务,自动执行
pool.execute(target);
pool.execute(target); // 核心线程用满
pool.execute(target); // 在任务队列中等待,复用核心线程
pool.execute(target);
pool.execute(target);
pool.execute(target); // 任务队列满了
pool.execute(target); // 开始使用临时线程
pool.execute(target); // 临时线程用完
pool.execute(target); // 开始拒绝新任务,这里由于AbortPolicy会抛出异常
pool.shutdown(); // 等待任务执行完毕后关闭线程池
// pool.shutdownNow(); // 直接关闭
线程池处理Callable对象/任务:
// 先定义类实现Callable<>接口,重写call方法
Future<String> f1 = pool.submit(new MyCallable(100)); // execute执行Runnable任务,submit执行Callable任务
Future<String> f2 = pool.submit(new MyCallable(200));
// 调用get方法获取执行结果
String res1 = f1.get();
String res2 = f2.get();
第二种方式:使用线程池的工具类Executors调用静态方法返回不同特点的线程池对象
方法 | 描述 |
---|---|
newFixedThreadPool(int nThreads) |
创建固定线程数量的线程池,如果某个线程因为异常而结束,线程池会补充一个新线程替代 |
newSingleThreadExecutor() |
创建只有一个线程的线程池,如果线程因异常而结束,线程池会补充一个新线程 |
newCachedThreadPool() |
线程数量随任务增加而增加,如果线程任务执行完毕后空闲了60s会被回收 |
newScheduledThreadPool(int corePoolSize) |
创建一个线程池,可以在给定延迟后运行任务,或定期执行任务 |
这些方法实际上都是在调用ThreadPoolExecutor。
核心线程的数量应该设置为多少?无定论,建议计算密集型任务,设置为CPU核数+1;IO密集型任务,设置为CPU核数乘以二。
注意,阿里的编程规范要求,不能使用Executors的静态方法创建线程池,而需要使用ThreadPoolExecutor手动指定各项参数。这种方式明确线程池的规则和设定,避免系统资源的耗尽。
标签:Runnable,Java,target,任务,初识,线程,new,多线程,pool From: https://www.cnblogs.com/louistang0524/p/18331576