线程并发总是学不懂,看看这篇文章吧,可能茅塞顿开!

JUC线程并发

JUC介绍: java.util.concurrent在并发编程中使用的工具类

进程:一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

线程:通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。

线程状态:6种

新建准备就绪阻塞不见不散过时不候终结
NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED
lock锁

锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。

使用方法:

class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...
   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

lock锁于synchronized锁的区别:
1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

生产者与消费者问题
8锁问题
/**
1 标准访问,先打印短信还是邮件                        
2 停4秒在短信方法内,先打印短信还是邮件
3 普通的hello方法,是先打短信还是hello
4 现在有两部手机,先打印短信还是邮件
5 两个静态同步方法,1部手机,先打印短信还是邮件
6 两个静态同步方法,2部手机,先打印短信还是邮件
7 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
8 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
运行答案:
1、短信
2、短信
3、Hello
4、邮件
5、短信
6、短信
7、邮件
8、邮件
*/

注意:集合使用多态写法,方便后期维护(只改变集合类型),除非调用本集合特有方法。

集合不安全

**原因:**读写不一致(读快写慢)

抛出异常:

//高并发异常(线程不安全)
java.util.ConcurrentModificationException 

CopyOnWrite容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,
而是先将当前容器Object[]进行Copy,复制出一个新的容器Object[] newElements,然后向新的容器Object[] newElements里添加元素。
添加元素后,再将原容器的引用指向新的容器setArray(newElements)。
这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。
所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

Arraylist不安全

**底层:**数组(【索引】查询快,增删改慢)

  • vector

  • Collections工具类

    List<Object> alist = Collections.synchronizedList(new ArrayList<>());
    
  • CopyOnWriteArrayList(写时复制一个,读写分离)

    List<String> list = new CopyOnWriteArrayList<>();
    
Hashset不安全

底层(Hashmap):

//源码
public HashSet() {
    map = new HashMap<>();
}
//add方法添加的是hashmap的key,value永远为固定的常量
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

解决办法:

  • Collections工具类
Collections.synchronizedSet(new HashSet<>());
  • CopyOnWriteArraySet

    Set<String> set = new CopyOnWriteArraySet<>();
    
Hashmap不安全

底层:Node(键值对)节点类型的单向链表+红黑树(hash冲突【8】)

初始值16(可能被改),负载因子0.75,等于16*0.75=12时扩容一倍16->32->64…

优化:增大初始值,避免频繁扩容。

  • ConcurrentHashMap();

    // 线程安全的hashmap       
    Map<String,String> map = new ConcurrentHashMap();
    
  • Collections工具类

    Collections.synchronizedMap(new HashMap<String,String>());
    

BlockingQueueDemo阻塞队列

种类分析

ArrayBlockingQueue:由数组结构组成的有界阻塞队列。

LinkedBlockingQueue:由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列。

BlockingQueue核心方法

抛出异常当阻塞队列满时,再往队列里add插入元素会抛IllegalStateException:Queue full

当阻塞队列空时,再往队列里remove移除元素会抛NoSuchElementException

特殊值插入方法,成功ture失败false移除方法,成功返回出队列的元素,

队列里没有就返回null一直阻塞当阻塞队列满时,生产者线程继续往队列里put元素,

队列会一直阻塞生产者线程直到put数据or响应中断退出当阻塞队列空时,消费者线程试图从队列里take元素,

队列会一直阻塞消费者线程直到队列可用超时退出当阻塞队列满时,队列会阻塞生产者线程一定时间,超过限时后生产者线程会退出

JUC强大辅助工具类

CountDownLatch 减少计数
  • CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞。
  • 其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞),
  • 当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行。

例子:最后一个人关门案例

CyclicBarrier 循环栅栏

字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,

  • 让一组线程到达一个屏障(也可以叫同步点)时被阻塞,
  • 直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。
  • 线程进入屏障通过CyclicBarrier的await()方法。

例子:七颗龙珠召唤神龙!

Semaphore 信号灯

在信号量上我们定义两种操作:

  • acquire(获取) 当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),
  •   要么一直等下去,直到有线程释放信号量,或超时。
    
  • release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。
  • 信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制

例子:占用空车位,位置有限

传值练习

public class TestTransferValue {
    public void changeValue(int age){
        age=30;
    }
    public void changeValue1(Person person){
        person.setName("xxx");
    }
    public void changeValue2( String S){
         S="新址";
    }

    public static void main(String[] args) {
        TestTransferValue test =new TestTransferValue();
        int age=20;
        test.changeValue(age);
        System.out.println("age------>"+age);
        System.out.println("========================");
        Person person =new Person("abc");
        test.changeValue1(person);
        System.out.println(person.getName());
        System.out.println("========================");
        String S ="旧址";
        test.changeValue2(S);
        System.out.println(S);
    }
}
//答案:
age------>20
========================
xxx
========================
旧址

线程池

优势:

线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。

主要特点:

线程复用;控制最大并发数;管理线程。

用处:

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
第二:提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

架构:

Java中的线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类

七大参数
核心线程数最大线程数工作队列活跃时间线程工厂(默认)活跃时间单位拒绝策略
corePoolSizemaximumPoolSizeworkQueuekeepAliveTimethreadFactoryunithandler
底层原理

1、在创建了线程池后,开始等待请求。
2、当调用execute()方法添加一个请求任务时,线程池会做出如下判断:
如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
3、当一个线程完成任务时,它会从队列中取下一个任务来执行。
4、当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。
所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。

最大线程容量=队列数+池中最大线程数

代码实现
public class threadpool_test {
    public static void main(String[] args) {
//        ExecutorService threadpool = Executors.newFixedThreadPool(5);//一池固定5个线程
//        ExecutorService threadpool = Executors.newSingleThreadExecutor();//一池个工作线程
        ExecutorService threadpool = Executors.newCachedThreadPool();//可扩容。可伸缩,一池n的线程
        try {
            for (int i = 0; i < 10; i++) {
                threadpool.execute(()-> System.out.println(Thread.currentThread().getName()+"执行任务!"));
            }
        }
        catch (Exception e){
            e.printStackTrace();
        }finally {
            threadpool.shutdown();
        }
    }
}

分支合并框架

相关类

ForkJoinPool

ForkJoinTask

RecursiveTask

Fork:把一个复杂任务进行分拆,大事化小
Join:把分拆任务的结果进行合并

四大函数式接口

Consumer<T()> 消费型接口

只有输入参数,无返回值

Supplier <T()> 供给型接口

没有参数,只有返回值

Function<T,R>函数型接口

传入参数T,返回类型R

Predicate<T()>断定型接口

有一个输入参数,返回值只能是布尔值!

异步回调

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Steve_hanhaiLong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值