一、以往知识补充
1.程序、进程、线程之间的区别
程序:指令和数据的有序集合,其本身没有任何运行的含义,静态概念
进程:程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。
线程:一个进程至少包含一个线程,否则无存在的意义。CPU调度和执行的单位,也就是cpu时间片是根据所有总线程数来分的,一个进程线程越多,所占的cpu处理时间就相对越多。
2.线程的四种创建方式
- 继承Thread类;
- 重写run方法并实例化子类的对象t,t.start()开启线程;
- 实现Runnable接口,重写run方法,new Thread(runnable).start()
- (本身Thread类也实现了Runnable接口,推荐使用,避免单继承)
- 实现Callable接口,重写call方法(返回值)+ FuterTask
- FuterTask需要传递Callable,实现了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
管程法