一、 概述
多个线程并发执行时,CPU是随机切换线程的,是无序的,当我们需要用多个线程来共同完成一个操作,就需要某种通信机制来协调线程,如果没有协调通信的机制,会造成多个线程对共享资源的争夺,会造成严重的数据污染问题。比如说现在共有5个苹果,A拿走5个同时B放进去3个,那么此时剩余的苹果可能是0、3、8,这就是线程间不通信造成的。
二、线程间的通信方式
1、synchronized对象锁
给某个对象加锁,锁住的是对象
synchronized关键字可以修饰在方法methodA()上,因为调用该方法是 Object.methodA(),所以修饰在方法上锁住的是对象object。
也可以锁住代码块,都是锁住某一个对象。
class Obj{
public synchronized void methodA(){
}
public void methodB(){
synchronized(this){ 或这样 都是一样的 都是给Obj加锁
//要执行这一块,必须要获取Obj的锁,如果被methodA先获取了,就要等methodA()执行结束之后,释放锁 才能执行
}
}
}
举例子:
模拟多线程对数据库中的一条数据同时修改,会引起数据覆盖的问题或者读脏数据的情况
public class Demo0907 {
public static void main(String[] args) {
User user = new User(10,"10","10");
UserDao userDao1 = new UserDao(user);
UserThread1 t1 = new UserThread1(userDao1);
UserThread2 t2 = new UserThread2(userDao1);
t1.start();
t2.start();
}
}
class UserThread1 extends Thread{
private UserDao userDao;
public UserThread1(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void run() {
userDao.updateTwo("11",11,11);
}
}
class UserThread2 extends Thread{
private UserDao userDao;
public UserThread2(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void run() {
userDao.updateOne();
}
}
class UserDao{
private User user ;
public UserDao(User user) {
this.user = user;
}
public synchronized void updateOne(){
System.out.println(user.toString()+"---updateOne");
}
public synchronized void updateTwo(String name,int age,String sex){
user.setName(name);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
user.setAge(age);
user.setSex(sex);
System.out.println(user.toString()+"---updateTwo");
}
}
class User{
private int age;
private String sex;
private String name ;
//省略get set 构造器 toString 方法
}
如果不加synchronized关键字 方法updateTwo只修改了一个参数就sleep了,此时updateOne读取的数据就会是(10,10,11),也就是数据不同步,正确的数据是(11,11,11)。
若多个线程拥有同一个MyObject类的对象,则这些方法只能以同步的方式执行。即,执行完一个synchronized修饰的方法后,才能执行另一个synchronized修饰的方法。
2、 wait()、notify/notifyAll()
wait() 与 notify/notifyAll() 是Object类的被final修饰的方法,不能被重写
wait()使当前线程阻塞,前提是 必须先获得锁,一般配合synchronized 关键字使用,即,一般在synchronized 同步代码块里使用 wait()、notify/notifyAll() 方法。
执行过程:
当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。
只有当 notify/notifyAll() 被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。
注意:notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况。所以在编程中,尽量在使用了notify/notifyAll() 后立即退出临界区,以唤醒其他线程
例如:
生产者和消费者的问题。当库存为零时,生产者开始生产,当库存为5时,消费者开始消费。模拟生产20个 ,消费20个。
public class Demo0903 {
public static void main(String[] args) {
Store store = new Store();
Productor p1 = new Productor(store);//构造多线程
Consumer c1 = new Consumer(store);
p1.setName("生产者-1");
c1.setName("消费者-2");
p1.start();//开启多线程
c1.start();
}
}
class Productor extends Thread{
private Store store;
public Productor(Store store) {
this.store=store;
}
@Override
public void run() {
store.pushApple();
}
}
class Consumer extends Thread{
private Store store;
public Consumer(Store store) {
this.store=store;
}
@Override
public void run() {
store.getApple();
}
}
class Store{
//共享资源库存
private LinkedList<Apple> store= new LinkedList<Apple>();
public synchronized void pushApple(){//生产者放苹果
synchronized(store){
for(int i = 1;i<20;i++){
Apple apple = new Apple(i);
while (store.size()==5){//当库存中有5个苹果了,此时该线程进入等待状态,并释放store锁
try {
store.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
store.addFirst(apple);
System.out.println(Thread.currentThread().getName()+"存放:"+apple.toString());
store.notifyAll();//此时唤醒消费者线程 唤醒之后也不会立即执行 因为获取不到锁 要等库存=5时 wait()释放锁之后 消费者线程才能执行
}
}
}
public void getApple(){//消费者取苹果
synchronized(store){
for(int i = 1;i<20;i++){
while (store.size()==0){
try {
store.wait(); //一开始,因为库存为0,所以该线程就进入了等待 等待被唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
Apple apple = store.removeFirst();
System.out.println(Thread.currentThread().getName()+"吃掉:"+apple.toString());
store.notifyAll();//唤醒生产者线程,直到消费者消费完了所有的苹果,进入wait()释放锁 ,唤醒的生产者线程才能执行
}
}
}
}
class Apple{
private int id;
public Apple(int id){
this.id=id;
}
@Override
public String toString() {
return "苹果:"+id;
}
}