java线程高并发,JAVA多线程高并发学习

工欲善其事,必先利其器

postman 可作为一个简单的并发测试

JMeter相比于postman更加强大

JMeter入门: https://www.jianshu.com/p/0e4daecc8122

线程安全性:

定义:当多个线程访问某个类时,不管采用任何调度方式,不需要额外的同步或者协调,这个类都能表现出正确的结果,这个类就成为是线程安全的

线程安全性主要体现在

原子性: 互斥访问,同一时间只能有一个线程操作

可见性: 一个线程对主内存的修改可以及时被其他线程观察到

有序性: 一个线程观察其他线程中指令执行顺序,由于指令的重排序的存在,结果一般都是无序的.

原子性:

atomic包:https://www..com/chenpi/p/5375805.html

ad269e6844f8fec6d9d522371bbeeba0.png

原子性: 锁 synchronized(依赖jvm) lock(代码实现)

7d259cc3c5d04f4d55bb5e963ec49d45.png

可见性

不可见的原因: (1)线程交叉执行 (2)重排序结合线程交叉执行 (3)共享变量更新的值没在工作内存和主内存间及时更新

可见性–synchronized

实现方式:JVM关于synchronized两条规定:

(1)线程解锁前,必须把共享变量的最新之刷新到主内存中

(2)线程加锁前,清空工作内存共享变量的值,使用共享变量时需从主内存中重新读取最新的值

可见性-volatile (并不是线程安全的,一般不使用)

关于volatile介绍: https://www.ibm.com/developerworks/cn/java/j-jtp06197.html

实现方式: 通过加入内存屏障和禁止重排序优化实现

简单来说:

写操作后会加入store屏障指令强制将本地内存中的共享变量刷新到主内存中

读之前会加入load屏障指令从主内存中读取共享变量:

有序性:

5e85101aa14317886b129a34e3381d5e.png

安全发布对象:

发布对象:使一个对象能被当前代码范围之外的代码所使用

对象溢出:一种错误发布;当一个对象还没构建完成时,就被其他线程所见

安全发布对象的方式:

在静态初始化函数中初始化一个对象引用

将对象的引用保存到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..com/skywang12345/p/3496716.html

Java并发编程:Callable、Future和FutureTask https://www..com/dolphin0520/p/3949310.html

线程池:

线程池优点:

重用存在的线程,减少对象创建消亡的开销

可有效控制最大并发线程数,提高系统资源利用率,同时可避免过多资源竞争,避免阻塞

提供定时执行,定期执行,单线程,并发数控制等功能

ThreadPoolExecutor学习

coprePoolSize:核心线程数量 (有线程就放在里面执行,即便有线程是空闲的,也创建新的线程)

maximumPoolSize:最大线程数 (当workQueue满了才会创建新的线程执行)

workQueue:阻塞队列,存储等待执行的任务,线程池满的时候未执行的线程会放在workQueue中

keepAliveTime:线程没有任务执行时最多保持多久时间终止(核心线程中的线程空闲时间)

threadFactory:线程工厂,用来创建线程

rejectHandler:拒绝策略 workQueue满了.线程池满了,再有新线程提交(有四种策略1,直接抛出异常(默认);2,用调用者所在的线程执行任务;3,丢弃阻塞队列中靠最前的任务;4,直接丢弃)

线程池状态: https://blog..net/L_kanglin/article/details/57411851

bd58c2f7112dededb3559b92623de344.png

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 线程已经退出。

线程池方法:

execute():提交任务,交给线程池执行

submit() : 提交任务能返回执行结果 execute+Future

shutdown():关闭线程池,等任务执行完

shutdownNow() :关闭线程池,不等任务执行完

getTaskCount() 线程池执行和未执行任务总数

getCompletedTaskCount(): 已完成任务数量

getPoolSize() :线程池当前线程数量

9 ) getActiveCount() 当前线程池中正在执行任务的线程数量

多线程高并发最佳实践:

使用本地变量

使用不可变类

最小化锁的作用域范围

使用线程池的executor 而不是直接new Thread

宁愿使用同步,也不要使用线程的wait和notify

使用BlockingQueue实现生产消费

使用并发集合而不是加了锁的同步集合

使用semaphore创建有界的访问

使用同步代码块而不是使用同步方法

避免使用静态变量

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值