黑马程序员--Java基础之多线程(2)

------------------ android培训java培训、期待与您交流! ---------------------

四、线程间通信---等待唤醒机制

1、线程间通信:就是多个线程在操作同一个资源,但是操作的动作不同。

      如生产者和消费者的例子,比如轮流对人对象姓名年龄的输入和输出动作。

2线程间的通信其实就是---不同动作间的同步问题!

       操作同一资源的代码分别在不同的地方,要将他们同步在一起,并且还要轮流操作。

       比如对人对象进行姓名年龄的输入和输出动作,输入动作和输出动作分别在不同的线程中,

       而且这两个操作必须同步,还要轮流进行操作。

3、解决办法:使用同步Synchronized,用同一个锁使不同动作同步(可以使用要操作的同一个对象作为锁)。

    使用flag标记位,通过判断标记位决定是否可以操作,结合wait方法等待,notify方法唤醒单个线程,

        notifyAll方法唤醒所有等待的线程,实现不同动作的轮流操作。

    wait(),notify(),notifyAll()方法必须使用在同步中,且必须拥有同步的锁。

4、示例代码:轮流对人对象姓名年龄的输入和输出动作)

public class ThreadCommunication {

	public static void main(String[] args) {
		Person p = new Person();
		Input in = new Input(p);//两个线程操作同一个对象,可以将该对象作为参数传入。
								//也可以使用单例设计模式,但是太麻烦
		Output out = new Output(p);
		Thread t1 = new Thread(in);
		Thread t2 = new Thread(out);
		t1.start();
		t2.start();

	}
 
}
class Person{
	private String name;
	private String sex;
	private boolean flag = false;
	
	//设置姓名和性别的方法set
	public synchronized void set(String name,String sex){
		while(flag){//如果flag为true说明已经设置好姓名和性别,设置操作需要等待一下。
			//如果flag为false说明设置的东西已经被取走,可以进行设置操作了,则继续往下走。
			//这里使用while方法,不适用if方法,这是因为while循环使线程被唤醒时依然会判断一下标记!
				try {this.wait();} catch (InterruptedException e){}//同步函数的锁是this
		}
		this.name = name;
		this.sex = sex;
		
		flag = true;//设置完毕,将标志位设置为true,表示已经设置完毕
		this.notifyAll();//设置完毕唤醒其他线程
		//this.notify();//notify()方法会造成死锁,因为它通常会唤醒线程池中第一个wait的方法。
	}
	
	//打印姓名和性别的方法get
	public synchronized void get(){
		while(!flag){
			try {this.wait();} catch (InterruptedException e) {}
		}
		System.out.println(this.name+"-------------"+this.sex);
		
		flag = false;
		this.notifyAll();
	}
	public boolean isFlag() {
		return flag;
	}
	public void setFlag(boolean flag) {
		this.flag = flag;
	}
}

class Input implements Runnable{
	private Person p;
	private int x = 1;
	Input(Person p){
		this.p = p;
	}
	@Override
	public void run() {
		while(true){
			if(x==1){
				p.set("丽丽","女");
				x++;
			}
			else{
				p.set("John","man");
				x--;
			}
		}
	}
}

class Output implements Runnable{
	private Person p;
	Output(Person p){
		this.p = p;
	}
	@Override
	public void run() {
		while(true){
			p.get();
		}	
	}
}

5notify方法会造成死锁,

   这是因为,等待的线程都按顺序存放在线程池中,notify通常唤醒线程池中的第一个线程,

   也就是第一个wait的线程,这回造成死锁。notifyAll()方法用来解决死锁问题。但是对方线程会抢占资源。

6JDK1.5改进:Locklockunlock。 Conditionawaitsignal。显式的锁机制,以及显式的锁上的等待唤醒机制。

   JDK1.5中提供了多线程升级解决方案,将同步Synchronized替换成现实Lock操作。

   将Object中的waitnotifynotifyAll 替换成了condition对象。可以通过Lock的锁,获取condition对象。

 下面是多个生产者和消费者的代码示例:

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

public class ProAndCon {
	public static void main(String[] args){
		Resource res = new Resource();
		Producer pro = new Producer(res);
		Consumer con = new Consumer(res);
		Thread t1 = new Thread(pro);//制造多个生产者和消费者
		Thread t2 = new Thread(pro);
		Thread t3 = new Thread(pro);
		Thread t4 = new Thread(con);
		Thread t5 = new Thread(con);
		Thread t6 = new Thread(con);
		
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
		t6.start();
	}
}

class Resource{
	private int num;
	private boolean flag = false;
	private Lock lock = new ReentrantLock();//定义一个锁,它的锁和解锁操作代替了同步操作
	private Condition pro_condition = lock.newCondition();//生产者的
	private Condition con_condition = lock.newCondition();//消费者的
	
	public void set(){
		lock.lock();//首先上锁
		try {
			while(flag){
				pro_condition.await();//如果有产品,生产者wait
			}
			++num;
			System.out.println(Thread.currentThread().getName()+"生产的第"+num+"个产品");
			flag = true;//生产后设置标记位为true
			con_condition.signalAll();//唤醒所有消费者
		} catch (InterruptedException e) {
		}finally{
			lock.unlock();//最后别忘了解锁,解锁放在finally语句中,确保一定会执行解锁
		}
	}
	
	public void get(){
		try {
			lock.lock();
			while(!flag){
				con_condition.await();
			}
		
			System.out.println(Thread.currentThread().getName()+"消费了第+++++++++++"+num+"个产品+++++++++++");
			flag = false;
			pro_condition.signalAll();//唤醒所有生产者
		} catch (InterruptedException e) {
			
		}finally{
			lock.unlock();
		}
	}
	
}

class Producer implements Runnable{
	private Resource res ;
	Producer(Resource res){
		this.res = res;
	}
	@Override
	public void run() {
		while(true){
			res.set();
		}
	}

}

class Consumer implements Runnable{
	private Resource res;
	Consumer(Resource res){
		this.res = res;
	}
	@Override
	public void run() {
		while(true){
			res.get();
		}
	}
}

五、停止线程、守护线程、线程优先级以及join方法yield方法

1、停止线程

    stop()方法已经过时,一般代码运行完毕,该线程就结束了。

    特殊情况:循环状态下,当线程处于冻结状态,就不会读取标记,也就不能结束线程。这时可以使用Thread类提供的方法 interrupt();对冻结清楚,然后修改标记,使循环结束。

2、守护线程

    setDaemon (true)将该线程设置为守护线程。

    守护线程又称后台线程,后台线程依赖于前台线程,前台线程只要一结束,后台线程也就随之结束。

    注意:该方法必须在启动线程前调用。当正在运行的线程都是守护线程时,Java虚拟机退出,程序结束。

3、线程优先级

    线程优先级共有是个级别:1-10 

    一般分为三个层次:MAX_PRIORITY(10);MIN_PRIORITY(1);NORM_PRIORITY(5)

    可以通过setPriority(int newPriority);来改变线程的优先级状态。

4join方法,yield方法。

    join(),等待该线程终止。当A线程执行到了B线程的join方法时,A就会等待,等B线程都执行完,A才会执行。

    Join()可以用来临时加入线程执行。

    yield(),暂停当前正在执行的线程对象,并执行其他线程。临时释放执行权,有利于线程的平均运行。








评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值