多线程
概述
程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
而进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的的单位。
很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以模拟出了多线程。
- 线程就是独立的执行路径
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程
- main()称之为主线程,为系统的入口,用于执行整个程序;
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的。
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
- 线程会带来额外的开销,如cpu调度时间,并发控制开销
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
创建方式
- Thread class(继承Thread类) 已经实现了runnable 不建议使用,避免oop单继承局限性
- Runnable 接口(实现runnable接口) 灵活方便
- callable接口 实现返回值,可以抛出异常
线程开启不一定立刻执行,cpu调度才开始执行
调用run方法会先执行完run才执行其他方法,而通过start开启线程会交替执行
并发问题
在多个线程同时访问同一个资源时,会出现紊乱,导致线程不安全。
静态代理
静态代理模式:
真实对象和代理对象都实现同一个接口
代理对象要代理真实角色
好处:
代理对象可以做很多真实对象无法完成的操作
真实对象专注于做自己的事情
多线程实现的底层原理!
Lambda表达式
- lambda表达式只有一行代码时才可以直接简化为一行,若有多行,需要用代码块的形式来简化。即大括号包围。
- 前提为函数式接口,即接口中只有一个方法
- 多个类型也可以去掉参数类型,要去都去掉
线程停止
- 建议线程正常停止,利用次数,不建议死循环
- 建议使用标志位,flag = false,则终止线程运行
- 不推荐使用JDK提供的stop()、destroy()方法
线程休眠
- 利用网络延时放大错误的发生性
- thread.sleep();
线程礼让
线程礼让不一定成功,看cpu的调用
thread.yield();
线程强制执行
“线程插队”join
强制插入到第一个线程,必修在其执行结束才可以执行其他线程
线程的状态
- new
- runnable
- timed_waiting
- terminated
- blocked
线程的优先级
先设置优先级再启动线程
并不是优先级高的一定先执行,会出现性能倒置的情况
线程的同步
在需要同步的方法前添加synchronized关键字,各个线程排队执行
synchronized锁的只是this对象,并不会对其他上锁,所以需要同步块
需要上锁的对象是需要进行增删改的对象,在run方法中对对应的对象上锁,使用同步块的方式
lock锁
-
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放
-
Lock只有代码块锁,synchronized有代码块锁和方法锁
-
使用Lock锁,JVM将花费较少的时间来调度线程性能更好。并且具有更好的扩展性(提供更多的子类)
-
优先使用顺序:
Lock >同步代码块 (已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)
线程协作
生产者消费者问题
- 管程法
并发协作模型“生产者/消费者模式”—>管程法
生产者:负责生产数据的模块(可能是方法,对象,线程,进程);
消费者:负责处理数据的模块(可能是方法,对象,线程,进程)缓冲区;
消费者不能直接使用生产者的数据,他们之间有个“缓冲区
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
- 信号灯法
使用一个标志位flag来标识是否通知或等待达到线程通信的目的
线程池
JDK 5.0起提供了线程池相关API
ExecutorService 和 Executors
ExecutorService: 真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command): 执行任务/命令,没有返回值,一般用来执行Runnable
Futuresubmit(Callable task): 执行任务,有返回值,一般又来执行Callable
void shutdown():关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池