【java基础】- 多线程-part2

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:执行该方法的线程唤醒在等待池中等待的任意一个线程,把线程转到锁池中等待.

扫描二维码关注公众号,回复: 3263449 查看本文章

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方法而退出(正常死亡).

②:遇到异常而退出(出现异常之后,程序就会中断)(意外死亡).

线程一旦终止,就不能再重新启动,否则报错.

猜你喜欢

转载自blog.csdn.net/lovexiaotaozi/article/details/81326545