java并发编程:多线程基础

本文详细介绍了Java并发编程的三大要素:原子性、可见性和有序性,并深入讲解了内存模型。接着,讨论了线程的创建方式,重点分析了volatile关键字的作用和使用场景,以及synchronized的同步机制。文章还通过实例演示了如何使用线程池、阻塞队列以及四种线程状态。最后,提到了线程中断机制和线程间的协作方法,如wait/notify和Condition。

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

并发编程三要素

  • 原子性:和数据库事务特性的原子性类似,一组操作要么全部失败,要么全部成功
  • 可见性:某个线程对变量的修改,对其它线程是可见的
  • 有序性:java编译器为了提升效率,会改变代码顺序,这违背了有序性,通过happen-before先行发送原则可以保证有序性

并发编程内存模型

在这里插入图片描述

多线程

多线程的目的是为了提高cpu的利用率,我们在学设计模式的时候单例模式的完美方案是要考虑多线程的,在学多线程我们需要了解操作系统讲的进程和线程、死锁,了解jvm的知识

创建线程的三种方式

  • 继承Thread,一般不推荐,因为java是单继承的
  • 实现Runnable接口,重写run方法
  • 实现Callable接口,Callable 可以有返回值,返回值通过 FutureTask 进行封装,

volatile

参考: https://jenkov.com/tutorials/java-concurrency/volatile.html

  • 不满足原子性
  • 满足可见性
  • 可以通过内存屏障防止指令重排保证有序性

场景

参考: https://blog.csdn.net/xichenguan/article/details/119425408

  • 当一个变量依赖其他变量或变量的新值依赖旧值时,不能用volatile

  • 适用场合:多个线程读,一个线程写的场合

  • 使用场景:通常被 作为标识完成、中断、状态的标记,值变化应具有原子性

这里简单说一说状态标志:比如有个boolean变量,如果多个线程都要访问它,作为判断标志,这时就可以使用volatile修饰该变量,可以保证多线程对变量的可见性

synchronized

Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock

思考:volatile怎么保证可见性、volatile怎么保证有序性

参考博客:https://blog.csdn.net/weixin_37990128/article/details/110955558

四个窗口卖500张票:加sychronized

package interview.sort.test;

public class SellTicket implements Runnable {
    int ticket = 500;

    public void sellTicket() throws InterruptedException {
        synchronized (this) {
            while (ticket > 0) {
                Thread.sleep(10);
                System.out.println(Thread.currentThread().getName() + "卖出第" + ticket-- + "张票");
            }
        }
    }

    @Override
    public void run() {
        try {
            sellTicket();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class Test {
    public static void main(String[] args) {
        SellTicket sellTicket = new SellTicket();
        Thread t1 = new Thread(sellTicket);
        Thread t2 = new Thread(sellTicket);
        Thread t3 = new Thread(sellTicket);
        Thread t4 = new Thread(sellTicket);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t4.setName("窗口4");

        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }
}

四个窗口卖500张票:加可重入锁ReentrantLock

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class SellTickets implements Runnable {
    int ticket = 500;

    private Lock lock = new ReentrantLock();

    public void sellTicket() throws InterruptedException {
        lock.lock();
        while (ticket > 0) {
            Thread.sleep(10);
            System.out.println(Thread.currentThread().getName() + "卖出第" + ticket-- + "张票");
        }
        lock.unlock();
    }

    @Override
    public void run() {
        try {
            sellTicket();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class Test {
    public static void main(String[] args) {
        SellTickets sellTicket = new SellTickets();
        Thread t1 = new Thread(sellTicket);
        Thread t2 = new Thread(sellTicket);
        Thread t3 = new Thread(sellTicket);
        Thread t4 = new Thread(sellTicket);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t4.setName("窗口4");

        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }
}

四个窗口卖500张票:使用AtomicInteger实现用的是底层是CAS算法

import java.util.concurrent.atomic.AtomicInteger;

public class SellTicket implements Runnable {
    AtomicInteger ticket = new AtomicInteger(500);

    public void sellTicket() throws InterruptedException {
            while (ticket.get() > 0) {
                Thread.sleep(10);
                System.out.println(Thread.currentThread().getName() + "卖出第" + ticket.decrementAndGet() + "张票");
            }
    }

    @Override
    public void run() {
        try {
            sellTicket();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class Test {
    public static void main(String[] args) {
        SellTicket sellTicket = new SellTicket();
        Thread t1 = new Thread(sellTicket);
        Thread t2 = new Thread(sellTicket);
        Thread t3 = new Thread(sellTicket);
        Thread t4 = new Thread(sellTicket);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t4.setName("窗口4");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

区别

  • 实现:synchronized是jvm实现的,ReentrantLock是jdk实现的
  • 性能:sychronized做了优化,速度和ReentrantLock差不多,前者的锁,jvm会帮我们自动释放,后者需要手动释放锁
  • 公平:公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁,synchronized是公平锁,ReetrantLock是非公平锁
  • 中断:当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情,ReentrantLock 可中断,而 synchronized 不行
  • 绑定多对象:ReentrantLock可以绑定多个Condition对象

线程池

建立线程池好处:

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

常见线程池

  • CachedThreadPool:一个任务创建一个线程;
  • FixedThreadPool:所有任务只能使用固定大小的线程;
  • SingleThreadExecutor:相当于大小为 1 的 FixedThreadPool;
  • newScheduledThreadPool:创建一个可以执行延迟任务的线程池;
  • newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池
  • newWorkSteadingThreadPool: 创建一个抢占式执行的线程池(任务执行顺序不确定),此方法是 JDK 1.8 版本新增的

ExecutorService executorService1 = Executors.newScheduledThreadPool(12);

ThreadPoolExecutor是Executor的实现类,可以通过它的execute()方法创建线程,构造方法里面需要设置很多参数

在这里插入图片描述

ThreadPoolExcutor在这里插入图片描述

  • corePoolSize: 核心线程数
  • maximumPoolSize:最大线程数
  • keepAliveTime:空闲线程存活时间,当线程数量大于核心线程数小于最大线程数时,存在空闲的线程它们的存活时间就是这个,当存活的线程数小于等于核心线程数就失效了
  • unit:空闲线程的存活时间的单位
  • workQueue:工作队列,当线程数大于最大线程数时会将线程放到工作队列中,这里用的是阻塞队列BlockingQueue
  • threadFactory:线程工厂,用于创建工作线程
  • handler: 当工作队列达到上限的时候采取拒绝策略,常见的拒绝策略就是抛出异常

阻塞队列

  • ArrayBlockingQueue:先进先出,阻塞队列,插入元素时队列满了会一直等待。新元素插入到队列的尾部,队列获取操作则是从队列头部开始获得元素。

  • SynchronousQueue:数组结构的阻塞队列。队列满了以后,任何一次插入操作的元素都要等待相对的删除/读取操作。

  • LinkedBlockingQueue:先进先出,链表结构非阻塞队列,适合插入和删除较多的操作。

  • DelayedWorkQueue:延迟队列,延迟的时间还未到的获取的任务会返回空。

  • PriorityBlockingQueue:优先级队列,元素必须实现Comparable接口,优先级最高的始终在队列的头部,任务会优先执行。

线程中断

  • 在捕获异常中抛出中断异常
  • 将interrupted()方法当作标志状态,做判断,没中断在执行后面的语句
  • Executor可以通过shutdown()中断所有线程,如果要中断某一个线程就是用submit()方法会返回一个Future对象,该对象调用cancel(true)方法中断线程

基础线程机制

  • 线程睡眠sleep():单位是毫秒,是个本地方法,执行此方法会让线程睡眠

  • yield():是个本地方法,执行此方法代表当前线程已经执行差不都了,可以切换另外的线程;该方法只是向线程调度器提供一个建议,建议具有相同优先级的其它线程可以运行

  • 守护线程setDaemon():这时final修饰的方法,执行此方法是将线程设置为守护线程(后台线程),守护线程是指在后台执行的一种服务线程,这种线程不是必须的,当所有的非守护线程执行完毕后,程序停止,会杀死所有的守护线程

  • 线程优先级setPriority():final修饰的方法,为线程设置优先级,可以是1-10级,10级最高,目的是为了告诉线程调度器优先执行优先级高的线程

线程之间的协作

join(): final()修饰的方法,执行此方法当前线程将会挂起,执行目标线程后,在执行当前线程

wait()、notify()、notifyAll()

  • 调用wait()方法会使当前线程挂起,会释放锁,其它线程执行满足这个条件时其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程
  • final修饰,是本地方法,并且是Object里面的方法
  • 只能在同步方法或者同步控制块中使用

sleep()和wait()的区别

  • sleep()是Thread类的方法,wait()是Object的方法
  • 执行wait()时会释放锁,sleep()则不会释放锁
  • wait()只能在synchronized中使用

await()、signal()、signalAll()

  • juc包下Condition的方法,一个Lock可以创建多个Condition,synchronized块搭配wait()、notify()、notifyAll(),相当于仅有一个Condition,所有线程的调度通信都是由这个Condition完成的,不够灵活
  • await()对用wait(),signal()对用notify(),signalAll()对用notifyAll()

线程的状态

参考:https://www.cnblogs.com/IUbanana/p/7110297.html

  • 新建状态(New):新线程对象已经创建,还没有在其上调用start()方法

  • 就绪状态(Runnable):当前线程调用了start()方法,随时等待CPU调度执行

  • 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

  • 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

    • 等待阻塞 – 运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
    • 同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
    • 其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态
  • 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,当前线程的任务已处理完毕,释放CPU资源,运行结束的状态;

线程的状态这个人讲的不错:https://blog.csdn.net/weixin_44673534/article/details/121344594

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值