多线程小结
多线程小结
参考于:首页-KuangStudy
https://blog.csdn.net/pange1991/article/details/53860651
什么是线程
线程:通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义,线程可以利用进程所有拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。
创建线程的方式
继承Thread类(lamda表达式:new Thread(()->System.out.println(“多线程学习。。。。”)).start();)
- 自定义线程类继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
实现Runnable接口(推荐使用,因为Java单继承的局限性)
- 自定义实现Runnable接口
- 实现run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
实现Callable接口
- 实现Callable接口,需要返回值类型
- 重写call()方法,需要抛出异常
- 创建目标对象
- 方式一:通过线程池
- 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
- 提交执行:Future
future = ser.submit(callable); - 获取结果:boolean r1 = future .get()
- 关闭服务:ser.shutdownNow();
- 方式二:FutureTask(适配类)
- FutureTask futureTask = new FutureTask(myThread); // 适配类
- new Thread(futureTask,”A”).start(); // 调用执行
- Integer result = (Integer) futureTask.get(); // 获取返回值
- 方式一:通过线程池
创建线程池
使用Executors的静态方法(不推荐使用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 有且只有一个固定的线程
ExecutorService threadPool = Executors.newSingleThreadExecutor();
//创建一个线程池,一池有N个固定的线程,有固定线程数的线程
ExecutorService threadPool = Executors.newFixedThreadPool(5);//执行长期任务性能好
//线程池根据需要创建新线程,但在先构建的线程可用时将重用他们。可扩容,遇强则强
ExecutorService threadPool = Executors.newCachedThreadPool();//执行很多短期异步任务
/**
*阿里巴巴Java开发手册
*4. 【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样
*的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
*说明:Executors 返回的线程池对象的弊端如下:
* 1)FixedThreadPool 和 SingleThreadPool:
*允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
* 2)CachedThreadPool 和 ScheduledThreadPool:
*允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
*/线程池源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}线程池源码解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public ThreadPoolExecutor(
int corePoolSize,//核心线程数。在创建了线程池后,线程中没有任何线程,等到有任务到来时才创建线程去执行任务。
//默认情况下,在创建了线程池后,线程池中的线程数为0,
//当有任务来之后,就会创建一个线程去执行任务,
//当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。
int maximumPoolSize,//最大线程数。表明线程中最多能够创建的线程数量,此值必须大于等于1
long keepAliveTime,//空闲的线程保留的时间。
TimeUnit unit,//空闲线程的保留时间单位
/*
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
*/
BlockingQueue<Runnable> workQueue,//阻塞队列,存储等待执行的任务。参数有ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue可选
ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可
RejectedExecutionHandler handler//拒绝策略。阻塞队列已满,且任务量大于最大线程的异常处理策略。参数有
/*
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务 (重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
*/
)创建线程池
1
2//创建线程池
ExecutorService threadPool = new ThreadPoolExecutor( 2,5,2L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy());ThreadPoolExecutor 底层工作原理
线程状态
1 | public enum State { |
Java线程的6种状态及切换(透彻讲解) 原文链接:https://blog.csdn.net/pange1991/article/details/53860651
初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。阻塞(BLOCKED):表示线程阻塞于锁。
等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
终止(TERMINATED):表示该线程已经执行完毕。
死锁的四个必要条件
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不可剥夺条件 : 进程已获得的资源,在末使用完之前,不能强行剥夺。
- 循环等待条件 : 若干进程之间形成一种头尾相接的循环等待资源关系。
Synchronized 与 Lock 的对比
首先synchronized是java内置关键字,在jvm层面;Lock是个java类,是api层面的锁;
synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
Lock只有代码块锁,synchronized有代码块锁和方法锁
使用Lock锁,JVM将花费较少的时间来调度线程,性能好。并且具有更好的扩展性(提供更多的子类)
**synchronized(隐式锁)会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock(显式锁)**需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可中断、可公平(两者皆可)
Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
提示:Synchronized中不使用if而使用while可以防止虚假唤醒
wait / sleep 的区别
来自不同的类
这两个方法来自不同的类分别是,sleep来自Thread类,和wait来自Object类。
sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用了b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。
有没有释放锁(释放资源)
最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
sleep是线程被调用时,占着cpu去睡觉,其他线程不能占用cpu,os认为该线程正在工作,不会让出系统资源,wait是进入等待池等待,让出系统资源,其他线程可以占用cpu。
使用范围不同
wait、notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
是否需要捕获异常
sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。
8锁问题
1 | /** |
小结
synchronized实现同步的基础:java中的每一个对象都可以作为锁具体的表现为以下三种形式:
对于同步代码块,锁是synchronized括号里面的配置对象
对于普通同步方法,锁的是当前实例对象
对于静态同步方法,锁的是当前的Class对象。