首页 > 编程语言 >java多线程编程问题以及解决办法

java多线程编程问题以及解决办法

时间:2024-06-22 14:09:29浏览次数:27  
标签:count java Thread 编程 static 线程 new 多线程 public

java多线程编程问题以及解决办法

    多线程编程虽然可以提高程序的性能和响应速度,但也带来了许多复杂的问题,如竞态条件、死锁、线程安全问题、内存一致性错误等。常用的解决方法包括使用同步机制(如 synchronized 和 ReentrantLock)、线程池、volatile 关键字、以及合适的线程间通信机制(如 wait/notify 和 Condition)。

   下面多线程编程中常见的一些问题以及对应的解决办法

   一、竞态条件(Race Conditions)

   竞态条件是指多个线程同时访问和修改共享数据时,由于操作顺序的不确定性,可能导致数据的不一致或程序行为异常。竞态条件是多线程编程中最常见的问题之一。

   1. 示例

 1 public class Counter {
 2     private int count = 0;
 3 
 4     public void increment() {
 5         count++;
 6     }
 7 
 8     public int getCount() {
 9         return count;
10     }
11 
12     public static void main(String[] args) {
13         Counter counter = new Counter();
14         Runnable task = () -> {
15             for (int i = 0; i < 1000; i++) {
16                 counter.increment();
17             }
18         };
19         Thread thread1 = new Thread(task);
20         Thread thread2 = new Thread(task);
21         thread1.start();
22         thread2.start();
23 
24         try {
25             thread1.join();
26             thread2.join();
27         } catch (InterruptedException e) {
28             e.printStackTrace();
29         }
30 
31         // 预期值是2000,但是可能输出不正确的结果
32         System.out.println(counter.getCount());
33     }
34 }

   说明:上面的运行结果是不稳定的

   2. 解决办法

   使用同步机制,如 synchronized 关键字或显式锁(ReentrantLock)来确保对共享数据的访问是互斥的。

   1) synchronized

 1 public class Counter {
 2     private int count = 0;
 3 
 4     public synchronized void increment() {
 5         count++;
 6     }
 7 
 8     public synchronized int getCount() {
 9         return count;
10     }
11 }

   2)ReentrantLock  

 1 public class ReentrantLockCounter {
 2     public static ReentrantLock lock = new ReentrantLock();
 3     public static int count = 0;
 4 
 5     public void increment() {
 6         count++;
 7     }
 8 
 9     public int getCount() {
10         return count;
11     }
12     /**
13      * @param args
14      * @throws InterruptedException
15      */
16     public static void main(String[] args) throws InterruptedException {
17         ReentrantLockCounter counter = new ReentrantLockCounter();
18         Runnable task = () -> {
19             for (int i = 0; i < 1000; i++) {
20                 lock.lock();
21                 try {
22                     System.out.println(Thread.currentThread().getName()  + " " + i);
23                     counter.increment();
24                 } finally {
25                     lock.unlock();
26                 }
27             }
28         };
29         Thread thread1 = new Thread(task);
30         Thread thread2 = new Thread(task);
31         thread1.start();
32         thread2.start();
33 
34         try {
35             thread1.join();
36             thread2.join();
37         } catch (InterruptedException e) {
38             e.printStackTrace();
39         }
40         
41         System.out.println(counter.getCount());
42     }
43 }

   二、死锁(Deadlock)

   死锁是指两个或多个线程相互等待对方释放锁,从而导致线程永久阻塞。死锁通常发生在多个锁的情况下,当线程获取锁的顺序不一致时容易产生死锁。

   1. 示例

 1 public class DeadlockDemo {
 2     private static final Object lock1 = new Object();
 3     private static final Object lock2 = new Object();
 4 
 5     public static void main(String[] args) {
 6         Thread thread1 = new Thread(() -> {
 7             synchronized (lock1) {
 8                 System.out.println("Thread 1: Holding lock 1...");
 9                 try { Thread.sleep(10); } catch (InterruptedException e) {}
10                 synchronized (lock2) {
11                     System.out.println("Thread 1: Holding lock 1 & 2...");
12                 }
13             }
14         });
15 
16         Thread thread2 = new Thread(() -> {
17             synchronized (lock2) {
18                 System.out.println("Thread 2: Holding lock 2...");
19                 try { Thread.sleep(10); } catch (InterruptedException e) {}
20                 synchronized (lock1) {
21                     System.out.println("Thread 2: Holding lock 2 & 1...");
22                 }
23             }
24         });
25 
26         thread1.start();
27         thread2.start();
28     }
29 }

    2. 解决办法

    避免嵌套锁定,尽量减少锁的数量,或通过使用 tryLock 方法来避免死锁。

 1 public class AvoidDeadlockDemo {
 2     private static final Lock lock1 = new ReentrantLock();
 3     private static final Lock lock2 = new ReentrantLock();
 4 
 5     public static void main(String[] args) {
 6         Thread thread1 = new Thread(() -> {
 7             try {
 8                 if (lock1.tryLock() && lock2.tryLock()) {
 9                     System.out.println("Thread 1: Acquired locks...");
10                 }
11             } finally {
12                 lock1.unlock();
13                 lock2.unlock();
14             }
15         });
16 
17         Thread thread2 = new Thread(() -> {
18             try {
19                 if (lock2.tryLock() && lock1.tryLock()) {
20                     System.out.println("Thread 2: Acquired locks...");
21                 }
22             } finally {
23                 lock2.unlock();
24                 lock1.unlock();
25             }
26         });
27 
28         thread1.start();
29         thread2.start();
30     }
31 }

   三、线程安全问题

   多线程访问共享数据时,如果没有适当的同步机制,可能导致数据的不一致性和程序行为的不可预知性。这类问题统称为线程安全问题。

   使用同步机制确保线程安全,如 synchronized 关键字、显式锁(ReentrantLock)或使用线程安全的类(如 AtomicInteger)。

   还是以竞态条件中的累加计算例子来说明:

 1 public class SafeCounter {
 2     private AtomicInteger count = new AtomicInteger(0);
 3 
 4     public void increment() {
 5         count.incrementAndGet();
 6     }
 7 
 8     public int getCount() {
 9         return count.get();
10     }
11 }

   三、 上下文切换开销

   线程切换(Context Switching)是指CPU从一个线程切换到另一个线程的过程。频繁的线程切换会导致CPU时间片的浪费,降低程序性能。上下文切换的开销包括保存和恢复线程状态,以及处理操作系统调度器。

   示例:大量创建和销毁线程,或者频繁的线程切换,都会增加上下文切换的开销。

   代码如下:

1 public class ContextSwitchDemo {
2     public static void main(String[] args) {
3         for (int i = 0; i < 10000; i++) {
4             new Thread(() -> {
5                 System.out.println("Thread " + Thread.currentThread().getId());
6             }).start();
7         }
8     }
9 }

    说明:上面的代码会创建10000个线程

    解决方法: 使用线程池来重用线程,减少创建和销毁线程的开销。

    代码如下:

 1 public class ThreadPoolDemo {
 2     public static void main(String[] args) {
 3         ExecutorService executor = Executors.newFixedThreadPool(10);
 4         for (int i = 0; i < 10000; i++) {
 5             executor.submit(() -> {
 6                 System.out.println("Thread " + Thread.currentThread().getId());
 7             });
 8         }
 9         executor.shutdown();
10     }
11 }

    说明:这里面只会创建10线程来循环执行任务

    五、内存一致性错误

    内存一致性错误(Memory Consistency Errors)是指由于缺乏适当的同步机制,不同线程对共享变量的修改在内存中的可见性不一致,导致线程读取到过期或不正确的数据。

    1. 示例

 1 public class MemoryConsistencyDemo {
 2     private static boolean ready = false;
 3     private static int number;
 4 
 5     private static class ReaderThread extends Thread {
 6         public void run() {
 7             while (!ready) {
 8                 Thread.yield();
 9             }
10             System.out.println(number);
11         }
12     }
13 
14     public static void main(String[] args) {
15         new ReaderThread().start();
16         number = 42;
17         ready = true;
18     }
19 }

   2. 解决办法

   使用 volatile 关键字或同步机制来确保变量的可见性。

   将ready属性设置为:private static  volatile boolean ready = false;

 

    参考链接:

    https://www.aolifu.org/article/thread_question

标签:count,java,Thread,编程,static,线程,new,多线程,public
From: https://www.cnblogs.com/hld123/p/18262234

相关文章

  • 【JavaScript脚本宇宙】终极对决:六大虚拟DOM库横评
    深度剖析:六大虚拟DOM库的奥秘与应用场景前言虚拟DOM(DocumentObjectModel)是用于表示和操作HTML文档的抽象数据结构。虚拟DOM库是构建用户界面的重要工具,它们提供了高效的更新机制、组件化开发等功能,使开发者能够轻松地开发高性能、可维护的Web应用程序。本文将介绍几个流......
  • 基于javaweb房产中介房屋租赁系统设计与实现
      博主介绍:黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育和辅导。所有项目都配有从入门到精通的基础知识视频课程,学习后应对毕业设计答辩。项目配有对应开发文档、开题报告、任务书......
  • Java计算机毕业设计博物馆管理系统(开题报告+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着信息技术的飞速发展,传统博物馆的管理模式已经难以适应现代社会的需求。博物馆作为重要的文化传承和展示机构,需要更加高效、智能的管理系统来支撑......
  • java毕业设计之在线考试系统(springboot完整源码+说明文档+演示视频)
    1项目介绍本系统主要包括管理员和用户两个角色组成;主要包括首页、个人中心、用户管理、教师管理、课程信息管理、班级信息管理、试题管理、在线试题管理、考试管理等功能的管理系统。2、项目技术项目后端框架:Java+ssm项目前端框架:vue2,ssm3、开发环境springboot环境......
  • 分享:Javascript开源桌面环境-Puter
    Puter这是一个运行在浏览器里的桌面操作系统,提供了笔记本、代码编辑器、终端、画图、相机、录音等应用和一些小游戏。该项目作者出于性能方面的考虑没有选择Vue和React技术栈,而是采用的JavaScript和jQuery构建,支持Docker一键部署和在线使用。 简介:Puter是一......
  • 阶段一:Java基础进阶期末题型
    目录前言第一题第二题第三题第四题第五题第六题前言java基础进阶结课期末题第一题需求某小型商城系统的订单信息在素材下的orders.xml文件中,现在要求把xm!中的订单信息,封装成一个一个的订单对象,将订单对象保存到ArrayList集合中。具体功能点要求1)定义订单类......
  • JAVA 医院管理住院系统
    本系统采用的是自顶向下的分层模块设计方法,由于医院住院管理系统分为:医生信息管理、病人信息管理、病床信息管理、收费信息管理、统计分析和系统管理等功能,我们在设计过程中按其功能把它分成不同的模块。医院管理住院系统的主模块如图所示:图5-1程序流程图1.2 系统登录......
  • java基础知识面试准备 第三天
    &和&&的区别        &和&&都是Java中的逻辑运算符,用于判断两个布尔表达式的逻辑关系(&和&&的优先级不同,&&的优先级比&高),它们的区别如下:1.&是逻辑与运算符,它的两个操作数都会被求值,只有当两个操作数都为true时,结果才为true;即使第一个操作数为false,第二个操作数也会......
  • 01 Shell编程规范与变量
    1、Shell脚本概述在一些复杂的Linux维护工作中,大量的重复性的输入和交互操作不仅费力费时,而且容易出错,而编写一个恰到好处的Shell脚本程序,可以批量处理、自动化地完成一系列维护任务,大大减轻管理员的负担。Shell脚本是什么?简单的说,只要将平时使用的各种Linux命令按顺序保存......
  • 02 Shell编程之条件语句
    1、条件测试操作要使Shell脚本程序具备一定的智能,面临的第一个问题就是如何区分不同的情况以确定执行何种操作。例如,当磁盘使用率超过95%时,发送告警信息;当备份目录不存在时,能够自动创建;当源码编译程序时,若配置失败则不再继续安装等。注:Shell环境根据命令执行后的返回状态值......