并发编程相关
创建线程的几种方式?
继承Thread类,创建线程类,重写run()方法
- 定义一个Thread的子类,并重写run()方法,run()方的方法提代表了线程要完成的任务,因此把run()方法成为执行体。
- 创建Thread子类实例,即创建了线程对象
- 调用线程对象的start()方法启动该线程
//创建10个线程,并显示出每个线程线程名
public class ThreadTestDemo extends Thread{
@Override
public void run() {
System.out.println("线程名: " + Thread.currentThread().getName());
}
public static void main(String[] args) throws InterruptedException {
for(int i=0;i<10;i++){
ThreadTestDemo threadTestDemo = new ThreadTestDemo();
threadTestDemo.setName(String.valueOf(i));
sleep(1000);
threadTestDemo.start();
}
}
}
实现Runable,接口创建线程类,重写run()方法
- 定义Runable接口的实现类,实现Runable接口
- 创建Runable接口的实现类的实例,并且以该实例作为target创建Thread对象,该Thread对象才是真正的线程对象
- 调用线程对象的start()方法启动该线程
public class ThreadRunableDemo implements Runnable{
@Override
public void run() {
System.out.println("目前阶段的线程名称是:" + Thread.currentThread().getName());
}
public static void main(String[] args) throws InterruptedException {
ThreadRunableDemo threadDemo2 = new ThreadRunableDemo();
for(int i=0;i<10;i++){
Thread thread = new Thread(threadDemo2, String.valueOf(i));
//System.out.println("thread: " + thread);
Thread.sleep(1000);
thread.start();
}
}
}
使用Callable和Future创建线程
- 定义Callable接口实现类,并实现call()方法,该方法将作为线程的执行体,并且有返回值。
- 创建Callable实例,使用FutureTask类来包装Callable对象(FutureTask实现的定制化),该FutureTask对象封装了该Callable对象的call()方法的返回值。FutureTask是一个包装器,通过接受Callable对象来创建。同时实现了Future和Runable接口
- 使用FutureTask对象作为Thread对象的target创建并启动新线程
- 调用FutureTask对象的get()方法来获得子线程的返回值
public class CallableTestDemo implements Callable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableTestDemo callableTestDemo = new CallableTestDemo();
FutureTask futureTask = new FutureTask(callableTestDemo);
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName() + " 的循环变量i的值" +i);
if(i == 5){
new Thread(futureTask,"有返回值的线程").start();
}
}
try {
System.out.println("子线程的返回值:" + futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
@Override
public Object call() throws Exception {
int i = 0;
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " --------" + i);
}
return i;
}
}
使用线程池创建线程
使用Executors框架创建
class ExecutorsTest {
//创建一个实现Runnable接口的类型
static class MyRunnable implements Runnable{
//实现Runnable的抽象run()方法
@Override
public void run() {
for (int i=0;i<100;i++) {
System.out.println(i);
}
}
}
public static void main(String[] args) {
//创建ExecutorServiceduix(创建线程池)
ExecutorService excutorService = Executors.newFixedThreadPool(10);
//创建了实现Runnable类的对象
MyRunnable myRunnable = new MyRunnable();
//System.out.println(myRunnable);
//把实现Runnable类的对象最为参数放入ExecutorService类的对象execute() 方法
excutorService.execute(myRunnable);//Runnable用execute()方法,Callable用submit()方法
excutorService.shutdown();
}
}
使用ThreadPoolExecutor创建
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数(包含核心线程数)
- keepAliveTime:空闲线程的存活时间
- TimeUnit:时间单位
- BlockingQueue:任务队列,用于存储线程池的待执行任务的
- ThreadFactory:线程工厂,用于生成线程
- handler:拒绝策略
public class ThreadPoolDemo10 {
public static void main(String[] args) {
ThreadFactory factory=new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread=new Thread(r);
return thread;
}
};
ThreadPoolExecutor executor=new ThreadPoolExecutor(2,5,10,
TimeUnit.SECONDS,new LinkedBlockingDeque<>(2),factory,new ThreadPoolExecutor.DiscardPolicy());
for (int i = 1; i < 6; i++) {
int finalI=i;
executor.submit(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务:"+finalI);
});
}
}
}
线程池策略
- 当线程池中有任务时,判断线程池是否还在运行状态,否->任务拒绝(执行拒绝策略),是->进行下一步;
- 判断线程池中线程数是否小于核心线程数(corePoolSize),是->创建工作线程运行此任务,且任务结束后将该线程保留在线程池中,不做销毁处理,否->进行下一步;
- 判断阻塞队列/工作队列(workQueue)是否已满,否->将任务添加到工作队列中,是->进行下一步;
- 判断线程池中的线程数是否小于最大线程数(maxmumPoolSize),是->采用饱和策略来处理,否->新建一个工作线程来执行这个任务。【注意】当线程池中的线程数大于maxmumPoolSize时,当线程的空闲时间超过空闲线程的存活时间(keepAliveTime)时,这个线程就会被终止,知道线程中的数量不大于corePoolSize。
总结:corePoolSize(线程池中线程数是否大于,否:创建新线程执行,是:进行下一步)–>workQueue(工作队列是否已满,否:将任务添加到工作队列,是:进行下一步)–>maxmumPoolSize(线程池中线程数是否大于,否:创建新工作线程来执行任务,是:采用饱和策略(拒绝策略)才处理)。
饱和策略(拒绝策略 handler)
当工作队列满且线程个数达到maximunPoolSize后所采取的策略 java4个 自定义一个
AbortPolicy:默认策略;新任务提交时直接抛出未检查的异常RejectedExecutionException,该异常可由调用者捕获。
CallerRunsPolicy:既不抛弃任务也不抛出异常,使用调用者所在线程运行新的任务。
DiscardPolicy:丢弃新的任务,且不抛出异常。
DiscardOldestPolicy:调用poll方法丢弃工作队列队头的任务,然后尝试提交新任务
自定义策略:根据用户需要定制。
线程的创建看起来有四种方式: 继承 Thread 类、实现 Runnable 接口、实现 Callable 接口、使用线程池。
***但是本质上,线程的创建方式其实只有一种:
new 一个 Thread 对象,并给它一个 Runnable 类型的对象
Thread 对象负责启动线程,Runnable 对象负责执行线程***
ThreadLocal
ThreadLocal 有什么用?
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢?JDK 中自带的ThreadLocal类正是为了解决这样的问题。 ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是ThreadLocal变量名的由来。他们可以使用 get() 和 set() 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。
总结:类似每个线程中各自拥有的程序计数器,虚拟机栈,本地方法栈,都是这个线程私有的。
如何使用 ThreadLocal?
代码描述
创建0-9共十个线程,为每个线程变量赋值后初始化simpleDateFormat的值,对比变量formatter前后的差异。
public class ThreadLocalDemo implements Runnable {
//每一个线程专属的变量,
private static final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm"));
//上面这段创建专属变量的代码等价于下面,重写ThreadLocal的initialValue()方法,返回一个指定的日期格式,但是在Java8中提供了withInitial方法将 Supplier接口作为参数,使用了lambda表达式简化了代码
private static final ThreadLocal<SimpleDateFormat> formatter2 = new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue(){
return new SimpleDateFormat("yyyyMMdd HHmm");
}
};
SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
public static void main(String[] args) throws InterruptedException{
ThreadLocalDemo obj = new ThreadLocalDemo();
for(int i=0;i<10; i++){
Thread thread = new Thread( obj,"" +i);
//随机0-1000毫秒(ms),如果采取这种方式休眠,则苏醒时间不固定,查看结果比较混乱,不利于理解,可以用下面1s
//Thread.sleep(new Random().nextInt(1000));
//间隔1s执行
Thread.sleep(1000);
thread.start();
}
}
@Override
public void run() {
//赋值给了第i个线程的专属变量
System.out.println("线程: " + Thread.currentThread().getName() + "的变量被修改为:" + formatter.get().toPattern());
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
//返回描述此日期格式的模式字符串
System.out.println("初始化日期格式后变量:"+ Thread.currentThread().getName()+ " 的值为 " +simpleDateFormat.toPattern());
formatter.set(new SimpleDateFormat());
System.out.println("---------线程" + Thread.currentThread().getName() + " 被初始化后的变量值为:" +formatter.get().toPattern());
}
}
ThreadLocal 原理了解吗?
public class Thread implements Runnable {
//......
//与此线程有关的ThreadLocal值。由ThreadLocal类维护
ThreadLocal.ThreadLocalMap threadLocals = null;
//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
//......
}
Thread中有两个变量,threadLocals 和 inheritableThreadLocals 他们都是ThreadLocalMap类型(理解为ThreadLocal类实现的定制化HashMap),默认情况下都是null。只有当前线程调用ThreadLocal类的set()或者get()方法时才创建他们,调用这两个方法时,我们实际上调用的是ThreadLocalMap类对应的get(),set()方法。
public void set(T value) {
//获取当前请求的线程
Thread t = Thread.currentThread();
//取出 Thread 类内部的 threadLocals 变量(哈希表结构)
ThreadLocalMap map = getMap(t);
if (map != null)
// 将需要存储的值放入到这个哈希表中
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
实际上,我们创建的变量最终是存放再练当前线程的ThreadLocalMap中,并不是存放在ThreadLocal上,ThreadLocal可以理解为知识ThreadLocalMap的封装,传递了变量值,ThrealLocal 类中可以通过Thread.currentThread()获取到当前线程对象后,直接通过getMap(Thread t)可以访问到该线程的ThreadLocalMap对象。
每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为 key ,Object 对象为 value 的键值对。
> 总结:当我们为当前线程创建一个变量时,他是存储在ThreadLocalMap里的,一个key-value的Hash Map 他是以ThreadLocal 为key Object为对象的键值对.
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue){
}
ThreadLocal 数据结构如下图所示:
总结:如果在一个线程创建两个变量(声明两个ThreadLocal对象),那么他们都是存放在ThreadLocalMap里,它的key是ThreadLocal对象,value是ThreadLocal对象调用set()方法设置的值。
ThreadLocal 内存泄露问题是怎么导致的?
ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。这样一来,ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap 实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal方法后 最好手动调用remove()方法
> 总结:ThreadLocalMap是一个结构为<ThreadLocal,Object>的HashMap,它的key是若引用,value是强引用。在垃圾回收时,key会被清理调,而value不会被清理。这就产生了一个key为null而value不为null的一个Enrty(键值对),如果不做任何措施,这个value永远不会被GC(垃圾回收器)回收,这时就有可能产生内存泄漏(占用了内存,无法回收)。但是ThreadLocalMap已经考虑了这种情况,et()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录,但是我们使用完 ThreadLocal方法后 最好手动调用remove()方法。
线程池
线程池是管理一系列线程的资源池。当有任务需要处理,直接从线程池获取线程来处理,处理完成之后线程并不会立即被销毁,而是会等待下一个任务。(池化技术,类似数据库连接池,Http连接池,都是对这个思想的应用,其目的是为了减少每次获取资源的消耗,提高对资源的利用率)
优势
降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗(当任务完成后线程并不会立即被销毁,而是会等待下一个任务)。
提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行(提前创建了线程)。
提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
线程池的创建方式
1.通过ThreadPoolExecutor构造函数创建(阿里巴巴开发手册强制用这个)【强制使用】
2.通过 Executor 框架的工具类 Executors 来创建(内置线程池,在《阿里巴巴 Java 开发手册》规范中不被允许在应用中自行显式创建线程)。
ThreadPoolExecutor的三个重要参数
corePoolSize(线程池的核心线程数量) : 任务队列未达到队列容量时,最大可以同时运行的线程数量。
maximumPoolSize(线程池的最大线程数) :任务队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
workQueue:新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。
多线程的内存泄漏
在应用程序中,未引用的对象会被垃圾回收器(GC)回收,而引用的对象不会。但是可能出现,对象不再被使用而GC无法回收它,这就是内存泄漏。无用的对象并不都是未引用的对象,有些无用对象也可能是引用对象,这正是引起内存泄漏的来源。(曾经有用到过的对象,但是目前没有使用到且仍然占用着内存,没有及时释放,会导致内存占用率越来越高,影响系统性能,导致进程甚至系统崩溃)
Future类
Future类的作用
Future类是异步思想的典型运用,主要用在一些需要执行耗时任务的场景,避免程序一直在原地等待耗时任务执行完成,执行效率太低。为了避免这种情况,我们可以将这个耗时任务交给一个子线程去异步执行,同时我们可以去干其他的事情,不用再傻傻等待耗时任务执行完成,等我们其他事情干完后,再通过Future类获取到耗时任务的执行结果,这样一来我们程序的执行效率就明显提高了。
> 总结:在java中,Future类就是为了去获取异步调用后的结果,那么这其实是多线程中的Future模式,可以看做是一种设计模式,其核心思想是异步调用,主要用在多线程领域,并非java语言得有。
Future类源码
// V 代表了Future执行的任务返回值的类型
public interface Future<V> {
// 取消任务执行
// 成功取消返回 true,否则返回 false
boolean cancel(boolean mayInterruptIfRunning);
// 判断任务是否被取消
boolean isCancelled();
// 判断任务是否已经执行完成
boolean isDone();
// 获取任务执行结果
V get() throws InterruptedException, ExecutionException;
// 指定时间内没有返回计算结果就抛出 TimeOutException 异常
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutExceptio
}
简单理解就是:我有一个任务,提交给了 Future 来处理。任务执行期间我自己可以去做任何想做的事情。并且,在这期间我还可以取消任务以及获取任务的执行状态。一段时间之后,我就可以 Future 那里直接取出任务执行结果。
Callable和Future的关系
FutureTask提供了Future接口的基本实现,常用来封装Callable和Runable,具有取消任务,查看任务是否执行完成以及获取任务执行结果的方法。ExecutorService.submit() 方法返回的其实就是 Future 的实现类 FutureTask 。