第二章 Basic Thread Synchronization (基础线程同步) 【上】
程序员文章站
2022-06-01 11:09:59
...
涉及的内容
- 同步一个方法
- 同步类中分配一个独立属性
- 在同步代码中使用条件
- 使用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、注意避免死锁
下一篇: 图片添加文字水印,自动换行,左右留白