Java学习之线程同步与线程间通信详解

Java学习之线程同步与线程间通信详解

为什么需要线程同步和线程间通信

在多线程编程中,由于多个线程可能同时执行同一任务,可能会导致竞态条件(Race Condition)的出现,即数据被多个线程同时修改,从而导致程序运行出错。为了避免这种情况,需要通过线程同步机制来协调多个线程的共同操作。

而线程间通信则是线程同步机制的一种实现方式,它可以让线程之间传递消息或者其它数据,从而协调彼此的执行顺序。

关键字synchronized

Java中的synchronized关键字用于实现线程同步,可以用来修饰方法或者代码块。

当一个线程访问一个被synchronized修饰的方法或代码块时,需要获得对象的锁,如果此时锁被其它线程持有,则这个线程就会被阻塞等待锁的释放。只有获得锁的线程才能执行synchronized修饰的代码块,其它线程需要等待。

示例一:使用synchronized关键字实现线程同步

class Counter {
    private int count;

    public synchronized void increment() {
        count++;
    }

    public synchronized void decrement() {
        count--;
    }

    public int getCount() {
        return count;
    }
}

public class Main {
    public static void main(String[] args) {
        Counter counter = new Counter();

        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                counter.increment();
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                counter.decrement();
            }
        });

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(counter.getCount()); // 0
    }
}

上述代码中,Counter类表示一个计数器,increment()方法用于增加计数器的值,decrement()方法用于减少计数器的值。由于这两个方法被synchronized修饰,所以它们的访问是互斥的,即同一时间只有一个线程可以执行其中的方法,从而避免了竞态条件的出现。

在main方法中,我们创建了两个线程,分别用于对count变量进行加操作和减操作,最终输出的结果应该为0,证明线程同步机制有效地保证了count的正确性。

示例二:获得对象锁实现线程同步

除了对方法或代码块使用synchronized之外,还可以通过获得对象锁的方式实现线程同步。通过synchronized(obj)来获取obj对象的锁,从而执行同步代码块。

class PrintThread implements Runnable {
    private Object lock;

    public PrintThread(Object lock) {
        this.lock = lock;
    }

    public void run() {
        synchronized (lock) {
            for (int i = 0; i < 10; i++) {
                System.out.print(i + " ");
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Object lock = new Object();

        Thread thread1 = new Thread(new PrintThread(lock));
        Thread thread2 = new Thread(new PrintThread(lock));

        thread1.start();
        thread2.start();
    }
}

上述代码中,我们通过创建一个包含10个元素的数组,并将它传递给两个线程使用synchronized(lock)协调彼此的顺序。由于syncronized(lock)语句块的存在,两个线程的执行顺序被互相协调,最终输出结果为:

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 

线程间通信

在线程间通信中,涉及到两个关键方法wait()和notify()/notifyAll()。

wait()

wait()方法使当前线程进入等待状态,并释放持有的对象锁,直到其它线程调用了相同对象的notify()或notifyAll()方法来唤醒线程。

notify()/notifyAll()

notify()方法会随机唤醒一个正在等待相同对象锁的线程,而notifyAll()方法会唤醒所有正在等待相同对象锁的线程。

示例三:使用wait()和notify()实现简单线程间通信

class Producer implements Runnable {
    private List<Integer> buffer;

    public Producer(List<Integer> buffer) {
        this.buffer = buffer;
    }

    public void run() {
        for (int i = 0; i < 10; i++) {
            synchronized (buffer) {
                buffer.add(i);
                System.out.println("Producer produced " + i);
                buffer.notify();
            }

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Consumer implements Runnable {
    private List<Integer> buffer;

    public Consumer(List<Integer> buffer) {
        this.buffer = buffer;
    }

    public void run() {
        while (true) {
            synchronized (buffer) {
                if (!buffer.isEmpty()) {
                    int num = buffer.remove(0);
                    System.out.println("Consumer consumed " + num);
                } else {
                    try {
                        buffer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        List<Integer> buffer = new ArrayList<>();

        Thread producerThread = new Thread(new Producer(buffer));
        Thread consumerThread = new Thread(new Consumer(buffer));

        producerThread.start();
        consumerThread.start();
    }
}

上述代码中,我们通过创建一个包含10个元素的List,并交替地向其中添加和删除元素实现了基本的线程间通信。在Producer类中,我们通过synchronized(buffer)来获得对象锁,然后调用了buffer.notify()方法唤醒正在等待buffer对象锁的线程;在Consumer类中,我们通过synchronized(buffer)来获得对象锁,然后调用了buffer.wait()方法释放对象锁,并进入等待状态,等待其它线程调用notify()方法来唤醒它。

在Main方法中,我们创建了共享的buffer对象,并将它传递给Producer和Consumer线程,从而实现了两者之间的基本通信。

综合应用

线程同步和线程间通信是多线程编程中最基础和也是最关键的知识点,仅仅阅读理论是远远不够的,需要我们通过实际操作来掌握和理解。建议读者通过实践来加深对于线程同步和线程间通信这两个知识点的理解。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java学习之线程同步与线程间通信详解 - Python技术站

(0)
上一篇 2023年5月18日
下一篇 2023年5月18日

相关文章

  • 2种Java删除ArrayList中的重复元素的方法

    当我们使用ArrayList集合存储元素时,有时候需要删除其中的重复元素。为此我们需要使用一些适当的方法。以下是两种Java删除ArrayList中重复元素的方法: 方法一: 使用LinkedHashSet 借助LinkedHashSet的特性,我们可以很容易实现重复元素的删除。该方法具体分为以下步骤: 创建一个ArrayList对象并添加需要删除重复元素的…

    Java 2023年6月15日
    00
  • Struts2 漏洞分析及如何提前预防

    Struts2 是一个流行的 Java Web 应用程序框架,由于其广泛的应用和不断的开发,一些漏洞也逐渐被发现和修复。但是,攻击者仍然可以利用一些未经修补的漏洞对 Struts2 应用程序进行攻击。本文将详细讲解 Struts2 的漏洞及如何在应用程序中提前预防这些漏洞。 Struts2 漏洞分析 Struts2 漏洞的危害 Struts2 的漏洞可能会导…

    Java 2023年5月20日
    00
  • spring-transaction源码分析(1)概述和事务传播级别

    spring-tx概述 spring-tx包使用注解驱动和AOP通知将事务开启、提交/回滚、以及复杂的传播机制封装了起来,开发者不再需要编写事务管理的代码,而是可以只关注自己的业务逻辑。 本文将简单介绍spring-tx使用步骤以及七种事务传播级别。 后续文章会阅读源码,深入分析spring-tx aop通知、七种事务传播级别以及事务开启/提交/回滚的实现方…

    Java 2023年5月6日
    00
  • 关于SpringBoot的热部署方案

    关于Spring Boot的热部署方案,可以分为以下两种方式: 1. 使用Spring Dev Tools Spring Dev Tools是一套由Spring官方开发维护的工具集,其中包含了实现热部署的功能。在Spring Boot的项目中使用Spring Dev Tools,只需要在pom.xml中引入以下依赖即可: <dependency>…

    Java 2023年5月31日
    00
  • Java实现简单通讯录管理系统

    Java实现简单通讯录管理系统的完整攻略包含以下步骤: 1. 需求分析 首先要了解用户的需求,确定要开发哪些功能并对其进行分析及设计。通讯录管理系统需要实现的功能如下: 添加联系人 查询联系人 修改联系人 删除联系人 显示所有联系人 2. 数据存储 数据存储是通讯录管理系统的核心,因此需要确定使用哪种方式来存储联系人信息。可以选择文件存储、数据库存储或者内存…

    Java 2023年5月23日
    00
  • Java String字符串内容实现添加双引号

    Java String 类是一个经常使用的类, 它可用于存储字符串。但有时候我们需要在字符串中添加双引号,本文将为您介绍Java中添加双引号的几种方法。 方法一:手动添加双引号 如果只需要添加一个双引号,可以直接手动在字符串两侧添加双引号,如下所示: String str = "\"Hello World!\""; S…

    Java 2023年5月26日
    00
  • 浅谈Springboot之于Spring的优势

    浅谈Spring Boot之于Spring的优势攻略 简介 Spring Boot是一种基于Spring框架的快速开发框架。相对于Spring框架,Spring Boot可以更快速地构建和部署Spring应用程序。本文将介绍使用Spring Boot构建应用程序相对于使用Spring框架构建的优势,并提供一些示例。 优势 快速构建应用程序 Spring Bo…

    Java 2023年5月15日
    00
  • 详解使用Spring MVC统一异常处理实战

    下面我将为您讲解一下使用 Spring MVC 统一异常处理的完整攻略。 一、概述 在开发过程中,我们经常会遇到各种异常情况,如空指针、数据库连接超时、网络异常等。如果不加处理直接让这些异常直接抛出,会给用户带来不好的用户体验。因此,我们需要对这些异常进行统一处理,以便更好的提示给用户。 Spring MVC 提供了一种统一处理异常的方式,即通过定义一个异常…

    Java 2023年5月27日
    00
合作推广
合作推广
分享本页
返回顶部