线程之间的通信与线程锁

多线程

线程的通信—打印机打印–不断的输入不断的输出

  • 分析:
    • 需要两个线程—输入线程和输出线程
    • 需要两个任务—输入任务和输出任务
    • 需要一份数据
public class Demo2 {
	public static void main(String[] args) {
		//1.创建数据了对象
		Des des = new Des();
		//2.创建任务类对象并绑定数据
		Input input = new Input(des);
		Output output = new Output(des);
		//3.创建线程
		Thread inputThread = new Thread(input);
		Thread outputThread = new Thread(output);
		//4.开启线程
		inputThread.start();
		outputThread.start();
		
	
	}
}

//创建数据类
class Des{
	String name;
	String sex;
}

//创建输入任务
class Input implements Runnable{
    Des des;
	public Input(Des des) {
		this.des = des;
	}
	public void run() {
		/*
		 * 分析:需要输入任务与输出任务共用一把锁,保证两个任务之间是同步的.
		 * 给两个任务加一把锁,锁可以是des或Object.class
		 * 
		 * 不建议使用Object.class,原因:由于Object的使用范围太大,会造成不必要的错误
		 * 使用des最合适,因为他只被两个线程共享.
		 * 
		 * 注意:只给一个任务加锁,无法实现两个线程的同步.
		 */
		int i= 0;
		while (true) {
			synchronized (des) {
				if (i==1) {
					des.name = "凤姐";
					des.sex = "男";
				}else {
					des.name = "芙蓉姐姐";
					des.sex = "女";
				}
				
				i=(i+1)%2;
			}
		}
		
	}
}
//创建输出任务
class Output implements Runnable{
    Des des;
	public Output(Des des) {
		this.des = des;
	}
	public void run() {
		while (true) {
			synchronized (des) {
				System.out.println("姓名:"+des.name+"   性别:"+des.sex);
			}
		}
		
	}
}

线程的通信—实现:打印机打印–一次输入一次输出

  • 分析:

    • 需要两个线程—输入线程和输出线程
    • 需要两个任务—输入任务和输出任务
    • 需要一份数据
  • 使用唤醒等待机制实现:wait()/notify()/notifyall()等

  • wait():让当前的线程进入等待的状态,他会被放入一个池子(线程池),失去了抢cpu的能力,等待唤醒.(锁—相当于给当前的线程做了一个标记)

  • notify():让当前的线程从等待线程唤醒,相当于从池子中取出线程.(唤醒的是同一把锁下的任意一个线程)

  • notifyall():唤醒的是同一把锁下的所有线程.

public class Demo3 {
	public static void main(String[] args) {
		//1.创建数据了对象
		Des1 des = new Des1();
		//2.创建任务类对象并绑定数据
		Input1 input = new Input1(des);
		Output1 output = new Output1(des);
		//3.创建线程
		Thread inputThread = new Thread(input);
		Thread outputThread = new Thread(output);
		//4.开启线程
		inputThread.start();
		outputThread.start();
		
	
	}
}

//创建数据类
//封装类的原则:是你的活儿你做,不是你的活儿不要做.
class Des1{
	String name;
	String sex;
	
	boolean flag = false;//创建一个标识,控制唤醒与等待的切换
	
	//获取数据--处理输入
	public synchronized void setData(String name,String sex) {
			if (flag == true) {
				try {//让输入线程等待,当flag值为true的时候
					//在执行代码的时候,这里对应的是哪个线程,锁对象操作的就是哪个线程.
					wait();//只要一致性wait,线程会立刻停在这里,等待下次唤醒.
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			
			this.name = name;
			this.sex = sex;
			
			flag = !flag;
			//唤醒输出线程
			//当执行唤醒的时候,在线程池中没有找到被当前的锁标记的线程,我们称为空唤醒,空唤醒对程序没有影响,程序允许空唤醒.
			notify();
		
	}
	//将数据打印到控制台-处理输出
	public synchronized void getData(){
			if (flag == false) {
				try {
					wait();//让输出线程处于等待
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println("姓名:"+name+"   性别:"+sex);
			flag = !flag;
			
			notify();//将输入线程唤醒
	}
}

//创建输入任务
class Input1 implements Runnable{
    Des1 des;
	public Input1(Des1 des) {
		this.des = des;
	}
	public void run() {
		/*
		 * 分析:需要输入任务与输出任务共用一把锁,保证两个任务之间是同步的.
		 * 给两个任务加一把锁,锁可以是des或Object.class
		 * 
		 * 不建议使用Object.class,原因:由于Object的使用范围太大,会造成不必要的错误
		 * 使用des最合适,因为他只被两个线程共享.
		 * 
		 * 注意:只给一个任务加锁,无法实现两个线程的同步.
		 */

		int i= 0;
		while (true) {
			if (i == 1) {
				des.setData("凤姐", "男");
			}else {
				des.setData("芙蓉姐姐", "女");
			}
			
			i=(i+1)%2;
		}
	}
}
//创建输出任务
class Output1 implements Runnable{
    Des1 des;
	public Output1(Des1 des) {
		this.des = des;
	}
	public void run() {
		while (true) {
			des.getData();
		}
	}
}

多线程的设计模式:生产者消费者

两类:

  1. 单生产者单消费者
  2. 多生产者多消费者
单生产者单消费者

image

  • 分析:
    • 生产线程,消费线程
    • 生产任务,消费任务
    • 产品
public class Demo4 {
	public static void main(String[] args) {
		//1.创建产品
		Product product = new Product();
		//2.创建生产任务,消费任务
		Producer producer = new Producer(product);
		Consumer consumer = new Consumer(product);
		
		//3.创建生产线程,消费线程
		Thread t1 = new Thread(producer);
		Thread t2 = new Thread(consumer);
		
		//4.开启线程
		t1.start();
		t2.start();
	}
}

//创建产品类
class Product{
	String name;
	double price;
	int count;
	
	boolean flag = false;//用来在唤醒与等待之间进行切换
	
	//为生产准备数据
	public synchronized void setProduce(String name,double price){
		if (flag == true) {
			try {
				wait();//让生产线程等待
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		this.name = name;
		this.price = price;
		
		System.out.println(Thread.currentThread().getName()+"  生产了:"+name+"  产品的数量:"+count+"  产品的价格:"+price);
		count++;
		
		flag = ! flag;
		notify();//唤醒消费线程
	}
	//为消费准备数据
	public  synchronized void consume() {
		if (flag == false) {
			try {
				wait();//让消费线程等待
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		System.out.println(Thread.currentThread().getName()+"  消费了:"+name+"  产品的数量:"+count+"  产品的价格:"+price);
		
		flag = ! flag;
		notify();//唤醒生产线程
	}
}

//创建生产任务
class Producer implements Runnable{
	Product product;
	public Producer(Product product) {
		super();
		this.product = product;
	}
	public void run() {
		while (true) {
			product.setProduce("bingbing", 10);
		}
	}
}
//创建消费任务
class Consumer implements Runnable{
	Product product;
	public Consumer(Product product) {
		super();
		this.product = product;
	}
	public void run() {
		while (true) {
			product.consume();
		}
	}
}
先研究多生产者多消费者
  • 分析:
    • 生产线程,消费线程有多个
    • 生产任务,消费任务各有一个
    • 产品一个
      image
public class Demo5 {
	public static void main(String[] args) {
		//1.创建产品
		Product1 product = new Product1();
		//2.创建生产任务,消费任务
		Producer1 producer = new Producer1(product);
		Consumer1 consumer = new Consumer1(product);
		
		//3.创建生产线程,消费线程
		Thread t0 = new Thread(producer);
		Thread t1 = new Thread(producer);
		Thread t2 = new Thread(consumer);
		Thread t3 = new Thread(consumer);
		
		//4.开启线程
		t0.start();
		t1.start();
		t2.start();
		t3.start();
		
	}
}

//创建产品类
class Product1{
	String name;
	double price;
	int count;
	
	boolean flag = false;//用来在唤醒与等待之间进行切换
	
	//为生产准备数据
	public synchronized void setProduce(String name,double price){
		while (flag == true) {
			try {
				wait();//让生产线程等待
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		this.name = name;
		this.price = price;
		
		System.out.println(Thread.currentThread().getName()+"  生产了:"+name+"  产品的数量:"+count+"  产品的价格:"+price);
		count++;
		
		flag = ! flag;
		//notify();//唤醒消费线程
		notifyAll();
	}
	//为消费准备数据
	public  synchronized void consume() {
		while (flag == false) {
			try {
				wait();//让消费线程等待
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		System.out.println(Thread.currentThread().getName()+"  消费了:"+name+"  产品的数量:"+count+"  产品的价格:"+price);
		
		flag = ! flag;
		//notify();//唤醒生产线程
		notifyAll();
	}
}

//创建生产任务
class Producer1 implements Runnable{
	Product1 product;
	public Producer1(Product1 product) {
		super();
		this.product = product;
	}
	public void run() {
		while (true) {
			product.setProduce("bingbing", 10);
		}
	}
}
//创建消费任务
class Consumer1 implements Runnable{
	Product1 product;
	public Consumer1(Product1 product) {
		super();
		this.product = product;
	}
	public void run() {
		while (true) {
			product.consume();
		}
	}
}

Lock:

  • 比较synchronized和Lock

    1. synchronized:从jdk1.0开始使用----隐式同步

      synchronized(锁对象){//获取锁 我们将这里的锁称为锁旗舰或者监听器同步的代码
      }//释放锁

    2. Lock:从jdk1.5开始使用----显示同步

      • 原理:Lock是接口,我们要通过他的子类工作
      • 具体的工作流程:
        1. 首先调用lock的lock()方法,获取锁
        2. 进行同步操作的代码
        3. 调用lock的unlock()方法,释放锁
  • 使用场景总结:

    • 当进行多生产者的消费者的时候,使用lock,其他的使用synchronized
  • 使用效率上lock比synchronized高.

image

public class Demo6 {
	public static void main(String[] args) {
		//1.创建产品
		Product2 product = new Product2();
		//2.创建生产任务,消费任务
		Producer2 producer = new Producer2(product);
		Consumer2 consumer = new Consumer2(product);
		
		//3.创建生产线程,消费线程
		Thread t0 = new Thread(producer);
		Thread t1 = new Thread(producer);
		Thread t2 = new Thread(consumer);
		Thread t3 = new Thread(consumer);
		
		//4.开启线程
		t0.start();
		t1.start();
		t2.start();
		t3.start();
		
	}
}

//创建产品类
class Product2{
	String name;
	double price;
	int count;
	
	boolean flag = false;//用来在唤醒与等待之间进行切换
	
	//创建Lock的对象
	Lock lock = new ReentrantLock();
	
	//创建用于生产线程的Condition对象
	Condition proCon = lock.newCondition();
	//创建用于消费线程的Condition对象
	Condition conCon = lock.newCondition();
	
	//为生产准备数据
	public  void setProduce(String name,double price){
		try {
			lock.lock();//获取锁
			while (flag == true) {
				try {
					//wait();//让生产线程等待
					proCon.await();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			
			this.name = name;
			this.price = price;
			
			System.out.println(Thread.currentThread().getName()+"  生产了:"+name+"  产品的数量:"+count+"  产品的价格:"+price);
			count++;
			
			flag = ! flag;
			//notify();//唤醒消费线程
			//notifyAll();
			conCon.signal();
		
		} finally {//必须执行的代码
			lock.unlock();//释放锁
		}
		
	}
	//为消费准备数据
	public   void consume() {
	    try {
			lock.lock();
			while (flag == false) {
				try {
					//wait();//让消费线程等待
					conCon.await();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			
			System.out.println(Thread.currentThread().getName()+"  消费了:"+name+"  产品的数量:"+count+"  产品的价格:"+price);
			
			flag = ! flag;
			//notify();//唤醒生产线程
			//notifyAll();
			proCon.signal();
		} finally {
			lock.unlock();
		}
		
	}
}

//创建生产任务
class Producer2 implements Runnable{
	Product2 product;
	public Producer2(Product2 product) {
		super();
		this.product = product;
	}
	public void run() {
		while (true) {
			product.setProduce("bingbing", 10);
		}
	}
}
//创建消费任务
class Consumer2 implements Runnable{
	Product2 product;
	public Consumer2(Product2 product) {
		super();
		this.product = product;
	}
	public void run() {
		while (true) {
			product.consume();
		}
	}
}

守护线程

相当于后台线程,依赖于前台线程.正常情况下,当前台线程结束的时候,不管守护线程有没有结束,都会立刻结束.

  • 当程序调用setDaemon的时候,并且将参数设置成true,他就变成了守护线程.
  • 典型的守护线程:垃圾回收线程
  • 注意:这个方法一定要在start方法之前调用
public class Demo7 {
	public static void main(String[] args) {
		Test test = new Test();
		Thread thread = new Thread(test);
		/*
		 * 当程序调用setDaemon的时候,并且将参数设置成true,他就变成了守护线程.
		 * 注意:这个方法一定要在start方法之前调用
		 */
		thread.setDaemon(true);
		thread.start();
		
		int i=0;
		while (true) {
			if (i++ == 10) {
				System.out.println(Thread.currentThread().getName()+" i:"+i);
				break;//主线程结束
			}
		}
	}
}

class Test implements Runnable{
	public void run() {
		while (true) {
			System.out.println("守护线程");
		}
		
	}
}

join()方法:

  • 原理:程序一旦调用了join方法,他的优先级会高于主线程.意思是说,主线程会等当前线程执行完后再去执行.
  • 注意点:这个线程的优先级只比main高,对其他的线程没有影响.
  • 当线程开始工作后,让thread0调用join方法,他的优先级会高于main线程.
  • join方法必须在线程开始工作后执行.
线程状态图

线程状态图

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值