JAVA多线程高并发学习

java 专栏收录该内容
72 篇文章 0 订阅

工欲善其事,必先利其器
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<Integer, Integer> 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 线程已经退出。

线程池方法:

  1. execute():提交任务,交给线程池执行
  2. submit() : 提交任务能返回执行结果 execute+Future
  3. shutdown():关闭线程池,等任务执行完
  4. shutdownNow() :关闭线程池,不等任务执行完
  5. getTaskCount() 线程池执行和未执行任务总数
  6. getCompletedTaskCount(): 已完成任务数量
  7. getPoolSize() :线程池当前线程数量
    9 ) getActiveCount() 当前线程池中正在执行任务的线程数量

多线程高并发最佳实践:

  1. 使用本地变量
  2. 使用不可变类
  3. 最小化锁的作用域范围
  4. 使用线程池的executor 而不是直接new Thread
  5. 宁愿使用同步,也不要使用线程的wait和notify
  6. 使用BlockingQueue实现生产消费
  7. 使用并发集合而不是加了锁的同步集合
  8. 使用semaphore创建有界的访问
  9. 使用同步代码块而不是使用同步方法
  10. 避免使用静态变量
  • 1
    点赞
  • 0
    评论
  • 23
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值