Java学习笔记5

以下内容是从任小龙讲师课堂笔记中整理。

线程通信

线程通信:不同的线程执行不同的任务,如果这些任务有某种关系,线程之间必须能够通信,协调完成工作。
经典的生产者和消费者案例(Producer/Consumer):
分析案例:
1.生产者和消费者应该操作共享的资源(实现方式来做)
2.使用一个或多个线程表示消费者(consumer)
3.使用一个或多个线程表示生产者(producer)

 

为什么生产者不直接把数据给消费者,而是把数据存储到共享资源,然后消费者再从共享资源中取出数据,再消费?
在这里体现了面向对象的设计理念:低耦合
高(紧)耦合:直接使用生产者把包子给消费者,那么生产者中得存在消费者的引用,同理,消费者要消费生产者生产的包子,消费者中也得存在生产者对象的引用。

public class Producer{
  private Consumer con;
}

public class Consumer{
  private Producer pro;
}
低(松)耦合:使用一个中间对象,屏蔽了生产者和消费者的直接数据交互。

//生产者
public class Producer{
  private ShareResource resouce;
}

//消费者
public class Consumer{
  private ShareResource resouce;
}

 

public class Producer implements Runnable {
    private ShareResource resource = null;

    public Producer(ShareResource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        for(int i = 0; i < 50; i++){
            if(i%2 == 0){
                resource.push("春哥","男");
            }else{
                resource.push("凤姐","女");
            }
        }
    }
}

 

public class ShareResource {
    private String name;
    private String gender;

    public void push(String name, String gender){
        this.name = name;
        this.gender = gender;
    }

    public void popup(){
        System.out.println(this.name + "-" + this.gender);
    }
}

 

public class Consumer implements Runnable {
    private ShareResource resource = null;

    public Consumer(ShareResource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        for(int i = 0; i < 50; i++ ){
            resource.popup();
        }
    }
}

 

public class test{
    public static void main(String[] args){
        ShareResource resource = new ShareResource();
        new Thread(new Producer(resource)).start();
        new Thread(new Producer(resource)).start();
        new Thread(new Consumer(resource)).start();
        new Thread(new Consumer(resource)).start();
    }
}

 

如果将ShareResource作如下修改

public class ShareResource {
    private String name;
    private String gender;

    public void push(String name, String gender){
        this.name = name;
        try{
            Thread.sleep(10L);
        }catch(Exception e){
            System.out.println(e.getStackTrace());
        }
        this.gender = gender;
    }

    public void popup(){
        try{
            Thread.sleep(10L);
        }catch(Exception e){
            System.out.println(e.getStackTrace());
        }
        System.out.println(this.name + "-" + this.gender);
    }
}

生产者和消费者案例存在的问题:
使用sleep(10)使问题更明显
问题1:性别紊乱现象
生产者先生产出春哥-男,此时消费者还没来得及消费,生产者继续生产出凤姐,性别还没来得及赋值,此时消费者开始消费了。

解决方案:只要保证生产姓名和性别过程同步,中间不能被消费者线程进来取走数据消费。
可以使用同步代码块/同步方法/Lock机制保持同步性

问题2:应该出现生产一个数据,消费一个数据,即春哥-男->凤姐-女交替出现
重复出现凤姐-男->凤姐女的原因:
当出现问题1之后,生产者继续赋值,将性别赋值女后,消费者继续消费凤姐-女

解决方案:得使用等待和唤醒机制

同步锁池:
同步锁必须选择多线程共同的资源对象。
当前生产者在生产数据的时候先拥有同步锁,其他线程就在锁池中等待获取锁。
当线程执行完同步代码块的时候,就会释放同步锁,其他线程开始抢锁的使用权。

 

public class ShareResource {
    private String name;
    private String gender;

    synchronized public void push(String name, String gender){
        this.name = name;
        try{
            Thread.sleep(10L);
        }catch(Exception e){
            System.out.println(e.getStackTrace());
        }
        this.gender = gender;
    }

    synchronized public void popup(){
        try{
            Thread.sleep(10L);
        }catch(Exception e){
            System.out.println(e.getStackTrace());
        }
        System.out.println(this.name + "-" + this.gender);
    }
}

 

wait和notify

wait和notify方法介绍:
java.lang.Object类提供了两类用于操作线程通信的方法。
wait():执行该方法的线程对象释放同步锁,JVM把该线程存放到等待池中,等待其他线程唤醒该线程。
notify():执行该方法的线程唤醒在等待池中等待的任意一个线程,把线程转到锁池中等待。
notifyAll():执行该方法的线程唤醒在等待池中等待的所有线程,把线程转到锁池中等待。
注意:上述方法只能在被同步监听锁对象来调用,否则报错IllegalMonitorStateException.

假设A线程和B线程共同操作一个x对象,A,B线程可以通过对象的wait和notify方法来进行通信,流程如下:
1.当A线程执行x对象的同步方法时,A线程持有x对象的锁,B线程在x对象的锁池中等待。
2.A线程在同步方法中执行x.wait()方法时,A线程释放x对象的锁,进入x对象的锁池中等待。
3.在x对象的锁池中等待的B线程获取x对象的锁,执行x的另一个同步方法。
4.B线程在同步方法中执行x,notify()方法时,JVM把A线程从对象x的等待池中移动到x对象的锁池中,等待获取锁。
5.B线程执行完同步方法,释放锁。A线程获得锁,继续执行同步方法。

//解决重复消费的问题
public class ShareResource {
    private String name;
    private String gender;
    private boolean isEmpty = true;

    synchronized public void push(String name, String gender){
        try {
            while(!isEmpty){

                this.wait();
            }
            this.name = name;
            Thread.sleep(10L);
            this.gender = gender;
            isEmpty = false;
            this.notify();
        }catch(Exception e){
            System.out.println(e.getStackTrace());
        }

    }

    synchronized public void popup(){
        try{
            while(isEmpty){
                this.wait();
            }
            Thread.sleep(10L);
            System.out.println(this.name + "-" + this.gender);
            isEmpty = true;
            tHis.notify();
         }catch(Exception e){
            System.out.println(e.getStackTrace());
         }
     }
}

 

使用Lock和Condition接口:
wait和notify方法只能被同步监听锁对象来调用,否则报错IllegalMonitorStateException.
Lock机制根本没有同步锁,也就没有自动获取锁和自动释放锁的概念。
因为没有同步锁,所以Lock就不能调用wait和notify方法。

解决方案:Java5中提供Lock机制的同时,提供了处理Lock机制的通信控制Condition接口。

从java5开始:
1.可以使用Lock机制取代synchronized代码块和synchronized方法
2.使用Condition接口对象的await,signal,signalAll方法代替wait,notify,notifyAll方法。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ShareResource {
    private String name;
    private String gender;
    private boolean isEmpty = true;
    private final Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    public void push(String name, String gender){
        lock.lock();
        try {
            while(!isEmpty){

                condition.await();
            }
            this.name = name;
            Thread.sleep(10L);
            this.gender = gender;
            isEmpty = false;
            condition.signal();
        }catch(Exception e){
            System.out.println(e.getStackTrace());
        }finally {
            lock.unlock();
        }

    }

    public void popup(){
        lock.lock();
        try{
            while(isEmpty){
                condition.await();
            }
            Thread.sleep(10L);
            System.out.println(this.name + "-" + this.gender);
            isEmpty = true;
            condition.signal();
        }catch(Exception e){
            System.out.println(e.getStackTrace());
        }finally {
            lock.unlock();
        }
    }
}

多线程通信的时候很容易造成死锁,死锁无法解决,只能避免:
当A线程等待由B线程持有的锁,而B线程正在等待A线程持有的锁时,发生死锁现象。JVM不检测也不试图避免这种情况,所以程序员必须保证不导致死锁。

避免死锁法则:当多个线程都要访问共享的资源A,B,C时,保证每一个线程都按照相同的顺序去访问它们,比如都先访问A,接着访问B,最后是C。

Thread类中过时的方法:
suspend():使正在运行的线程放弃CPU,暂停运行
resume():使暂停的线程恢复运行

注意:因为容易导致死锁,所以已经被废弃了。
死锁情况:
A线程获得对象所,正在执行一个同步方法,如果B线程调用A线程的suspend方法,此时A线程暂停运行,此时A线程放弃CPU,但是不会放弃占用的资源。

 

线程在生命周期中的状态

 

1.新建状态(new):使用new关键字创建一个线程对象,仅仅在堆中分配内存空间,在调用start之前
新建状态下,线程压根就没有启动,仅仅只是存在一个线程对象而已。
Thread t = new Thread();

2.可运行状态(runnable):分成两种状态,ready和running。分别表示就绪状态和运行状态。当新建状态下的线程对象调用start方法,此时从新建状态进入可运行状态。
就绪状态:线程对象调用start方法之后,等待JVM的调度。
运行状态:线程对象获得JVM调度,如果存在多个CPU,那么允许多个线程并行运行。

线程对象的start方法只能调用一次。

3.阻塞状态(blocked):正在运行的线程,因为某些原因放弃CPU,暂时停止运行,就会进入阻塞状态。
此时JVM不会给线程分配CPU,直到线程重新进入就绪状态才有机会转到运行状态。
阻塞状态只能先进入就绪状态,不能直接进入运行状态。

    1).当线程A处于运行状态时,试图获取同步锁时,却被B线程获取,此时JVM把当前A线程存到对象的锁池中,A线程进入阻塞状态。.
    2).当线程处于运行状态时,发出了IO请求,此时进入阻塞状态。

4.等待状态(waiting)
当线程处于运行状态,调用了wait()方法,此时JVM把当前线程存在对象等待池中
当线程执行了sleep()方法

5.计时等待状态(timed waiting)
当线程处于运行状态时,调用了wait(long time)方法,此时JVM把当前线程存在对象等待池中
当前线程执行了sleep(long time)方法。

6.终止状态(terminated)
正常执行完run方法而退出
因为出现异常而退出

 

posted @ 2016-12-11 22:52  alianblog  阅读(192)  评论(0)    收藏  举报