java中的多线程和数据同步问题

编程中有一个经典的多线程问题:有两个线程,一个负责写入数据,一个负责读出数据,当两个线程同步运行时,如何保证数据的准确性和安全性?人们将这一问题抽象成生产者-消费者模型。

//有一个数据模型Person类,包含三个属性以及写入和读取这三个属性的方法。
public class Person {
	private String name;
	private String gender;
	private int age;
	public Person() {};
	public Person(String name,String gender,int age){
		this.name=name;
		this.gender=gender;
		this.age=age;
	}
	public  void setName(String name) {
		this.name = name;
	}
	public  void setGender(String gender) {
		this.gender = gender;
	}
	public  void setAge(int age) {
		this.age = age;
	}
	public  void show() {
		System.out.println(name+"-"+gender+"-"+age);
	}
}

//生产者模型,一个Porducer类实现了Runnable借口,负责写入Person类的三个属性

public class Producer implements Runnable {
	Person p;
	public Producer(Person p) {
		this.p=p;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		try {
			Thread.sleep(500);
		}catch(Exception e) {
			e.printStackTrace();
		}
		int i=0;
		while(true) {
			if(i==0) {
				p.setName("mike");
				p.setGender("male");
				p.setAge(20);
			}
			else {
				p.setName("mary");
				p.setGender("famale");
				p.setAge(17);
			}
                        i=(i+1)%2;
		}
	}

}

//消费者模型Consunmer,同样实现了Runnable接口,负责读取此时Person的三个属性值

public class Consumer implements Runnable {
	public Person p;
	public Consumer(Person p) {
		this.p=p;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true) {
			try {
				Thread.sleep(500);
			}catch(Exception e) {
				e.printStackTrace();
			}
			p.show();
		}
	}

}

//为了使结果显著,我们给生产者和消费者500ms的中断,同时让生产者不断写入不同的两组数值(mike-male-20和mary-female-17)。

//主程序
public class Run {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Person p=new Person();
		
		new Thread(new Producer(p)).start();
		new Thread(new Consumer(p)).start();
		
	}

}

//运行主程序,得到输出结果:
mike-famale-17
mike-male-20
mary-male-17
mike-male-17
mike-male-17
mike-famale-20
mary-male-20
mary-famale-20

//我们看到两组数据互相混淆,上述程序并不能保证数据的准确性和安全性。
//此时我们可以引入java中的关键字synchronized,synchronized关键字常用来修饰类中的方法和代码,
//它的作用是锁住(locked)这一段代码,使得当一个线程在退出这段代码之前,其他线程无法处理这一段
//代码(其他线程若访问这段代码,会被放入锁住这段代码的锁的entry-set中,处于等待状态)
//我们将Person的每个方法加上synchronized修饰,修改Person代码如下:

public class Person {
	private String name;
	private String gender;
	private int age;
	public Person() {};
	public Person(String name,String gender,int age){
		this.name=name;
		this.gender=gender;
		this.age=age;
	}
	public synchronized void setName(String name) {
		this.name = name;
	}
	public synchronized void setGender(String gender) {
		this.gender = gender;
	}
	public synchronized void setAge(int age) {
		this.age = age;
	}
	public synchronized void show() {
		System.out.println(name+"-"+gender+"-"+age);
	}
}

//再次运行主程序,得到结果如下:
null-null-0
mike-male-17
mary-male-20
mary-famale-17
mike-male-17
mike-male-20

//能够看到效果仍然不理想,这是因为我们写入数据时,用的是三个分开的synchronized修饰的方法,此时
//当CPU在不同的线程之间跳转时,仍然有可能在数据还未写入完整时就载入读取线程,此时读到的数据自然
//是不准确的了,我们将三个写入方法合并成一个,修改Preson类和Producer类如下:


public class Person {
	private String name;
	private String gender;
	private int age;
	public Person() {};
	public Person(String name,String gender,int age){
		this.name=name;
		this.gender=gender;
		this.age=age;
	}
	public synchronized void set(String name,String gender,int age) {
		this.name=name;this.gender=gender;this.age=age;
	}
	public synchronized void show() {
		System.out.println(name+"-"+gender+"-"+age);
	}
}


public class Producer implements Runnable {
	Person p;
	public Producer(Person p) {
		this.p=p;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		try {
			Thread.sleep(500);
		}catch(Exception e) {
			e.printStackTrace();
		}
		int i=0;
		while(true) {
			if(i==0) 
				p.set("mike", "male", 20);
			else
				p.set("mary", "female", 17);
			i=(i+1)%2;
		}
	}

}

//运行主程序得到:
null-null-0
mary-female-17
mike-male-20
mary-female-17
mike-male-20
mary-female-17
mary-female-17
mike-male-20
mary-female-17
mike-male-20
mary-female-17
mary-female-17
mary-female-17
mike-male-20
mary-female-17
mike-male-20

//结果显示我们得到了想要的结果,上述程序可以保证数据的准确性和安全性,但仍然有一个问题,就是数
//据的多次写入和多次读取,我们想要使数据的写入和读取交替进行,就要在线程之间进行通信。
//线程之间的通信有wait()、notify()、stop()、sleep()等方法
//wait():告诉当前线程放弃监视器并进入睡眠状态,直到其他线程进入同一监视器并调用notify()为止
//notify():唤醒同一对象监视器中调用wait()的第一个线程,类似排队买票,一个买完之后,后面的可
//以继续买。
//我们给Person对象增加一个表示自身状态的属性state(boolean),当state=true时,消费者可以读
//取数据,生产者进入等待状态,当state=false时,消费者进入等待状态,生产者可以写入数据。
//改变Person如下:


public class Person {
	private String name;
	private String gender;
	private int age;
	private boolean state=false;
	public Person() {};
	public Person(String name,String gender,int age){
		this.name=name;
		this.gender=gender;
		this.age=age;
	}
	public synchronized void set(String name,String gender,int age) {
		if(state)
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			//System.out.println(Thread.currentThread().getName());
			this.name=name;
			this.gender=gender;
			this.age=age;
			state=true;
			notify();
		
	}
	public synchronized void show() {
		if(!state)
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		//System.out.println(Thread.currentThread().getName());
		System.out.println(name+"-"+gender+"-"+age);
		state=false;
		notify();
	}
}

//调用主程序:

mike-male-20
mary-female-17
mike-male-20
mary-female-17
mike-male-20
mary-female-17
mike-male-20
mary-female-17

//我们得到了想要的结果
//事实上,我们还可以利用java.util.concurrent.locks.Lock来锁住代码块,改变Person如下:
import java.util.concurrent.locks.*;
public class Person {
	private String name;
	private String gender;
	private int age;
	private Lock lock=new ReentrantLock();
	public Person() {};
	public Person(String name,String gender,int age){
		this.name=name;
		this.gender=gender;
		this.age=age;
	}
	public  void set(String name,String gender,int age) {
		lock.lock();
		this.name=name;
		this.gender=gender;
		this.age=age;
		lock.unlock();
	}
	public void show() {
		lock.lock();
		System.out.println(name+"-"+gender+"-"+age);
		lock.unlock();
	}
}

//利用类Lock的lock()方法锁住,再利用unlock()方法解锁,同样能够保证数据的安全性和准确性,此时
//不需要synchronized关键字修饰,但也不能在锁住的代码块中直接调用notify()、wait()等方法
//输出结果如下:

mike-male-20
mike-male-20
mike-male-20
mike-male-20
mike-male-20
mike-male-20
mary-female-17
mike-male-20
mary-female-17
mike-male-20
mike-male-20
mike-male-20
mary-female-17
mike-male-20
mary-female-17
mary-female-17
mary-female-17
mike-male-20


 

你可能感兴趣的:(JAVA)