线程安全

本文深入探讨Java中的线程安全概念,包括线程的创建、状态及应用场景,详细解析线程池技术、锁机制、原子类以及线程安全容器的实现,旨在优化多线程环境下的程序执行效率。

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

     线程安全包括多线程环境下,各个线程执行时,临界资源的安全性;也包括,如何让确定高并发下,线程数量的创建,在有限的资源下如何让达到计算机最优效率。

  • 线程的基本概念

     1. 线程的定义

         在计算机中,线程是这样定义的:线程是计算机运行的基本的单位,一个进程拥有多个线程,多个线程共享同一个进程拥有的系统资源。而在Java中,我们可以将线程定义为:计算机指令的集合,可以用来执行一个任务。

     2. Java中线程的创建

2.1 实现Runnable接口

2.2 new Thread对象

2.3 线程池中实现Callable接口,重写call方法,提交一个任务到线程池中。

Java创建的线程是依靠操作系统的线程,Java中生成线程需要耗费的资源为:

      1、生成一个Java对象,会耗费堆内存。

      2、操作系统生成一个线程,会耗费操作系统内存。

     3. 线程的状态

3.1 NEW状态

      Java中new一个线程(还没调用start方法)时,该线程处于NEW状态。

      线程创建时(如Java中Thread调用start方法之后)处于就绪状态,等待操作系统的调度。

3.2 RUNNABLE状态

      线程处于CPU正在执行的状态下。

3.3 BLOCKED状态

      线程处于阻塞状态,线程等待进入一个同步代码块。

3.4 WAITING状态

      线程等待其他线程通知执行,比如调用Object的wait方法,等待其他线程调用notify或notifyAll方法;调用线程的join方法,等待其他线程执行完成等。

3.5 TIMED_WAITING状态

      线程处于多长时间的等待状态,调用这些方法可以进入这种状态:Thread.sleep\TimeUnit.sleep、Object.wait、Thread.join、LockSupport.parkNanos、LockSupport.parkUntil

3.6 TERMINATED状态

      线程执行结束,处于终止状态。

    4. 守护(daemon)线程

     守护线程是系统后台守护其他线程正常执行、销毁的线程,守护线程在其被守护所有线程执行完成后也会自动销毁。

  • 多线程应用场景分析

使用多线程,主要是解决现代计算机,CPU与内存等其他电脑硬件运行速度不匹配的问题,现代计算机CPU的运行速度非常块。在高并发场景下,如何利用多线程提高系统的并发性;创建线程过多,系统调度会变得困难,甚至于操作系统内存会用完,造成系统卡死。创建线程过少,CPU的利用率不高。如何再两者之间平衡创建线程的多少,对于优化程序执行效率起着决定性的作用。为此,下面分析两种场景:

1、系统任务多是执行IO等任务,这样CPU就长时间处于正在等待;这时,我们可以适当多的创建线程,使CPU的执行效率达到最高,一般我们可以创建CPU核心数的20-30倍的线程数。

2、系统执行任务是计算密集型的,每个线程在CPU的执行时间都特别长,这时,再创建过多的线程对于系统的优化并无·意义,反而会造成线程执行等待时间长,响应慢等问题。此时,一般我们可以创建CPU核心数的2-3倍线程数量即可。

另外创建线程也许需要CPU执行的,也是需要时间执行的;如果再某种场景下,线程的创建+线程的销毁时间>线程的执行时间,那么这时,创建线程就完全无意义了,直接在主线程下执行反而更有效率。

一个Java程序最少有以下五个线程:

JVM线程
线程名作用
Attach ListenerAttach Listener线程是负责接收到外部的命令,而对该命令进行执行的并且吧结果返回给发送者。
Signal Dispatchersignal dispather线程去进行分发到各个不同的模块处理命令,并且返回处理结果。
Finalizer调用对象的finalize方法的线程,JVM中防止GC时多个线程同时调用对象的finalize方法,造成不安全的问题
Reference Handler主要用于处理引用对象本身的垃圾回收问题
Main应用程序main方法线程
  • 线程池

                通过前面的分析,我们知道,系统创建线程是需要耗费资源(内存和CPU)的,计算机的资源是有限的,为了能最大程度提高系统效率,我们需要实现线程的可重用,避免系统重复的创建和销毁线程,这时,我们就需要用到线程池技术。

                JDK中线程池都是实现了Executor接口,该接口只有一个方法,即execute线程方法;ExecutorService接口继承了Executor接口,将线程池变为服务,声明了关闭线程池提交任务等方法:

                

       1、 ThreadPoolExecutor主要是通用线程池的实现。此处不做详述,可以参考下文中关于线程池的部分:

https://github.com/bennet-xiao/springboot-duubo-demo/blob/master/notes/Java.md

       2、 ForkJoinPool 主要基于JDK中的ForkJoin框架实现,将一个复杂任务查分成多个任务,分别执行后汇总,主要用于计算型特别密集的任务

      4、ScheduledThreadPoolExecutor  线程池的定时任务的默认实现,主要提供以下几个方法:

          Executors为JDK中提供的创建线程池工具类,不建议使用,此处不做详述。

         Java中对锁的实现主要提供了以下几种:

            1、synchronized关键字

                   1.1 修饰普通方法

                         限制该对象的一个方法在同一时间内只能由一个线程进入执行。

                   1.2 修饰代码块

                         限制该对象的这块代码在同一时间内只能由一个线程进入执行。

                   1.3 修饰静态方法

                        限制该类的所有对象在同一时间内只能由一个线程进入执行。

           2、对象显式的调用lock和unlock方法

                   主要用于代码块中显式的调用控制

           3、并发工具包中的ReentrantLock类实现的锁机制

                   其中提供了两种锁,FairSync和NonfairSync,两者都是基于同步队列AbstractQueuedSynchronizer的不同实现,其中FairSync,当一个线程尝试进入该同步代码块时,会先看队列中是否存在等待的线程,存在则排在队列最后;NonfairSync直接尝试获取锁,获取不到排到队列最后;默认构造函数为NonfairSync。

  • 原子类

         java.util.concurrent.atomic包下存在JDK实现的几种原子类,原子类是基于乐观锁实现的线程安全的原子操作类,其内部实现采用CAS(比较交换)原理,保证每次仅有一个线程能对值进行修改。

        原子类中可修改的变量多采用volatile进行修饰,保证执行过程中,该值的修改对所有线程都是可见的。

        主要包括以下几个类:

    

  • 线程安全容器

          java.util.concurrent包下提供以下几种线程安全的容器的实现:

这里就不对所有容器展开做一一讲解了,其中还包括了非常重要的类,比如CountDownLatch等,可以实现多线程下控制线程执行的顺序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值