您的位置:首页 > 其它

线程同步基础

2015-08-19 15:22 288 查看
两种基本的同步机制:synchronized与锁

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;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: