其他分享
首页 > 其他分享> > 多线程和并发 GuideLine

多线程和并发 GuideLine

作者:互联网

多线程和并发 GuideLine

基本概念

串行和并行

并行的优点

并行存在的风险

多线程

JAVA实现多线程的两种方式

Thread常用的几种方法

普通方法

静态方法

线程生命周期图

在这里插入图片描述

实现多线程的原则

原子性

可见性

有序性

Java虚拟机的内存模型

线程同步

线程同步机制简介

线程同步机制是一套用于协调线程之间的数据访问的机制。该机制可以保障线程安全。

Java平台提供的线程同步机制包括:锁,volatile关键字,final关键字,static关键字,以及相关的API例如Object.wait()/Object.notify()方法等

锁概述

简介

锁相关的概念

内部锁:synchronized关键字

Java中的每个对象都有一个与之相关的内部锁(Intrinsic lock)。这种锁也称为监视器(Monitor),这种内部锁是一种排他锁,可以保障原子性,可见性和有序性

内部锁是通过synchronized关键字实现的

synchronized可以锁一个对象,也可以修饰一个普通方法。修饰方法时,调用这个方法时,锁住的是这整个方法。修饰静态方法时,锁住的是这个类.class文件(运行时对象)。上述分别称为同步实例方法和同步静态方法

轻量级锁:volatile关键字

volatile简介

synchronized与volatile的比较

volatile不具备原子性

也就是volatile可以保证写这个对象会被刷新到主存,也就是这个对象的变化对所有线程可见。但是不代表这个对象的加减操作等具有原子性

CAS

线程间的通信

等待/通知机制

什么是等待通知机制

等待/通知机制的实现

Object类中的wait()方法可以使执行当前代码的线程等待,暂停执行,直到街道通知或者被中断为止。

注意:

Object类中的notify()可以唤醒线程,该方法也必须子啊同步代码块中由锁对象调用。

如果没有使用锁对象调用wait()/notify(),会抛出IllegalMonitorStateException异常。

notify()会唤醒一个线程,如果有多个线程等待唤醒,无法确定会唤醒哪个一个线程

interrupt()方法会中断wait()

当线程处于wait()状态是,调用该线程的interrupt()方法会中断wait()并曝出InterruptedException异常

notifyAll()方法

和notify()一样,但是会唤醒所有线程

wait(long)

wait()方法的重载,当设定时间还没有被唤醒,那么该线程会自动唤醒

join()

生产者消费者模式

使用管道进行线程间通信

ThreadLocal的使用

ThreadLocal可以把共享变量变为私有变量,也就是相当于一个容器(比如在main中定义),其他的线程调用这个容器,都是私有的,也就是拿到的都是干净的空的容器,仅属于这个线程的

显式锁Lock

Lock是一个对象可以实现锁。其中ReentrantLock类是其实现,ReentrantLock中还实现了两个内部类分别是FairSync和NonfairSync

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dgS1vJMA-1607066289161)(C:\Users\J\AppData\Roaming\Typora\typora-user-images\image-20201125212849589.png)]

Lock具有可重入性:

可以在一段程序中重复对这个锁进行加锁操作,但是锁几次就得解锁几次,不然会出问题

ReentrantLock

基本使用:

static Lock lock = new ReentrantLock();
try{
    lock.lock();
    {...}
}finally{
    lock.unlock();
}
关键方法

公平锁与非公平锁

大多数情况下,锁的申请都是非公平的。非公平是指系统分配锁时,是随机从阻塞线程中选择一个,不能保证其公平性。

公平锁会按照时间顺序保证先到先得。

公平锁不会出现线程饥饿现象,而非公平锁可能会出现饥饿现象。

ReentrantLock提供构造方法,传递true可以设置该锁为公平锁。

ReentrantReadWriteLock读写锁

前面的synchronized锁或者ReentrantLock都是排他锁。允许一个线程持有

ReentrantReadWriteLock是改进的排他锁,允许多个线程持有进行读,但是只允许一个线程进行写

读写锁获得的是一个锁的两个不同角色,而不是两个不同的锁

线程管理

线程组

Thread有几个构造方法可以指定线程组

默认线程组为创建该线程的线程所属的线程组(属于父线程所在线程组)

JVM创建main()线程时会为它指定一个线程组

可以调用getThreadGroup()方法返回线程组

类为 ThreadGroup()

线程组的基本操作

捕获线程的执行异常

在线程的run方法中,如果有受检异常,必须进行驳货处理。如果想要获得run()方法中出现的运行时异常信息,可以通过回调UncaughtExceptionHandler接口获得哪个线程出现了运行时异常。

在Thread类中有关处理运行时异常的方法:

如果线程出现运行时异常,JVM会调用Thread类的dispatchUncaughtException(Throwable e)方法,该方法会调用getUncaughtExceptionHandler().uncaughtException(this,e);;因此如果想要获得线程中出现异常的信息,就需要设置线程的UncaughtExceptionHandler方法

注入Hook钩子线程

很多软件包括MySQL,Zookeeper,kafka等都存在Hook线程的校验机制,目的是校验进程是否已启动,防止重复启动程序。

Hoook线程也称为狗子线程,当JVA退出的时候会执行Hook线程。经常在程序启动时创建一个.lock文件,用.lock文件校验程序是否启动。在程序退出时删除该.lock文件。

在Hook线程中除了防止重新启动进程外,还科研做资源释放,尽量避免在Hook线程中进行复杂的操作。

线程池

线程池简介

预先创建一定数量的工作线程,客户端代码直接将任务作为对象提交给线程池,线程池将这些任务缓存在工作队列中,线程池中的工作线程不断被取出,执行。执行完毕后返回线程池。

节省创建和销毁线程的开销

JDK对线程池的支持

JDK提供了一套Executor框架,可以帮助开发人员有效的使用线程池

核心线程池的底层实现

线程池的拒绝策略

ThreadPoolExecutor构造方法的最后一个参数指定了拒绝策略。当提交给线程池的任务量超过设定时,会执行拒绝策略。

自定义异常处理策略:

new RejectedExecutionHandler(){
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor){
        //...具体逻辑
    }
}

线程工厂ThreadFacoty

监控线程池

ThreadPoolExecutor提供了一组方法用于监控线程池

int getActiveCount() 获得线程池中当前活动线程的数量

getCompletedTaskCount() 返回线程池完成任务的数量

getCorePoolSize() 线程池中核心线程的数量

getLargestPoolSize() 返回线程池曾经达到的线程的最大数

扩展线程池

ThreadPoolExecutor线程池提供了两个方法,用于对线程池进行扩展

在线程池执行某个方法之前会调用before方法,结束(或任务异常退出)会执行after

【源码分析】ThreadPoolExecutor线程池的内部类worker就是线程实例,worker在执行前会执行before,结束会执行after

优化线程池大小

线程池大小对系统性能有一定影响,过大或者过小都会无法发挥最优的系统性能,线程池大小不需要非常精确,只要避免极大或者极小的情况即可。一般来说:

线程池大小 = CPU数量*目标CPU的使用率*(1+等地啊时间与计算时间比)

线程池死锁

任务A执行过程向线程池提交任务B执行,A的结束要等待B,但是B由于请求太多被放在了等待队列中,类似这种情况造成所有工作线程都处于等待任务处理结果,而这些任务都在阻塞队列中,就造成了死锁

适合给线程池提交相互独立的任务,而不是彼此依赖的任务。对于有依赖的任务最好可以提交给不同线程池。

线程池中的异常处理

线程池可能会吃掉程序中的异常

解决方法:

ForkJoinPool线程池(JDK7)

系统对ForkJoinPool线程池进行了优化,提交的任务与线程的数量不一定时一对一的关系。在多数情况下,一个物理线程实际上需要处理多个逻辑任务(每个线程有自己的任务队列?

线程A把自己的任务执行完毕,线程B的任务队列中还有若干的任务等待执行,线程A会从线程B的等待队列中取任务帮助线程B完成。线程A在帮助线程B执行任务时,总是从线程B的等待队列底部开始取任务。

例如使用线程进行归并排序操作,就可能会叫其他线程帮忙一起排序。类似的还有大量数据累加,打印大量数据等待可以拆分且数量巨大的任务。由于任务的分割无法做到都一样,所以还是用了一种“工作窃取”的算法,让已经完成工作空闲的线程可以去帮忙完成其他还没完成的线程的工作。这样可以使用少量的线程完成大量的工作,充分利用了硬件

重要方法:

两个重要子类:

保障线程安全的设计技术

从面向对象设计的角度除法介绍几种保障线程安全的设计技术,这些技术可以使得我们在不必借助锁的情况下保障线程安全,避免锁可能导致的问题及开销

Java运行时储存空间

栈空间:由于线程栈是相互独立的,一个线程不能访问另外一个线程的栈空间,因此线程对局部变量以及只能通过当前线程的局部变量才能访问的对象进行的操作具有固定的线程安全性

堆空间:是在JVM启动时分配的一段可以动态扩容的内存空间。创建对象时,在堆空间中给对象分配储存空间,实例变量就是储存在堆空间中的,堆空间是多个线程之间可以共享的空间,因此实例变量可以被多个线程共享,多个线程同时操作实例变量可能存在线程安全问题

非堆空间(Non-Heap Space):用于储存常量,类的元数据等,非堆空间也是在JVM启动时分配的一段可以动态扩容的储存空间。类的元数据包括静态变量,类的哪些方法及这些方法的元数据(方法名,参数,返回值等)。非堆空间也是多个线程可以共享的,因此访问非堆空间中的静态变量也可能存在线程安全问题

无状态对象

对象就是数据及堆数据操作的封装,对象包含的数据称为对象的状态(State),实例变量与静态变量称为状态变量。

如果一个类的同一个实例被多个线程共享并不会使这些线程储存共享的状态,那么该类的实例就称为无状态对象(Stateless Object)。反之如果一个类的实例被多个线程共享会使这些线程存在共享状态,那么该类的实例称为有状态对象。实际上无状态对象就是不包含任何实例变量也不包含任何静态变量的对象。

线程安全问题的前提是多个线程存在共享的数据,实现线程安全的一种办法就是避免在多个线程之间共享数据,使用无状态对象就是这种方法。

不可变对象

不可变对象是指一经创建它的状态就保持不变的对象,不可变对象具有固有的线程安全性。当不可变对象现实实体的状态发生变化时,系统会创建一个新的不可变对象,就如String字符串对象。Integer等包装类。

String,Integer等包装类最好不要用作synchronized锁,因为一旦被改变,这个对象会指向一个新的引用(就是指向新的内存),而你原来锁的信息在原来的那个内存的对象的对象头中,指向Monitor,所以这时候调用wait()/notify()就会报错。

一个不可变对象需要满足以下条件:

1)类本身使用final修饰,防止通过创建子类来改变它的定义

2)所有的字段都是final修饰的,final字段在创建对象时必须显式初始化,并不能被修改

3)如果字段引用了其他状态可变的对象(集合,数组),则这些字段必须时private私有的

不可变对象状态的修改是通过创建新对象的的,所以频繁创建新对象对垃圾回收可能会有影响。但是也会出现年轻代引用年老代对象的情况,又对垃圾回收比较友好

不可变对象主要的应用场景:

1)被建模对象的状态变化不频繁

2)同时对一组相关数据进行写操作,可以应用不可变对象,既可以保障原子性也可以避免锁的使用

3)使用不可变对象作为安全可靠的Map键,HashMap键值对的储存位置与键的hashCode()有关,如果键的内部状态发生了变化会导致键的哈希码不同,可能会影响键值对的储存位置。如果HashMap的键是一个不可变对象,则hashCode()方法的返回值恒定,储存位置时固定的。

线程特有对象

我们可以选择不共享非线程安全的对象,对于非线程安全的对象,每个线程都创建一个该对象的实例,各个线程访问各自创建的实例,不能访问其他线程创建的实例。这样就能保证线程安全,又避免了锁的开销。

例如ThreadLocal<T>类

装饰器模式

装饰器模式可以用来实现线程安全。其基本思想是为非线程安全的对象创建一个相应的线程安全的外包装对象,客户端代码不直接访问非线程安全的对象而是访问它的外包装对象。

外包装对象与非线程安全对象具有相同的接口,即使用方式相同。而外包装对象内部通常会借助锁,以线程安全的方式调用相应的非线程安全对象的方法。

在java.util.Collections工具类中提供了一组synchronizedXXX(xxx)方法,可以把不是线程安全的xxx集合转换为线程安全的集合,它就是采用了这种装饰器模式。这个方法返回值就是指集合的外包装对象。

锁的优化及注意事项

有助于提高锁性能的几点建议

JVM对锁的优化

乐观锁:

悲观锁

标签:队列,synchronized,对象,GuideLine,并发,任务,线程,多线程,方法
来源: https://blog.csdn.net/qq_39117858/article/details/110646129