您的位置:首页 > 编程语言 > Java开发

java并发编程之线程同步基础(一)

2016-12-19 13:29 295 查看

1.简介

多个执行线程共享一个资源的情景 是最常见的并发编程常见之一 在并发应用中常常遇到这样的的场景  多个线程读或者写相同的数据 或者访问相同的文件或数据库连接。为了防止这些共享资源可能出现的错误或者数据不一致 我们必须实现一些机制来防止这些错误的发生 为了解决这些问题 人们引入了临界区 临界区是一个用以访问共享资源的代码块 这个代码块在同一个时间内只允许一个线程执行
java语言提供了两种基本同步机制
synchronized关键字机制
Lock接口及其实现机制

1.1:使用synchronized实现同步方法
   每一个用synchronized关键字声明的方法都是临界区 在java中 同一个对象的临界区 在同一时间只有一个允许被访问
   注意:静态方法则不一样  用synchronized关键字声明的静态方法 同时只能被一个执行线程访问 但是其他线程可以访问这个对象的非静态方法 。因为两个线程可以同时访问一个对象的两个不同的synchronied方法 即其中一个是静态方法 另一个是非静态方法

创建名为Account的账号类
public class Account {
private double balance;

//实现setbalance和getBalance方法来写入和读取余额
public double getBalance() {
return balance;
}

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

//实现addAmount方法 吧传入的值加入到余额中 并且在同一时间只允许一个线程改变这个值 所以我们使用synchronized关键字标记成临界区
public synchronized void addAmount (double amount){
double tmp=balance;
try{
Thread.sleep(10);
}catch (InterruptedException e) {
e.printStackTrace();
}
tmp+=amount;
balance=tmp;
}
public synchronized void substractAmount(double amount){
double tmp=balance;
try{
Thread.sleep(10);
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
tmp-=amount;
balance=tmp;
}

}

模拟实现ATm类 对账户进行扣除 这个类实现Runnable接口作为线程执行
public class Bank implements Runnable {

private Account account;
public Bank(Account account){
this.account=account;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<100;i++){
account.substractAmount(1000);
}
}

}

实现模拟往账户充值


public class Company implements Runnable {

private Account account;
public Company(Account account){
this.account=account;
}
@Override

public void run() {
// TODO Auto-generated method stub
for(int i=0;i<100;i++){
account.addAmount(1000);
}
}

}

创建主类


public class Main {

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
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("Account : Inital Balance :%.1f\n",account.getBalance());
companyThread.start();
bankThread.start();
try{

//使用join方法等待两个子线程运行完成
companyThread.join();
bankThread.join();
System.out.printf("Account : Inital Balance :%.1f\n",account.getBalance());
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}

}

运行结果
Account : Inital Balance :1000.0

Account : Inital Balance :1000.0


可以得出 一个对象采用了synchronized关键字进行声明 只能被一个线程访问 如果线程A正在执行一个同步方法,线程b要执行这个对象的其他同步方法 线程B讲被柱塞知道线程A访问玩 但是如果线程B访问的是同一个类的不同对象 那么两个线程都不会被助赛


1.2使用非依赖属性实现同步

  当使用synchronized关键字来保护代码块时  必须把对象引用作为传入参数  通常情况下 使用this关键字来引用执行方法所属的对象 也可以使用其他的对象对其进行引用 一般来说 这些对象就是为了这个目的而创建的 

下面就来模拟电影院售票场景  有两个屏幕 和两个售票处的电影院   一个售票处卖 一张票只能用于其中一个电影院 不能同时用于两个电影院 因此每个电影院的剩余票数都是独立的
创建电影类


public class Cinema {
private long vacanciesCinema1;//每个电影院剩余的座位数
private long vacanciesCinema2;

private final Object controlCinema1,controlCinema2;

public Cinema(){
controlCinema1=new Object();
controlCinema2=new Object();
vacanciesCinema1=20;
vacanciesCinema2=20;
}

//当电影院有票卖出的时候调用这个方法 使用controlCinemal对象控制同步快的访问
public boolean sellTickets1(int number){
synchronized (controlCinema1) {
if(number<vacanciesCinema1){
vacanciesCinema1-=number;
return true;
}else{
return false;
}
}
}
public boolean sellTickets2(int number){
synchronized (controlCinema2) {
if(number<vacanciesCinema2){
vacanciesCinema2-=number;
return true;
}else{
return false;
}
}
}

//当电影票有票退回的时候调用这个方法  使用controlCinemal对象控制同步代码快的访问
public boolean returnTikets1(int number){
synchronized (controlCinema1) {
vacanciesCinema1+=number;
return true;
}
}
public boolean returnTikets2(int number){
synchronized (controlCinema2) {
vacanciesCinema2+=number;
return true;
}
}

public long getVacanciesCinema1(){
return vacanciesCinema1;
}
public long getVacanciesCinema2(){
return vacanciesCinema2;
}

}

实现电影院一售票类
public class TicketOfficel implements Runnable {

private Cinema cinema;
public TicketOfficel(Cinema cinema){
this.cinema=cinema;
}
@Override
public void run() {
// TODO Auto-generated method stub
cinema.sellTickets1(3);
cinema.sellTickets1(2);
cinema.sellTickets2(2);
cinema.returnTikets1(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){
this.cinema=cinema;
}
@Override
public void run() {
// TODO Auto-generated method stub
cinema.sellTickets2(2);
cinema.sellTickets2(4);
cinema.sellTickets1(2);
cinema.sellTickets1(1);
cinema.returnTikets2(2);
cinema.sellTickets1(3);
cinema.sellTickets2(2);
cinema.sellTickets1(2);
}

}

创建主类


public class Main {

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Cinema cinema=new Cinema();
TicketOfficel ticketOfficel=new TicketOfficel(cinema);
Thread thread1=new Thread(ticketOfficel,"ticketOfficel");
TicketOffice2 ticketOffice2=new TicketOffice2(cinema);
Thread thread2=new Thread(ticketOffice2,"ticketOffice2");

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

//等待子线程完成任务
thread1.join();
thread2.join();
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.printf("Room 1 Vacancies:%d\n",cinema.getVacanciesCinema1());
System.out.printf("Room 2 Vacancies:%d\n",cinema.getVacanciesCinema2());
}

}

用synchronized关键字保护代码块时  我们使用对象作为它的传入参数 jvm保证同一时间只有一个线程能够访问这个对象的代码块
在这个例子中是同了一个对象来控制对vacanciesCinemal属性的访问 所以同一时间只有一个线程能够修改这个属性  使用了另一个对象来控制vacanciesCinema2属性的访问 所以同一时间只有一个线程能够修改这个属性 


1.3在同步代码中使用条件

在并发编程中一个典型的问题是生产者和消费者问题 我们有一个缓冲区 一个会在多个数组将把这个数据存入缓冲区 一个或多个数据消费者将数据从缓冲区取走
这个缓冲区是一个共享数据结构 必须使用同步机制控制对它的访问  例如是同synchronized关键字  会受到很多限制  如果缓冲区是满的 生产者 就不能再放入数据 如果缓冲区是空的 消费者就不能读取数据

java在Object中提供了wait(),notify(),notifyAll()方法 线程可以在同步快中调用wait方法  当一个线程调用wait方法是 jvm将这个线程置入休眠 并且释放控制这个同步代码块的对象 同时允许其他线程执行这个对象控制的其他同步代码块 为了 唤醒这个线程,这个对象控制的某个同步快代码中调用notify()或者notify()方法

接下来实现生产者和消费者问题
创建数据存储类
package ch2.synchronize.wait;

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<>();
}

//实现同步方法 它保存数据到储存立标storage中 首先 它兼职列表 是不是满的 如果已满  就调用wait()方法挂起线程并等待空余的空间出现 在这个方法最后我们调用notifyAll()方法唤醒所有因wait()方法进入休眠的线程
public synchronized void set(){
while(storage.size()==maxSize){
try{
wait();
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
storage.add(new Date());
System.out.printf("Set: %d",storage.size());
notifyAll();
}
}

public  synchronized void get(){
while(storage.size()==0){
try{
wait();
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}
System.out.printf("Get: %d: %s\n",storage.size(),((LinkedList<?>)storage).poll());
notifyAll();
}

}

创建生产类
public class Producer implements Runnable {

private EventStorage eventStorage;
public Producer(EventStorage eventStorage){
this.eventStorage=eventStorage;
}
@Override
public void run() {
// TODO Auto-generated method stub

//模拟产生数据
for(int i=0;i<100;i++){
eventStorage.set();
}
}

}

创建消费者类


public class Consumer implements Runnable {

private EventStorage eventStorage;
public Consumer(EventStorage eventStorage){
this.eventStorage=eventStorage;
}
@Override
public void run() {
// TODO Auto-generated method stub

//模拟消费者获取数据
for(int i=0;i<100;i++){
eventStorage.get();
}
}

}

创建主类
public class Main {

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
EventStorage eventStorage=new EventStorage();
Producer producer=new Producer(eventStorage);
Thread thread1=new Thread(producer);
Consumer consumer=new Consumer(eventStorage);
Thread thread2=new Thread(consumer);
thread1.start();
thread2.start();
}

}


 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息