logo 范 · 拾光录
网址收集 关于作者 Github Gitee
杂文随笔5
Hexo博客:基础使用Hexo博客:Next主题Hexo博客:Next进阶使用Hexo博客:Next高级配置基于Node的WIKI管理
前端知识16
HTML常用知识CSS常用知识CSS美化checkbox复选框JavaScript常用知识JavaScript格式化时间戳JavaScript窗口宽高处理JavaScript黑夜主题切换实现方案JavaScript数字转大写简易图片查看器TypeScript基础知识Threejs基础三要素Threejs网格辅助和轨道控制器Threejs物体绘制Electron基础使用Nodejs基础知识animate.css页面动画
Vue框架19
Vite的使用及扩展Vue3父子组件Vue3使用Marked解析MarkdownMermaid图表生成库初始化页面加载动画Axios表单提交二维码解决方案NProgress加载进度条Vue3动态菜单实现Vue3使用ECharts图表Vue3处理Excel导入导出keep-alive页面缓存及setup问题Element:文件上传Element:结合Pinia实现动态菜单Element:图片上传组件Element:自定义统一弹窗组件Element:表格自定义指令控制按钮显示(鉴权)可视化大屏使用缩放适配分辨率
UniApp15
UniApp的基础使用封装网络请求工具及文件上传uni-app的开发记录微信小程序分享原生文件上传Pinia取消滚动条(兼容小程序)tabbar消息数量显示scroll-view上滑到底部加载数据状态栏高度动态设配数据共享与传递uview-plus导航栏实现背景融合Wot UIWot UI实现顶部背景图融合uni-app x
Java基础知识10
基础知识面向对象Lambda表达式常用API常用知识积累try-with-resource注解反射多线程经纬度距离计算
SpringBoot31
application配置Maven创建聚合项目全局异常处理锁机制项目启动初始化数据方式邮件功能集成原生定时任务异步集成阿里云OSS阿里OSS预签名上传基于hutool读excelJSR303WebSocketWebSocket版AI接口流式调用Smart-Doc接口文档生成器application配置信息加密雪花算法工具AOP实现请求参数脱敏思路JWT生成Token及工具类SpringBoot默认JSON与对象转换若依框架:安装使用若依框架:优化和调整文件上传若依框架:管理后台页面优化若依框架:后端接口代码优化SpringAISpringBoot实现AI接口流式调用服务启动时创建MySQL连接自建项目工程树形结构处理工具微信支付代码微信手机号登录
SpringMVC14
跨域处理拦截器RESTful风格伪前后端分离Jackson转换器调整Thymeleaf基于拦截器做权限校验AOP打印接口请求响应日志AOP打印接口请求响应耗时文件上传和回显POST请求加解密实现(AES)POST请求加解密实现(RSA+AES)参数动态校验实现方案真实IP和归属地
MyBatis8
MyBatis基本使用与配置Mapper使用相关MaBatis多数据源配置MyBatisPlus数据统计类处理方案MyBatisPlus条件查询正向工程的实现(H2)mybatis-plus-join
SpringCloud15
Netflix:微服务与搭建Netflix:服务的消费与提供Netflix:EurekaNetflix:ActuatorNetflix:RibbonNetflix:FeignNetflix:HystrixNetflix:ZuulAlibaba:简介与搭建Alibaba:Nacos注册中心Alibaba:RibbonAlibaba:OpenFeignAlibaba:Nacos配置中心Alibaba:GetewayAlibaba:Sentinel
MySQL6
MySQL基础知识MySQL多表查询与事务MySQL常用函数及解决方案MySQL视图MySQL索引安装MySQL
Redis7
Redis介绍和安装Redis配置文件Redis持久化Redis集群Redis语法基础Redis相关问题及解决方案SpringBoot集成Redis使用记录
MongoDB10
Linux安装MongoDBMongoDB基础语法MongoTemplate及SpringBoot配置MongoTemplate中Update操作MongoTemplate中聚合查询MongoTemplate日期归档示例项目使用相关知识归纳地理位置存储与距离查询MongoDB副本集与事务获取类名和属性名工具类
其他数据库1
H2数据库
Python编程6
Python基础知识Python语法yolo目标检测OpenCV的使用及树莓派平台condauv
工具集合13
IDEAMavenGradleGitNginx安装Nginx配置VSCodeJMeter压测DockerOllamaRustFSPicGoObs录制
Linux知识11
Linux常用命令Jar启动脚本VirtualBox安装CentOSVirtualBox安装Ubuntu树莓派安装及使用frp内网穿透ArchLinux:基础系统安装ArchLInux:图形化界面安装ArchLinux:常用软件ArchLinux:深度优化ArchLinux:Niri
创意设计2
Blender:入门知识UI设计基础知识
AI相关9
Claude CodeHermes AgentOpenAI基本使用OpenAI工具调用OpenAI记忆管理OpenAI推理执行OpenAI开发框架Langchainllama.cpp

普通方法与多线程方法区别

普通方法与多线程方法的执行顺序如下

一个进程中可以有多个线程,进程由系统分配

多线程的创建方式

继承Thread类

/**
 * 多线程创建
 */
public class ThreadTest1 extends Thread {

    // 重写
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("多线程..." + i);
        }
    }

    public static void main(String[] args) {
        
        ThreadTest1 threadTest1 = new ThreadTest1();
        // 启动线程
        threadTest1.start();

        for (int i = 0; i < 50; i++) {
            System.out.println("主方法..." + i);
        }

    }

}

注意:线程不一定立即执行,由CPU安排调度

Thread类常见方法:

Thread类也是实现了Runnable,因为继承具有局限性(只能单继承),所以推荐使用实现Runnable方式

实现Runnable接口

/**
 * 多线程
 */
public class ThreadTest2 implements Runnable {

    // 重写
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("多线程..." + i);
        }
    }

    public static void main(String[] args) {
        ThreadTest1 threadTest2 = new ThreadTest1();
        // 启动线程
        new Thread(threadTest2).start();
        for (int i = 0; i < 50; i++) {
            System.out.println("主方法..." + i);
        }
    }

}

并发问题

例如抢票问题

/**
 * 多线程问题
 */
public class Ticket implements Runnable {

    // 票数
    private int ticketNum = 10;

    @Override
    public void run() {
        while (ticketNum > 0) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "拿到第" + ticketNum-- + "张票");
        }
    }

    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(ticket, "张三").start();
        new Thread(ticket, "李四").start();
        new Thread(ticket, "王五").start();
    }
    
}

执行结果

王五拿到第10张票
张三拿到第9张票
李四拿到第10张票
张三拿到第8张票
李四拿到第7张票
王五拿到第6张票
张三拿到第3张票
王五拿到第5张票
李四拿到第4张票
王五拿到第2张票
李四拿到第1张票
张三拿到第0张票

数据出现紊乱,线程不安全

实现Callable接口

/**
 * 多线程创建
 */
public class ThreadTest3 implements Callable<Boolean> {
    @Override
    public Boolean call() throws Exception {
        // 操作内容
        System.out.println(Thread.currentThread().getName());
        return false;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadTest3 item1 = new ThreadTest3();
        ThreadTest3 item2 = new ThreadTest3();
        ThreadTest3 item3 = new ThreadTest3();

        // 创建执行服务
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        // 提交执行并获取结果
        Boolean result1 = executorService.submit(item1).get();
        Boolean result2 = executorService.submit(item2).get();
        Boolean result3 = executorService.submit(item3).get();

        // 关闭服务
        executorService.shutdown();
    }
}

三种实现方式的区别

方式 有点 缺点
继承Thread类 编程简单,可直接使用Thread中方法 可扩展性差,无法获得结果
实现Runnable接口 扩展性强 不能直接使用Thread类中方法,无法获得结果
实现Callable接口 扩展性强,可获得结果 不能直接使用Thread类中方法,编程复杂

线程状态

线程方法

方法 说明
setPriority(int newPriority) 更改线程的优先级
static void sleep(long millis) 线程休眠时间,毫秒
void join() 等待该线程终止
static void yield() 暂停当前正在执行的线程对象,并执行其他线程
void interrupt() 中断线程
boolean isAlive() 测试线程是否处于活动状态

线程停止

JDK提供的方法不建议使用了,推荐线程自己结束,示例

public class ThreadTest implements Runnable {

    // 线程是否结束标识
    private boolean flag = true;

    @Override
    public void run() {
        while (flag) {
            System.out.println("run...");
        }
    }

    // 对外提供结束方法
    public void stop() {
        this.flag = false;
    }

}

线程休眠

sheep()方法能指定当前线程阻塞多少毫秒,存在InterruptedException异常,当阻塞结束后会进入就绪状态,用于模拟网络延时,倒计时等,每个对象都有一个锁,sheep()不会释放锁

守护进程

// 默认是false表示用户进程,设置为true为守护进程
thread.setDaemon(true);

线程分为用户进程守护进程,虚拟机不用等待守护进程执行完毕,也就是说,当同一个方法中的用户线程结束后,即便守护进程是个死循环在下一轮监监控也会将守护进程结束

并发

同一个资源(对象、数据)被多个线程同时操作

线程同步

由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问的准确性,在访问时会加入锁机制(synchronized),当一个对象获得对象的排它锁,独占资源,其他线程必须等待,使用后再释放锁,存在问题:

同步方法

public synchronized void method() {
    // TODO
}

synchronized方法控制对象的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象锁才能执行,否则线程阻塞,方法一旦执行,就独占该锁,直到方法返回才释放锁,后面阻塞的线程才能获得锁,继续执行

缺陷:若将一个大的方法声明为synchronized将会影响效率

锁的对象就是变化的量,需要增删改的对象

同步代码块

synchronized (Obj) {
	// TODO
}

Obj称为同步监视器,可以是任何对象,是需要锁的变化的量,同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,即对象本身,或者class

CopyOnWriteArrayList

JUC下的一个集合,线程安全

Lock锁

从JDK5.0开始,Java提供了更强大的线程同步锁机制–显式定义同步锁对象来实现同步

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具

private final ReentrantLock lock = new ReentrantLock();

@Override
public void run () {
    try {
        // 加锁
        lock.lock();
    } finally {
        // 解锁
        lock.unlock();
    }
}

synchronized和lock

使用顺序:Lock > 同步代码块 > 同步方法

生产者和消费者

涉及到线程之间的通信问题,生产者生产出来后通知消费者消费,如果消费者消费发现没有就等待

管程法

核心在于需要一缓冲区,来负责商品的控制

// 消费者消费商品
public synchronized void pop() {
    // 没有货物
    if(countGoods == 0) {
        try {
            // 等待
            this.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    // 等待生产者通知开始消费
    countGoods--;
    // 消费完,通知生产者生成
    this.notifyAll();
}

// 生产者同理

信号灯法

同理

线程池

线程的频繁创建和销毁,使用了大量的资源,尤其在并发情况下,对性能影响很大

解决:提前创建好多个线程,放入线程池中,使用时直接获取,用完放回池中

优点:

线程池使用

JDK5.0提供了线程池相关API:ExecutorService和Executors

ExecutorService:真正的线程池接口

Executors:工具类、连接池的工厂类,用于创建并返回不同类型的线程池

public class ThreadTest2 {

    public static void main(String[] args) {
        // 创建线程池,参数为线程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);
        // 启动线程
        service.execute(new ThreadTest());
        service.execute(new ThreadTest());
        service.execute(new ThreadTest());
        // 关闭线程池
        service.shutdown();
    }

}

class ThreadTest implements Runnable {

    @Override
    public void run() {
        System.out.println("执行");
    }

}

Callable结合FutureTask获取结果

// 查询用户总数
Callable<Integer> callableUserCount = () -> userService.count();
FutureTask<Integer> taskUserCount = new FutureTask<>(callableUserCount);
Thread threadUserCount = new Thread(taskUserCount);

// 查询用户总数(直接写代码方式)
Callable<Long> callableAll = () -> mongoTemplate.count(new Query(), InvitationRegister.COLLECTION_NAME);
FutureTask<Long> taskAllUserNum = new FutureTask<>(callableAll);
Thread threadAllUser = new Thread(taskAllUserNum);

// 查询会员总数
Callable<Integer> callableVipCount = () -> userService.count(wrapperVip);
FutureTask<Integer> taskVipCount = new FutureTask<>(callableVipCount);
Thread threadVipCount = new Thread(taskVipCount);

// 统一启动线程
threadUserCount.start();
threadVipCount.start();

// 获取结果
Integer userCount = taskUserCount.get(10, TimeUnit.SECONDS);
Integer vipCount = taskVipCount.get(10, TimeUnit.SECONDS);
普通方法与多线程方法区别
多线程的创建方式
继承Thread类
实现Runnable接口
并发问题
实现Callable接口
三种实现方式的区别
线程状态
线程方法
线程停止
线程休眠
守护进程
并发
线程同步
同步方法
同步代码块
CopyOnWriteArrayList
Lock锁
synchronized和lock
生产者和消费者
管程法
信号灯法
线程池
线程池使用
Callable结合FutureTask获取结果