Java多线程编程之线程安全

简介: 在多线程环境下,访问相同的资源,有可以会引发线程不安全问题。

在多线程环境下,访问相同的资源,有可以会引发线程不安全问题。


一、临界资源问题


多一个线程同时运行,有时线程之间需要共享数据,一个线程需要其他线程的数据,否则就不能保证 程序运行结果的正确性。

例如有一个航空公司的机票销售,每一天机票数量是有限的,很多售票点同时销售这些机票。

下面是 一个模拟销售机票系统,示例代码如下:

public class TicketDB {
//    机票数量
    private int ticketCount = 5;
//    获得当前机票数量
    public  int getTicketCount(){
        return ticketCount;
    }
//    销售机票
    public void sellTicket(){
        try {
//            等于用户付款
//            等线程休眠
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("第%d号票已经售出\n",ticketCount);
        ticketCount--;
    }
}

调用代码如下:

//测试代码
public class HelloWorld {
    public static void main(String[] args) {
        TicketDB ticketDB = new TicketDB();
//        创建线程
        Thread t1 = new Thread(() -> {
           while (true){
               int currentTicketCount = ticketDB.getTicketCount();
//               查询是否有票
               if (currentTicketCount > 0) {
                   ticketDB.sellTicket();
               } else {
//                   无票退出
                   break;
               }
           }
        });
//       开始线程
        t1.start();
//        创建线程t2
        Thread t2 = new Thread(() ->{
           while (true) {
               int currentTicketCount = ticketDB.getTicketCount();
//               查询是否有票
               if (currentTicketCount > 0) {
                   ticketDB.sellTicket();
               } else {
//                   无票退出
                   break;
               }
           }
        });
//        开始线程
        t2.start();
    }
}

运行结果:

第5号票已经售出
第5号票已经售出
第3号票已经售出
第3号票已经售出
第1号票已经售出
第1号票已经售出

从结果看还是能发现一些问题:同一张票重复销售。这些问题的根本原因是多个线程间共享的数据导致数据的不一致性。

多个线程间共享的数据称为共享资源或临界资源,由于是CPU负责线程的调度,程序员无 法精确控制多线程的交替顺序。这种情况下,多线程对临界资源的访问有时会导致数据的不一致 性。


二、多线程同步


为了防止多线程对临界资源的访问有时会导致数据的不一致性,Java提供了“互斥”机制,可以为这些 资源对象加上一把“互斥锁”,在任一时刻只能由一个线程访问,即使该线程出现阻塞,该对象的被锁 定状态也不会解除,其他线程仍不能访问该对象,这就多线程同步。线程同步保证线程安全的重要手 段,但是线程同步客观上会导致性能下降。


解决方法:可以通过两种方式实现线程同步,两种方式都涉及到使用synchronized关键字,


一种是synchronized方 法,使用synchronized关键字修饰方法,对方法进行同步;

另一种是synchronized语句,使用 synchronized关键字放在对象前面限制一段代码的执行。

先介绍synchronized方法:synchronized关键字修饰方法实现线程同步,方法所在的对象被锁定。

//synchronized方法
public class TicketDB {
//    机票数量
    private int ticketCount = 5;
//    获得当前机票数量
    public synchronized int getTicketCount(){
        return ticketCount;
    }
//    销售机票
    public synchronized void sellTicket(){
        try {
//            等于用户付款
//            等线程休眠
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("第%d号票已经售出\n",ticketCount);
        ticketCount--;
    }
}

再介绍synchronized语句:synchronized语句方式主要用于第三方类,不方便修改它的代码情况。

//synchronized语句
//测试代码
public class HelloWorld {
    public static void main(String[] args) {
        TicketDB ticketDB = new TicketDB();
//        创建线程
        Thread t1 = new Thread(() -> {
           while (true){
               synchronized (ticketDB) {
                   int currentTicketCount = ticketDB.getTicketCount();
//               查询是否有票
                   if (currentTicketCount > 0) {
                       ticketDB.sellTicket();
                   } else {
//                   无票退出
                       break;
                   }
               }
           }
        });
//       开始线程
        t1.start();
//        创建线程t2
        Thread t2 = new Thread(() ->{
           while (true) {
               synchronized (ticketDB) {
                   int currentTicketCount = ticketDB.getTicketCount();
//               查询是否有票
                   if (currentTicketCount > 0) {
                       ticketDB.sellTicket();
                   } else {
//                   无票退出
                       break;
                   }
               }
           }
        });
//        开始线程
        t2.start();
    }
}

测试代码:

第5号票已经售出
第4号票已经售出
第3号票已经售出
第2号票已经售出
第1号票已经售出

使用synchronized语句,将需要同步的代码用大括号括起来。synchronized 后有小括号,将需要同步的对象括起来。


相关文章
|
8小时前
|
安全 Java 调度
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第12天】 在现代软件开发中,多线程编程是提升应用程序性能和响应能力的关键手段之一。特别是在Java语言中,由于其内置的跨平台线程支持,开发者可以轻松地创建和管理线程。然而,随之而来的并发问题也不容小觑。本文将探讨Java并发编程的核心概念,包括线程安全策略、锁机制以及性能优化技巧。通过实例分析与性能比较,我们旨在为读者提供一套既确保线程安全又兼顾性能的编程指导。
|
8小时前
|
Java
Java中的多线程编程:基础知识与实践
【5月更文挑战第13天】在计算机科学中,多线程是一种使得程序可以同时执行多个任务的技术。在Java语言中,多线程的实现主要依赖于java.lang.Thread类和java.lang.Runnable接口。本文将深入探讨Java中的多线程编程,包括其基本概念、实现方法以及一些常见的问题和解决方案。
|
8小时前
|
安全 算法 Java
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第13天】 在Java开发中,并发编程是一个复杂且重要的领域。它不仅关系到程序的线程安全性,也直接影响到系统的性能表现。本文将探讨Java并发编程的核心概念,包括线程同步机制、锁优化技术以及如何平衡线程安全和性能。通过分析具体案例,我们将提供实用的编程技巧和最佳实践,帮助开发者在确保线程安全的同时,提升应用性能。
10 1
|
8小时前
|
Java 调度
Java一分钟之线程池:ExecutorService与Future
【5月更文挑战第12天】Java并发编程中,`ExecutorService`和`Future`是关键组件,简化多线程并提供异步执行能力。`ExecutorService`是线程池接口,用于提交任务到线程池,如`ThreadPoolExecutor`和`ScheduledThreadPoolExecutor`。通过`submit()`提交任务并返回`Future`对象,可检查任务状态、获取结果或取消任务。注意处理`ExecutionException`和避免无限等待。实战示例展示了如何异步执行任务并获取结果。理解这些概念对提升并发性能至关重要。
17 5
|
8小时前
|
Java
Java一分钟:线程协作:wait(), notify(), notifyAll()
【5月更文挑战第11天】本文介绍了Java多线程编程中的`wait()`, `notify()`, `notifyAll()`方法,它们用于线程间通信和同步。这些方法在`synchronized`代码块中使用,控制线程执行和资源访问。文章讨论了常见问题,如死锁、未捕获异常、同步使用错误及通知错误,并提供了生产者-消费者模型的示例代码,强调理解并正确使用这些方法对实现线程协作的重要性。
14 3
|
8小时前
|
安全 算法 Java
Java一分钟:线程同步:synchronized关键字
【5月更文挑战第11天】Java中的`synchronized`关键字用于线程同步,防止竞态条件,确保数据一致性。本文介绍了其工作原理、常见问题及避免策略。同步方法和同步代码块是两种使用形式,需注意避免死锁、过度使用导致的性能影响以及理解锁的可重入性和升级降级机制。示例展示了同步方法和代码块的运用,以及如何避免死锁。正确使用`synchronized`是编写多线程安全代码的核心。
57 2
|
8小时前
|
安全 Java 调度
Java一分钟:多线程编程初步:Thread类与Runnable接口
【5月更文挑战第11天】本文介绍了Java中创建线程的两种方式:继承Thread类和实现Runnable接口,并讨论了多线程编程中的常见问题,如资源浪费、线程安全、死锁和优先级问题,提出了解决策略。示例展示了线程通信的生产者-消费者模型,强调理解和掌握线程操作对编写高效并发程序的重要性。
43 3
|
8小时前
|
安全 Java
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第11天】在Java并发编程中,线程安全和性能优化是两个重要的主题。本文将深入探讨这两个方面,包括线程安全的基本概念,如何实现线程安全,以及如何在保证线程安全的同时进行性能优化。我们将通过实例和代码片段来说明这些概念和技术。
4 0
|
Java
Java多线程编程核心技术(三)多线程通信(下篇)
线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体。线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时还会使程序员对各线程任务在处理的过程中进行有效的把控与监督。
662 0
|
Java
Java多线程编程核心技术(三)多线程通信(上篇)
线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体。线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时还会使程序员对各线程任务在处理的过程中进行有效的把控与监督。
2529 0