java第二十天

线程间的通信方法

在这里插入图片描述

1. 线程通信简介

一般而言,在一个应用程序(即进程)中,一个线程往往不是孤立存在的,常常需要和其它线程通信,以执行特定的任务。如主线程和次线程,次线程与次线程,工作线程和用户界面线程等。这样,线程与线程间必定有一个信息传递的渠道。这种线程间的通信不但是难以避免的,而且在多线程编程中也是复杂和频繁的。线程间的通信涉及到4个问题:

  1. 线程间如何传递信息;
  2. 线程之间如何同步,以使一个线程的活动不会破坏另一个线程的活动,以保证计算结果的正确合理;
  3. 当线程间具有依赖关系时,如何调度多个线程的处理顺序;
  4. 如何避免死锁问题。

在windows系统中,线程间的通信一般采用四种方式:全局变量方式、消息传递方式、参数传递方式和线程同步法。

2. 全局变量方式

由于属于同一个进程的各个线程共享操作系统分配该进程的资源,故解决线程间通信最简单的一种方法是使用全局变量。对于标准类型的全局变量,我们建议使用volatile修饰符,它告诉编译器无需对该变量作任何的优化,即无需将它放到一个寄存器中,并且该值可被外部改变。

实例演示:该实例采用全局变量来控制时间显示线程的显示格式,比较简单。

3. 参数传递方式

该方式是线程通信的官方标准方法,多数情况下,主线程创建子线程并让其子线程为其完成特定的任务,主线程在创建子线程时,可以通过传给线程函数的参数和其通信,三类创建线程的函数都支持参数的传递(哪三类?看前面的介绍吧!)。所传递的参数是一个32位的指针,该指针不但可以指向简单的数据,而且可以指向结构体或类等复杂的抽象数据类型。

4. 消息传递方式

在Windows程序设计中,应用程序的每一个线程都拥有自己的消息队列,甚至工作线程也不例外,这样一来,就使得线程之间利用消息来传递信息变的非常简单。我们可以在一个线程的执行函数中向另一个线程发送自定义的消息来达到通信的目的。一个线程向另外一个线程发送消息是通过操作系统实现的。利用Windows操作系统的消息驱动机制,当一个线程发出一条消息时,操作系统首先接收到该消息,然后把该消息转发给目标线程,接收消息的线程必须已经建立了消息循环。该方式可以实现任意线程间的通信,所以是比较常见和通用的方式。

5. 线程同步法

还可以通过线程同步来实现线程间通信。例如有两个线程,线程A写入数据,线程B读出线程A准备好的数据并进行一些操作。这种情况下,只有当线程A写好数据后线程B才能读出,只有线程B读出数据后线程A才能继续写入数据,这两个线程之间需要同步进行通信。关于线程同步的方法和控制是编写多线程的核心和难点,方法也比较多。

方式一:使用 volatile 关键字

基于 volatile关键字来实现线程间相互通信是使用共享内存的思想,大致意思就是多个线程同时监听一个变量,当这个变量发生变化的时候 ,线程能够感知并执行相应的业务。这也是最简单的一种实现方式

方式二:使用Object类的wait() 和 notify() 方法

众所周知,Object类提供了线程间通信的方法:wait()、notify()、notifyaAl(),它们是多线程通信的基础,而这种实现方式的思想自然是线程间通信。

注意: wait和 notify必须配合synchronized使用,wait方法释放锁,notify方法不释放锁

方式三:使用JUC工具类 CountDownLatch
方式四:使用 ReentrantLock 结合 Condition
方式五:基本LockSupport实现线程间的阻塞和唤醒

LockSupport 是一种非常灵活的实现线程间阻塞和唤醒的工具,使用它不用关注是等待线程先进行还是唤醒线程先运行,但是得知道线程的名字。

1.通过实现了 创建接口对象 ReentrantLock

2.在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁

3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁

public class RunnableImpl1 implements Runnable {

	//总票数
	private static int ticket=10;
	Object Obj=new Object();
	Lock l=new ReentrantLock();
	//设置线程任务 买票
	@Override
	public void run() {
		// TODO Auto-generated method stub
		//使用无限循环 让买票操作重复执行
		
		//1.创建lock接口的对象
		
		
		
		while (true) {
			//锁对象 任意
			//synchronized (Obj) {
				
				//extracted();
				
			//}
			
			//加锁
			l.lock();
			
			if (ticket>0) {
				try {
					Thread.sleep(10);//模拟出票的过程
					System.out.println(Thread.currentThread().getName()+"正在买第---->"+ticket+"张票");
					
					ticket--;
				} catch (Exception e) {
					// TODO: handle exception
				}
				finally {
					
					//释放锁/释放资源
					l.unlock();
				}
			}
			
		}
	}
	//非静态方法 锁的是this
	private  synchronized void extracted() {
		synchronized(this){
			
			if (ticket>0) {
				try {
					Thread.sleep(10);//模拟出票的过程
				} catch (Exception e) {
					// TODO: handle exception
				}
				System.out.println(Thread.currentThread().getName()+"正在买第---->"+ticket+"张票");
				
				ticket--;
			}
		}
	}
	
	//静态的同步方法   了解
	//锁对象的问题  this是创建对象之后产生的   静态方法属于类 优先于this 所以静态没有this
	
	//静态方法的锁对象是本类class属性  --->class文件对象(反射)
	private static  synchronized void extracted1() {
		synchronized(RunnableImpl1.class){
			
			
			if (ticket>0) {
				try {
					Thread.sleep(10);//模拟出票的过程
				} catch (Exception e) {
					// TODO: handle exception
				}
				System.out.println(Thread.currentThread().getName()+"正在买第---->"+ticket+"张票");
				
				ticket--;
			}
		}
	}
	
}

线程之间的通信:

strat之前

running 运行状态

synchronized 锁阻塞状态 排队

sleep 秒表

wait() 等待 对应的方法 notify/notifayall

线程之间的通信:

创建一个顾客线程(消费者) 告知老板要的包子的种类和数量 调用wait方法 放弃cpu执行,进入到waiting的状态

创建一个老板线程(生成者) 花了5秒钟做包子 做好包子之后 调用notify方法 唤醒顾客包子做好了

注意:

  • 顾客和老板线程必须使用同步代码块包裹起来 保证等待和唤醒只能有一个在执行(包子是一个共享资源)
  • 同步使用的锁对象必须保证唯一

方法的调用 wait和notify 都是属于Object的方法

只有锁对象才能调用 wait和notify/notifyAll

public class Test {

	static Object obj=new Object();//成员变量  对象唯一
	public static void main01(String[] args) {
		
		//消费者线程1
		new Thread(){
			@Override
			public void run() {
				// TODO Auto-generated method stub
				while (true) {
					synchronized (obj) {
						System.out.println("老板,来一个包子");
						
						try {
							obj.wait();
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						
						System.out.println("顾客1:包子已经做好了,开吃");//被通知之后执行的
						System.out.println("----------------");
					}
				}
			}
		}.start();
		
		new Thread(){
			@Override
			public void run() {
				// TODO Auto-generated method stub
				while (true) {
					synchronized (obj) {
						System.out.println("老板,来一个包子");
						
						try {
							obj.wait();
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						
						System.out.println("顾客2:包子已经做好了,开吃");//被通知之后执行的
						System.out.println("----------------");
					}
				}
			}
		}.start();
		
		//商家线程
		new Thread(){
			@Override
			public void run() {
				
				while (true) {
					try {
						Thread.sleep(5000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}//花了五秒钟做包子
					
					
					
					synchronized(obj){
						System.out.println("老板:包子做好了,来取餐");
						
						//obj.notify();//通知正在等待线程
						obj.notifyAll();
					}
					
					
				}
			};
		}.start();
		
		
	}
    	public static void main02(String[] args) {
		//等待唤醒机制
		Baozi b=new Baozi();
		
		Baozifu bzf=new Baozifu(b);
		bzf.start();
		Programmer pro=new Programmer(b);
		pro.start();
	}

等待唤醒机制:

点餐(进入等待)—>做餐(需要时间,通知顾客,等待)—>吃餐(需要 时间,吃完之后)—>点餐—>做餐

public class Baozifu extends Thread {

	//前提  保证锁对象是唯一的
	private Baozi  baozi;
	
	public Baozifu(Baozi zi){
		this.baozi=zi;
	}
	
	
	@Override
	public void run() {//生成
		int count=0;
		// TODO Auto-generated method stub
		//包子铺一致做包子
		while (true) {
			
			
			synchronized(baozi){
				if (baozi.flag==true) {
					//包子铺进入等待
					try {
						baozi.wait();//等待
					} catch (Exception e) {
						// TODO: handle exception
					}
				}
				
				//如果没有包子  被唤醒之后执行的脚本 生产包子
				if (count%2==0) {
					baozi.pi="白面皮";
					baozi.xian="猪肉";
				}
				else {
					baozi.pi="黑面皮";
					baozi.xian="牛肉";
				}
				
				count++;
				System.out.println("包子铺正在生产:"+baozi.pi+baozi.xian);
				
				
				
				try {
			         Thread.sleep(3000);
				} catch (Exception e) {
					// TODO: handle exception
					e.printStackTrace();
				}
				
				
				//包子铺修改包子状态
				baozi.flag=true;
				
				baozi.notify();
				
				System.out.println("包子铺生产好了:"+baozi.pi+baozi.xian);
			}
		}
	}
}
public class Baozi {

	String pi;
	
	String xian;
	
	boolean flag=false;
	//包子的状态  如果有true  如果没有false
	
}
public class Programmer extends Thread {

	private Baozi bz;
	
	public Programmer(Baozi baozi) {
		// TODO Auto-generated constructor stub
		this.bz=baozi;
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			synchronized(bz){
				if (bz.flag==false) {
					try {
						bz.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					
					
				}
				
				//被唤醒之后执行的脚本
				System.out.println("程序员正在吃:"+bz.pi+bz.xian);
				//吃完了
				
				bz.flag=false;
				bz.notify();//通知商家接着做
				System.out.println("程序员已经吃完了"+bz.pi+bz.xian);
				
				System.out.println("------------------");
				
				
				
			}
		}
	}
}
public static void main(String[] args) {
		//等待唤醒机制
		Baozi b=new Baozi();
		
		Baozifu bzf=new Baozifu(b);
		bzf.start();
		Programmer pro=new Programmer(b);
		pro.start();
	}
	

线程池 常量池

我们使用线程的时候 每次都是创建线程 频繁创建对象/线程 开销 性能消耗
好处

1.降低资源消耗 减少创建 线程和销毁线程的次数 每个工作线程可以重复利用 可以执行多个任务

2.提高相应速度 当任务多的时候 可以、不用等待立即执行

3.提高线程的可管理性 可以根据系统的承受能力 调整池的数量

Java1.5之后提供 线程池
Java.util.concurrent.excutors 线程池的工厂类 用来生产线程池

Excutors类中的方法

static ExecutorService newFixedThreadPool(int nThreads)

创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。

返回值 ExecutorService接口 返回的是接口的实现类对象 接口接收这个对象

接口中的方法

submit 提交 提交一个任务 直接使用线程池中的线程 执行任务

shutdown 关闭线程

实现步骤:

1.使用线程池工厂类 创建对象

2.创建一个类 实现runnable接口 重写run方法 设置任务

3.调用ExecutorService中submit方法 传递线程任务 开启线程 执行run方法

4.调用ExecutorService 中shutdown方法 销毁线程(不建议执行)

public static void main(String[] args) {
		//1
		ExecutorService serv= Executors.newFixedThreadPool(3);//创建包含3个线程的线程池
	
		//2-3.task  任务
		serv.submit(new RunnableImp());
		serv.submit(new RunnableImp());
		serv.submit(new RunnableImp());
		serv.submit(new RunnableImp());
		serv.submit(new RunnableImp());
		serv.submit(new RunnableImp());
		serv.submit(new RunnableImp());
		
		//3.销毁
	    serv.shutdown();
	
	} 
public class RunnableImp implements Runnable {

	@Override
	public void run() {
		// TODO Auto-generated method stub
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"创建了一个新的线程执行");
	}

}

Lambda表达式

Java 8的一个大亮点是引入Lambda表达式,使用它设计的代码会更加简洁。当开发者在编写Lambda表达式时,也会随之被编译成一个函数式接口。

下面这个例子就是使用Lambda语法来代替匿名的内部类,代码不仅简洁,而且还可读。

public class RunnableImpl implements  Runnable {

	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println("哈哈哈 美滋滋");
	}

}

使用匿名内部类的问题

Thread类 需要runnable接口作为参数 其中的抽象run方法 是用来指定线程的任务的核心

为了指定run的方法体 不得不 需要runnable接口的实现类

为了省去定义一个runnable 实现了 不得不使用匿名内部类

必须覆盖重写 抽象方法run 所以方法名 返回值卡 参数列表 必须重写一遍 而且不能写错

而实际上 只有run方法的方法体才是我们关心的

lambda表达式替换匿名内部类

lambda表达式 ()->{}

由三部分组成:
a.一些参数 ()
b.一个箭头->
c.一段代码

格式说明:

(参数列表)->{一些重写方法的代码}
():接口中抽象方法的参数列表 没有参数 空着,有参数就写对应的参数 参数数量和顺序不能错
->传递的意思 把参数传递给方法体
{} 重写接口的抽象方法的方法体

函数式编程:(C#委托类型) 把一个方法 直接作为另外一个方法的参数

ublic class Test {

	public static void main(String[] args) {
		//-------------第一种写法--------------------
		RunnableImpl  ru=new RunnableImpl();
		Thread td=new Thread(ru);
		td.start();
		//------------第二种 匿名内部类-----------------------------
		Runnable r=new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("美滋滋1");
			}
		};
		new Thread(r).start();
		
		//-----------第三种-------------------------------
		new Thread(new  Runnable() {
			@Override
			public void run() {
				System.out.println("美滋滋2");
			}
		}).start();
		
		//----------------------------------------
		new Thread(()->{}).start();
		
	}

1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值