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

Posted leoliu168

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java并发编程之线程安全线程通信相关的知识,希望对你有一定的参考价值。

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关键字修饰类的实例方法(非static方法),可以实现线程安全类,即该类在多线程访问中,可以保证可变成员的数据一致性。

 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(可重入读写锁)。

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
",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
",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 }

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

 

以上是关于Java并发编程之线程安全线程通信的主要内容,如果未能解决你的问题,请参考以下文章

转:Java并发编程之十一:线程间通信中notify通知的遗漏(含代码)

Java并发多线程编程——集合类线程不安全之HashMap的示例及解决方案

Java并发编程之十二:线程间通信中notifyAll造成的早期通知问题(含代码)

转:Java并发编程之八:多线程环境中安全使用集合API(含代码)

Java并发多线程编程——集合类线程不安全之HashSet的示例及解决方案

Java并发多线程编程——集合类线程不安全之ArrayList的示例及解决方案