Java并发编程挑战与解决方案:上下文切换、死锁、资源限制及底层实现原理

这篇具有很好参考价值的文章主要介绍了Java并发编程挑战与解决方案:上下文切换、死锁、资源限制及底层实现原理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

并发编程的挑战

上下文切换

  • 即使是单核处理器也支持多线程执行代码,CPU 通过给每个线程分配 CPU 时间片来实现这个机制。

  • CPU 通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。

  • 任务从保存到再加载的过程就是一次上下文切换

  • 切换上下文需要一定的消耗

如何减少上下文的切换
  • 使用最少线程:避免创建不需要的线程

  • CAS 算法:JavaAtomic 包使用 CAS 算法来更新数据,而不需要加锁。

  • 无锁并发编程:多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁

死锁

  • 避免一个线程同时获取多个锁。

  • 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。

  • 尝试使用定时锁,使用 lock.tryLock(timeout)来替代使用内部锁机制。

  • 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况

资源限制的挑战

  • 在并发编程中,将代码执行速度加快的原则是将代码中串行执行的部分变成并发执行,但是如果将某段串行的代码并发执行,因为受限于资源,仍然在串行执行,这时候程序不仅不会加快执行,反而会更慢,因为增加了上下文切换和资源调度的时间

Java 并发机制的底层实现原理

volatile 的应用

  • volatile保证共享变量的可见性

  • volatile 是轻量级的 synchronized

synchronized 的实现原理与应用

  • 对于普通同步方法,锁是当前实例对象。

  • 对于静态同步方法,锁是当前类的 Class 对象。

  • 对于同步方法块,锁是 Synchonized 括号里配置的对象。

三大特性
  • 原子性:一个或多个操作要么全部成功,要么全部失败。保证只有一个线程拿到锁访问共享资源

  • 可见性:当一个线程对共享变量进行修改后,其他线程可以立刻看到

  • 有序性:程序的执行顺序会按照代码的先后顺序执行。

实现原理
  • Java虚拟机则是通过进入和退出Monitor对象来实现方法同步和代码块同步的

  • 同步代码块的实现是由monitorentermonitorexit指令完成的,其中monitorenter指令所在的位置是同步代码块开始的位置,第一个monitorexit指令是用于正常结束同步代码块的指令

Java 并发编程基础

  • 线程作为操作系统调度的最小单元,多个线程能够同时执行,这将显著提升程序性能,在多核环境中表现得更加明显。

  • 过多地创建线程和对线程的不当管理也容易造成问题。

线程简介

  • 操作系统调度的最小单元是线程,也叫轻量级进程(Light Weight Process),在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换,让使用者感觉到这些线程在同时执行。

使用多线程的原因
  • 提升处理器的使用效率

  • 异步处理请求,提升系统响应速度提升用户体验

  • Java 为多线程编程提供了良好、考究并且一致的编程模型,使开发人员能够更加专

    注于问题的解决

线程优先级
  • 线程优先级的范围从1-10,默认优先级是5

  • 针对频繁阻塞(休眠或者 I/O 操作)的线程需要设置较高优先级,而偏重计算(需要较多 CPU

    时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。

  • 线程优先级不能作为程序正确性的依赖

线程的状态

Java,Java并发编程,Java 上下文切换,Java死锁、资源限制、线程间通信相关问题

  • 测试.java

public class ThreadTest {

    public static void main(String[] args) {
        new Thread(new TimeWaiting(), "超时等待线程").start();
        new Thread(new Waiting(), "等待线程").start();
        // 使用两个 Blocked 线程,一个获取锁成功,另一个被阻塞
        new Thread(new Blocked(), "阻塞线程1").start();
        new Thread(new Blocked(), "阻塞线程2").start();
    }

    // 该线程不断地进行睡眠
    static class TimeWaiting implements Runnable {
        private static final Logger logger = LoggerFactory.getLogger(TimeWaiting.class);
        @Override
        public void run() {
            while (true) {
                logger.info(Thread.currentThread().toString());
                second(100);
            }
        }
    }

    // 该线程在 Waiting.class 实例上等待
    static class Waiting implements Runnable {
        private static final Logger logger = LoggerFactory.getLogger(Waiting.class);

        @Override
        public void run() {
            while (true) {
                synchronized (Waiting.class) {
                    try {
                        logger.info(Thread.currentThread().toString());
                        Waiting.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    // 该线程在 Blocked.class 实例上加锁后,不会释放该锁
    static class Blocked implements Runnable {
        private static final Logger logger = LoggerFactory.getLogger(Blocked.class);

        public void run() {
            synchronized (Blocked.class) {
                while (true) {
                    logger.info(Thread.currentThread().toString());
                    second(100);
                }
            }
        }
    }

    public static final void second(long seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
        }
    }}

  • 运行结果

[超时等待线程] INFO util.thread.ThreadTest$TimeWaiting - Thread[超时等待线程,5,main]
[等待线程] INFO util.thread.ThreadTest$Waiting - Thread[等待线程,5,main]
[阻塞线程1] INFO util.thread.ThreadTest$Blocked - Thread[阻塞线程1,5,main]

  • Jstack查看线程信息

// 线程被阻塞
"阻塞???程2" #14 prio=5 os_prio=0 tid=0x00000000282d2800 nid=0x231a0 waiting for monitor entry [0x0000000028f0f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at util.thread.ThreadTest$Blocked.run(ThreadTest.java:61)
        - waiting to lock <0x00000007167efbb8> (a java.lang.Class for util.thread.ThreadTest$Blocked)
        at java.lang.Thread.run(Thread.java:748)
// 线程获取到了 Blocked.class 的锁
"阻塞线程1" #13 prio=5 os_prio=0 tid=0x00000000282d2000 nid=0x25670 waiting on condition [0x0000000028e0f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:340)
        at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
        at util.thread.ThreadTest.second(ThreadTest.java:70)
        at util.thread.ThreadTest$Blocked.run(ThreadTest.java:62)
        - locked <0x00000007167efbb8> (a java.lang.Class for util.thread.ThreadTest$Blocked)
        at java.lang.Thread.run(Thread.java:748)
 // 等待线程 线程在 Waiting 实例上等待
"等待线程" #12 prio=5 os_prio=0 tid=0x00000000282d1000 nid=0x24514 in Object.wait() [0x0000000028d0e000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000007167ecd70> (a java.lang.Class for util.thread.ThreadTest$Waiting)
        at java.lang.Object.wait(Object.java:502)
        at util.thread.ThreadTest$Waiting.run(ThreadTest.java:45)
        - locked <0x00000007167ecd70> (a java.lang.Class for util.thread.ThreadTest$Waiting)
        at java.lang.Thread.run(Thread.java:748)
// 超时等待
"超时等待线程" #11 prio=5 os_prio=0 tid=0x00000000282cf800 nid=0x254d8 waiting on condition [0x0000000028c0e000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:340)
        at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
        at util.thread.ThreadTest.second(ThreadTest.java:70)
        at util.thread.ThreadTest$TimeWaiting.run(ThreadTest.java:30)
        at java.lang.Thread.run(Thread.java:748)

线程状态变迁
  • 线程创建之后,调用 start()方法开始运行。当线程执行 wait()方法之后,线程进入等待状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而超时等待状态相当于在等待状态的基础上增加了超时限制,也就是超时时间到达时将会返回到运行状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到阻塞状态。线程在执行 Runnablerun()方法之后将会进入到终止状态。
    Java,Java并发编程,Java 上下文切换,Java死锁、资源限制、线程间通信相关问题

  • Java 将操作系统中的运行和就绪两个状态合并称为运行状态。

  • 阻塞状态是线程阻塞在进入 synchronized 关键字修饰的方法或代码块(获取锁)时的状态,但是阻塞在 java.concurrent 包中 Lock 接口的线程状态却是等待状态,因为 java.concurrent 包中Lock 接口对于阻塞的实现均使用了 LockSupport 类中的相关方法。

Daemon 线程
  • Daemon 属性需要在启动线程之前设置,不能在启动线程之后设置。

  • Daemon 线程被用作完成支持性工作,但是在 Java 虚拟机退出时 Daemon 线程中的 finally块并不一定会执行。

启动和终止线程

构造线程
  • 构造一个线程对象

  • 提供线程所属的线程组、线程优先级、是否是 Daemon 等属性

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {}

  • 一个新构造的线程对象是由其 parent 线程来进行空间分配的,而child 线程继承了 parent 是否为 Daemon、优先级和加载资源的 contextClassLoader 以及可继承的 ThreadLocal,同时还会分配一个唯一的 ID 来标识这个 child 线程。至此,一个能够运行的线程对象就初始化好了,在堆内存中等待着运行。

启动线程
  • 当前线程(即 parent 线程)同步告知 Java 虚拟机,只要线程规划器空闲,应立即启动调用 start()方法的线程

new Thread(new TimeWaiting(), "超时等待线程").start();

理解中断
  • 线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作。中断好比其他线程对该线程打了个招呼,其他线程通过调用该线程的interrupt()方法对其进行中断操作。

过期的 suspend()、resume()和 stop()
  • suspend()resume()stop()方法完成了线程的暂停、恢复和终止工作

  • 这些方法是过期的不建议使用:因为 suspend()resume()stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法,而暂停和恢复操作可以用后面提到的等待/通知机制来替代。

安全地终止线程
  • 通过中断操作和 cancel()方法均可使线程得以终止。这种通过标识位或者中断操作的方式能够使线程在终止时有机会去清理资源,而不是武断地将线程停止,因此这种终止线程的做法显得更加安全和优雅。

@Testpublic void test1() throws InterruptedException {
    Thread thread = new Thread(() -> {
        while (!Thread.currentThread().isInterrupted()) {
            logger.info("Current date:{}", System.currentTimeMillis());
        }
    });
    thread.start();
    Thread.sleep(3000);
    thread.interrupt();
    if(thread.isInterrupted()){
        logger.info("Thread was interrupted..");
    }
    Thread.sleep(3000);}

线程间通信

volatile 和 synchronized 关键字
  • 关键字volatile可以用来修饰字段(成员变量),就是告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性。

  • 关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。

等待/通知机制
  • 一个线程修改一个对象的值,另一个线程感知变化。线程A调用对象Owait()进入等待状态,另一个线程B调用对象O的notify()或者 notifuAll()方法,线程A 收到通知后从对象Owait()方法返回,执行后续操作。两个线程通过对象O来完成交互,而对象上的wait()notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

  • 等待通知相关方法如下
    Java,Java并发编程,Java 上下文切换,Java死锁、资源限制、线程间通信相关问题

  • Consume.java

public class Consume {

    private static final Logger logger = LoggerFactory.getLogger(Consume.class);

    private final Object lockValue;

    public Consume(Object object) {
        this.lockValue = object;
    }

    /**
     * 生产者赋值
     */
    public void getValue() {
        synchronized (lockValue) {
            if (ObjectUtils.isEmpty(ProductConsumeValue.value)) {
                try {
                    lockValue.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            logger.info("Consume :{}", ProductConsumeValue.value);
            ProductConsumeValue.value = "";
            lockValue.notifyAll();
        }
    }}

  • Product.java

public class Product {

    private static final Logger logger = LoggerFactory.getLogger(Consume.class);

    private Object lockValue;

    public Product(Object lockValue) {
        this.lockValue = lockValue;
    }

    /**
     * 生产者赋值
     */
    public void setValue() {
        synchronized (lockValue) {
            if (!ObjectUtils.isEmpty(ProductConsumeValue.value)) {
                try {
                    lockValue.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            ProductConsumeValue.value = System.currentTimeMillis() + "_" + System.nanoTime();
            logger.info("Product :{}", ProductConsumeValue.value);
            lockValue.notify();
        }
    }}

  • Test.java

public static void main(String[] args) {
    String value = "";
    Product product = new Product(value);
    Consume consume = new Consume(value);
    ProductThread productThread = new ProductThread(product);
    ConsumerThread consumerThread = new ConsumerThread(consume);
    productThread.start();
    consumerThread.start();}

等待方遵循如下原则
  • 获取对象的锁。

  • 如果条件不满足,那么调用对象的 wait()方法,被通知后仍要检查条件。

  • 条件满足则执行对应的逻辑。

    对应的伪代码如下:

synchronized(对象) { 
    while(条件不满足) 
    {  
    	对象.wait();  
    }  
	对应的处理逻辑 
}

通知方遵循如下原则
  • 获得对象的锁。

  • 改变条件

  • 通知所有等待在对象上的线程。

对应的伪代码如下:

synchronized(对象){  
	改变条件  
	对象.notifyAll(); }

管道输入/输出流
  • 管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它 主要用于线程之间的数据传输,而传输的媒介为内存。管道输入/输出流主要包括了如下4 种具体实现:PipedOutputStreamPipedInputStreamPipedReaderPipedWriter,前两种面向字节,而后两种面向字符。

Thread.join()的使用
  • 如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

  • 如果一个线程 A 执行了 thread.join()语句,其含义是:当前线程 A 等待 thread 线程终止之后才从 thread.join()返回。

public class ThreadJoinTest {

    private static final Logger logger = LoggerFactory.getLogger(ThreadJoinTest.class);

    /**
     * 如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到 join() 方法了。
     *
     * @param args args
     * @throws InterruptedException 中断异常
     */
    public static void main(String[] args) throws InterruptedException {
        Thread currentThread = Thread.currentThread();
        for (int i = 0; i < 10; i++) {
            JoinThreadTest joinTestTread = new JoinThreadTest(currentThread);
            Thread thread = new Thread(joinTestTread, "线程 " + i);
            thread.start();
            currentThread = thread;
        }
        Thread.sleep(5000);
    }

    private static class JoinThreadTest implements Runnable {
        private final Thread thread;

        private JoinThreadTest(Thread currentThread) {
            thread = currentThread;
        }

        @Override
        public void run() {
            try {
                thread.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            logger.info("当前线程:{}", Thread.currentThread().getName());
        }
    }}

ThreadLocal 的使用
  • 线程变量,每个线程可以根据一个 ThreadLocal 对象查询到绑定在这个线程上的一个值。可以通过 set(T)方法来设置一个值,在当前线程下再通过 get()方法获取到原先设置的值。文章来源地址https://www.toymoban.com/news/detail-427493.html

public class ThreadLocalTest {

    private static final Logger logger = Logger.getLogger(String.valueOf(ThreadLocalTest.class));

    /**
     * 当使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程提供独立的变量副本,
     * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
     * 为了避免重复创建TSO(thread specific object,即与线程相关的变量) 使用 static final 修饰
     */
    private static final ThreadLocal<Map<String, String>> THREAD_LOCAL_MAP = new ThreadLocal<>();

    @Test
    public void test1() {
        Map<String, String> map = new HashMap<>();
        map.put("methodTest", "张三");
        map.put("test2", "李四");
        THREAD_LOCAL_MAP.set(map);
        getThreadLocalMap();
        THREAD_LOCAL_MAP.remove();
    }

    private void getThreadLocalMap() {
        Map<String, String> map = THREAD_LOCAL_MAP.get();
        logger.info(String.valueOf(map));
    }}

到了这里,关于Java并发编程挑战与解决方案:上下文切换、死锁、资源限制及底层实现原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 智能资产管理的安全挑战与解决方案

    智能资产管理(Smart Asset Management,SAM)是一种利用人工智能(AI)和大数据技术来优化资产管理过程的方法。它涉及到资产跟踪、资产分析、资产预测和资产优化等方面。随着资产管理的复杂化和数据量的增加,智能资产管理的安全性变得越来越重要。 在智能资产管理中,资产可

    2024年02月22日
    浏览(30)
  • 论文-分布式-并发控制-并发控制问题的解决方案

    目录 参考文献 问题 解法与证明 易读版本 参考文献 Dijkstra于1965年发表文章Solution of a Problem in Concurrent Programming Control,引出并发系统下的互斥(mutual exclusion)问题,自此开辟了分布式计算领域 Dijkstra在文中给出了基于共享存储原子性访问的解决方案只有十多行代码,但阅读起来

    2024年02月08日
    浏览(34)
  • 容错技术在云计算中的挑战与解决方案

    云计算是一种基于互联网的计算资源分配和共享模式,它允许用户在需要时从任何地方访问计算能力、存储和应用程序。随着云计算的普及和发展,其规模和复杂性不断增加,这使得云计算系统面临着许多挑战,其中容错技术在云计算中的应用是非常重要的。容错技术是一种

    2024年02月22日
    浏览(28)
  • 云计算需求激增带来的基础设施挑战及解决方案

    云计算的指数级增长迅速改变了我们消费和存储数字信息的方式。随着企业和个人越来越依赖基于云的服务和数据存储,对支持这些服务的强大且可扩展的基础设施的需求已达到前所未有的水平。 云计算需求的快速增长 我们的日常生活越来越多地被新技术所渗透。流媒体服

    2024年02月16日
    浏览(24)
  • 生成式AI入门必读:基本概念、数据挑战与解决方案

    随着生成式 AI(通常简称为 GenAI)的兴起,我们的世界发生了翻天覆地的变化。而随着 AI 生成内容的革命性应用程序的出现,人们也认为生成式 AI 将从根本上影响社会的各个行业和部门。 组织们都在竞相捕捉生成式 AI 的潜力。如果您也是其中一员,那么您的首要任务就是了

    2024年04月26日
    浏览(23)
  • “Docker 技术在企业中的应用及挑战解决方案“

    Docker 技术是一种基于容器化的应用部署和管理技术。随着云计算的普及和应用的不断增多,Docker 技术在企业中的应用越来越广泛。本文将介绍 Docker 技术的基本概念、优势和应用场景,并讨论如何在企业中应用 Docker 技术。 一、Docker 技术概述 Docker 技术是一种基于容器化的应

    2024年02月08日
    浏览(25)
  • Elasticsearch并发写入版本冲突解决方案

    搜索公众号, AmCoder 干货及时送达👇  众所周知,es经常被用于存储日志数据,其中在某些场景下,日志产生的时机不同,并且需要将多类具备关联关系的日志写入同一个document,就会带来同一个文档可能会被其它文档覆盖,或者missing等问题。 大家都知道es是不支持事务的,

    2023年04月19日
    浏览(39)
  • 白鲸开源 X SelectDB 金融大数据联合解决方案公布!从源头解决大数据开发挑战

    随着互联网技术的发展、云计算技术的成熟、人工智能技术的兴起和数字化经济的崛起,数据已成为企业的核心资产。在金融行业中,数字化已成为了支撑各类业务场景的核心力量,包括个人理财、企业融资、股票交易、保险理赔、贷款服务、支付结算、投资咨询、资产管理

    2024年02月09日
    浏览(38)
  • 深入探讨MySQL并发事务的问题及解决方案

    人不走空                                                                          目录         🌈个人主页:人不走空       💖系列专栏:算法专题 ⏰诗词歌赋:斯是陋室,惟吾德馨 1. 脏读(Dirty Read) 2. 不可重复读(Non-repeatable Read) 3. 幻读(Phantom Rea

    2024年03月09日
    浏览(77)
  • 从传统到智能化:汽车内部通信的安全挑战与SecOC解决方案

    01/需求背景 Demand background 在传统的汽车电子结构中,车内的电控单元(ECU)数量和复杂性受到限制,通信带宽也受到限制。因此,人们普遍认为车内各个ECU之间的通信是可靠的。只要ECU节点接收到相应的消息,就会对其进行处理。然而,随着汽车行业和互联网的持续发展,汽

    2024年02月10日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包