您的位置:首页 > 其它

第二章 Basic Thread Synchronization (基础线程同步) 【上】

2017-08-12 15:50 597 查看
涉及的内容

同步一个方法
同步类中分配一个独立属性
在同步代码中使用条件
使用Lock锁定代码块
同步数据的读写锁
修改Lock公平模式
在Lock中使用多条件

简介

同步类似就是车过收费站,一杆一车,排队出收费站,不许并行。

对于同步的代码块称为critical section (临界断面)

为了实现这个同步将会介绍两种同步方法:

关键字:synchronized
Lock的接口和它的实现类

1、同步方法(synchronized)

注意:对于一个类有个static的同步方法和非static方法,两个线程可以同时进入同一对象不同方法,如果同时修改同一个数据,将会产生数据不一致。

例子:模拟取存款过程

public class Account {
private double balance;

public double getBalance() {
return balance;
}

public void setBalance(double balance) {
this.balance = balance;
}

public synchronized void addAmount(double amount){
System.out.printf("增加了金额++++++:%f\n", amount);
double tmp = balance;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
tmp += amount;
balance=tmp;
}

public synchronized void substractAmount(double amount){
System.out.printf("减少了金额------:%f\n", amount);
double tmp = balance;
try {
Thread.sleep(10);
} catch (InterruptedException e){
e.printStackTrace();
}
tmp -= amount;
balance = tmp;
}

}

public class Bank implements Runnable{

private Account account;

public Bank(Account account) {
super();
this.account = account;
}

@Override
public void run() {
for (int i=0; i< 10; i++){
account.substractAmount(1000);
}
}

}

public class Company implements Runnable{

private Account account;

public Company(Account account) {
super();
this.account = account;
}

@Override
public void run() {
for(int i=0; i<10; i++){
account.addAmount(1000);
}
}

}

public class Main {
public static void main(String[] args){
Account account = new Account();
account.setBalance(1000);
Company company = new Company(account);
Thread companyThread = new Thread(company);
Bank bank = new Bank(account);
Thread bankThread = new Thread(bank);

System.out.printf("账户:初始化金额: %f\n", account.getBalance());
companyThread.start();
bankThread.start();
try{
companyThread.join();
bankThread.join();
System.out.printf("账户:最终金额:%f\n", account.getBalance());
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
日志:



总结:

1、synchronized进行同步操作保证最后的结果是准确的。
2、你可以删除synchronized测试结果。

2、在同步类分配独立属性

模拟电影院两个放映室和两个购票处

public class Cinema {
private long vacanciesCinema1;
private long vacanciesCinema2;

private final Object controlCinema1, controlCinema2;

/**
* 初始20张票
*/
public Cinema(){
controlCinema1 = new Object();
controlCinema2 = new Object();
vacanciesCinema1 = 20;
vacanciesCinema2 = 20;
}

/**
* 售票
* @param number
* @return
*/
public boolean sellTickets1(int number){
synchronized (controlCinema1){
if(number < vacanciesCinema1){
vacanciesCinema1 -= number;
return true;
} else {
return false;
}
}
}

/**售票
* @param number
* @return
*/
public boolean sellTickets2 (int number){
synchronized (controlCinema2){
if(number < vacanciesCinema2){
vacanciesCinema2 -= number;
return true;
} else {
return false;
}
}
}

/**
* 退票
* @param number
* @return
*/
public boolean returnTicket1 (int number){
synchronized (controlCinema1){
vacanciesCinema1 += number;
return true;
}
}
/**
* 退票
* @param number
* @return
*/
public boolean returnTicket2 (int number){
synchronized (controlCinema2){
vacanciesCinema2 += number;
return true;
}
}

/**
* 返回余票
* @return
*/
public long getVacanciesCinema1(){
return vacanciesCinema1;
}

/**
* 返回余票
* @return
*/
public long getVacanciesCinema2(){
return vacanciesCinema2;
}
}

public class TicketOffice1 implements Runnable{

private Cinema cinema;

public TicketOffice1(Cinema cinema) {
super();
this.cinema = cinema;
}

@Override
public void run() {
cinema.sellTickets1(3);
cinema.sellTickets1(2);
cinema.sellTickets2(2);
cinema.returnTicket1(3);
/*cinema.sellTickets1(5);
cinema.sellTickets2(2);
cinema.sellTickets2(2);
cinema.sellTickets2(2);*/
}

}
public class TicketOffice2 implements Runnable{

private Cinema cinema;

public TicketOffice2(Cinema cinema) {
super();
this.cinema = cinema;
}

@Override
public void run() {
cinema.sellTickets2(3);
cinema.sellTickets2(4);
/*cinema.sellTickets1(2);
cinema.sellTickets1(1);
cinema.returnTicket2(2);
cinema.sellTickets1(3);
cinema.sellTickets2(2);
cinema.sellTickets1(2);*/
}

}

public class Main {
public static void main(String[] args){
Cinema cinema = new Cinema();
TicketOffice1 ticketOffice1 = new TicketOffice1(cinema);
Thread thread1 = new Thread(ticketOffice1, "TicketOffice1");
TicketOffice2 ticketOffice2 = new TicketOffice2(cinema);
Thread thread2 = new Thread(ticketOffice2, "TicketOffice2");

thread1.start();
thread2.start();

try{
thread1.join();
thread2.join();

}catch (InterruptedException e){
e.printStackTrace();
}
System.out.printf("放映室1空闲的数量:%d\n", cinema.getVacanciesCinema1());
System.out.printf("放映室2空闲的数量:%d\n", cinema.getVacanciesCinema2());
}
}


总结:
1、创建的controlCinema1和controlCinema2并没有实际的作用,它只是作为关联锁定花括号的代码,

(说白了就是如果锁定controlCinema2代码中所有对象,每次只一个线程访问这些对象)。

3、在同步代码中使用条件

生产者-消费者模型(wait() notify() notifyAll())

package com.jack;

import java.util.Date;
import java.util.LinkedList;
import java.util.List;

public class EventStorage {
private int maxSize;
private List<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.add(new Date());
System.out.printf("++当然前仓库: %d\n",storage.size());
notifyAll();
}

public synchronized void get(){
while (storage.size()==0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.printf("--当然前仓库: %d: %s\n",storage.
size(),((LinkedList<?>)storage).poll());
notifyAll();
}
}

package com.jack;

public class Producer implements Runnable{

private EventStorage storage;

public Producer(EventStorage storage) {
super();
this.storage = storage;
}

@Override
public void run() {
for (int i=0; i <100; i++){
storage.set();
}
}

}

package com.jack;

public class Consumer implements Runnable{
private EventStorage storage;

public Consumer(EventStorage storage) {
super();
this.storage = storage;
}

@Override
public void run() {
for (int i=0; i<100; i++){
storage.get();
}
}

}

package com.jack;

public class Main {
public static void main(String[] args){
EventStorage storage = new EventStorage();
Producer producer = new Producer(storage);
Thread thread1 = new Thread(producer);

Consumer consumer = new Consumer(storage);
Thread thread2 = new Thread(consumer);

thread2.start();
thread1.start();

}
}


总结:

1、创建一个仓库类,有添加和删除,创建生产线程和消费线程。当等于10时候等待,唤醒对方。
2、wait等待,notifyAll()唤醒所有线程

4、采用Lock锁定同步块

Lock优点(ReentrantLock)

1、允许同步块更加灵活。
2、Lock接口比synchronized提供额外的功能,增加tryLock()返回同步块的状态
3、Lock允许读写分离
4、Lock性能比synchronized关键字更优。

例子:使用Lock同步代码块模拟打印队列

package com.jack;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class PrintQueue {
private final Lock queueLock = new ReentrantLock();

public void printJob(Object document){
queueLock.lock();
try {
Long duration = (long) (Math.random()*10000);
System.out.printf(Thread.currentThread().getName()
+ ":打印队列:打印一个工作持续时间 %s ",(duration/1000)
+ " seconds");
Thread.sleep(duration);
} catch (InterruptedException e){
e.printStackTrace();
}finally{
queueLock.unlock();
}
}
}

package com.jack;

public class Job implements Runnable{

private PrintQueue printQueue;

public Job(PrintQueue printQueue) {
super();
this.printQueue = printQueue;
}

@Override
public void run() {
System.out.printf("%s:去打印一个文档\n", Thread.currentThread().getName());
printQueue.printJob(new Object());
System.out.printf("%s: 这个文档已经打印了\n", Thread.currentThread().getName());
}

}

package com.jack;

public class Main {
public static void main(String[] args){
PrintQueue printQueue = new PrintQueue();
Thread thread[] = new Thread[10];
for (int i=0; i<10; i++){
thread[i]= new Thread(new Job(printQueue), "线程  " + i);
}

for(int i=0; i<10; i++){
thread[i].start();
}
}
}


总结:

1、private final Lock queueLock = new ReentrantLock(); 是关键。首先为该类配一个锁
2、lock()方法锁定的意思,第一个进来之后锁住,直到执行完,(类似卵子受精就是这个例子)
3、ReentrantLock允许递归调用
4、注意避免死锁
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: