当前位置: 首页 > news >正文

并发编程-

一、简述

线程:线程是cpu可执行的最小单位,而进程是操作系统可分配的最小资源单位。一个进程中可以有多个线程。

线程的五个状态:
新建(new Thread())
就绪 (thread.start())
运行(cpu开始执行该线程)
阻塞(线程在等待获得锁)
销毁(线程执行完毕或出现异常)。

创建线程:
1.继承Thread类并重写run方法
2.实现runnable接口并重写run方法
3.实现callable接口并重写call方法
        call方法和run方法不同之处在于,run方法时无返回值的,call方法有返回值,是个泛型。
4.使用线程池

二、多线程

并发执行:所谓的并发执行就是多个线程交替执行。
并发执行的优点:

        1.多个线程共享资源,可以减少内存的加载和释放,从而提高执行效率
        2.多个线程因为操作系统的调度可能会被分配到不同的cpu核心上执行,更好的利用了多核处理器的资源,从而提升了程序的可扩展性。

并发执行遇到的问题:

1.不可见性:

        由于Java内存模型(JMM)的原因,JMM分为主内存区和本地内存区,每个线程都会有自己的本地内存区。多核处理器允许多个线程并行执行在不同的核心上,如果多个线程同时对主内存区中的数据进行操作,就需要现将其加载到该线程的本地内存区中,当第一个线程对数据修改完写回到本地内存后,第二个线程不知道本地内存中的数据已经被修改,此时就会导致堆数据的操作出现错误。

2.乱序性

        为了优化性能,有时候cpu会将后面的指令提前执行,这样指令的运行顺序就被打乱了。

3.非原子性

        cpu执行指令,指令是原子性的,但是高级语言的语句却是非原子性的,一条高级语句往往可以拆成多条指令。非原子性就会导致线程交叉操作,使得结果错误。

总结:java缓存模型导致了不可见性。编译器优化导致了乱序性。线程交换导致了非原子性。

4.解决方法:

1.volatile关键字

volatile关键字对共享变量修饰后
1.该变量一旦被线程修改,对其他线程来说是立即可见的。
2.禁止了指令的重排序
3.仍不能解决非原子性

2.如何保证原子性
2.1synchronized锁

        synchronized锁是一种独占锁,因此只有持有锁的线程才能被执行,虽然不能阻止线程交换,但是当其他线程想要执行时,会因为没有锁而阻塞,变相的保证了原子性。

2.2原子类

        该方式是以volatile+CAS来实现的,volatile保证了主内存数据的可见性,而CAS即比较和交换,在具体实现中,当多个线程对同一数据进行操作,当一个线程加载主内存的数据到对本地内存时,此时会对该数据记录此时的值为预期值,当对数据进行修改后,写回到主内存区之前会对预期值和当前主内存区的值进行比较,如果已被更改就重新进行修改操作。

CAS缺点:CAS使用自旋锁的方式,由于该锁会不断循环判断,因此不会类似synchronize 线程阻塞导致线程切换。但是不断的自旋,会导致CPU的消耗,在并发量大的时候容易导致CPU跑满。

5.Java中的锁分类

1.乐观锁/悲观锁

        乐观锁和悲观锁并非是真实存在的锁,而是一种思想。
        乐观锁认为多线程开发中不需要加锁,例如使用原子类,采用不加锁的方式解决问题。
        悲观锁认为多线程开发中一定要加锁,否则会出现问题。

2.可重入锁

        可重入锁又名递归锁,是指在外层方法获得锁后,进入内层方法后会再次获得锁。reentrantlock就是可重入锁。reentrantlock锁可以避免死锁。

3.读写锁

        读写锁有以下特点,多个线程同时读取数据时不会互斥,但是一旦有线程进行写操作,就会互斥,阻止该操作。

4.共享锁/独占锁

        共享锁是允许多个获得该锁的线程,在不发生写操作的前提下,可以同时对数据进行读取操作。而独占锁一次只允许一个线程进入锁代码块中。

5.分段锁

        分段锁也是一种思想,主张将数据分段,在每个分段上都加锁,以提高并发效率。如ConcurrentHashMap,ConcurrentHashMap底层哈希表有16个空间,可以用每一个位置上的第一个节点当做锁,这样可以同时由不同的线程操作不同的位置,只是同一个位置多个线程不能同时操作。

6.自旋锁

        自旋锁是指,在线程进行抢锁的过程中,如果没抢到锁会多次进行抢锁操作,实在抢不到锁才会将该线程阻塞。但是自旋过程中不会释放cpu资源,因此比较耗费cpu,但是在低并发情况下会有较高的效率。

7.公平锁/非公平锁

        公平锁是指按照请求锁的顺序分配,拥有稳定获得锁的机会。
        非公平锁是指不按照请求锁的顺序分配,不一定拥有获得锁的机会。

8.偏向锁/ 轻量级锁/重量级锁

锁的状态:无锁,偏向锁,轻量级锁、重量级锁
无锁状态: 没有任何线程获取锁
偏向锁状态: 偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。 降低获取锁的代价。
轻量级锁状态: 当锁状态为偏向锁时, 继续有其他线程过来获取锁,锁状态升级为轻量级锁,线程不会进入到阻塞状态,一直自旋获得锁。
重量级锁状态: 当锁状态为轻量级锁时, 线程数量持续增多,且线程自旋次数到一定数量时,锁状态升级为重量级锁,线程会进入到阻塞状态,等待操作系统调度执行。

6.synchronized锁实现

        synchronized锁相当于一个监视器,当线程进入锁修饰的代码块或方法中时,就会自动获得锁,其他线程再访问该方法/代码块时就会阻塞,直到有锁的线程运行完毕或被wait,释放了锁。

1.原子性:synchronized锁不能同时加到多个线程上,因此其保证了操作的原子性。
2.可见性:synchronized锁会在线程释放锁后才会将本地内存中的数据刷新到主内存中。等到其他线程从主内存中读取数据时,已经被最新的值了。
3.有序性:synchronized锁会阻止操作系统对线程中的指令进行重排序,以确保操作的有序性

synchronized控制同步,是依靠底层的指令实现的.
如果是同步方法,在指令中会为方法添加ACC_SYNCHRONIZED标志
如果是同步代码块,在进入到同步代码块时,会执行monitorenter, 离开同步代码块时或者出异常时,执行monitorexit

7.AQS(AbstractQueuedSynchronizer)

抽象同步队列,并发包中很多类的底层都用到了AQS,在该队列中,将线程放到Node类中的thread变量上。

class AbstractQueuedSynchronizer {
    private transient volatile Node head;

    private transient volatile Node tail;

    private volatile int state; //表示有没有线程访问共享数据  默认是0 表示没有线程访问
 
    //修改状态的方法(CAS)
     protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
    
     static final class Node {
         volatile Node prev;
         volatile Node next;
         volatile Thread thread;
     }
    
}

Reentrantlock

Reentrantlock类公平锁和非公平锁都可以实现,可以对资源进行共享同步,和synchronized一样是支持可重入的。其内部有三个类Sync、FairSync、NonfairSync

 class ReentrantLock{
     abstract static class Sync extends AbstractQueuedSynchronizer {
            abstract void lock();
     }
     //非公平锁
     static final class NonfairSync extends Sync {
          void lock(){
              
          }
         
     }
     //公平锁
     static final class FairSync extends Sync {
         void lock(){
              
          }
     }
 }
7.1获取锁的时机(acquire()调用时机)

1.线程被唤醒
2.调用lock方法
3.线程在同步队列中等待,直到被前驱节点唤醒或者轮到自己尝试获取锁。

7.2公平锁和非公平锁

线程进队列时,如果是公平锁,则会默默等待前驱结点被唤醒或轮到自己尝试获取锁。而在非公平锁中,线程进来会调用方法比较当前锁的状态是否为0,如果是为其设置状态为1

ReentrantLock中,acquire方法的作用是尝试获取锁。如果获取锁成功,则当前线程获得锁并继续执行;如果获取锁失败,则当前线程会被加入到同步队列中等待。

8.JUC常用类

8.1ConcurrentHashMap

        该类相比HashMap的优点在于,相对于HashMap而言是线程安全的;相对于HashTable是高效的,原因在于,他加synchronized锁方式是分段锁(JDK5-7之前),在JDK8以后是在哈希表中每个位置的头一个元素上加锁,这样如果多个线程如果操作不同的位置,那么相互不影响,只有多个线程操作同一个位置时,才会等待,如果位置上没有任何元素,那么采用cas机制插入数据到对应的位置。
        ConcurrentHashMap中的元素键和键值都不可以为null,这是为了防止产生歧义,如

          map.put("b","b")
          System.out.println(map1.get("a"));//null  值是null  还是键不存在返回null
          map.put("a",null)
8.2CopyOnWriteArrayList

        CopyOnWriteArrayList 是对写方法加了Reentrantlock锁,他的效率比Vector高,一是因为读取数据没有了锁,多个线程就可同时对数据进行读操作。而写操作不会直接在原数组上修改,是先复制一个数组,然后对复制出来的数组进行修改,最后将底层数组切换为新的数组。
        因此CopyOnWriteArrayList写的效率较低,适合高并发读多写少的情况。但是因写方法的特殊性,CopyOnWriteArrayList可以在迭代过程中直接对集合的底层数组进行修改,而不需要使用迭代器的对象对数组修改。普通数组如果在迭代过程中对集合底层数组进行修改,java为了保证迭代操作的一致性和安全性,会禁止该操作,抛出ConcurrentModificationException异常。

9.线程池

        在高并发过程中,频繁地创建线程和销毁线程都会大大降低运行的效率,因此在jdk5引入了线程池,一般使用ThreadPoolExecutor来创建线程池。

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
9.1线程池中的参数

        corePollSize:记录核心线程池的线程最大数量
        maximumPollSize:记录线程池中的线程最大数量(包含核心线程池中的线程数)
        keepAliveTime:记录非核心线程池中的空闲线程的空闲生命,如果非核心线程池中的线程长时间未被调用,就会被销毁。
        unit:时间单位
        workQueue:等待队列,当核心线程池中的线程都在被使用时,如果再进来新的任务,就会存放在等待队列中,如果等待队列也满了,就会在非核心线程池中创建新的线程。
        threadFactory:线程工厂,负责创建新的线程。
        handler:拒绝策略,当核心线程池和非核心线程池中的线程都在使用中,且等待队列也已经满了,此时会执行拒绝策略。

9.2线程池工作流程

        当有任务进入线程池,如果核心线程池中有空闲的线程,就交给核心线程池中的线程执行。否则判断等待队列是否有空,如果有则放进等待队列,否则判断非核心线程池是否还有容量,如果有就使用线程工厂创建新的线程来执行该任务,否则执行拒绝策略。

9.3四种拒绝策略

AbortPolicy: 抛异常
CallerRunsPolicy: 将新来的线程交给提交任务的线程去执行
DiscardOldestPolicy: 丢弃等待时间最长的任务
DiscardPolicy: 丢弃最后的任务

9.4提交任务的方法

        执行任务除了可以使用execute方法和submit方法。它们的主要区别 是:execute没有返回值,而submit会有返回值。

9.5关闭线程池

shutdown(),执行该方法后,会停止接收新的任务,会将线程池中的所有任务执行完毕再彻底关闭线程池。
shutdownNow(),执行该方法后会立刻关闭线程池,正在执行的任务也会停止。

10.ThreadLocal

        ThreadLocal是本地线程变量,他会为每个线程设置一个本地变量,初始值由ThreadLocal构造器生成。本地线程变量只在当前线程中使用。

相关文章:

  • 数据传输对象 DTO
  • 【Kubernetes】Service 的类型有哪些?ClusterIP、NodePort 和 LoadBalancer 的区别?
  • Nginx 目录浏览功能显示的日期格式设置为数字
  • 表达式和语句的区别
  • 86.HarmonyOS NEXT 组件通信与状态共享:构建高效的组件协作机制
  • 206. 反转链表
  • 施磊老师c++(七)
  • 【人工智能基础2】Tramsformer架构、自然语言处理基础、计算机视觉总结
  • DeepSeek进阶应用(二):结合Kimi制作PPT(双AI协作教程)
  • ASP.NET Webform和ASP.NET MVC 后台开发 大概80%常用技术
  • 过滤空格(信息学奥赛一本通-2047)
  • 我的世界1.20.1forge模组进阶开发教程生物篇(1)——生成
  • 上位机数据可视化:使用QtCharts绘制波形图
  • STM32 - 在机器人领域,LL库相比HAL优势明显
  • 电磁兼容性|电子设备(EMC)测试与系统化整改
  • HarmonyOS NEXT个人开发经验总结
  • 爬虫获取 item_get_video 接口数据:小红书笔记视频详情的深度解析
  • 鸿蒙 @ohos.arkui.drawableDescriptor (DrawableDescriptor)
  • 为训练大模型而努力-分享2W多张卡通头像的图片
  • Symbian(塞班)操作系统
  • 网页和网站的联系/网络公司网页设计
  • 广州网站建设studstu/吴中seo页面优化推广
  • wordpress 模特模板/国内seo公司哪家最好
  • 有专业做网站的吗gre考/爱站网关键词
  • 苏州相城区做网站公司/精准营销系统价值
  • 凡科网代理登录/谷歌seo服务