黑马程序员--【学习日记五】——多线程

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

知识列表

  • 线程与进程
  • 线程安全
  • 死锁
  • 线程间通信
  • 等待唤醒机制
  • 停止线程
  • 守护线程、join方法、优先级,yield

1)线程与进程:

进程:是一个正在执行中的程序。一个控制单元。

线程:就是进程中的一个独立的控制单元,线程控制进程的执行。

一个进程中至少有一个线程。

自定义线程方式一,继承Thread 步骤: 
1,定义类继承Thread。 
2,复写Thread类中的run方法。 
3,调用线程的start方法, 
发现运行结果每一次都不同。因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。明确一点,在某一个时刻,只能有一个程序在运行。(多核除外),cpu在做着快速的切换,以达到看上去是同时运行的效果。 
这就是多线程的一个特性:随机性。

覆盖run方法的原因:

Thread类用于描述线程。 
该类就定义了一个功能,用于存储线程要运行的代码,该功能就是run方法。

自定义线程方式二,实现Runnable 步骤: 
1,定义类实现Runnable接口 
2,覆盖Runnable接口中的run方法。 
      将线程要运行的代码存放在该run方法中。 
3,通过Thread类建立线程对象。 
4,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。 
       为什么要将Runnable接口的子类对象传递给Thread的构造函数。 
       因为,自定义的run方法所属的对象是Runnable接口的子类对象。 
       所以要让线程去指定指定对象的run方法。就必须明确该run方法所属对象。 
5,调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

      实现方式好处:避免了单继承的局限性。 
     在定义线程时,建议使用实现方式。

两种方式的区别:

      继承Thread:线程代码存放Thread子类run方法中。 
      实现Runnable,线程代码存在接口的子类的run方法。


2)线程安全:

卖票程序:

  1: class Ticket implements Runnable//extends Thread
  2: {
  3: 	private  int tick = 100;
  4: 	public void run()
  5: 	{
  6: 		while(true)
  7: 		{
  8: 			if(tick>0)
  9: 			{
 10: 				System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
 11: 			}
 12: 		}
 13: 	}
 14: }
 15: class  TicketDemo
 16: {
 17: 	public static void main(String[] args) 
 18: 	{
 19: 		Ticket t = new Ticket();
 20: 		Thread t1 = new Thread(t);//创建了一个线程;
 21: 		Thread t2 = new Thread(t);//创建了一个线程;
 22: 		Thread t3 = new Thread(t);//创建了一个线程;
 23: 		Thread t4 = new Thread(t);//创建了一个线程;
 24: 		t1.start();
 25: 		t2.start();
 26: 		t3.start();
 27: 		t4.start();
 28: 	}
 29: }

?

多线程容易出现安全问题:

原因: 
??? 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。

解决办法: 
??? 对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。通过加锁的方式来完成,synchronized(对象){需要被同步的代码}。

  1: class Ticket implements Runnable
  2: {
  3: 	private  int tick = 1000;
  4: 	Object obj = new Object();
  5: 	public void run()
  6: 	{
  7: 		while(true)
  8: 		{
  9: 			synchronized(obj)//同步
 10: 			{
 11: 				if(tick>0)
 12: 				{
 13: 					//try{Thread.sleep(10);}catch(Exception e){}
 14: 					System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
 15: 				}
 16: 			}
 17: 		}
 18: 	}
 19: }
 20: class  TicketDemo2
 21: {
 22: 	public static void main(String[] args) 
 23: 	{
 24: 
 25: 		Ticket t = new Ticket();
 26: 
 27: 		Thread t1 = new Thread(t);
 28: 		Thread t2 = new Thread(t);
 29: 		Thread t3 = new Thread(t);
 30: 		Thread t4 = new Thread(t);
 31: 		t1.start();
 32: 		t2.start();
 33: 		t3.start();
 34: 		t4.start();
 35: 	}
 36: }
 37: 

同步(synchronized)的前提: 
1,必须要有两个或者两个以上的线程。 
2,必须是多个线程使用同一个锁。

必须保证同步中只能有一个线程在运行。

好处:解决了多线程的安全问题。 
弊端:多个线程需要判断锁,较为消耗资源。

同步有两种表现形式:同步函数和同步代码块。

同步时必须明确哪些是需要同步的,哪些是不需要同步的,并且要保证同步函数使用的是同一把锁,也就是synchronized(对象)中的对象是同一个。

静态函数同步使用的锁是所在类的字节码文件对象。

懒汉式中的安全问题:

  1: public class TicketDemo {
  2: 	private TicketDemo(){}
  3: 	private static TicketDemo t = null;
  4: 	public static TicketDemo getInstance(){
  5: 		if(t==null){
  6: 			synchronized(TicketDemo.class){
  7: 				if(t==null)
  8: 					t = new TicketDemo();
  9: 			}
 10: 		}
 11: 		return t;
 12: 	}
 13: }

3)死锁

同步中嵌套同步时:

  1: 
  2: class Ticket implements Runnable
  3: {
  4: 	private  int tick = 1000;
  5: 	Object obj = new Object();
  6: 	boolean flag = true;
  7: 	public  void run()
  8: 	{
  9: 		if(flag)
 10: 		{
 11: 			while(true)
 12: 			{
 13: 				synchronized(obj)
 14: 				{
 15: 					show();
 16: 				}
 17: 			}
 18: 		}
 19: 		else
 20: 			while(true)
 21: 				show();
 22: 	}
 23: 	public synchronized void show()//this
 24: 	{
 25: 		synchronized(obj)
 26: 		{
 27: 			if(tick>0)
 28: 			{
 29: 				try{Thread.sleep(10);}catch(Exception e){}
 30: 				System.out.println(Thread.currentThread().getName()+"....code : "+ tick--);
 31: 			}
 32: 		}
 33: 	}
 34: }
 37: class  DeadLockDemo
 38: {
 39: 	public static void main(String[] args) 
 40: 	{
 42: 		Ticket t = new Ticket();
 44: 		Thread t1 = new Thread(t);
 45: 		Thread t2 = new Thread(t);
 46: 		t1.start();
 47: 		try{Thread.sleep(10);}catch(Exception e){}
 48: 		t.flag = false;
 49: 		t2.start();
 52: 	}
 53: }

?

?

4)线程间通信:

  1: class Res
  2: {
  3: 	String name;
  4: 	String sex;
  5: 	boolean flag = false;
  6: }
  7: class Input implements Runnable
  8: {
  9: 	private Res r ;
 10: 	Input(Res r)
 11: 	{
 12: 		this.r = r;
 13: 	}
 14: 	public void run()
 15: 	{
 16: 		int x = 0;
 17: 		while(true)
 18: 		{
 19: 			synchronized(r)//加锁,对象是r
 20: 			{
 24: 				if(x==0)
 25: 				{
 26: 					r.name="mike";
 27: 					r.sex="man";
 28: 				}
 29: 				else
 30: 				{
 31: 					r.name="丽丽";
 32: 					r.sex = "女女女女女";
 33: 				}
 34: 				x = (x+1)%2;
 35: 				r.flag = true;
 36: 				r.notify();
 37: 			}
 38: 		}
 39: 	}
 40: }
 41: 
 42: class Output implements Runnable
 43: {
 44: 	private Res r ;
 45: 	
 46: 	Output(Res r)
 47: 	{
 48: 		this.r = r;
 49: 	}
 50: 	public void run()
 51: 	{
 52: 		while(true)
 53: 		{
 54: 			synchronized(r)//加锁,对象是r
 55: 			{
 58: 				System.out.println(r.name+"...."+r.sex);
 59: 				r.flag = false;
 60: 				r.notify();
 61: 			}
 62: 		}
 63: 	}
 64: }
 65: class  InputOutputDemo
 66: {
 67: 	public static void main(String[] args) 
 68: 	{
 69: 		Res r = new Res();
 70: 
 71: 		Input in = new Input(r);
 72: 		Output out = new Output(r);
 73: 
 74: 		Thread t1 = new Thread(in);
 75: 		Thread t2 = new Thread(out);
 76: 
 77: 		t1.start();
 78: 		t2.start();
 79: 	}
 80: }

正确结果为:

  • mike….man
  • 丽丽….女女女女女

交替打印

如果不加同步,或者同步使用的对象不是同一个,就会出现以下结果。

Untitled?

5)等待唤醒机制:

线程池:等待线程都存在线程池中。

  1: class Res
  2: {
  3: 	String name;
  4: 	String sex;
  5: 	boolean flag = false;
  6: }
  7: class Input implements Runnable
  8: {
  9: 	private Res r ;
 10: 	Input(Res r)
 11: 	{
 12: 		this.r = r;
 13: 	}
 14: 	public void run()
 15: 	{
 16: 		int x = 0;
 17: 		while(true)
 18: 		{
 19: 			synchronized(r)
 20: 			{
 21: 
 22: 				if(r.flag)
 23:                                         //等待
 24: 					try{r.wait();}catch(Exception e){}
 25: 				if(x==0)
 26: 				{
 27: 					r.name="mike";
 28: 					r.sex="man";
 29: 				}
 30: 				else
 31: 				{
 32: 					r.name="丽丽";
 33: 					r.sex = "女女女女女";
 34: 				}
 35: 				x = (x+1)%2;
 36: 				r.flag = true;
 37: 				r.notify();//唤醒
 38: 			}
 39: 		}
 40: 	}
 41: }
 42: class Output implements Runnable
 43: {
 44: 	private Res r ;
 45: 	
 46: 	Output(Res r)
 47: 	{
 48: 		this.r = r;
 49: 	}
 50: 	public void run()
 51: 	{
 52: 		while(true)
 53: 		{
 54: 			synchronized(r)
 55: 			{
 56: 				if(!r.flag)
 57:                                         //等待
 58: 					try{r.wait();}catch(Exception e){}
 59: 				System.out.println(r.name+"...."+r.sex);
 60: 				r.flag = false;
 61: 				r.notify();//唤醒
 62: 			}
 63: 		}
 64: 	}
 65: }
 66: class  InputOutputDemo
 67: {
 68: 	public static void main(String[] args) 
 69: 	{
 70: 		Res r = new Res();
 71: 
 72: 		Input in = new Input(r);
 73: 		Output out = new Output(r);
 74: 
 75: 		Thread t1 = new Thread(in);
 76: 		Thread t2 = new Thread(out);
 77: 
 78: 		t1.start();
 79: 		t2.start();
 80: 	}
 81: }

?

wait;notify();notifyAll(); 都使用在同步中,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。

这些方法之所以定义在Object类中是因为这些方法所使用的对象是任意对象,即任意对象都能调用这些方法。 
这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁, 
只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。 
不可以对不同锁中的线程进行唤醒。

生产者消费者的例子:

  1: class Resource
  2: {
  3: 	private String name;
  4: 	private int count = 1;
  5: 	private boolean flag = false;
  6: 			//  t1    t2
  7: 	public synchronized void set(String name)
  8: 	{
  9: 		while(flag)
 10: 			try{this.wait();}catch(Exception e){}//t1(放弃资格)  t2(获取资格)
 11: 		this.name = name+"--"+count++;
 12: 
 13: 		System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
 14: 		flag = true;
 15: 		this.notifyAll();//唤醒所有等待线程,防止出现全部线程等待的情况
 16: 	}
 17: 
 18: 
 19: 	//  t3   t4  
 20: 	public synchronized void out()
 21: 	{
 22: 		while(!flag)
 23: 			try{wait();}catch(Exception e){}//t3(放弃资格) t4(放弃资格)
 24: 		System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);
 25: 		flag = false;
 26: 		this.notifyAll();//唤醒所有等待线程,防止出现全部线程等待的情况
 27: 	}
 28: }
 29: 
 30: class Producer implements Runnable
 31: {
 32: 	private Resource res;
 33: 
 34: 	Producer(Resource res)
 35: 	{
 36: 		this.res = res;
 37: 	}
 38: 	public void run()
 39: 	{
 40: 		while(true)
 41: 		{
 42: 			res.set("+商品+");
 43: 		}
 44: 	}
 45: }
 46: 
 47: class Consumer implements Runnable
 48: {
 49: 	private Resource res;
 50: 
 51: 	Consumer(Resource res)
 52: 	{
 53: 		this.res = res;
 54: 	}
 55: 	public void run()
 56: 	{
 57: 		while(true)
 58: 		{
 59: 			res.out();
 60: 		}
 61: 	}
 62: }

使用notifyAll之后虽然解决的出现全部等待的情况的问题,但是新问题又出现了,唤醒对方线程的同时,将本方线程也唤醒了。

JDK1.5中提供了多线程升级解决方法,将synchronized替换成lock,unlock,condition这些现实化的操作方式,将Object中的notify和notifyAll方法替换成了condition.signal和condition.signalAll方法,可以通过不同的condition实现只唤醒对方等待线程的操作,也就是一个锁对应多个condition。显示的锁机制和现实的等待唤醒机制。

6)停止线程:

stop方法已经过时,只用结束run方法结束线程,而要结束run方法,就要控制住循环。

特殊情况: 
当线程处于了冻结状态。 
就不会读取到标记。那么线程就不会结束。

当没有指定的方式让冻结的线程恢复到运行状态是,这时需要对冻结进行清除。interrupt()方法,强制让线程恢复到运行状态。会受到一个InterruptException异常。强制让线程恢复到运行状态中来后,可以操作标记让线程结束。

7)守护线程、join方法、优先级,yield

当所有的线程都是守护线程时,JVM就自动退出了。Thread t = new Thread();t.setDanemon();//将t设置为守护线程(经典的圣斗士星矢例子);

作后台线程讲更好理解。当所有的前台线程都结束后后台线程就结束。

join方法:

主线程运行到join方法时,join对应的线程执行完以后主线程才会继续执行。一般使用于临时加入线程的情况。

优先级:

1-10,默认为5,setPriority();优先级10通常写为Thread.MAX_PRIORITY,1写成MIN_PRIORITY,5写成NORM_PRIORITY。

yield:相当于强制释放CPU执行权。

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值