iis服务器助手广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Java多线程之搞定最后一公里详解
  • 182
分享到

Java多线程之搞定最后一公里详解

2024-04-02 19:04:59 182人浏览 八月长安

Python 官方文档:入门教程 => 点击学习

摘要

目录绪论一:线程安全问题1.1 提出问题1.2 不安全的原因1.2.1 原子性1.2.2 代码“优化”二:如何解决线程不安全的问题2.1 通过synchronized关键字2.2 v

绪论

上期介绍了多线程的概念、优势、创建方法以及几个常用的关键字。有了之前的基础过后,我们来讨论讨论线程安全问题以及其他线程进阶知识。

一:线程安全问题

1.1 提出问题

首先,给大家看一下这个代码:


public class yy1 {
    private static class Counter {
        private long n = 0;
        public void increment() {
            n++;
        }
        public void decrement() {
            n--;
        }
        public long value() {
            return n;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        final int COUNT = 1000_0000;
        Counter counter = new Counter();
        Thread thread = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                counter.increment();
            }
        }, "李四");
        thread.start();
        for (int i = 0; i < COUNT; i++) {
            counter.decrement();
        }
        thread.join();
// 期望最终结果应该是 0
        System.out.println(counter.value());
    }
}

大家看结果:

大家观察下是否适用多线程的现象是否一致?同时尝试思考下为什么会有这样的现象发生呢?

想给出一个线程安全的确切定义是复杂的,但我们可以这样认为:

如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。

1.2 不安全的原因

1.2.1 原子性

举个简单的例子,当我i们买票的时候,如果车站剩余票数大于0,就可以买。反之,买完一张票后,车站的票数也会自动减一。假设出现这种情况,两个人同时来买票,只剩最后一张票,前面那个人把最后一张票买了,但是短时间内票数还没减一也就是清零,这时另外一个人看到还有一张票,于是提交订单,但是其实已经没有多余的票了,那么问题就来了。这时我们引入原子性:

我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证, A 进入房间之后,还 没有出来; B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。 那我们应该如何解决这个问题呢?是不是只要给房间加一把, A 进去就把门锁上,其他人是不是就进不来了。这样 就保证了这段代码的原子性了。 有时也把这个现象叫做同步互斥,表示操作是互相排斥的。 不保证原子性, 如果一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是错误的。

1.2.2 代码“优化”

一段代码是这样的:

1. 去前台取下 U 盘

2. 去教室写 10 分钟作业

3. 去前台取下快递

如果是在单线程情况下, JVM 、 CPU 指令集会对其进行优化,比如,按 1->3->2 的方式执行,也是没问题,可以少跑 一次前台。这种叫做指令重排序。 刚才那个例子中,单线程情况是没问题的,优化是正确的,但在多线程场景下就有问题了,什么问题呢。可能快递是 在你写作业的10 分钟内被另一个线程放过来的,或者被人变过了,如果指令重排序了,代码就会是错误的。

二:如何解决线程不安全的问题

2.1 通过synchronized关键字

synchronized 的底层是使用操作系统的 mutex lock 实现的。 当线程释放锁时, JMM 会把该线程对应的工作内存中的共享变量刷新到主内存中 当线程获取锁时, JMM 会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内 存中读取共享变量 synchronized 用的锁是存在 Java对象头里的。 synchronized 同步快对同一条线程来说是可重入的,不会出现自己把自己锁死的问题; 同步块在已进入的线程执行完之前,会阻塞后面其他线程的进入。

锁的 SynchronizedDemo 对象


public class SynchronizedDemo {
public synchronized static void methond() {
}
public static void main(String[] args) {
method();
// 进入方法会锁 SynchronizedDemo.class 指向对象中的锁;出方法会释放
SynchronizedDemo.class 指向的对象中的锁
}
}

锁的 SynchronizedDemo 类的对象


public class SynchronizedDemo {
public synchronized static void methond() {
}
public static void main(String[] args) {
method();
// 进入方法会锁 SynchronizedDemo.class 指向对象中的锁;出方法会释放
SynchronizedDemo.class 指向的对象中的锁
}
}

明确锁的对象


public class SynchronizedDemo {
public synchronized static void methond() {
}
public static void main(String[] args) {
method();
// 进入方法会锁 SynchronizedDemo.class 指向对象中的锁;出方法会释放
SynchronizedDemo.class 指向的对象中的锁
}
}

public class SynchronizedDemo {
public void methond() {
// 进入代码块会锁 SynchronizedDemo.class 指向对象中的锁;出代码块会释放
SynchronizedDemo.class 指向的对象中的锁
synchronized (SynchronizedDemo.class) {
}
}
public static void main(String[] args) {
SynchronizedDemo demo = new SynchronizedDemo();
demo.method();
}
}

2.2 volatile

这里提一下volatile:

首先,被volatile关键字修饰的变量,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。

在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步

三:wait和notify关键字

3.1 wait方法

其实 wait() 方法就是使线程停止运行。

1. 方法 wait() 的作用是使当前执行代码的线程进行等待, wait() 方法是 Object 类的方法,该方法是用来将当前线程 置入 “ 预执行队列 ” 中,并且在 wait() 所在的代码处停止执行,直到接到通知或被中断为止。

2. wait() 方法只能在同步方法中或同步块中调用。如果调用 wait() 时,没有持有适当的锁,会抛出异常。

3. wait() 方法执行后,当前线程释放锁,线程与其它线程竞争重新获取锁。


public static void main(String[] args) throws InterruptedException {
Object object = new Object();
synchronized (object) {
System.out.println(" 等待中 ...");
object.wait();
System.out.println(" 等待已过 ...");
}
System.out.println("main 方法结束 ...");
}

这样在执行到 object.wait() 之后就一直等待下去,那么程序肯定不能一直这么等待下去了。这个时候就需要使用到了 另外一个方法唤醒的方法 notify() 。

3.2 notify方法

notify 方法就是使停止的线程继续运行。

  • 1. 方法 notify() 也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对 其发出通知 notify ,并使它们重新获取该对象的对象锁。如果有多个线程等待,则有线程规划器随机挑选出一个 呈 wait 状态的线程。
  • 2. 在 notify() 方法后,当前线程不会马上释放该对象锁,要等到执行 notify() 方法的线程将程序执行完,也就是退出 同步代码块之后才会释放对象锁。

class MyThread implements Runnable {
    private boolean flag;
    private Object obj;
    public MyThread(boolean flag, Object obj) {
        super();
        this.flag = flag;
        this.obj = obj;
    }
    public void waitMethod() {
        synchronized (obj) {
            try {
                while (true) {
                    System.out.println("wait()方法开始.. " +
                            Thread.currentThread().getName());
                    obj.wait();
                    System.out.println("wait()方法结束.. " +
                            Thread.currentThread().getName());
                    return;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    public void notifyMethod() {
        synchronized (obj) {
            try {
                System.out.println("notifyAll()方法开始.. " +
                        Thread.currentThread().getName());
                obj.notifyAll();
                System.out.println("notifyAll()方法结束.. " +
                        Thread.currentThread().getName());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    @Override
    public void run() {
        if (flag) {
            this.waitMethod();
        } else {
            this.notifyMethod();
        }
    }
}
public class TestThread {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        MyThread waitThread1 = new MyThread(true, object);
        MyThread waitThread2 = new MyThread(true, object);
        MyThread waitThread3 = new MyThread(true, object);
        MyThread notifyThread = new MyThread(false, object);
        Thread thread1 = new Thread(waitThread1, "wait线程A");
        Thread thread2 = new Thread(waitThread2, "wait线程B");
        Thread thread3 = new Thread(waitThread3, "wait线程C");
        Thread thread4 = new Thread(notifyThread, "notify线程");
        thread1.start();
        thread2.start();
        thread3.start();
        Thread.sleep(1000);
        thread4.start();
        System.out.println("main方法结束!!");
    }
}

从结果上来看第一个线程执行的是一个 waitMethod 方法,该方法里面有个死循环并且使用了 wait 方法进入等待状态 将释放锁,如果这个线程不被唤醒的话将会一直等待下去,这个时候第二个线程执行的是 notifyMethod 方法,该方 法里面执行了一个唤醒线程的操作,并且一直将 notify 的同步代码块执行完毕之后才会释放锁然后继续执行 wait 结束 打印语句。 注意: wait , notify 必须使用在 synchronized 同步方法或者代码块内。

3.3 wait和sleep对比(面试常考)

其实理论上 wait 和 sleep 完全是没有可比性的,因为一个是用于线程之间的通信的,一个是让线程阻塞一段时间, 唯一的相同点就是都可以让线程放弃执行一段时间。用生活中的例子说的话就是婚礼时会吃糖,和家里自己吃糖之间 有差别。说白了放弃线程执行只是 wait 的一小段现象。 当然为了面试的目的,我们还是总结下:

  • 1. wait 之前需要请求锁,而 wait 执行时会先释放锁,等被唤醒时再重新请求锁。这个锁是 wait 对像上的 monitor 
  • lock
  • 2. sleep 是无视锁的存在的,即之前请求的锁不会释放,没有锁也不会请求。
  • 3. wait 是 Object 的方法
  • 4. sleep 是 Thread 的静态方法

四:多线程案例

4.1 饿汉模式单线程


class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}

4.2 懒汉模式单线程


class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

4.3 懒汉模式多线程低性能版


class Singleton {
private static Singleton instance = null;
private Singleton() {}
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

4.4懒汉模式-多线程版-二次判断-性能高


class Singleton {
private static volatile Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

总结

多线程的部分暂时分享到这里,但其实还有很多没有没有涉及 ,等日后深刻理解后再来分享,码文不易,多谢大家支持,感激不尽!

到此这篇关于Java多线程之搞定最后一公里详解的文章就介绍到这了,更多相关Java 多线程内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Java多线程之搞定最后一公里详解

本文链接: https://www.lsjlt.com/news/137755.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Java多线程之搞定最后一公里详解
    目录绪论一:线程安全问题1.1 提出问题1.2 不安全的原因1.2.1 原子性1.2.2 代码“优化”二:如何解决线程不安全的问题2.1 通过synchronized关键字2.2 v...
    99+
    2024-04-02
  • Java多线程案例之定时器详解
    目录一.什么是定时器二.标准库中的定时器(timer)2.1什么是定时器2.2定时器的使用三.实现定时器3.1什么是定时器3.2最终实现代码一.什么是定时器 定时器也是软件开发中的一...
    99+
    2022-11-13
    Java多线程 定时器 Java 定时器 Java 多线程
  • Java多线程之线程状态详解
    目录 线程状态停止线程线程休眠模拟网络延迟(放大问题的发生性)模拟计时线程礼让插队(线程强制执行)线程状态观测线程优先级守护线程总结 线程状态 五个状态:新生、就...
    99+
    2024-04-02
  • Java多线程之Interrupt中断线程详解
    目录一、测试代码二、测试三、执行过程描述四、输出日志五、结论六、主要方法释义七、DEMO八、拓展程序九、实战一、测试代码 https://gitee.com/zture/sprin...
    99+
    2024-04-02
  • Java多线程之死锁详解
    目录1、死锁2、死锁经典问题——哲学家就餐问题 总结1、死锁 出现场景:当线程A拥有了A对象的锁,想要去获取B对象的锁;线程B拥有了B对象的锁,想要拥有A对象的锁,两个线程...
    99+
    2024-04-02
  • 【Java系列】详解多线程(一)
    个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【Java系列专栏】【JaveEE学习专栏】 本专栏旨在分享学习Java的一点学习心得,欢迎大家在评...
    99+
    2023-12-23
    java 开发语言 java-ee
  • Java多线程之线程安全问题详解
    目录1. 什么是线程安全和线程不安全?2. 自增运算为什么不是线程安全的?3. 临界区资源和竞态条件总结:面试题: 什么是线程安全和线程不安全?自增运算是不是线程安全的?如何保证多线...
    99+
    2024-04-02
  • Java多线程之线程状态的迁移详解
    目录一、六种状态二、状态迁移图三、线程状态模拟总结一、六种状态 java.lang.Thread 的状态分为以下 6 种,它们以枚举的形式,封装在了Thread类内部: NEW:表...
    99+
    2024-04-02
  • Java多线程之线程池七个参数详解
    目录corePoolSize:核心线程数maximumPoolSize:最大线程数keepAliveTime:空闲线程存活时间unit:时间单位workQueue:工作队列threa...
    99+
    2024-04-02
  • Java多线程之同步锁-lock详解
    目录一、题目描述二、解题思路三、代码详解一、题目描述 题目: 同步锁出现的目的就是为了解决多线程安全问题。 同步锁的几种方式 synchronized 1、同步代码块 2、同步方法 ...
    99+
    2024-04-02
  • java多线程编程之管道通信详解
    上一章节讲了wait/notify通信,这一节我们来探讨使用管道进行通信。 java中提供了IO流使我们很方便的对数据进行操作,pipeStream是一种特殊的流,用于不同线程间直接传送数据。一个线程将数据发送到输出管道,另一个线程从输入管...
    99+
    2023-05-30
    java 多线程 管道通信
  • Java多线程案例之阻塞队列详解
    目录一.阻塞队列介绍1.1阻塞队列特性1.2阻塞队列的优点二.生产者消费者模型2.1阻塞队列对生产者的优化三.标准库中的阻塞队列3.1Java提供阻塞队列实现的标准类3.2Block...
    99+
    2022-11-13
    Java多线程阻塞队列 Java 阻塞队列 Java多线程
  • java 多线程与并发之volatile详解分析
    目录CPU、内存、缓存的关系CPU缓存什么是CPU缓存为什么要有多级CPU CacheJava内存模型(Java Memory Model,JMM)JMM导致的并发安全问题可见性原子...
    99+
    2024-04-02
  • Java多线程之synchronized同步代码块详解
    目录1. 同步方法和同步块,哪种更好?2. synchronized同步代码块3. 如果同步块内的线程抛出异常会发生什么?总结面试题: 1同步方法和同步块,哪种更好? 2.如果同步块...
    99+
    2024-04-02
  • Java多线程之并发编程的核心AQS详解
    目录一、AQS简介1.1、AOS概念 1.2、AQS的核心思想1.3、AQS是自旋锁1.4、AQS支持两种资源分享的方式 二、AQS原理2.1、同步状态的管理2....
    99+
    2024-04-02
  • Java多线程之哲学家就餐问题详解
    目录一、题目二、题目解析三、代码实现四、运行效果截图五、结语一、题目 教材提供一个哲学家就餐问题的解决方案的框架。本问题要求通过pthreads 互斥锁来实现这个解决方案。 哲学家...
    99+
    2024-04-02
  • 一文精通Java 多线程之全方位解读
    目录并行和并发线程基础概念线程和进程多线程的好处线程的状态实现多线程的两种方式继承Thread类实现Runnable接口线程的安全性和原子性锁的概念和使用生产消费者模型生产消费者模型...
    99+
    2024-04-02
  • 【Java|多线程与高并发】定时器(Timer)详解
    文章目录 1. 前言2. 定时器的基本使用3. 实现定时器4. 优化上述的定时器代码5. 总结 1. 前言 在Java中,定时器Timer类是用于执行定时任务的工具类。它允许你安排一个...
    99+
    2023-10-05
    java jvm 开发语言
  • Java多线程开发工具之CompletableFuture的应用详解
    做Java编程,难免会遇到多线程的开发,但是JDK8这个CompletableFuture类很多开发者目前还没听说过,但是这个类实在是太好用了,了解它的一些用法后相信你会对它爱不释手...
    99+
    2023-03-20
    Java CompletableFuture多线程开发 Java CompletableFuture使用 Java CompletableFuture
  • Java多线程之生产者消费者模式详解
    目录1.生产者消费者模型2.实现生产者消费者模型3.生产者消费者模型的作用是什么总结问题: 1.什么是阻塞队列?如何使用阻塞队列来实现生产者-消费者模型? 2. 生产者消费者模型的作...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作