线程同步基础
2015-08-19 15:22
288 查看
两种基本的同步机制:synchronized与锁
1)Lock接口允许更加复杂的结构,synchronized关键字必须要用结构化的方法来获取或者释放同步代码块;
2)Lock接口提供了一些额外的功能。例如tryLock()方法。
3)当只有一个写和多个读的线程时,Lock接口允许读写操作的分离
4)Lock接口的性能更高
1. 同步方法
每一个被synchronized方法就是一个关键代码段,在同一时间,Java只能执行某个对象的其中一个关键代码块。然而,静态方法的行为不同,只有一个执行线程能够访问所有synchronized的静态方法中的一个,但是另一个线程可以访问该类的某个对象的非静态方法。也就是说,如果有两个同步方法,一个是静态的,另一个是非静态的,则两个线程是可以同时分别访问两个方法的。这一原则有可能导致两个方法去修改同一数据。例如,class Account { public static double balance; public static synchronized void addAmount(double amount) { balance += amount; } public synchronized void substractAmount(double amount) { balance -= amount; } }
2. 不同属性用不同对象同步
通常地,this关键字会被用来作为同步代码块的控制变量,但是也可以用其他的对象引用作为同步代码块的关键字。例如,用两个对象类分别作为某个类的两个属性的同步控制变量,这样两个线程可以同时独立地访问两个属性。class Cinema { private long vacanciesCinema1; private long vacanciesCinema2; private final Object controlCinema1; private final Object controlCinema2; public Cinema() { controlCinema1 = new Object(); controlCinema2 = new Object(); vacanciesCinema1 = 20; vacanciesCinema2 = 20; } public boolean sellTickets1(long number) { synchronized (controlCinema1) { if (number <= vacanciesCinema1) { vacanciesCinema1 -= number; return true; } else { return false; } } } public boolean sellTickets2(long number) { synchronized (controlCinema2) { if (number <= vacanciesCinema2) { vacanciesCinema2 -= number; return true; } else { return false; } } } public long getVacanciesCinema1() { return vacanciesCinema1; } public long getVacanciesCinema2() { return vacanciesCinema2; } }
3. wait/notify/notifyAll
在生产者-消费者模型中,通常用一个buffer作为共享的数据结构,当buffer满的时候,生产者不能再放入数据;当buffer空的时候,消费者不能取数据。对于以上两种情况,java在Object类中提供了wait notify notifyAll方法。wait方法需要在同步代码块中被调用,否则,将会抛出IllegalMonitorStateException。 当被调用时,JVM将会使当前线程sleep,同时释放控制同步代码块的对象锁,必须使用nofity或者notifyAll来唤醒该线程。class EventStorage { int maxSize; LinkedList<Date> storage; public EventStorage() { maxSize = 10; storage = new LinkedList<>(); } public synchronized void set() { while (storage.size() == maxSize) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } storage.offer(new Date()); System.out.printf("Set: %d\n", storage.size()); notifyAll(); } public synchronized void get() { while (storage.size() == 0) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } storage.poll(); System.out.printf("Get: %d\n", storage.size()); notifyAll(); } }
4. Lock
Java提供了另一种同步代码块的机制,它是基于Lock接口和它的实现类(例如ReentrantLock)来实现的,这种机制更加强大和灵活,主要的优点表现在:1)Lock接口允许更加复杂的结构,synchronized关键字必须要用结构化的方法来获取或者释放同步代码块;
2)Lock接口提供了一些额外的功能。例如tryLock()方法。
3)当只有一个写和多个读的线程时,Lock接口允许读写操作的分离
4)Lock接口的性能更高
class PrintQueue { private final Lock queueLock = new ReentrantLock(); public void printJob(Object document) { queueLock.lock(); try { long duration = (long)(Math.random()*10000); System.out.println(Thread.currentThread().getName() + ": PrintQueue: Printing a Job during " + (duration/1000) + " seconds"); Thread.sleep(duration); } catch (InterruptedException e) { e.printStackTrace(); } finally { queueLock.unlock(); } } } class Job implements Runnable { private PrintQueue printQueue; public Job(PrintQueue printQueue) { this.printQueue = printQueue; } @Override public void run() { System.out.printf("%s: Going to print a document\n", Thread.currentThread().getName()); printQueue.printJob(new Object()); System.out.printf("%s: The document has been printed\n", Thread.currentThread().getName()); } }
5. 读写锁
ReadWriteLock是所有Lock接口中最重要的接口之一,ReentrentReadWriteLock是它的唯一实现类。该类有两个锁,一个是读操作另一个是写操作。它能够同时包括多个读操作,但是只能有一个写操作。当某个线程执行写操作时,其他任何线程都不能执行读操作。class PricesInfo { private double price1; private double price2; private ReadWriteLock lock; public PricesInfo() { price1 = 1.0; price2 = 2.0; lock = new ReentrantReadWriteLock(); } public double getPrice1() { lock.readLock().lock(); double value = price1; lock.readLock().unlock(); return value; } public double getPrice2() { lock.readLock().lock(); double value = price2; lock.readLock().unlock(); return value; } public void setPrices(double price1, double price2) { lock.writeLock().lock(); this.price1 = price1; this.price2 = price2; lock.writeLock().unlock(); } }
6. 公平锁
ReentrantLock和ReentrantReadWriteLock的构造函数可以传入一个boolean型的参数fair,该参数默认为false,如果设置为true,则在多个线程等待同一个锁时,选择等待时间最长的线程优先执行。class MessageQueue implements MyQueue { private Lock queueLock = new ReentrantLock(true); public void printJob(Object document) { queueLock.lock(); try { long duration = (long)(Math.random()*10000); System.out.println(Thread.currentThread().getName() + ": PrintQueue: Printing a Job during " + (duration/1000) + " seconds"); Thread.sleep(duration); } catch (InterruptedException e) { e.printStackTrace(); } finally { queueLock.unlock(); } queueLock.lock(); try { long duration = (long)(Math.random()*10000); System.out.println(Thread.currentThread().getName() + ": PrintQueue: Printing a Job during " + (duration/1000) + " seconds"); Thread.sleep(duration); } catch (InterruptedException e) { e.printStackTrace(); } finally { queueLock.unlock(); } } }
7. 一个锁的多个condition
class Buffer { private LinkedList<String> buffer; private int maxSize; private ReentrantLock lock; private Condition lines; private Condition space; private boolean pendingLines; public Buffer(int maxSize) { this.maxSize = maxSize; buffer = new LinkedList<>(); lock = new ReentrantLock(); lines = lock.newCondition(); space = lock.newCondition(); pendingLines = true; //是否还会有lines insert到Buffer中 } public void insert(String line) { lock.lock(); try { while(buffer.size() == maxSize) { space.wait(); } buffer.offer(line); System.out.printf("%s: Inserted Line: %d\n", Thread.currentThread().getName(), buffer.size()); lines.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public String get() { String line = null; lock.lock(); try { while ((buffer.size() == 0) && hasPendingLines()) { lines.await(); } if(hasPendingLines()) { line = buffer.poll(); System.out.printf("%s: Line Readed: %d\n", Thread.currentThread().getName(), buffer.size()); space.signalAll(); } } catch(InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } return line; } public boolean hasPendingLines() { return pendingLines || buffer.size() > 0; } public void setPendingLines(boolean pendingLines) { this.pendingLines = pendingLines; } }
相关文章推荐
- bjfu 上这道题我不知道是放在dp里还是放在dfs里。。。
- [LeetCode] Linked List Cycle
- 视图、索引、存储过程优缺点
- php基础语法
- Redis常用的命令(二)------key的基本操作
- 基于system V共享内存学习
- SQL Server里ORDER BY的歧义性
- leetcode-38 Count And Say
- scanf()
- hdu 5402 Travelling Salesman Problem 模拟构造
- How to Use NSLog to Debug CGRect and CGPoint
- Java内存分析
- aauto学习系列之<10>名字空间
- Redis集群进阶-故障转移测试
- BPM那些事儿——BPM与SOA的演进与展望
- caffe 在已有模型上继续训练
- UVA1426-Add Bricks in The Wall
- Android Message和obtainMessage的区别
- ZooKeeper - O'Reilly Media ----Zookeeper Internals (1)
- hust Distinct Primes