并发基础学习小记

一个简单的demo

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
public class CountExample1 {

    // 请求总数
    public static int clientTotal = 5000;
    // 同时并发执行的线程数
    public static int threadTotal = 200;
    public static int count = 0;

    public static void main(String[] args) throws Exception {
    	// 线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        // 200个线程并发
        final Semaphore semaphore = new Semaphore(threadTotal);
        // 线程技术,await时线程处于等待状态直到countDownLatch 为0
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
    }

    private static void add() {
        count++;
    }
}

以上代码用于测试线程并发,不了解的类就去百度下。

实现线程安全的几种方式

1、线程原子类Automic

automic一个更为高效的方式CAS (compare and swap) ,避免了synchronized的高开销,执行效率大为提升;适用于并发不是很大的场景

2、synchronize 线程加锁

线程解锁前,必须把共享变量的最新值刷新到主内存中
线程加锁时, 必须将共享变量清空,从主内存中获取最新的值

3、 volatile 可见性

保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
volatile 变量的内存可见性是基于内存屏障(Memory Barrier)实现
注意:在对volatile 变量进行操作时不能保证线程时安全的
适用于做线程通信的状态码

线程安全策略

1、不可变对象
  • 挡final 修饰变量是,一旦初始化就不能被修改; 修饰对象时,对象的属性可以修改,但对象的引用不可变
  • 集合对象可以用Collections.unmodifiableXXXX包装一下变为不可变对象
2、线程封闭(ThreadLocal)
	从接收请求到返回响应所经过的所有程序调用都同属于一个线程。将一些非线程安全的变量以ThreadLocal存放,对象所访问的同一ThreadLocal变量都是当前线程所绑定的。

实验: 当 ThreadLocal set 赋值一个单例对象时, 再取出对对象内部属性进行修改,并不能保证线程对象安全;对于基本数据类型, 可保证线程隔离。(待说明

线程安全类和写法

1、StringBuilder -> StringBuffer
  • StringBuilder 线程不安全的
  • StringBuffer 线程安全的, 每个方法都加有synchronized 锁
2、SimpleDateFormat -> joda-time
  • SimpleDateFormat 当多线程使用同一个对象时会报错,
  • 解决办法:声明一个局部的对象 joda-time线程安全的,有空多了解下
3、HashSet、HashSet、ArrayList 线程不安全的
  • ArrayList -> Vector,Stack
  • HashMap -> HashTable(key、value 不能为null)
  • Collections.synchronizedXXX(List,Set,Map)
  • 同步容器,影响到性能
4、并发容器 J.U.C.
  • 实现了List接口
  • 内部持有一个ReentrantLock lock = new ReentrantLock();
  • 底层是用volatile transient声明的数组 array
  • 读写分离,写时复制出一个新的数组,完成插入、修改或者移除操作后将新数组赋值给array
  • ArrayList -> CopyOnWriteArrayList
  • HashSet、TreeSet -> CopyOnWriteArraySet、ConcurrentSkipListSet
  • HashMap、TreeMap -> ConcurrentHashMap、ConcurrentSkipListMap

J.U.C.并发编程

AQS 抽象队列化同步器(Abustact Queued Synchronizer )
1、CountDownLatch**

可用于线程计数或线程状态监控
例如: 子线程任务执行完了之后执行主线程任务
注意: 在线程中最好放在final中

final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
countDownLatch.countDown();
countDownLatch.await();
2、Semaphore 信号量
final Semaphore semaphore = new Semaphore(threadTotal);
semaphore.acquire();
semaphore.release();

用于控制线程的并行, 例如线程池

3、CyclicBarrier

描述: 描述所有线程被栅栏挡住了,当都达到时,一起跳过栅栏执行,也算形象。我们可以把这个状态就叫做barrier
代码:

@Log4j2
public class CyclicBarrierDemo {

    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);

    public static void main(String[] args) throws Exception {
        ExecutorService executors = Executors.newCachedThreadPool();
        for(int i  =0 ;i<10;i++){
            Thread.sleep(1000);
            final int index = i;
            executors.execute(()->{
                try {
                    show(index);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }
    public static void show(int i) throws BrokenBarrierException, InterruptedException {
        log.info("正在集合,{}:已经到达",i);
        cyclicBarrier.await();
        log.info("集合完毕,{}:开始跑了",i);
    }
}

输出:

22:53:40.636 [pool-1-thread-1] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,0:已经到达
22:53:41.622 [pool-1-thread-2] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,1:已经到达
22:53:42.622 [pool-1-thread-3] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,2:已经到达
22:53:43.623 [pool-1-thread-4] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,3:已经到达
22:53:44.625 [pool-1-thread-5] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,4:已经到达
22:53:44.626 [pool-1-thread-4] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,36] - 集合完毕,3:开始跑了
22:53:44.626 [pool-1-thread-3] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,36] - 集合完毕,2:开始跑了
22:53:44.626 [pool-1-thread-2] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,36] - 集合完毕,1:开始跑了
22:53:44.626 [pool-1-thread-1] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,36] - 集合完毕,0:开始跑了
22:53:44.626 [pool-1-thread-5] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,36] - 集合完毕,4:开始跑了
22:53:45.625 [pool-1-thread-5] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,5:已经到达
22:53:46.626 [pool-1-thread-1] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,6:已经到达
22:53:47.626 [pool-1-thread-3] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,7:已经到达
4、ReentrantLock 重入锁

synchronize 是jvm 实现的,是非公平锁,
reentrankLock 是通过JDK 实现的,既有公平锁又有非公平锁

ReentrankLock 优点:
1、既有公平锁又有非公平锁
2、提供一个condition类,可分组唤醒需要唤醒的线程
3、 提供能够中断等待锁的线程机制,lock.lockInterruptibly();

synchronize 优点:
1、不用去关心怎样释放锁
2、因为是jvm实现的, 调试的时候容易找出哪些线程的问题,比如死锁等问题;

5、ReentrantReadWriteLock读写锁

特征:
读锁和写锁不能同时执行,必须等其中一个锁执行完毕之后才能执行;
缺陷: 当读操作特别多的时候,写锁会一直等待

public class ReentrantLockDemo {
    private final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    private final Lock readLock = reentrantReadWriteLock.readLock();
    private final Lock writeLock = reentrantReadWriteLock.writeLock();
    Map<String,Object> map = new HashMap<>();
    public Object setValue(){
        readLock.lock();
        try {
            return "aa";
        }finally {
            readLock.unlock();
        }
    }

    public void setValue(Object a){
        writeLock.lock();
        try {
            map.put("key",a);
        }finally {
            writeLock.unlock();
        }
    }
}
6、StampedLock

J.U.C. 并发组件

1、Future 返回参数的线程
@Log4j2
public class FutureDemo {

     static class MyCallable implements Callable<String>{
        @Override
        public String call() throws Exception {
            log.info("正在加载子线程数据");
            Thread.sleep(5000);
            return "OK";
        }
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Future<String> future = executorService.submit(new MyCallable());
        log.info("加载主线程");
        String s = future.get(); // 当子线程在这一步没有执行完毕是,需要等待
        log.info("最后的结果:{}",s);
        executorService.shutdown();
        log.info("结束");
    }
}
2、FutureTask
演示源码:
@Log4j2
@RunWith(SpringRunner.class)
public class FutureTaskDemo {
    @Test
    public void show() throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<>(()->{
            log.info("正在加载子线程数据");
            Thread.sleep(5000);
            return "OK";
        });
        new Thread(futureTask).start();
        Thread.sleep(1000);
        log.info("加载主线程");
        String s = futureTask.get(); // 当子线程在这一步没有执行完毕是,需要等待
        log.info("最后的结果:{}",s);
        log.info("结束");
    }

}

并发最佳实现

推荐使用方式
  • 宁可同步,也不要使用wait和notify
  • 使用BlockingQueue实现生产-消费模式
  • 使用并发集合而不是加了锁的同步集合
  • 使用semaphore创建有界的资源
  • 宁可使用同步代码快也不使用同步方法
  • 避免使用静态变量
线程池

1、线程池参数:
corePoolSize : 核心线程数量
maximumPoolSize : 线程最大线程数
workQueue : 阻塞对列,存储等待执行的任务,很重要,会对线程池运行过程产生重大影响

2、poolSize、corePoolSize、maximumPoolSize三者的关系是如何的呢?
当新提交一个任务时:
(1)如果poolSize<corePoolSize,新增加一个线程处理新的任务。
(2)如果poolSize=corePoolSize,新任务会被放入阻塞队列等待。
(3)如果阻塞队列的容量达到上限,且这时poolSize<maximumPoolSize,新增线程来处理任务。
(4)如果阻塞队列满了,且poolSize=maximumPoolSize,那么线程池已经达到极限,会根据饱和策略RejectedExecutionHandler拒绝新的任务。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值