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

并发和多线程

一、简述

线程和进程:
‌线程‌:线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。线程共享进程所拥有的全部资源。
‌进程‌:进程是程序在执行过程中分配和管理资源的基本单位,每个进程都拥有独立的地址空间和系统资源

并发与并行:
‌并发‌:在同一时间间隔内发生两个或多个事件。在Java中,并发通常是通过多线程来实现的
并行‌:在同一时刻内发生两个或多个事件。并行需要多个CPU核心或处理器来真正同时执行多个任务。

二、多线程实现方式

2.1 实现Runnable接口 

public class ThreadImpl implements Runnable{
    //实现run方法
    @Override
    public void run() {
        System.out.println("线程1");
    }

    public static void main(String[] args) {
        //创建ThreadImpl的实例
        Runnable runnable = new ThreadImpl();
        //将实例交给线程对象(thread)处理,并开启线程
        new Thread(runnable).start();

        //也可以直接调用匿名内部类实现
        Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("线程1");
            }
        }; //等价于  Runnable runnable2 = () -> {System.out.println("线程1")};
        new Thread(runnable1).start();

        //还可以使用lambda表达式
        new Thread(()-> {
            System.out.println("线程1");
        }).start();
    }
}

2.2 继承Thread类

public class ThreadImpl extends Thread{
    //重写run方法
    @Override
    public void run() {
        System.out.println("线程1");
    }

    public static void main(String[] args) {
        //创建ThreadImpl的实例
        ThreadImpl thread = new ThreadImpl();
        //开启线程
        thread.start();
    }
}

2.3 实现Callable接口

Callable 接口与 Runnable 接口类似,但 Callable 接口可以返回结果,并且可以抛出异常

public class ThreadImpl implements Callable {
    //实现call方法
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int j = 0; j < 10 ; j++) {
            sum += 1;
        }
        return "计算的结果为" + sum;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建实例
        ThreadImpl threadCallable = new ThreadImpl();
        //封装
        //FutureTask实现了RunnableFuture接口,而RunnableFuture接口同时继承了Runnable和Future接口
        FutureTask<String> futureTask = new FutureTask<>(threadCallable);
        //启动线程
        new Thread(futureTask).start();

        //获取线程执行结果,这个方法会阻塞当前线程,直到结果可用或者抛出异常。
        String string = futureTask.get();
        System.out.println(string);
    }
}

三、线程安全问题

3.1 使用synchronized加锁

它能够确保在同一时间只有一个线程可以执行某个方法或代码块,从而避免多线程同时访问共享资源时可能产生的数据不一致或竞争条件问题

public class ThreadSafeImpl {
    private int count = 0;

    // 使用synchronized将访问共享资源的核心代码块加锁
    // 推荐使用this作为实例方法的锁对象;如果使用的是静态方法,推荐使用类名.class作为锁对象
    public void threadSafe() {
        synchronized (this) {
            count++;
        }
    }

    // 使用synchronized修饰实例方法,确保线程安全
    public synchronized void increment() {
        count++;
    }
}

3.2 使用 ReentrantLock锁

提供了比synchronized更丰富的功能。支持可中断的锁获取尝试、可定时的锁获取尝试、以及公平锁

public class ThreadSafeImpl {
    private final Lock lock = new ReentrantLock();
    private int count = 0;

    public void threadSafe2(){
        // 加锁
        lock.lock();
        try{
            count++;
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 解锁
            lock.unlock();
        }
    }
}

3.3 使用并发合集ConcurrentHashMap 

ConcurrentHashMap是线程安全的,允许多个线程同时进行读写操作而不会导致数据不一致。
支持高效的并发更新操作,如put、get和remove等。

在jdk1.8之前,使用分段锁机制实现线程安全,将哈希表分成多个段(segment),每个段维护一个独立的哈希表和锁

在jdk1.8后,采用数组、单链表、红黑树的存储结构,当链表长度超过一定阈值时(默认为8),链表会转换为红黑树以提高查找效率,查询的时间复杂度从 O(n) 降低到 O(logN);
使用CAS操作和synchronized关键字来实现更细粒度的锁控制。CAS是原子操作,可以在不使用锁的情况下更新共享变量

ConcurrentHashMap的扩容触发条件:
数组中元素个数大于数组长度✖负载因子(默认0.75)
需要在短时间内插入大量元素时,使用putAll方法可能会触发扩容
链表长度过长,链表长度大于8(默认阈值)时

ConcurrentHashMap扩容流程简述:
1、计算扩容标识戳
ConcurrentHashMap允许协助扩容,这个是用来协调多个线程的扩容操作
2、初始化新数组,通常为原数组的两倍
3、设置transferIndex变量‌
transferIndex变量用于表示已经分配给扩容线程的table数组索引位置。在扩容开始前,通常被设置为原数组的长度
4、计算扩容区间
每个线程会根据transferIndex和步长(stride,通常最小值为16)来计算自己需要迁移的数组区间
5、设置ForwardingNode节点
迁移完毕后,会将旧数组中的对应位置设置为ForwardingNode节点,以告知访问此桶的其他线程该节点已经迁移完毕。
6、检查并结束扩容
最后一个结束扩容的线程会检查整个数组的数据是否都已经迁移到新数组中,并更新相关状态变量以结束扩容操作

public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();

        Runnable runnable = () -> {
            for (int i = 0; i < 10; i++) {
                concurrentHashMap.put("key" + i + " value",  i);
            }
        };

        //创建线程
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.start();
        thread2.start();

        //等待线程执行完
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        concurrentHashMap.forEach((key, value) -> {
            System.out.println(key + value);
        });

相关文章:

  • 宇树机器人G1 SDK实战和交付
  • Linux下centos系统中使用docker容器中的ollama下载deepseek速度太慢解决办法
  • python中with语句和os模块讲解
  • Java 语法新特性(Records、Pattern Matching、Sealed Classes)深度解析(11/17/21)✨
  • 深入理解 Spring Bean 生命周期的执行流程
  • 数仓搭建(hive):DWS层(服务数据层)
  • 二级指针略解【C语言】
  • idea升级安装新版本无法启动
  • 【学习笔记】Cadence电子设计全流程(一)Cadence 生态及相关概念
  • 【大语言模型_3】ollama本地加载deepseek模型后回答混乱问题解决
  • 一文读懂 KYC:金融、IT 领域的关键应用与实践
  • 算法学习笔记之递推求解
  • (LLaMa Factory)大模型训练方法--监督微调(Qwen2-0.5B)
  • 利用多线程加速ESMC-6B模型API调用以及403Forbidden问题的解决
  • Redis数据结构总结-整数集合
  • 创建虚拟机遇到的问题
  • Mybatis MyBatis框架的缓存 一级缓存
  • Fino1: 关于推理增强型大型语言模型在金融领域的可迁移性
  • stable diffusion 人物高级提示词(四)朝向、画面范围、远近、焦距、机位、拍摄角度
  • 手写简易RPC(实践版)
  • 韩国代总统、国务总理韩德洙宣布辞职,将择期宣布参选总统
  • 上海科创再出发:“造星”的城和“摘星”的人
  • 匈牙利国会通过退出国际刑事法院的决定
  • 湖南华容县通报“大垱湖水质受污染”,爆料者:现场已在灌清水
  • 历史新高!上海机场一季度营收增至31.72亿元,净利润增34%
  • 伊朗港口爆炸死亡人数升至70人