Java并发编程进阶——多线程的安全与同步

多线程的安全与同步

多线程的操作原则

多线程 AVO 原则

A:即 Atomic,原子性操作原则。对基本数据类型变量的读和写是保证原子性的,要么都成功,要么都失败,这些操作不可中断。
V:即 volatile,可见性原则。使用 volatile 关键字,保证了变量的可见性,到主存拿数据,不是到缓存里拿。
O:即 order, 就是有序性。代码的执行顺序,在代码编译前的和代码编译后的执行顺序不变。

单 CPU 时代的多线程

概念:单核 CPU 上,同一时刻只能有一条线程运行,单核 CPU 上运行的单线程程序和多线程程序,从运行效率上看没有差别。换而言之,单 CPU 时代,没有真正的多线程并发效果,从这一点来看,多线程与 CPU 硬件的升级息息相关。

在单 CPU 时代,多任务是共享一个 CPU 的,当一个任务占用 CPU 运行时,其他任务就会被挂起,当占用 CPU 的任务时间片用完后,会把 CPU 让给其他任务来使用,所以在单 CPU 时代多线程编程是没有太大意义的,并且线程间频繁的上下文切换还会带来额外开销。
在这里插入图片描述

多 CPU 时代的多线程

如下图所示为双 CPU 配置,线程 A 和线程 B 各自在自己的 CPU 上执行任务,实现了真正的并行运行。
在这里插入图片描述

在多线程编程实践中,线程的个数往往多于 CPU 的个数,所以一般都称多线程并发编程而不是多线程并行编程。

为什么要进行多线程并发

意义:多核 CPU 时代的到来打破了单核 CPU 对多线程效能的限制。 多个 CPU 意味着每个线程可以使用自己的 CPU 运行,这减少了线程上下文切换的开销。

而随着对应用系统性能和吞吐量要求的提高,出现了处理海量数据和请求的要求,这些都对高并发编程有着迫切的需求。

线程安全问题

谈到线程安全问题,我们先说说什么是共享资源。

共享资源:所谓共享资源,就是说该资源被多个线程所持有或者说多个线程都可以去访问该资源。线程安全问题是指当多个线程同时读写一个共享资源并且没有任何同步措施时,导致出现脏数据或者其他不可预见的结果和问题。

对于线程安全问题,在进行实际的开发操作过程中,我们要分析一下几点内容,确保多线程环境下的线程安全问题。

  • 确定是否是多线程环境:多线程环境下操作共享变量需要考虑线程的安全性;
  • 确定是否有增删改操作:多线程环境下,如果对共享数据有增加,删除或者修改的操作,需要谨慎。为了保证线程的同步性,必须对该共享数据进行加锁操作,保证多线程环境下,所有的线程能够获取到正确的数据。如生产者与消费者模型,售票模型;
  • 多线程下的读操作:如果是只读操作,对共享数据不需要进行锁操作,因为数据本身未发生增删改操作,不会影响获取数据的准确性。

共享变量内存可见性问题

共享变量:非线程私有的变量,共享变量存放于主内存中,所有的线程都有权限对变量进行增删改查操作。

内存可见性:由于数据是存放于内存中的,内存可见性意味着数据是公开的,所有线程都可对可见性的数据进行增删改查操作。

Java 内存模型规定,将所有的变量都存放在主内存(共享内存)中,当线程使用变量时,会把主内存里面的变量复制到自己的工作空间或者叫作工作内存,也就是我们所说的线程私有内存,线程读写变量时操作的是自己工作内存中的变量。

当一个线程操作共享变量时,它首先从主内存复制共享变量到自己的工作内存,然后对工作内存里的变量进行处理,处理完后将变量值更新到主内存。

线程的状态详解

操作系统线程的生命周期

定义:当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过新建 (New)、就绪(Runnable)、运行(Running)、阻塞 (Blocked),和死亡 (Dead) 5 种状态。

从线程的新建 (New) 到死亡 (Dead),就是线程的整个生命周期。

下面我们分别对 5 种不同的状态进行概念解析。

新建 (New):操作系统在进程中新建一条线程,此时线程是初始化状态。

就绪 (Runnable):就绪状态,可以理解为随时待命状态,一切已准备就绪,随时等待运行命令。

运行 (Running):CPU 进行核心调度,对已就绪状态的线程进行任务分配,接到调度命令,进入线程运行状态。

阻塞 (Blocked):线程锁导致的线程阻塞状态。共享内存区域的共享文件,当有两个或两个以上的线程进行非读操作时,只允许一个线程进行操作,其他线程在第一个线程未释放锁之前不可进入操作,此时进入的一个线程是运行状态,其他线程为阻塞状态。

死亡 (Dead):线程工作结束,被操作系统回收。

Java 的线程的生命周期及状态

在这里插入图片描述

定义: 在 Java 线程的生命周期中,它要经过新建(New),运行(Running),阻塞(Blocked),等待(Waiting),超时等待(Timed_Waiting)和终止状态(Terminal)6 种状态。

从线程的新建(New)到终止状态(Terminal),就是线程的整个生命周期。

我们来看下 Java 线程的 6 种状态的概念。

新建 (New):实现 Runnable 接口或者继承 Thead 类可以得到一个线程类,new 一个实例出来,线程就进入了初始状态。

运行 (Running):线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一方式。

阻塞 (Blocked):阻塞状态是线程在进入 synchronized 关键字修饰的方法或者代码块时,由于其他线程正在执行,不能够进入方法或者代码块而被阻塞的一种状态。

等待 (Waiting):执行 wait () 方法后线程进入等待状态,如果没有显示的 notify () 方法或者 notifyAll () 方法唤醒,该线程会一直处于等待状态。

超时等待 (Timed_Waiting):执行 sleep(Long time)方法后,线程进入超时等待状态,时间一到,自动唤醒线程。

终止状态 (Terminal):当线程的 run () 方法完成时,或者主线程的 main () 方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。

新建(New)状态详解

实例

public class ThreadTest implements Runnable{
   
    @Override
    public void run() {
   
        System.out.println("线程:"+Thread.currentThread()+" 正在执行...");
    }
    public static void main(String[] args) throws InterruptedException {
   
        Thread t1 = new Thread(new ThreadTest()); //线程 创建(NEW)状态
    }
}

这里仅仅对线程进行了创建,没有执行其他方法。 此时线程的状态就是新建 (New) 状态。

Tips:新建(New)状态的线程,是没有执行 start () 方法的线程。

运行(Running)状态详解

定义: 线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一方式。

public class ThreadTest implements Runnable{
   
    .......
    public static void main(String[] args) throws InterruptedException {
   
        Thread t1 = new Thread(new ThreadTest()); //线程 创建(NEW)状态
        t1. start(); //线程进入 运行(Running)状态
    }
}

当线程调用 start () 方法后,线程才进入了运行(Running)状态。

阻塞(Blocked)状态详解

定义: 阻塞状态是线程阻塞在进入 synchronized 关键字修饰的方法或者代码块时的状态。
我们先来分析如下代码。

实例

public class DemoTest implements Runnable{
   
    @Override
    public void run() {
   
        testBolockStatus();
    }
    public static void main(String[] args) throws InterruptedException {
   
        Thread t1 = new Thread(new DemoTest()); //线程 t1创建(NEW)状态
        t1.setName("T-one");
        Thread t2 = new Thread(new DemoTest()); //线程 t2创建(NEW)状态
        t2.setName("T-two");
        t1. start(); //线程 t1 进入 运行(Running)状态
        t2. start(); //线程 t2 进入 运行(Running)状态
    }

    public static synchronized void testBolockStatus(){
    // 该方法被 synchronized修饰
        System.out.println("我是被 synchronized 修饰的同步方法, 正在有线程" +
                Thread.currentThread().getName() +
                "执行我,其他线程进入阻塞状态排队。");
    }
}

代码分析
首先,请看关键代码:

t1. start(); //线程 t1 进入 运行(Running)状态
t2. start(); //线程 t2 进入 运行(Running)状态

我们将线程 t1 和 t2 进行 运行状态的启动,此时 t1 和 t2 就会执行 run () 方法下的 sync testBolockStatus () 方法。

然后,请看关键代码:

public static synchronized void testBolockStatus(){
    // 该方法被 synchronized修饰

testBolockStatus () 方法是被 synchronized 修饰的同步方法。当有 2 条或者 2 条以上的线程执行该方法时, 除了进入方法的一条线程外,其他线程均处于 “阻塞” 状态。

最后,我们看下执行结果:

我是被 synchronized 修饰的同步方法, 正在有线程T-one执行我,其他线程进入阻塞状态排队。
我是被 synchronized 修饰的同步方法, 正在有线程T-two执行我,其他线程进入阻塞状态排队。

结果解析:这里有两条线程, 线程名称分别为: T-one 和 T-two。

  • 执行结果第一条: T-one 的状态当时为 运行(Running)状态,T-two 状态为 阻塞(Blocked)状态;
  • 执行结果第二条: T-two 的状态当时为 运行(Running)状态,T-one 状态为 阻塞(Blocked)状态。

等待(Waiting)状态详解

定义: 执行 wait () 方法后线程进入等待状态,如果没有显示的 notify () 方法或者 notifyAll () 方法唤醒,该线程会一直处于等待状态。

实例:

public class DemoTest implements Runnable{
   
    @Override
    public void run() {
   
        try {
   
            testBolockStatus();
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws InterruptedException {
   
        Thread t1 = new Thread(new DemoTest()); //线程 t1创建(NEW)状态
        t1.setName("T-one");
        t1. start(); //线程进入 运行 状态
    }
    public synchronized void testBolockStatus() throws InterruptedException {
   
        System.out.println("我是线程:" + Thread.currentThread().getName() + ". 我进来了。");
        this.wait(); //线程进入 等待状态 ,没有其他线程 唤醒, 会一直等待下去
        System.out.println("我是被 synchronized 修饰的同步方法, 正在有线程" +
                Thread.currentThread()
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值