13(多线程)

多线程

1多线程的两种使用方式

第一种:继承Thread类(不建议使用,因为无法多继承)

第二种:实现Runnable接口(建议使用)

栗子:直接用第二种

写一个类实现Runnable接口,并覆盖其中的方法

/*
 * 1,该类实现Runnable接口
 * 2,实现run()方法
 * 3,实例化该类,在new个Thread(Runnable),传入该类,用start启动
 * */
 class MyThreads implements Runnable{

	@Override
	public void run() {
		for(int i=0;i<20;i++){
			//线程都是有名字的,可以用Thread.currentThread()返回Thread对象,在掉用getName()获取该线程的名字
			System.out.println(Thread.currentThread().getName()+": "+i);
		}
	}
	 
 }
启动这个线程需要start()方法,因为Runnable接口中没有这个方法,所以需要Thread类来启动
	public static void main(String[] args) {
		//1先实例化出自己的线程类
		MyThreads mt = new MyThreads();
		
		//2要用start()方法启动线程,因为接口没有该方法所以需要Thread
		//Threa可以传入Runnable参数,还可以传入一个线程的名字
		Thread th = new Thread(mt,"MyThread");
		
		//3要用start()方法启动线程
		th.start();
		
		//main自己跑一个线程
		for(int i=0;i<20;i++){
			System.out.println(Thread.currentThread().getName()+": "+i);
		}
		
	}
这样两个线程就会交替的跑起来



2线程的调度

看最顶层的图:1,Statr()让线程进入Runnable状态(等待调用),调度表中轮到该线程时,就会进入Running(正在运行)

2,如果这时我不想在跑了,可以调用yield()返回到Runnable(等待调用状态),(注意的是对多核的计算机可能无效

3,sleep()让该线程去睡觉,进入Blocke(睡觉状态,不会在去竞争),直到睡觉时间结束。

4,join(),让该线程一直运行完,其他的线程才能去运行

5,synchronized(同步)关键字会让一段代码上锁,直到这段代码运行完(线程的同步)

6,wait()方法让该线程挂起来(暂停),不在参与竞争

7,notify()方法会让线程从新运行起来,注意的是要在线程都要挂在同一个对象上(也就是同一把钥匙),并且是随机唤醒其中的任意对象



3线程的终止

不应该调用stop()方法,这样不安全(并且已经过时),最佳停止方法是自己写一个停止方法,在该方法内释放资源

看栗子:

/*
 * 最佳停止方法
 * 1,定义一个flag
 * 2,break跳出循环
 * 3,写一个停止方法
 * */
class MyThre implements Runnable{
	//1定义一个flag
	private boolean flag = true;

	@Override
	public void run() {
		for(int i=0;i<100;i++){
			//2判断停止
			if(!flag) break;
			
			System.out.println("iiii"+i);
		}
	}
	
	//停止方法
	public void stopThread(){
		flag = false;
		//可以在这里释放资源
	}
}
条件满足是,掉用停止方法
	public static void main(String[] args) {
		MyThre mt = new MyThre();
		Thread t = new Thread(mt);
		t.start();
		
		//main自己的线程
		while(true){
			if(mt.i>=30){	//当条件满足时,调用停止方法
				mt.stopThread();
				
				break;
			}
		}
		
	}



4线程的同步

线程同步可以解决多线程竞争同一个资源保证其安全的一种手段

线程同步要用synchronized关键字,并且需要一个任意对象做为钥匙

当方法声明为同步方法是,会用this做为钥匙

栗子:

/*
 * 线程同步
 * */
 class MyThreads implements Runnable{
	 //定义一把钥匙,也可以用this做钥匙
	 Object o = new Object();
	 
	 int index = 0;

	 //实现的方法
	@Override
	public void run() {
		synchronized (o) {
			for(index =0;index<1000;index++){
				System.out.println(Thread.currentThread().getName()+": "+index);
			}
		}
	}
	 
 }

	public static void main(String[] args) {
		MyThreads mt = new MyThreads();
		
		//实例化两个线程
		//这样就不会同时修改一个数据了
		Thread t1 = new Thread(mt,"T1");
		Thread t2 = new Thread(mt,"T2");
		t1.start();
		t2.start();
		
	}


5同步问题(死锁)

用了线程同步后,如果操作不当会造成死锁等问题

栗子:

/*
 * 线程死锁
 * */
 class MyThreads implements Runnable{
	 //定义两把钥匙
	 Object key1 = new Object();
	 Object key2 = new Object();
	 //
	 boolean flag = true;
	 

	 //实现的方法
	@Override
	public void run() {
		//1判断是否进入,
		 if(flag){
			 synchronized (key1) {	//好人拿第一把钥匙进入
				 //2进来后就为false
				 flag = false;
				 System.out.println(Thread.currentThread().getName()+"我拿key1进来了");
				 //注意了,万一去睡觉了
				 try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				 
				 //拿第二把钥匙第二次进入
				 synchronized (key2) {
					 System.out.println(Thread.currentThread().getName()+"我拿key2进来了");
				}
			}
		 }else{
			 //第二条线程坏人先拿第二把钥匙进入
			 synchronized (key2) {
				//进来后要设为true,能让线程一第二次进入
				 flag = true;
				 System.out.println(Thread.currentThread().getName()+"我拿key2进来了");
				 //注意了,万一去睡觉了
				 try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				 
				 //该线程要拿第一把钥匙再次进入
				 synchronized (key1) {
					 System.out.println(Thread.currentThread().getName()+"我拿key1进来了");
				}
			}
		 }
	 }
	 
 }
启动线程
	public static void main(String[] args) {
		MyThreads mt = new MyThreads();
		
		//实例化两个线程
		//这样就不会同时修改一个数据了
		Thread t1 = new Thread(mt,"好人");
		Thread t2 = new Thread(mt,"坏人");
		t1.start();
		t2.start();
		
	}

解析:一般都是同步嵌套容易造成死锁问题

第一条线程好人拿着第一把钥匙进去,然后就跑去睡觉(false了),这样第二条线程坏人就会拿着第二把钥匙进去,然后它又睡觉去了(true了),就轮到好人要拿第二把钥匙进去,但是这第二把钥匙在坏人手中,它又在睡觉,好人就永远不可能等到第二把钥匙,这样就会等到死,造成坏人也不不可能拿到第一钥匙。



6生产者和消费者(wait和notify)

假如:一个厨师不停的做食物,做一个顾客就吃一个,直到厨师做完

厨师就是生产者,顾客就是消费者

看例子:注意的是notify需要同一个对象做钥匙

厨师类:

/*
 * 生产者:厨师
 * */
 class Cool implements Runnable{
	 String[] food;	//一组要生产的食物
	 String foods;	//生产好的食物
	 
	 boolean falg;	//判断是否做了食物
	 
	 //构造方法
	 public Cool() {
		 food = new String[]{"米饭","汉堡","沙拉","火锅","排骨"};
		 falg = false;	//开始是没做食物的
	}
	 
	 //做食物方法
	 public void make(){
		 //因为要一直做完才能拿去给顾客,所以要同步
		 //用自己做钥匙,应为顾客中也有厨师这个对象,要使用同一个对象做钥匙
		 synchronized (this) {
			 //判断是否做了食物
			if(!falg){
				try {
					int index = new Random().nextInt(food.length);
					foods = food[index];	//这里随机生产食物
					System.out.println(Thread.currentThread().getName()+":生产了"+foods);
					
					//生产完就要将falg设为true
					falg = true;
					
					//生产完本来是要挂自己,然后等顾客吃完再notify自己
					//但是因为notify是随机唤醒线程,所以万一顾客先抢到线程,顾客就会先挂起
					//所以这里就先要将顾客线程先唤醒释放掉,然后再挂起自己
					this.notify();
					//然后再挂起自己
					this.wait();	//wait()会抛异常,需要捕获
					
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	 }

	 //覆盖的方法
	@Override
	public void run() {
		//不停的生产食物
		for(int i=0;i<10;i++){
			make();
		}
	}
	 
 }

顾客类:
/*
 *  消费者:顾客
 *  */
 class Customer implements Runnable{
	 //需要厨师这个对象
	 Cool c;
	 
	 //构造方法
	 public Customer(Cool c) {
		 this.c = c;
	}
	 
	 //吃方法
	 public void eat(){
		// 一直要吃完才通知厨师去做,所以需要同步
		 //注意的要用同意把钥匙做对象
		 //这里要用厨师做钥匙
		 synchronized (c) {
			 //判断厨师是否做了食物
			if(c.falg){
				try {
					System.out.println(Thread.currentThread().getName()+" :正在吃 "+c.foods);
					//吃完要叫厨师做,先设为false,让食物为空,在唤醒厨师的线程
					c.falg = false;
					c.notify();
					
					//吃完将自己挂起来,等厨师去做
					c.wait();
					
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}else{
				//这里如果是顾客先抢到线程,但厨师还没做,这时就要把自己挂起来,等厨师的线程去做
				//所以为什么前面先要唤醒线程,在去挂起
				try {
					c.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	 }

	 //实现方法
	@Override
	public void run() {
		//不停的吃
		while(true){
			eat();
		}
	}
	 
 }

启动类:
	public static void main(String[] args) {
		Cool c = new Cool();	//实例化厨师
		Customer cu = new Customer(c);	//实例化顾客
		
		//创建线程,并未线程命名
		Thread ct = new Thread(c,"厨师");
		Thread cut = new Thread(cu,"顾客");
		//这里注意的是因为顾客是while死循环不停吃食物 
		//当厨师生产完所有实物后要终止程序,所以要将顾客线程设为后台线程
		//后台线程的意思是:当所有线程结束时,这个线程也就结束了。
		//用setDaemon(true)方法,注意这个方法一定要在线程启动之前声明
		cut.setDaemon(true);
		
		//然后再启动线程
		ct.start();
		cut.start();
		
	}
输出:

厨师:生产了火锅
顾客 :正在吃 火锅
厨师:生产了米饭
顾客 :正在吃 米饭
厨师:生产了沙拉
顾客 :正在吃 沙拉
厨师:生产了米饭
顾客 :正在吃 米饭

..........

........




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值