目录
- 往期Java阶段多线程相关,参考往期博客:Java学习篇21_异常、线程、Java学习篇22_线程、同步、Java学习篇23_线程池、Lambda表达式
- 补充
一、补充
1.程序、进程、线程之间的区别
- 程序:指令和数据的有序集合,其本身没有任何运行的含义,静态概念
- 进程:程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。
- 线程:一个进程至少包含一个线程,否则无存在的意义。CPU调度和执行的单位,也就是cpu时间片是根据所有总线程数来分的,一个进程线程越多,所占的cpu处理时间就相对越多(线程吸血鬼)
2.线程的四种创建方式
- 继承Thread类,重写run方法并实例化子类的对象t,
t.start()
开启线程 - 实现Runnable接口,重写run方法,
new Thread(runnable).start()
(本身Thread类也实现了Runnable接口,推荐使用,避免单继承) - 实现Callable接口,重写call方法(返回值)+ FuterTask
FuterTask需要传递Callable<T>,实现了Runnable
new Thread(futureTask).start()
- 使用线程池:
ExcutorService service = Executors.newFixedThreadPool(10);
service.execute(runnable)
service.submit(callable)、service.submit(runnable)
注意
- 获得当前线程名字
Thread.currentThread().getName()
- 线程睡眠
Thread.sleep()
3.静态代理模式
什么是静态代理模式
- 真实对象和代理对象都需要实现同一个接口
- 代理对象 绑定 真实对象的调用方法的基础上 ,不在由真实对象调用方法,而是 代理对象间接 、调用 方法 + 其他代理方法
- 优点就是 代理对象可以完成 真实对象 不能完成的方法,从而让真实对象专注一个自己的方法。
Runnable和Thread和静态代理之间的关系
- 线程底部实现原理就是 静态代理模式
- Thread可以理解为代理类,代理真实接口Runnable,真实对象的实例runnable 作为 thread 代理对象的参数,代理对象调用方法
4.Lambda表达式
演化概念
- 函数式接口:接口中只有一个方法,有了函数式接口才能使用Lambda
- 实现类需要重写该方法,实现类重写方法定义的位置
- 一般同级实现类
- 静态内部类:类内方法外,static修饰
- 局部内部类:方法内
- 匿名内部类:直接实例化接口,在接口中重写方法
- 使用Lambda:格式为
接口名 对象 = ()=> { //重写方法}
、接口名 对象 = (参数1)=> { //重写方法}
,简化
- 简化括号 :当参数为一个时,括号可以省略
接口名 对象 = a => { //重写方法}
- 简化花括号:当语句只有一条时,可以省略花括号
接口名 对象 = a => //重写方法
5.线程状态
状态
- 创建状态
- 就绪状态
- 阻塞状态
- 运行状态
- 死亡状态
线程方法
- setPriority(int priority) : 更改线程优先级
- static void sleep(long millis) :线程休眠
- void join() :线程强制执行
- static void yield():终止当前正在执行的线程,并执行其他线程
- void interrupt():终止线程,不推荐使用,建议使用标志位、次数使线程自己终止
- boolean isAlive() : 测试线程是否存活
注意
- Thread的stop和destory终止线程的方法已经过时,一般使用 自定义标识变量flag 控制线程结束
- sleep:每个对象都有一个锁,sleep不会释放锁
- yield:线程礼让,让cpu重新调度,但是不一定成功
- join:等到此线程结束完后,在执行其他线程,其他线程阻塞
- getState()方法可以查看当前线程的状态
- setPriority(优先级):线程优先级范围1-10,主线程默认优先级为5,大多数情况是优先级高的线程先被调度。
- setDaemon(true)设置守护线程:垃圾回收线程不用等待守护线程执行完毕,如后台纪录操作日志、监控内存、垃圾回收GC
6.线程同步
概念
- 并发:多个线程操作一个资源
- 处理多线程问题需要线程同步,就是等待机制
- 队列和锁,保证同步队列的安全,同一进程内的多个线程共享同一块存储空间,为了保证数据在方法中被访问的正确性,访问时给数据加上锁机制
synchorizoned
加锁出现的一些问题
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程的竞争下,加锁释放锁会导致比较多的上下文切换和调度延迟,引起性能问题
- 如果一个线程优先级高的线程等待一个线程优先级低的线程释放锁,会导致 优先级倒置,引起性能问题
集合线程不安全的问题
- ArrayList集合是线程不安全的集合,如,模拟1000个线程像其中添加数据,最后测试集合 小于 1000 个元素,因为多个线程同时添加的是一个位置,完成值的覆盖。
- 测试JUC安全类型的集合CopyOnWriteArrayList
7.同步synchorizoned
关键字
为了解决同步问题
- 同步方法:
-
syncchorizoned void xxx
控制对 对象 的访问,每个对象都对应一把锁,调用线程需要获得这个锁,否则就会线程阻塞。执行完方法,就会释放该锁 - 缺点:会大大影响效率,方法不仅有写,还会有读,而读并不需要加锁,因此引出同步代码块
- 同步代码块
synchorizoned(obj) {xxxx}
8.死锁、Lock锁
概念
- 死锁:多个相乘各自占有一些共享资源,并且互相等待对方占用的资源,都停止运行的情况,当一个同步块拥有 两个以上 锁对象 就可能发生 死锁 问题
- Lock锁,JUC下的包,JDK5.0提供了个更强大的线程同步机制通过显示的定义同步锁对象 来实现同步。同步锁使用Lock独享充当
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前鲜活的Lock对象
- ReentrantLock可重入锁实现了Lock,拥有与Synchorizoned相同的并发性和内存语义,在实现线程安全的控制中,较为常用,可以显式加锁、释放锁。举栗
Synchorizoned和Lock的对比
- Lock是显示锁(手动开启和关闭锁),synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁、方法锁
- Lock下JVM花费较少的时间调度进程,性能更好,并且由更好的拓展性(提供更多的子类)
- 优先使用顺序:Lock > 同步代码块 > 同步方法
9.线程协作(线程之间的通信)
概念
- 线程之间需要通信
- 场景就是生产者消费者模式,生产者和消费者共享一个资源,并且生产者和消费者之间互相依赖,互为条件
通信方法
-
wait()
: 线程一直等待,直到其他线程通知,与sleep不同的是会释放锁 -
wait(long timeout)
:指定等待的毫秒数 -
notify()
:唤醒一个正在wait的线程 -
notifyAll()
:唤醒ton规格对象上所有调用wait()方法的线程,优先级别高的线程优先调度
解决方式
- 并发协作模型 “生产者、消费者模式” 管程法
- 生产者:负责生产数据的模块
- 消费者:负责处理数据的模块
- 缓冲区:消费者不能直接使用生产者的数据,他们之间加一层缓冲区,缓存区 数据个数 控制 两个线程,中间加一层,里面根据逻数据个数+辑进行wait和notify
- 并发协作模型 “生产者、消费者模式” 信号灯法
- 生产者:负责生产数据的模块
- 消费者:负责处理数据的模块
- 信号标记灯:中间加一层,里面根据逻辑采用flag进行wait和notify
管程法举栗
信号灯法举栗
10.线程池
概念
- 经常创建和销毁、使用量特别大的源,比如并发情况下的线程,对性能影响很大
- 使用线程池可以提高响应速度、减低资源消耗、便于线程管理(核心池的大小、最大线程数、线程没有任务存活时间)
使用线程池
- JDK 5.0 提供了线程池相关API:
ExecutorService
和Exectors
- ExecutorService 真正的线程池接口。常见子类ThreadPoolExecutor
-
void execute(Runnable command)
执行命令、没有返回值,一般执行Runnable
-<T> Future<T> submit(Callable<T> task)
:执行任务,有返回值,一般执行Callable -
void shutdows()
: 关闭连接池
- Exectore:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
举栗