多线程
概念
1.进程与线程
进程:进程是程序的一次执行过程,是一个动态概念,是程序在执行过程中分配和管理资源的基本单位。
线程:线程是CPU调度和分派的基本单位,它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
联系: 线程是进程的一部分,一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
区别:理解它们的差别,从资源使用的角度出发。(所谓的资源就是计算机里的中央处理器,内存,文件,网络等等)
2.多线程的概念
对于多核cpu,进程中的多线程并行执行。对于单核cpu,多线程在单cpu中并发执行,根据时间片切换线程
3.并发编程中的三个概念
并发:在同一个cpu中同时执行多个任务
并行:在多个cpu中同时执行多个任务
①可见性: 一个线程对共享变量修改,另一个线程能看到最新的结果
②有序性:一个线程内代码按编写顺序执行
③原子性: 一个线程内多行代码以一个整体运行,期间不能有其它线程的
4.java中volatile关键字的作用
volatile是Java中的关键字,用来修饰被不同线程访问的变量。JMM(Java内存模型)是围绕并发过程中如何处理可见性、原子性和有序性这3个特征建立起来的,而volatile可以保证其中的两个特性。 volatile 能够保证共享变量的可见性与有序性,但并不能保证原子性
创建线程方法
1)继承Thread类创建线程
或者使用内部类的方式直接创建线程
2)实现Runnable接口创建线程
测试代码:
创建Runnable实现类:
3)使用Callable和Future创建线程
public class ThirdThread
{
public static void main(String[] args)
{
// 创建Callable对象
ThirdThread rt = new ThirdThread();
// 先使用Lambda表达式创建Callable<Integer>对象
// 使用FutureTask来包装Callable对象
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> {
int i = 0;
for ( ; i < 100 ; i++ )
{
System.out.println(Thread.currentThread().getName()
+ " 的循环变量i的值:" + i);
}
// call()方法可以有返回值
return i;
});
for (int i = 0 ; i < 100 ; i++)
{
System.out.println(Thread.currentThread().getName()
+ " 的循环变量i的值:" + i);
if (i == 20)
{
// 实质还是以Callable对象来创建、并启动线程
new Thread(task , "有返回值的线程").start();
}
}
try
{
// 获取线程返回值
System.out.println("子线程的返回值:" + task.get());
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}
4)使用线程池例如用Executor框架
创建线程池方法
线程池的创建方式总共包含以下 7 种(其中六种是通过 Executors 创建的,一种是通过 ThreadPoolExecutor 创建的):
1)Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
2)Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;
3)Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;
4)Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池;
5)Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;
6)Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
7)ThreadPoolExecutor:最原始的创建线程池的方式,它包含了 7 个参数可供设置,后面会详细讲。
线程池种的核心参数7个
- corePoolSize 核心线程数目 (最多保留的线程数)
- maximumPoolSize 最大线程数目 (核心线程+救急线程)
- keepAliveTime 生存时间 ( 针对救急线程)
- unit时间单位 ( 针对救急线程)
- workQueue (阻塞队列)
- threadFactory 线程工厂 (可以为线程创建时起个好名字)
- handler 拒绝策略(4种)
CallerRunsPolicy 谁创建谁运行该线程
AbortPolicy 报异常
DiscardPolicy 丢弃该任务(不会报异常)
DiscardoldestPolicy 丢掉最先加入的队列 将最新的任务加入到队列中
线程的状态
Java种的6种(新建 可运行 终结 阻塞 等待 有时限等待)
1、New新建状态:线程刚被创建,start方法之 前的状态。
2、Runnable运行状态:得到时间片运行中状态,Ready就绪,未得到时间片就绪状态。
3、Blocked阻塞状态: 如果遇到锁,线程就会变为阻塞状态等待另一个线程释放锁。
4、Waiting等待状态: 无限期等待。
5、Time_ Waiting超时等待状态:有明确结束时间的等待状态。
6、Terminated终 止状态:当线程结束完成之后就会变成此状态。
操作系统的5种(新建 就绪 运行 阻塞 终结)
sleep vs wait区别
共同点: wait() , wait(long)和sleep(long)的效果都是让当前线程暂时放弃CPU的使用权,进入阻塞状态
方法归属不同
①sleep(long) 是Thread的静态方法
②而wait(), wait(long)都是Object的成员方法,每个对象都有
醒来时机不同
①执行 sleep(long)和wait(long)的线程都会在等待相应毫秒后醒来
②wait(long) 和wait()还可以被notify唤醒,wait() 如果不唤醒就一直等下去
③它们都可以被打 断唤醒
锁特性不同
①wait 方法的调用必须先获取wait对象的锁,而sleep则无此限制
②wait方法执行后会释放对象锁,允许其它线程获得该对象锁( 我放弃,但你们还可以用)
③而sleep如果在synchronized代码块中执行,并不会释放对象锁( 我放弃,你们也用不了)
lock vs synchronized区别
二者均属于悲观锁、都具备基本的互斥、同步、锁重入功能
Synchronized 是关键字 在同步代码块中执行完就可以释放锁
lock 是接口 必须使用unlock手都才能释放 一般用在try finally中
Lock提供了许多synchronized不具备的功能,例如获取等待状态、公平锁、可打断、可超时、多条件变量
Lock有适合不同场景的实现,如ReentrantLock, ReentrantReadWriteL ock
在没有竞争时,synchronized 做了很多优化,如偏向锁、轻量级锁,性能不赖
在竞争激烈时,Lock 的实现通常会提供更好的性能
悲观锁与乐观锁
1.悲观锁的代表 是synchronized和Lock锁
①其核心思想是[线程只有占有了锁,才能去操作共享变量,每次只有一一个线程占锁成功,获取锁失败的线程,都得停下来等待
②线程从运行到阻塞、再从阻塞到唤醒,涉及线程上下文切换,如果频繁发生,影响性能
③实际上, 线程在获取synchronized和Lock锁时,如果锁已被占用,都会做几次重试操作,减少阻塞的机会
2.乐观锁的代表是 AtomicInteger,使用cas来保证原子性
①其核心思想是[无需加锁,每次只有一一个线程能成功修改共享变量,其它失败的线程不需要停止,不断重试直至成功I
②由于线程一直运行, 不需要阻塞,因此不涉及线程上下文切换
③它需要多核cpu支持,且线程数不应超过cpu核数
threadlocalmap
1.ThreadLocal可以实现资源对象]的线程隔离,让每个线程各用各的[资源对象] ,避免争用引发的线程安全问题,
2. ThreadLocal 同时实现了线程内的资源共享
3.其原理是, 每个线程内有一个ThreadLocalMap类型的成员变量,用来存储资源对象
①调用set 方法,就是以ThreadLocal自己作为key,资源对象作为value,放入当前线程的ThreadLocalMap集合中
②调用get方法,就是以ThreadLocal自己作为key,到当前线程中查找关联的资源值
③调用remove方法,就是以ThreadLocal自己作为key,移除当前线程关联的资源值
4.作用:解决线程隔离
5.为什么 ThreadLocalMap中的key (即ThreadLocal )要设计为弱引用?
是因为线程可能长时间运行,为了释放key中的内存,在jvm垃圾回收的时候可以释放弱引用(key)
多线程之间通信的5种方式(稍后补充代码部分)
方式一:使用Object类的wait() 和 notify() 方法
方式二:既然wait和notify配合synchronized使用能够实现,当然用 ReentrantLock 结合 Condition也可以完成;
方式三:使用 volatile 关键字
方式四:使用JUC工具类 CountDownLatch
方式五:基本LockSupport实现线程间的阻塞和唤醒