java多线程

本文深入讲解了进程与线程的概念,线程的状态及其转换过程,介绍了实现多线程的三种方式,探讨了线程池的配置及使用方法,并讨论了多线程可能遇到的问题及解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.线程/进程

进程: 在操作系统中能够独立运行,并且作为资源分配的基本单位。它表示运行中的程序。系统运行一个程序就是一个进程从创建、运行到消亡的过程。

线程:是一个比进程更小的执行单位,能够完成进程中的一个功能,也被称为轻量级进程。一个进程在其执行的过程中可以产生多个线程。

2.线程的状态

1.新建状态:新创建一个线程对象(比如 new thread())

2.就绪状态:调用了线程对象的start()方法就会进入就绪状态,但不意味着立即执行,还需要等待cpu

分配时间

3.运行状态: 获得了cpu使用权,进入运行状态,执行线程体的代码块.

4.阻塞状态: 由于某种原因导致线程失去了cpu的使用权,暂时停止运行,比如wait(),sleep();

5.死亡状态:线程执行完毕或因为异常推出了run()方法,该线程的生命周期结束.

状态转化图:

3.多线程的实现方式

1.继承Thread 重写run()方法

/**
 * 线程对象直接继承Thread 重写run方法
 *
 * @author Administrator
 */
public class ThreadDemo extends Thread {

    @Override
    public void run() {
        // 线程任务
        System.out.println("我是继承Thread的线程");
    }

    /**
     * 测试使用线程
     * @param args
     */
    public static void main(String[] args) {
        ThreadDemo thread = new ThreadDemo();
        thread.start();
    }
}

2.实现runnable 重写run()方法

/**
 * 实现runnable接口 重写run方法
 */
class RunnableDemo implements Runnable {

    @Override
    public void run() {
        // 线程任务
        System.out.println("我是实现runnable接口的线程");
    }
    
    
    /**
     * 测试使用线程
     * @param args
     */
    public static void main(String[] args) {
        RunnableDemo runnableDemo = new RunnableDemo();
        Thread thread2 = new Thread(runnableDemo, "runnable");
        thread2.start();
    }
}

3.实现callable重写call()方法

/**
 * 实现callable接口 从写call方法 可以有返回值
 */
class CallableDemo implements Callable<String> {

    @Override
    public String call(){
        // 线程任务
        return "我是实现callable接口的线程";
    }

     /**
     * 测试使用线程
     * @param args
     */
    public static void main(String[] args) {
        CallableDemo callableDemo = new CallableDemo();
        FutureTask<String> ft = new FutureTask<>(callableDemo);
        Thread thread3 = new Thread(ft, "callable");
        thread3.start();
        String integer1 = ft.get();
        System.out.println(integer1);
    }
    
}

为什么我们不直接run()方法,而是要调用线程的start()方法

调用Thread的start方法可以启动线程,并使线程进入就绪状态,而run方法只是线程对象的普通方法,还是在主线程上执行 

4.线程池的配置

1.spring线程池  ThreadPoolTaskExecutor

/**
 * springboot 线程池配置
 *
 * @author Administrator
 */
@Configuration
public class ThreadPoolTaskExecutorConfig {

    @Bean
    public TaskExecutor taskExecutor() {
        // spring 线程池配置
        // 核心配置
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        // 核心线程数
        taskExecutor.setCorePoolSize(3);
        // 最大线程数
        taskExecutor.setMaxPoolSize(10);
        // 设置队列大小
        taskExecutor.setQueueCapacity(Integer.MAX_VALUE);
        // 非核心线程的最大空闲时间(超过这个时间的空闲的非核心线程将被销毁) 单位s 秒
        taskExecutor.setKeepAliveSeconds(100);
        // 配置拒绝策略
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        // 初始化线程池
        taskExecutor.initialize();
        return taskExecutor;
    }
}

2.jdk 自带的线程池  ThreadPoolExecutor

/**
 * juc 线程池配置(jdk自带)
 *
 * @author Administrator
 */
@Configuration
public class ThreadPoolExecutorConfig {
    @Bean
    public ExecutorService taskExecutor() {

        // juc线程池配置(JDK)
        // int corePoolSize,                    核心线程
        // int maximumPoolSize,                 最大线程
        // long keepAliveTime,                  非核心线程最大等待时间
        // TimeUnit unit,                       参数的时间单位
        // BlockingQueue<Runnable> workQueue,   任务队列
        // ThreadFactory threadFactory,         创建线程的工厂类
        // RejectedExecutionHandler handler     拒绝策略
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                3,
                10,
                1000L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue(10),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        return executor;
    }
}

线程池的执行流程:

 任务被提交到线程池,先判断当前线程是否小于corePoolSize(核心线程数),如果小于,则创建新的线程来执行提交的任务. 否则将任务放入workQueue队列,如果队列满,则判断当前线程数是否小于最大线程数MaximumPoolSize,如果小于,则创建线程执行任务,否则就会根据拒绝策略,处理超出容量的任务.

使用线程池的好处:

提高线程的利用率

提高程序的响应速度

便于统一管理

可以控制最大并发数

5.多线程会产生的问题以及如何避免

多线程主要会带来以下两个问题:

线程安全问题

一个线程从开始到结束,其中的某一个时间,这个线程操作的数据被别的线程修改,对于当前线程而言,数据可能是错误的,这就是发生了线程安全问题.

发生线程安全问题的原因:

1.存在多线程.

2.存在共享资源,且多线程操作该共享资源.

3.对共享资源有非原子性操作.

如何避免:

1.尽量不是用共享变量,将不惜要的共享变量换成局部变量.

2.加锁,使用synchronized关键字或者lock锁操作

3.使用ThreadLocal为每一个线程创建一个变量副本,各个线程之间独立操作,互不影响.

性能问题

 产生的原因:

1.线程的生命周期开销是非常大的,一个线程的创建到销毁会占用大量的内存,

2.cpu在给每个线程分配时间片,需要不断的切换线程执行,这个切换的过程也比较耗时.

3.线程创建的数量不合理,也会导致性能问题,如果存在过多的线程闲置,闲置的内存会占用大量的内存

解决思路:

使用线程池来创建和管理线程

6.锁

synchronized 隐式锁:java中的一个关键字,它可以修饰实例方法,静态方法,以及代码块

修饰普通的同步方法: 锁是当前的实例对象

修饰静态的同步方法: 锁的是当前类的Class对象

修饰代码块:锁的是synchronized括号里面配置的对象

关于锁的释放: 得到锁的线程执行完同步代码块,或者发生了异常 jvm自动释放

Lock 显示锁:是jdk提供的一个接口 它只能修饰代码块

Lock需要手动的加锁与释放锁 

区别

 公平锁:遵循FIFO原则 (先入先出原则)

 非公平锁:锁一旦释放,大家一起竞争.先来的线程不一定能拿到锁

volatile关键字 只能修饰变量,是线程同步的轻量级实现,被volatile修饰的变量,每次被使用都要到主存中进行读取,

7.多线程的实际应用

 1.配置线程池 上面已说明配置方案

 2.在需要使用多线程的service中注入.  


@Slf4j
@Service
public class WsSupplyServiceImpl  implements IWsSupplyService {

    
    @Resource
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    
    public void test() {
        // 开启线程执行任务
        threadPoolTaskExecutor.execute(()->{
            // 线程任务 无返回值
            
        });
        Future<?> submit = threadPoolTaskExecutor.submit(() -> {
            // 线程任务 有返回值
            return "";
        });
        try {
            // 获取返回值
            Object o = submit.get();
        }catch (Exception e) {
            e.printStackTrace();
        }

        // 等待全部有返回的线程任务执行完成 可以传多个Future
        CompletableFuture.allOf(submit);

    }
}

在springboot中的应用

1.在线程池配置类添加注解@EnableAsync

2.在需要异步执行的方法上添加注解@Async("配置的bean的名称")

 

死锁: 线程一直等待某个资源被释放,这种一直等待的状态就是死锁.

如何避免死锁:

1.避免一个线程同时获取多个锁

2.避免一个线程在锁内同时占有多个资源

3.尝试使用定时锁.使用lock.tryLock(timeOut)

 sleep()与wait()的区别

相同点: 都可以暂停线程的执行,进入等待状态

不同点: sleep不会释放锁,wait会释放锁

             sleep属于Thread类的静态方法,wait是Object的实例方法 作用域对象本身

             执行sleep()后 可以调用Interrupt()方法唤醒休眠中的线程,而执行wait()需要通过notify()或者notifyall()方法唤醒.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值