Java并发编程之线程安全、线程通信

简介: Java多线程开发中最重要的一点就是线程安全的实现了。所谓Java线程安全,可以简单理解为当多个线程访问同一个共享资源时产生的数据不一致问题。为此,Java提供了一系列方法来解决线程安全问题。synchronizedsynchronized用于同步多线程对共享资源的访问,在实现中分为同步代码块和同步方法两种。

Java多线程开发中最重要的一点就是线程安全的实现了。所谓Java线程安全,可以简单理解为当多个线程访问同一个共享资源时产生的数据不一致问题。为此,Java提供了一系列方法来解决线程安全问题。

synchronized

synchronized用于同步多线程对共享资源的访问,在实现中分为同步代码块和同步方法两种。

同步代码块

 1 public class DrawThread extends Thread {
 2     
 3     private Account account;
 4     private double drawAmount;
 5     public DrawThread(String name, Account account, double drawAmount) {
 6         super(name);
 7         this.account = account;
 8         this.drawAmount = drawAmount;
 9     }
10     @Override
11     public void run() {
12         //使用account作为同步代码块的锁对象
13         synchronized(account) {
14             if (account.getBalance() >= drawAmount) {
15                 System.out.println(getName() + "取款成功, 取出:" + drawAmount);
16                 try {
17                     TimeUnit.MILLISECONDS.sleep(1);
18                 } catch (InterruptedException e) {
19                     e.printStackTrace();
20                 }
21                 account.setBalance(account.getBalance() - drawAmount);
22                 System.out.println("余额为: " + account.getBalance());
23             } else {
24                 System.out.println(getName() + "取款失败!余额不足!");
25             }
26         }
27     }
28 }

同步方法

使用同步方法,即使用synchronized关键字修饰类的实例方法或类方法,可以实现线程安全类,即该类在多线程访问中,可以保证可变成员的数据一致性。

同步方法中,隐式的锁对象由锁的是实例方法还是类方法确定,分别为该类对象或类的Class对象。

 1 public class SyncAccount {
 2     private String accountNo;
 3     private double balance;
 4     //省略构造器、getter setter方法
 5     //在一个简单的账户取款例子中, 通过添加synchronized的draw方法, 把Account类变为一个线程安全类
 6     public synchronized void draw(double drawAmount) {
 7         if (balance >= drawAmount) {
 8             System.out.println(Thread.currentThread().getName() + "取款成功, 取出:" + drawAmount);
 9             try {
10                 TimeUnit.MILLISECONDS.sleep(1);
11             } catch (InterruptedException e) {
12                 e.printStackTrace();
13             }
14             balance -= drawAmount;
15             System.out.println("余额为: " + balance);
16         } else {
17             System.out.println(Thread.currentThread().getName() + "取款失败!余额不足!");
18         }
19     }
20     //省略HashCode和equals方法
21 }

同步锁(Lock、ReentrantLock)

Java5新增了两个用于线程同步的接口Lock和ReadWriteLock,并且分别提供了两个实现类ReentrantLock(可重入锁)和ReentrantReadWriteLock(可重入读写锁)。

相比较synchronized,ReentrantLock的一些优势功能:

1. 等待可中断:指持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待。

2. 公平锁:多个线程等待同一个锁时,必须按照申请锁的时间顺序依次获取。synchronized是非公平锁,ReentrantLock可以通过参数设置为公平锁

3. 多条件锁:ReentrantLock可通过Condition类获取多个条件关联

Java 1.6以后,synchronized性能提升较大,因此一般的开发中依然建议使用语法层面上的synchronized加锁。

Java8新增了更为强大的可重入读写锁StampedLock类。

比较常用的是ReentrantLock类,可以显示地加锁、释放锁。下面使用ReentrantLock重构上面的SyncAccount类。

 1 public class RLAccount {
 2     //定义锁对象
 3     private final ReentrantLock lock = new ReentrantLock();
 4     private String accountNo;
 5     private double balance;
 6     //省略构造方法和getter setter
 7     public void draw(double drawAmount) {
 8         //加锁
 9         lock.lock();
10         try {
11             if (balance >= drawAmount) {
12                 System.out.println(Thread.currentThread().getName() + "取款成功, 取出:" + drawAmount);
13                 try {
14                     TimeUnit.MILLISECONDS.sleep(1);
15                 } catch (InterruptedException e) {
16                     e.printStackTrace();
17                 }
18                 balance -= drawAmount;
19                 System.out.println("余额为: " + balance);
20             } else {
21                 System.out.println(Thread.currentThread().getName() + "取款失败!余额不足!");
22             } 
23         } finally {
24             //通过finally块保证释放锁
25             lock.unlock();
26         }
27     }
28 }

死锁

当两个线程相互等待地方释放锁的时候,就会产生死锁。关于死锁和线程安全的深入分析,将另文介绍。

线程通信方式之wait、notify、notifyAll

Object类提供了三个用于线程通信的方法,分别是wait、notify和notifyAll。这三个方法必须由同步锁对象来调用,具体来说:

1. 同步方法:因为同步方法默认使用所在类的实例作为锁,即this,可以在方法中直接调用。

2. 同步代码块:必须由锁来调用。

wait():导致当前线程等待,直到其它线程调用锁的notify方法或notifyAll方法来唤醒该线程。调用wait的线程会释放锁。

notify():唤醒任意一个在等待的线程

notifyAll():唤醒所有在等待的线程

 1 /*
 2  * 通过一个生产者-消费者队列来说明线程通信的基本使用方法
 3  * 注意: 假如这里的判断条件为if语句,唤醒方法为notify, 那么如果分别有多个线程操作入队\出队, 会导致线程不安全.
 4  */
 5 public class EventQueue {
 6     
 7     private final int max;
 8     
 9     static class Event{
10         
11     }
12     //定义一个不可改的链表集合, 作为队列载体
13     private final LinkedList<Event> eventQueue = new LinkedList<>();
14     
15     private final static int DEFAULT_MAX_EVENT = 10;
16     
17     public EventQueue(int max) {
18         this.max = max;
19     }
20     
21     public EventQueue() {
22         this(DEFAULT_MAX_EVENT);
23     }
24     
25     private void console(String message) {
26         System.out.printf("%s:%s\n",Thread.currentThread().getName(), message);
27     }
28     //定义入队方法
29     public void offer(Event event) {
30         //使用链表对象作为锁
31         synchronized(eventQueue) {
32             //在循环中判断如果队列已满, 则调用锁的wait方法, 使线程阻塞
33             while(eventQueue.size() >= max) {
34                 try {
35                     console(" the queue is full");
36                     eventQueue.wait();
37                 } catch (InterruptedException e) {
38                     e.printStackTrace();
39                 }
40             }
41             console(" the new event is submitted");
42             eventQueue.addLast(event);
43             this.eventQueue.notifyAll();
44         }
45     }
46     //定义出队方法
47     public Event take() {
48         //使用链表对象作为锁
49         synchronized(eventQueue) {
50             //在循环中判断如果队列已空, 则调用锁的wait方法, 使线程阻塞
51             while(eventQueue.isEmpty()) {
52                 try {
53                     console(" the queue is empty.");
54                     eventQueue.wait();
55                 } catch (InterruptedException e) {
56                     e.printStackTrace();
57                 }
58             }
59             Event event = eventQueue.removeFirst();
60             this.eventQueue.notifyAll();
61             console(" the event " + event + " is handled/taked.");
62             return event;
63         }
64     }
65 }

线程通信方式之Condition

如果使用的是Lock接口实现类来同步线程,就需要使用Condition类的三个方法实现通信,分别是await、signal和signalAll,使用上与Object类的通信方法基本一致。

 1 /*
 2  * 使用Lock接口和Condition来实现生产者-消费者队列的通信
 3  */
 4 public class ConditionEventQueue {
 5     //显示定义Lock对象
 6     private final Lock lock = new ReentrantLock();
 7     //通过newCondition方法获取指定Lock对象的Condition实例
 8     private final Condition cond = lock.newCondition();
 9     private final int max;
10     static class Event{ }
11     //定义一个不可改的链表集合, 作为队列载体
12     private final LinkedList<Event> eventQueue = new LinkedList<>();
13     private final static int DEFAULT_MAX_EVENT = 10;
14     public ConditionEventQueue(int max) {
15         this.max = max;
16     }
17     
18     public ConditionEventQueue() {
19         this(DEFAULT_MAX_EVENT);
20     }
21     
22     private void console(String message) {
23         System.out.printf("%s:%s\n",Thread.currentThread().getName(), message);
24     }
25     //定义入队方法
26     public void offer(Event event) {
27             lock.lock();
28             try {
29                 //在循环中判断如果队列已满, 则调用cond的wait方法, 使线程阻塞
30                 while (eventQueue.size() >= max) {
31                     try {
32                         console(" the queue is full");
33                         cond.await();
34                     } catch (InterruptedException e) {
35                         e.printStackTrace();
36                     }
37                 }
38                 console(" the new event is submitted");
39                 eventQueue.addLast(event);
40                 cond.signalAll();;
41             } finally {
42                 lock.unlock();
43             }
44         
45     }
46     //定义出队方法
47     public Event take() {
48             lock.lock();
49             try {
50                 //在循环中判断如果队列已空, 则调用cond的wait方法, 使线程阻塞
51                 while (eventQueue.isEmpty()) {
52                     try {
53                         console(" the queue is empty.");
54                         cond.wait();
55                     } catch (InterruptedException e) {
56                         e.printStackTrace();
57                     }
58                 }
59                 Event event = eventQueue.removeFirst();
60                 cond.signalAll();
61                 console(" the event " + event + " is handled/taked.");
62                 return event;
63             } finally {
64                 lock.unlock();
65             }
66     }
67 }

Java 1.5开始就提供了BlockingQueue接口,来实现如上所述的生产者-消费者线程同步工具。具体介绍将另文说明。

 

目录
相关文章
|
3天前
|
IDE Java 物联网
《Java 简易速速上手小册》第1章:Java 编程基础(2024 最新版)
《Java 简易速速上手小册》第1章:Java 编程基础(2024 最新版)
10 0
|
4天前
|
安全 Java
深入理解 Java 多线程和并发工具类
【4月更文挑战第19天】本文探讨了Java多线程和并发工具类在实现高性能应用程序中的关键作用。通过继承`Thread`或实现`Runnable`创建线程,利用`Executors`管理线程池,以及使用`Semaphore`、`CountDownLatch`和`CyclicBarrier`进行线程同步。保证线程安全、实现线程协作和性能调优(如设置线程池大小、避免不必要同步)是重要环节。理解并恰当运用这些工具能提升程序效率和可靠性。
|
4天前
|
安全 Java 开发者
Java并发编程:深入理解Synchronized关键字
【4月更文挑战第19天】 在Java多线程编程中,为了确保数据的一致性和线程安全,我们经常需要使用到同步机制。其中,`synchronized`关键字是最为常见的一种方式,它能够保证在同一时刻只有一个线程可以访问某个对象的特定代码段。本文将深入探讨`synchronized`关键字的原理、用法以及性能影响,并通过具体示例来展示如何在Java程序中有效地应用这一技术。
|
4天前
|
安全 Java
java多线程(一)(火车售票)
java多线程(一)(火车售票)
|
5天前
|
安全 Java 调度
Java并发编程:深入理解线程与锁
【4月更文挑战第18天】本文探讨了Java中的线程和锁机制,包括线程的创建(通过Thread类、Runnable接口或Callable/Future)及其生命周期。Java提供多种锁机制,如`synchronized`关键字、ReentrantLock和ReadWriteLock,以确保并发访问共享资源的安全。此外,文章还介绍了高级并发工具,如Semaphore(控制并发线程数)、CountDownLatch(线程间等待)和CyclicBarrier(同步多个线程)。掌握这些知识对于编写高效、正确的并发程序至关重要。
|
5天前
|
安全 Java 程序员
Java中的多线程并发编程实践
【4月更文挑战第18天】在现代软件开发中,为了提高程序性能和响应速度,经常需要利用多线程技术来实现并发执行。本文将深入探讨Java语言中的多线程机制,包括线程的创建、启动、同步以及线程池的使用等关键技术点。我们将通过具体代码实例,分析多线程编程的优势与挑战,并提出一系列优化策略来确保多线程环境下的程序稳定性和性能。
|
6天前
|
缓存 分布式计算 监控
Java并发编程:深入理解线程池
【4月更文挑战第17天】在Java并发编程中,线程池是一种非常重要的技术,它可以有效地管理和控制线程的执行,提高系统的性能和稳定性。本文将深入探讨Java线程池的工作原理,使用方法以及在实际开发中的应用场景,帮助读者更好地理解和使用Java线程池。
|
6天前
|
Java API 数据库
深研Java异步编程:CompletableFuture与反应式编程范式的融合实践
【4月更文挑战第17天】本文探讨了Java中的CompletableFuture和反应式编程在提升异步编程体验上的作用。CompletableFuture作为Java 8引入的Future扩展,提供了一套流畅的链式API,简化异步操作,如示例所示的非阻塞数据库查询。反应式编程则关注数据流和变化传播,通过Reactor等框架实现高度响应的异步处理。两者结合,如将CompletableFuture转换为Mono或Flux,可以兼顾灵活性和资源管理,适应现代高并发环境的需求。开发者可按需选择和整合这两种技术,优化系统性能和响应能力。
|
6天前
|
存储 安全 Java
Java中的容器,线程安全和线程不安全
Java中的容器,线程安全和线程不安全
13 1
|
17天前
|
安全 Java 调度
深入理解Java中的线程安全与锁机制
【4月更文挑战第6天】 在并发编程领域,Java语言提供了强大的线程支持和同步机制来确保多线程环境下的数据一致性和线程安全性。本文将深入探讨Java中线程安全的概念、常见的线程安全问题以及如何使用不同的锁机制来解决这些问题。我们将从基本的synchronized关键字开始,到显式锁(如ReentrantLock),再到读写锁(ReadWriteLock)的讨论,并结合实例代码来展示它们在实际开发中的应用。通过本文,读者不仅能够理解线程安全的重要性,还能掌握如何有效地在Java中应用各种锁机制以保障程序的稳定运行。

热门文章

最新文章