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

来源:互联网 发布:贵州大数据发展 编辑:程序博客网 时间:2024/05/22 04:36

涉及的内容

  • 同步一个方法
  • 同步类中分配一个独立属性
  • 在同步代码中使用条件
  • 使用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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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、注意避免死锁