目录
多线程基础部分
- 什么是多线程:在同一时刻有多条不同的执行路径
- 目的:提高程序效率
- 什么是进程:进程是一个独立的应用程序,进程中会有很多线程,进程是所有线程的集合
- 创建线程的方式:
- 继承Thread类
- 缺点:java中不支持多继承,继承了Thread类后就不能再继承其他类
- 实现Runnable接口
- 优点:java中支持多实现
- 匿名内部类方式
- 继承Thread类
- 多线程五种状态
- 守护线程、用户线程、主线程
- 守护线程随主线程一起消亡,如GC线程
- 用户线程(非守护线程)用户自己创建的线程,是一条单独的路径,主线程终止,用户线程不会停止
- join方法:当在A线程内调用B线程的join方法时,线程A要让B执行完A才能执行
- sleep方法:让当前睡一会儿,并进入阻塞状态,不会释放锁,避免cup空转
- yieId方法:静态方法,暂停一下,直接进入就绪状态,可能下一次拿到锁的还是它
- setPriority方法:设置优先级,1-10,默认是5
- getpriority方法:得到优先级
- stop():停止一个线程,有可能抛出ThreadDeath异常,释放所占有的所有锁。这样会导致无法预测的异常发生。why?
因为这个线程正在持有一个对象的锁,正在进行同步代码块的执行,如果突然结束线程,锁住的代码块会立即释放锁,
其他争抢的线程获取锁,拿到上一次突然停止的参数值,会导致无法预测的结果。
- interrpet():中断一个进程。
- suspend():挂起一个进程。
- resume():恢复一个挂起的线程
- 常用的方法:wait()、notify、notifyAll
- 这三个方法只能在synchronized修饰的方法内或块内使用
- Wait():线程释放锁并且阻塞,扔到等待池放着,等待notify或notifyAll方法唤醒
- notify:唤醒一个调用了wait方法的线程
- notifyAll:唤醒所有阻塞的方法
多线程安全知识点
- 什么是线程安全?
- 当多个线程共享一个全局变量或静态变量,当做写的操作时可能会发生数据冲突,也就是线程安全
- 如何解决线程安全?
- 使用多线程同步锁synchronized或使用lock锁
- 什么是线程之间的同步?
- 当多个线程共享同一个资源,不会受到其他线程的干扰
- synchronized两种用法:
- 1.修饰需要进行同步的方法(所有访问状态变量的方法都必须进行同步),此时充当锁的对象为调用同步方法的对象
- 2.同步代码块(推荐使用)和直接使用synchronized修饰同步方法是一样的,但是锁的粒度会更细,并且充当锁的对象不一定是this还可以是其他对象。
- 使用线程同步
- 优点:保证了多线程的安全
- 缺点:多个线程需要判断锁,较为消耗资源,
- 同步方法使用的是什么锁?
- this锁
- 什么是静态同步方法?
- 用static和synchronized修饰的方法,静态同步方法使用的锁是该方法所属字节码文件对象可以用getClass方法获取,也可以用当前 类名.class表示,如:synchronized (ThreadTrain3.class)
- 什么是多线程死锁?
- 同步方法中嵌套同步容易发生死锁
- ThreadLocal(底层通过map集合实现)
- 作用:为每一个线程提供一个自己的局部变量,每一个线程中的局部变量互不影响,
- 四个方法:
- set()方法:设置当前线程的值
- get()方法:获得当前线程的值
- remove()方法:删除当前局部变量的值,目的是为了减少内存的占用,当线程结束后,当前线程的局部变量会自动被垃圾回收,所以调用该方法不是必须的,但可以加快内存的回收速度。
- initialValue():返回该线程局部变量的初始值,这个方法是一个延迟调用方法,在线程第一次调用get()或set()是才执行,且仅执行一次
- 多线程的三大特性
- 原子性:即一个操作或者多个操作要么同时执行,且同时执行成功,要么都不执行,原子性就是为了保护数据的一致,典型的转账问题
- 可见性:当多个线程访问一个全局变量时,其中一个线程修改了这个变量的值,其他线程能够立即看到修改后的值
- 有序性:程序执行的顺序按照代码的先后顺序执行
- 重排序:编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。
- 处理器为了提高程序运行的效率,可能会对代码进行优化,它不保证各个语句执行的顺序同代码中的顺序一致,它只保证执行结果和代码顺序执行的结果是一样的。
- 重排序对单线程执行是没有任何问题的,但是多线程就不一定了,所以我们在多线程是就要考虑重排序的问题
- as-if-serial语义的意思指:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。
- Java内存模型(JMM)
- 指的是共享内存模型,JMM决定一个线程对共享变量写入时,对另一个线程可见
- JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本,本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,缓冲区,寄存器以及其他的硬件和编译期优化。
- 从上图看线程A要与线程B通讯的话要经过2个步骤
- 1.首先,线程A把本地变量修改的值刷新到主内存中。
- 2.然后,线程B到主内存中读取线程A更新过的值
- 总结:
- 什么是Java内存模型?
- Java内存模型简称JMM,定义了一个线程对另一个线程可见。共享变量存储在主内存中,每个线程都有自己的本地内存,当多个线程同时访问一个数据时,可能本地内存没有及时刷新到主内存中,所以就发生了线程安全问题。
- 什么是Java内存模型?
- Volatile
- 什么是Volatile?
- 修饰变量,保证线程可见性,当一个线程修改了一个被volatile修饰的变量,它会保证修改的值立即更新到主内存中,当其他线程需要读取时可以立即得到修改后的值。
- 在Java中,为了提高程序的运行效率,对一些变量的操作通常是在该线程的寄存器或cpu缓存中进行,之后才会更新到主内存中,而加上Volatile关键字后会直接读写主存
- volatile特性:
- 1、保证了线程间共享变量的可见性,并不保证原子性
- 2、进制指令重排序,有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障
- Volatile性能:
- volatile读性能消耗和普通变量几乎相同,但写操作较慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行
- 什么是Volatile?
- volatile和synchronized的区别
- volatile保证了线程间的可见性,并不保证原子性
- synchronized保证了线程间的原子性和可见性
- 性能方面:synchronized关键字是防止多个线程同时执行一段代码,会影响程序的运行效率,volatile在某些情况下性能会优于synchronized
- 注意:volatile不能替代synchronized,因为volatile不能保证原子性
- 并发队列
- 分为非阻塞式队列 和 阻塞式队列
- 两种队列都继承Queue
- ConcurrentLinkedQueue:非阻塞式队列
- ConcurrentLinkedQueue
- 是一个适用于高并发场景下的队列,通过无锁的方式实现了高并发下的高性能
- 常用方法:
- add和offer:都是加入元素的方法,在ConcurrentLinkedQueue中没有任何区别
- poll和peek:获取元素,前者会删除队列中的元素,后者不会
- ConcurrentLinkedQueue
- BlockingQueue:阻塞式队列(线程安全),它是一个接口位于java.util.concurrent
- 是一个基于链接节点的无界安全队列(链表)
- 与普通队列的区别:
- 1、当队列为空时,从队列中获取元素的操作会被阻塞,直到其他线程往队列中添加元素
- 2、当队列是满时,往队列中添加元素的操作会被阻塞,直到其他线程从队列中取元素
- 特点:先进先出(FIFO),后进后出(LIFO)
- ArrayBlockingQueue:
- 底层数组,有边界阻塞队列,初始化时要指定容量大小,一旦指定就不可变
- LinkedBlockingQueue:
- 底层链表结构
- 如果初始化时指定了容量大小,那它就是有界的,否则无界
- PriorityBlockingQueue:
- 无界,允许插入null对象,所有插入到此队列的对象必须实现java.lang.Comparable接口,队列优先级排序规则就是按照这个接口的实现来定义的
- 可以从此队列中获得一个Iterator,但这个迭代器并不保证按照优先级顺序迭代
- SynchronousQueue:
- 此队列内部只允许容纳一个元素。
- 线程池原理
- 优点:1.降低资源消耗 2.提高响应速度 3.提高线程可管理性
- 线程池的总接口是Executor,其中只有一个void execute(Runnable command)方法体
- ExecutorService接口继承了Executor接口,抽象类AbstractExecutorService又实现了ExecutorService接口,实现了部分父类的方法,核心类ThreadPoolExecutor实现了AbstractExecutorService抽象类,
- ThreadPoolExecutor中构造方法参数:
- 1.corePoolSize:核心线程数,表示正在运行的线程数
- 2.maxmumPoolSize:能创建的最大线程数
- keepAliveTime:达到最大线程数数时候,线程池的工作线程空闲后,保持存活的时间
- 4.TimeUnit unit:keepAliveTime单位
- 5.BlockingQueue<Runnable> workQueue #阻塞队列
- 线程池四中创建方式:
- newCachedThreadPool:创建一个可缓存线程池,无限大小,jvm自动回收
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
- 提交方法:
- 1.execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功
- 2.submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit),在指定的时间内会等待任务执行,超时则抛出超时异常,等待时候会阻塞当前线程。
- 合理配置线程池
- IO密集:IO密集型,即该任务需要大量的IO操作,即大量的阻塞。CPU空转的时间越长,故需要多配置最大线程数,2*cpu核数
- CPU密集:CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。大概和机器的cpu核数相当