1.线程之间的通信
生产者和消费者案例,生产者负责生产姓名和性别,消费者负责消费姓名和性别,贴个DEMO:
//共享资源对象(姓名-性别)
public class ShareResource {
private String name;
private String gender;
/**生产者向资源对象发送消息*/
synchronized public void push(String name,String gender) {//synchronized关键字为了解决性别出现紊乱
this.name = name;
try {
Thread.sleep(10);//为了演示让性别出现紊乱
} catch (InterruptedException e) {
e.printStackTrace();
}
this.gender = gender;
}
/**消费者从资源对象中消费消息*/
synchronized public void pull() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.name+"-"+this.gender);
}
}
//生产者,负责生产姓名和性别
public class Producer implements Runnable {
private ShareResource shareResource = null;
public Producer(ShareResource shareResource) {
this.shareResource = shareResource;
}
@Override
public void run() {
for(int i=0;i<500;i++) {
if(i%2==0) {
shareResource.push("春哥", "男");
}else {
shareResource.push("凤姐", "女");
}
}
}
}
//消费者
public class Consumer implements Runnable {
private ShareResource shareResource = null;
public Consumer (ShareResource shareResource) {
this.shareResource = shareResource;
}
@Override
public void run() {
for(int i=0;i<500;i++) {
shareResource.pull();
}
}
}
//测试类
public class App {
private static final ShareResource shareResource = new ShareResource();
public static void main(String[] args) {
//启动生产者
new Thread(new Producer(shareResource)).start();
//启动消费者
new Thread(new Consumer(shareResource)).start();
}
}
为了解决出现如下图所示的性别出现紊乱的问题,为生产和消费添加了同步方法/同步代码块/锁都可以解决.
--添加synchronized后->>>
同步锁池:同步锁应(建议必须)选择多个线程共同的资源,当前生产者在生产数据时先拥有同步锁,其他线程在同步锁池中等待,当生产者生产完数据后,生产者释放锁资源,其他线程竞争该锁的使用权.
虽然性别紊乱问题解决了,但依旧不能按理想的情况:春哥和凤姐交替出现,为了解决这个问题,引出下面的wait和notify
2.wait和notify
java.lang.Object类提供两类用于操作线程通信的方法.
wait():执行该方法的线程对象释放同步锁,JVM把该线程存放到等待池中,等待其他的线程唤醒该线程.
notify:执行该方法的线程唤醒在等待池中等待的任意一个线程,把线程转到锁池中等待.
notifyAll():执行该方法的线程唤醒在等待池中的所有线程,把线程转到锁池中等待.
注意:上述方法只能被同步监听锁对象来调用,否则会报illegalMonitorStateException.
为了解决上面消费者生产者案例中出现的问题,下面贴个DEMO,通过wait和notify来完美解决该问题:
生产者,消费者,测试类中的代码与1中贴出的代码一致,只是对共享资源对象做了修改.
//共享资源对象(姓名-性别)
public class ShareResource {
private String name;
private String gender;
private boolean isEmpty = true;
/**生产者向资源对象发送消息*/
synchronized public void push(String name,String gender) {//synchronized关键字为了解决性别出现紊乱
try {
while(!isEmpty) {
this.wait();
}
this.name = name;
Thread.sleep(10);//为了演示让性别出现紊乱
this.gender = gender;
isEmpty = false;
this.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/** 消费者从资源对象中消费消息 */
synchronized public void pull() {
try {
while(isEmpty) {
this.wait();
}
Thread.sleep(10);
System.out.println(this.name + "-" + this.gender);
isEmpty = true;
this.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.使用Lock和Condition接口
wait和notify方法只能被同步监听锁对象来调用,否则会报illegalMonitorStateException.
但Lock机制根本就没有同步锁,也就没有自动获取锁和释放锁的概念,因为没有没有同步锁,所以Lock机制无法调用wait和notify方法.
解决方案:jdk1.5中提供了Lock机制的同时提供了处理Lock机制的线程通信控制的接口Condition.
可以用Lock机制提供的Condition接口中的await和signal和signalAll代替同步代码块和同步方法中的wait和notify和notifyAll.
//共享资源对象(姓名-性别)
public class ShareResource {
private String name;
private String gender;
private boolean isEmpty = true;
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
/**生产者向资源对象发送消息*/
public void push(String name,String gender) {//synchronized关键字为了解决性别出现紊乱
try {
lock.lock();
while(!isEmpty) {
condition.await();
}
this.name = name;
Thread.sleep(10);//为了演示让性别出现紊乱
this.gender = gender;
isEmpty = false;
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
/** 消费者从资源对象中消费消息 */
public void pull() {
try {
lock.lock();
while(isEmpty) {
condition.await();
}
Thread.sleep(10);
System.out.println(this.name + "-" + this.gender);
isEmpty = true;
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
4.使用Lock和synchronized以及同步代码块有何区别?
在功能上,三者并无明显差异,但使用Lock更能体现面向对象编程,是jdk1.5之后新出现的.
5.线程通信-死锁
在多线程通信的时候很容易造成死锁,死锁无法解决,只能避免.
避免死锁的法则:当多个线程都要访问共享的资源A,B,C时,保证每个线程都按照相同的顺序去访问他们,比如都先访问A,再访问B,最后C.
感兴趣可以百度"哲学家就餐问题".
5.线程的生命周期和状态
线程的各种状态:
1>新建状态(new):使用new创建一个线程对象,仅仅在堆中分配内存空间.
新建状态下,线程压根就没有启动,仅仅是一个线程对象而已.
当新建状态下的线程调用了start方法,此时线程从新建状态进入可运行状态
2>可运行状态(runnable):分为两种状态,ready和running.分别表示就绪状态和运行状态.
就绪状态:线程对象调用start方法之后,等待JVM的调度.
运行状态:线程对象获得JVM调度,如果存在多个CPU,那么允许多个线程并行运行.
3>阻塞状态(blocked):正在运行的线程因为某些原因放弃CPU,暂时停止运行,就会进入阻塞状态.
此时JVM不会给线程分配CPU,知道线程重新进入就绪状态,才有机会转到运行状态.
阻塞状态的两种情况:
①:当线程A处于运行过程时,师徒获取同步锁时,却被线程B获取,此时JVM把当前A线程存到对象的锁池中,A线程进入阻塞状态.
②:当线程处于运行过程时,发出了IO请求时,此时进入阻塞状态
4>等待(waiting)状态:
①:当线程处于运行过程时,调用了wait方法,此时JVM把当前线程存入对象等待池中
②:当前线程执行了sleep()方法.
5>计时等待状态(timed waiting):
①:当前线程处于运行过程时,调用了wait(long time)方法,此时JVM把当前线程存在对象等待池中.
②:当前线程执行了sleep(long time)方法
6>终止状态(terminated):通常称为死亡状态,表示线程终止.
①:正常执行完run方法而退出(正常死亡).
②:遇到异常而退出(出现异常之后,程序就会中断)(意外死亡).
线程一旦终止,就不能再重新启动,否则报错.