1、使用关键字synchronized
-
一个对象使用synchronized关键字声明,则只有一个执行线程可访问它,如果其他线程试图访问,这些线程将会被挂起,直到第一个拥有的的线程执行完
-
当使用synchronized修饰一个对象的非静态方法时,当一个线程访问该方法时,其他线程不能访问该对象的其他被synchronized修饰的方法,但可以访问未被synchronized修饰的方法
-
当使用synchronized修饰静态方法时,该方法同时只能被同一线程访问,但其他线程可访问该对象的其他非静态方法
2、使用wait()
、notify()
和notifyAll()
-
wait()
方法可让线程挂起 -
notify()
和notifyAll()
用于唤醒线程 -
使用队列实现生产-消费者模型
public class WaitAndNotify { /** * 模拟存储的队列. */ @Data private class Storage { private int maxSize; private LinkedList<Integer> data; public Storage(int maxSize) { this.maxSize = maxSize; this.data = new LinkedList<>(); } /** * 添加数据. */ public synchronized void add(Integer data) { while (this.data.size() == maxSize) { try { System.out.println("队列已满"); wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("添加数据"); this.data.add(data); notifyAll(); } /** * 获取数据 */ public synchronized Integer get() { while (this.data.size() == 0) { try { System.out.println("队列已空"); wait(); } catch (InterruptedException e) { e.printStackTrace(); } } Integer result = this.data.poll(); System.out.println("获取数据: " + result); notifyAll(); return result; } } public void run() { Random random = new Random(); Storage storage = new Storage(20); Thread producer = new Thread(() -> { for (int i = 0; i < 100; i++) { int n = random.nextInt(100); storage.add(n); } }); Thread consumer = new Thread(() -> { for (int i = 0; i < 100; i++) { storage.get(); } }); producer.start(); consumer.start(); } public static void main(String[] args) { WaitAndNotify test = new WaitAndNotify(); test.run(); } }
3、使用Lock
实现同步
Java除了使用
synchronized
实现同步代码块外,还提供了另一种同步代码块机制,这种机制基于Lock
及其实现类(如:ReentrantLock)
-
Lock
与synchronized
的区别-
Lock
更加灵活:使用synchronized
关键字,只能在同一块synchronized
块结构中获取和释放。而Lock
可实现更复杂的临界结构,如获取和释放不再同一块结构中 -
Lock
提供了更多的功能:如tryLock()
-
Lock
接口允许读写分离操作,允许多个读线程和一个写线程 -
Lock
的性能更好
-
4、使用读写锁实现同步数据访问
接口
ReadWriteLock
和其唯一实现类ReentrantReadWriteLock
(该类有两个锁,分别为读操作锁和写操作锁)可实现读写锁
-
读写锁示例
public class ReadAndWriter { private int price1; private int price2; ReadWriteLock readWriteLock; public ReadAndWriter(int price1, int price2) { this.price1 = price1; this.price2 = price2; this.readWriteLock = new ReentrantReadWriteLock(); } /** * 获取数据(使用读操作锁) * @return */ public int getPrice1() { readWriteLock.readLock().lock(); int price = this.price1; readWriteLock.readLock().unlock(); return price; } /** * 获取数据(使用读操作锁) * @return */ public int getPrice2() { readWriteLock.readLock().lock(); int price = this.price2; readWriteLock.readLock().unlock(); return price; } /** * 设置数据(使用写操作锁) * @param price1 * @param price2 */ public void setPrices(int price1, int price2) { readWriteLock.writeLock().lock(); this.price1 = price1; this.price2 = price2; readWriteLock.writeLock().unlock(); } public static void main(String[] args) { ReadAndWriter readAndWriter = new ReadAndWriter(0, 1); Random random = new Random(); new Thread(() -> { for (int i = 0; i < 3; i++) { System.out.println("writer"); readAndWriter.setPrices(random.nextInt(10000), random.nextInt(10000)); System.out.println("modified"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); for (int i = 0; i < 5; i++) { new Thread(() -> { for (int j = 0; j < 5; j++) { System.out.println(Thread.currentThread().getName() + " : " + readAndWriter.getPrice1()); System.out.println(Thread.currentThread().getName() + " : " + readAndWriter.getPrice2()); } }).start(); } } }
5、使用Condition
一个锁可能关联一个或多个条件,这些条件通过
Condition
接口声明,Condition
接口提供了线程的挂起和唤醒机制
-
与锁绑定的条件对象都是通过
Lock
接口声明的newCondition()
方法创建的。 -
在使用条件时,必须拥有绑定的锁,即,所有带条件的代码必须在调用
Lock
对象的lock()
和unlock()
方法之间 -
当线程调用条件
await()
时,会自动释放绑定的锁 -
如果调用了条件
await()
,但没有调用他的signal()
,这个线程将会永远休眠