工欲善其事,必先利其器
postman 可作为一个简单的并发测试
JMeter相比于postman更加强大
JMeter入门: https://www.jianshu.com/p/0e4daecc8122
线程安全性:
定义:当多个线程访问某个类时,不管采用任何调度方式,不需要额外的同步或者协调,这个类都能表现出正确的结果,这个类就成为是线程安全的
线程安全性主要体现在
原子性: 互斥访问,同一时间只能有一个线程操作
可见性: 一个线程对主内存的修改可以及时被其他线程观察到
有序性: 一个线程观察其他线程中指令执行顺序,由于指令的重排序的存在,结果一般都是无序的.
原子性:
atomic包:https://www.cnblogs.com/chenpi/p/5375805.html
原子性: 锁 synchronized(依赖jvm) lock(代码实现)
可见性
不可见的原因: (1)线程交叉执行 (2)重排序结合线程交叉执行 (3)共享变量更新的值没在工作内存和主内存间及时更新
可见性–synchronized
实现方式:JVM关于synchronized两条规定:
(1)线程解锁前,必须把共享变量的最新之刷新到主内存中
(2)线程加锁前,清空工作内存共享变量的值,使用共享变量时需从主内存中重新读取最新的值
可见性-volatile (并不是线程安全的,一般不使用)
关于volatile介绍: https://www.ibm.com/developerworks/cn/java/j-jtp06197.html
实现方式: 通过加入内存屏障和禁止重排序优化实现
简单来说:
写操作后会加入store屏障指令强制将本地内存中的共享变量刷新到主内存中
读之前会加入load屏障指令从主内存中读取共享变量:
安全发布对象:
发布对象:使一个对象能被当前代码范围之外的代码所使用
对象溢出:一种错误发布;当一个对象还没构建完成时,就被其他线程所见
安全发布对象的方式:
在静态初始化函数中初始化一个对象引用
将对象的引用保存到volatile类型或者AtomicReference对象中
将对象的引用保存到某个正确构造对象的final类型域中
将对象的引用保存到一个有锁保护的域中
知识点
普通的懒汉式单列模式并不是线程安全的 **(静态域,和静态代码块的顺序执行)**
处理为线程安全的方式
(1)在静态工厂方法中添加synchronized关键之(不推荐,性能开销大)
(2) volatile+双重检测机制
private volatile static SingletonExample5 instance = null;
// 静态的工厂方法
public static SingletonExample5 getInstance() {
if (instance == null) { // 双重检测机制
synchronized (SingletonExample.class) { // 同步锁
if (instance == null) {
instance = new SingletonExample5();
}
}
}
return instance;
}
饿汉式单利模式是线程安全的(但初始化时可能开销比较大)
使用枚举创建饿汉式单例也是安全的(推荐)
public class SingletonExample7 {
// 私有构造函数
private SingletonExample7() {
}
public static SingletonExample7 getInstance() {
return Singleton.INSTANCE.getInstance();
}
private enum Singleton {
INSTANCE;
private SingletonExample7 singleton;
// JVM保证这个方法绝对只调用一次
Singleton() {
singleton = new SingletonExample7();
}
public SingletonExample7 getInstance() {
return singleton;
}
}
}
不可变对象: (只要发布了就是安全的)
不可变对象需要满足的条件:
(1)对象创建后器状态不能修改
(2)对象所有域都是final类型
(3)对象都是正确创建的(在对象创建中,this引用没有逸出)
final :
修饰类:不能被继承
修饰方法: 不被继承类覆盖
修饰变量: 基本类型变量 赋值一次后不能被修改.引用类型变量赋值后不能再指向其他对象
Collections.unmodifiableXX 处理的Collection List Set Map 都不允许改变
ImmutableXXX 处理的Collection List Set Map 都不允许改变
Map map = new HashMap<>();
map.put(1, 2);
map.put(3, 4);
map.put(5, 6);
map = Collections.unmodifiableMap(map); //这里处理之后
map.put(1, 3); //运行这里会报错
System.out.println(map.get(1));
线程封闭 (把对象封装到一个线程中,即使对象不是线程安全的这不会出现线程安全,)
Ad_hoc线程封闭:程序实现,最糟糕,忽略
堆栈封闭: 局部变量 无并发问题
ThreadLocal线程封闭:相当好的封闭方法(例如数据库jdbc连接)
(ThreadLocal内部维护了一个Map Map的Key是线程的名称 Map的值就是要封闭的对象)
线程不安全的类
StringBuilder 不安全; (StringBuffer)
SimpleDateFormat 不安全 (安全类 JodaTime)
ArrayList HashSet HashMap 等Collections (java中线程安全的集合有Vector和HashTable)
线程安全----同步容器(效率低,还不确保安全,容器遍历时进行增删容易出问题)
1\本身就安全的类 Vector Stack HashTable( k v 不能为null) (都是使用synchronized)
2\Collections.synchronizedXXX(List,Set,Map)
线程安全----并发容器(JUC)(JDK1.5之后引入的并发包)
CopyOnWriteArrayList(ArrayList):(写操作时复制,写完后再刷到原有数组中,读写分离)
缺点1:写操作时需要拷贝,会消耗内存
缺点2:不能用于实时读的场景,更适合读多写少的场景
CopyOnWriteArraySet(HashSet):
ConcurrentSkipListSet(TreeSet):
ConcurrentHashMap(HashMap): 不允许null值
ConcurrentSkipListMap(TreeMap):实现原理SkipList(Key有序)(在多线程中应使用可以更好的提高并发度)
安全共享对象策略:
1\线程限制:一个被线程限制的对象,由线程独占,并且只能被占有他的线程修改
2\共享只读:一个共享只读对象,在没有额外同步的情况下可以被多个线程并发访问,但不允许任何线程修改
3\线程安全对象:线程安全的对象或者容器,在内部通过同步机制保证安全,其他线程无需通过额外的同步就可以去访问它
4\被守护对象:被守护对象只能通过获取特定的锁来访问
线程安全----并发容器(JUC)----同步器(AQS)
AbstractQueuedSynchronizer - JUC的核心
底层实现方式,双向链表
AQS设计
1\使用Node实现FIFO队列,可以用于构建锁或者其他同步装置的基础框架
2\利用一个int类型表示状态
3\ 使用方法时继承
4\子类通过继承并通过实现它的方法管理其状态{acquire和release}的方法操作状态
5\可以同时实现排他锁和共享锁(独占 共享)
AQS 同步组件有
CountDownLatch Semaphore CyclicBarrier ReenTrantLock Condition FutureTask
CountDownLatch:同步辅助类
通过它可以完成类似阻塞当前线程的功能 即 一个线程或者多个线程一直等待,直到其他线程执行的操作完成
有一个给定的计数器进行初始化(该计数器时原子操作),调用该类await()方法,会让线程一直处于阻塞状态.直到其他线程调用countDown()将当前计数器的值变为0,这种操作只会出现一次,计数器是不能被重置的
private final static int threadCount = 200;
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
try {
test(threadNum);
} catch (Exception e) {
log.error("exception", e);
} finally {
countDownLatch.countDown(); //会让计数器中threadCount -1
}
});
}
countDownLatch.await();//当前线程会被挂起,知道计数器中的数值为0才会被唤醒
// countDownLatch.await(10, TimeUnit.MILLISECONDS);//等待指定时间,到期自动唤醒
log.info("finish");
exec.shutdown(); //关闭线程池 当前已有的线程执行完
}
private static void test(int threadNum) throws Exception {
Thread.sleep(100); log.info("{}", threadNum); Thread.sleep(100);
}
Semaphore(信号量 ):可以控制同一时间访问某个资源的个数
提供两个核心方法acquire(获取许可,没有就等待)和release(操作完成后释放许可)
Semaphore相关方法
void acquire() //获取一个许可
void acquire(int permits) //获取permits个许可
void release() //释放一个许可
void release(int permits) //释放permits个许可
boolean tryAcquire() ; //尝试获取一个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
boolean tryAcquire(long timeout, TimeUnit unit) ; //尝试获取一个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
boolean tryAcquire(int permits); //尝试获取permits个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
boolean tryAcquire(int permits, long timeout, TimeUnit unit) ; //尝试获取permits个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
private final static int threadCount = 20; //线程数
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(3); //几个许可
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
try {
semaphore.acquire(); // 获取一个许可
//semaphore.acquire(3); // 获取多个许可
test(threadNum);
semaphore.release(); // 释放一个许可
} catch (Exception e) {
log.error("exception", e);
}
});
}
exec.shutdown();
}
private static void test(int threadNum) throws Exception {
log.info("{}", threadNum);
Thread.sleep(1000);
}
CyclicBarrier: 回环栅栏 (同步辅助类) 也是可以控制同一时间访问某个资源的个数
可以完成多个线程之间相互等待(与CountDownLatch区别),只有当每个线程都准备就绪后才能往下执行 也是使用计数器实现(计数器初始值为零.计数器可以重复使用 与CountDownLatch区别)
通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。
CyclicBarrier提供2个构造器:
CyclicBarrier(int parties, Runnable barrierAction)
public CyclicBarrier(int parties)
参数parties指让多少个线程或者任务等待至barrier状态;参数barrierAction为当这些线程都达到barrier状态时会执行的内容。
CyclicBarrier中最重要的方法就是await方法
用来挂起当前线程,直至所有线程都到达barrier状态再同时执行后续任务;
await(long timeout, TimeUnit unit)方法:
让这些线程等待至一定的时间,如果还有线程没有到达barrier状态就直接让到达barrier的线程执行后续任务。
private static CyclicBarrier barrier = new CyclicBarrier(5, () -> {
log.info("callback is running");
});
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int threadNum = i;
Thread.sleep(1000);
executor.execute(() -> {
try {
race(threadNum);
} catch (Exception e) {
log.error("exception", e);
}
});
}
executor.shutdown();
}
private static void race(int threadNum) throws Exception {
Thread.sleep(1000);
log.info("{} is ready", threadNum);
barrier.await(); //计数器加1 //直到计数器值为5时下面的才会执行
log.info("{} continue", threadNum); //同时执行了
}
ReenTrantLock:
可重入锁:线程获取一次数,计数器加1,释放一次,计数器减一 计数器为零时锁才被释放
锁的实现:JDK实现的 (synchronized是JVM实现的)
性能:synchronized优化后官方推荐 synchronized
功能: synchronized使用便捷,可以自动释放 ReenTrantLock需要手动释放 ReenTrantLock更灵活
ReenTrantLock独有功能:
(1)可指定是公平锁还是非公平锁
(2)提供了一个Condition类可以分组唤醒需要的线程(synchronized随机唤醒,或者全部唤醒)
(3)提供能够中断等待锁的线程机制,
StampedLock它是java8在java.util.concurrent.locks新增的一个API。 http://www.importnew.com/14941.html
StampedLock控制锁有三种模式(写,读,乐观读),一个StampedLock状态是由版本和模式两个部分组成,锁获取方法返回一个数字作为票据stamp,它用相应的锁状态表示并控制访问,数字0表示没有写锁被授权访问。在读锁上分为悲观锁和乐观锁。速度比ReentrantReadWriteLock要快很多
总结
(1) synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定;
(2) ReentrantLock、ReentrantReadWriteLock,、StampedLock都是对象层面的锁定,要保证锁定一定会被释放,就必须将unLock()放到finally{}中;
(3)StampedLock 对吞吐量有巨大的改进,特别是在读线程越来越多的场景下;
(4)StampedLock有一个复杂的API,对于加锁操作,很容易误用其他方法;
(5)当只有少量竞争者的时候,synchronized是一个很好的通用的锁实现;
(6)当线程增长能够预估,ReentrantLock是一个很好的通用的锁实现;
Condition: https://www.cnblogs.com/skywang12345/p/3496716.html
Java并发编程:Callable、Future和FutureTask https://www.cnblogs.com/dolphin0520/p/3949310.html
线程池:
线程池优点:
重用存在的线程,减少对象创建消亡的开销
可有效控制最大并发线程数,提高系统资源利用率,同时可避免过多资源竞争,避免阻塞
提供定时执行,定期执行,单线程,并发数控制等功能
ThreadPoolExecutor学习
coprePoolSize:核心线程数量 (有线程就放在里面执行,即便有线程是空闲的,也创建新的线程)
maximumPoolSize:最大线程数 (当workQueue满了才会创建新的线程执行)
workQueue:阻塞队列,存储等待执行的任务,线程池满的时候未执行的线程会放在workQueue中
keepAliveTime:线程没有任务执行时最多保持多久时间终止(核心线程中的线程空闲时间)
threadFactory:线程工厂,用来创建线程
rejectHandler:拒绝策略 workQueue满了.线程池满了,再有新线程提交(有四种策略1,直接抛出异常(默认);2,用调用者所在的线程执行任务;3,丢弃阻塞队列中靠最前的任务;4,直接丢弃)
线程池状态: https://blog.csdn.net/L_kanglin/article/details/57411851
RUNNING: 线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
SHUTDOWN: 线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
STOP: 线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
TIDYING: 当所有的任务已终止,线程任务数量为0,线程池会变为TIDYING状态.当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
TERMINATED: 线程池彻底终止,就变成TERMINATED状态。
线程状态分为 ; https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr034.html
NEW 该线程尚未启动
RUNNABLE 线程正在JVM中执行。
BLOCKED 线程被阻塞等待监视器锁定
WAITING 线程无限期地等待另一个线程执行特定操作。
TIMED_WAITING 线程正在等待另一个线程执行最多指定等待时间的操作。
TERMIMNATED 线程已经退出。
线程池方法:
多线程高并发最佳实践: