黑马程序员_java多线程总结

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

一、多线程概述

现实生活中许多事情并不是按照顺序发生的,很多时候都是同时发生,例如人体中可以同时进行呼吸、血液流动、思考问题这些活动,用户操作电脑可以同时听音乐、玩游戏和打印文件。这些问题投射在java中就引出了并发执行的问题,而java就靠多线程机制来解决这些问题。

(1)进程:
是一个正在执行中的程序。
每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。

(2)线程:
就是进程中的一个独立的控制单元。
线程在控制着进程的执行。一个进程中至少有一个线程。

java中体现:java JVM启动的时候,会有一个进程java.exe。该进程中至少有一个线程在负责java程序的执行。

                     而且这个线程运行的代码存在于main方法中。该线程称为主线程。

扩展知识:jvm启动不止一个线程,因为还有负责垃圾回收机制的线程。


二、线程类及线程实现

1、Thread(线程类)

java体系中,任何机制都是基于类的,线程机制也是基于Thread类。

Thread类的对象实例就表示java程序中的线程,通过它可以对线程采取多种操作。

Thread类常见方法:

yield();引起当前执行线程暂停,允许其他线程执行。

sleep(long millis);

start();使线程由新建状态变成可运行,并自动调用线程中的run方法

run();线程在启动时执行的功能

stop();停止当前线程

wait();使当前线程等待直到被唤醒或者超过指定的等待时间

isAlive();测试线程是否是活动的

setPriority(int new);设置线程优先级

notify();唤起一个等待的线程

notifyAll();唤起所有等待的线程

线程运行状态示例图:


2、实现线程的两种方式

(1)继承Thread类

Thread类位于java.lang包中,且java.lang包自动的被导入每个java程序中,省去了建立线程还要进行导入类的操作,说明java对线程进行了彻底支持。

继承Thread类实现线程步骤及代码实例:

①建立一个类使其继承Thread类。

②在类中复写Thread类的run方法,就是将希望运行的代码写进run中,让其在线程启动的时候执行。

③新建一个该类的对象,即一个线程。

④使用该对象调用start方法启动线程。

实例:

//Demo继承Thread类
class Demo extends Thread{
	//复写了Thread类的run方法
	public void run(){
		for(int i=0;i<10;i++){
			System.out.println("Demo run  "+i);
		}
	}
}
public class ThreadDemo {
	public static void main(String[] args) {
		Demo d =new Demo();//创建Demo的对象,即新建了一个线程
		d.start();//调用start方法,此线程进入可运行状态
		for(int i=0;i<10;i++){
			System.out.println("main run  "+i);
		}//main函数的执行代码,其实主程序也是线程,而且一般都是主线程。
	}
}

将代码运行多次发现每一次的结果都不相同。

原因:因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。

在上述实例中,Demo的线程和主程序的主线程在抢夺cpu执行权,

由于每次运行时的cpu运行环境不同,就造成了每次运行时它们之间抢夺cpu执行权的结果也不同,就造成了程序运行结果不同。

明确一点,在某一个时刻,只能有一个程序在运行。(多核除外)
cpu在做着快速的切换,以达到看上去是同时运行的效果。
我们可以形象把多线程的运行行为在互相抢夺cpu的执行权。
这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,cpu说的算。


(2)实现Runnable接口

通过继承Thread类来实现线程确实很方便,但又出现了一个问题,如果类已经继承了一个类,由于java不允许多继承,类想要实现线程就无法继承Thread类,这时候又不想创建别的类,这时候就应该使该类通过实现Runnable接口来实现线程。

实现Runnable接口实现线程步骤及代码实例:

①建立一个类使其实现Runnable接口

②在类中实现Runnable接口的run方法,就是将希望运行的代码写进run中,让其在线程启动的时候执行。

③新建一个该类的对象,并使用参数为该Runnable对象的构造方法创建Thread实例

④使用Thread实例调用start方法启动线程。

实例:

//Demo类实现Runnable接口
class Demo implements Runnable{
	//实现了Runnable接口的run方法
	public void run(){
		for(int i=0;i<10;i++){
			System.out.println("Demo run  "+i);
		}
	}
}
public class ThreadDemo {
	public static void main(String[] args) {
		Demo d =new Demo();//创建Demo的对象,即新建了一个Runnable对象
		
		Thread t = new Thread(d);
		/*调用Thread类中的Thread(Runnable target)构造方法建立线程实例,
		 *就是将实现Runnable接口的类对象传递给Thread类的对象即传递给新线程,为新线程提供方法和数据。
		*/		
		t.start();//Thread类实例调用start方法,此线程进入可运行状态
		for(int i=0;i<10;i++){
			System.out.println("main run  "+i);
		}//main函数的执行代码,其实主程序也是线程,而且一般都是主线程。
	}
}

继承Thread和实现Runnable的区别:
    继承Thread:线程代码存放在Thread子类run方法中。无法多继承。
    实现Runnable:线程代码存放在接口的子类的run方法中。可以多实现。

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


3、run和start特点

为什么要覆盖run方法呢?
Thread类用于描述线程。
该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法。也就是说Thread类中的run方法,用于存储线程要运行的代码。

class  ThreadA implements Runnable{
	//实现了Runnable接口的run方法
	public void run(){
		for(int i=0;i<10;i++){
			System.out.println("ThreadA run "+i);
		}
	}
}
class  ThreadB implements Runnable{
	//实现了Runnable接口的run方法
	public void run(){
		for(int i=0;i<10;i++){
			System.out.println("ThreadB run "+i);
		}
	}
}
public class ThreadDemo {
	public static void main(String[] args) {
		
		Thread t1= new Thread(new ThreadA());
		Thread t2= new Thread(new ThreadB());
		t1.start();
		t2.start();
		//t1.run();
		//t2.run();
		for(int i=0;i<10;i++){
			System.out.println("main run  "+i);
		}
	}
}
通过实例发现:

start可以实现多线程,run就不行。

run():是Thread类和Runnable接口中的方法,对其进行复写或实现后,其中存储的是线程启动时要执行的代码。

直接调用run方法只是执行了其中的代码,并不能启动线程。

start():此方法并没有执行代码,它的作用就是使一个新建线程开启并处于就绪状态,等待cpu执行权获得后自动调用run方法。


run方法可以看成只要一运行就获得了cpu执行权,没有其他方法与之争抢。

例如A线程的run()和B线程的run()。先让A线程的run方法开始执行,它会一直执行下去直到完毕,接着才执行B线程的run方法。

start方法可以看成多个线程在争抢cpu执行权,获得了cpu执行权的方法才能执行。

例如A线程的start()和B线程的start()。两个线程开启后,它们会开始争抢cpu执行权,A线程的run方法和B线程的run方法会根据哪个线程抢到了cpu的执行权而交替执行。

4、获取线程对象和名称

currentThread() 返回对当前正在执行的线程对象的引用。
setName() 设置线程名称。
getName():获取线程名称。
System.currentTimeMillis(); 获取当前毫秒数。
System.nanoTime(); 获取当前毫微秒(1毫秒=1000微秒=1000000毫微秒)

(1)怎么获取主线程的名称?
Thread类有一个静态方法currentThread(),直接类名调用得到主线程的对象,该对象调用getName()获得名称。
String mainThreadName = Thread.currentThread().getName(); 

(2)为什么在主函数中无法用this调用getName()方法获取线程名?
因为主函数是静态的,类一加载,函数就存在了,但还没有对象,所以不能使用this。

class Test extends Thread
{	//构造函数,设置线程名称
	Test(String name)
	{
		super(name);//访问父类的设置名称构造函数
	}
	public void run()
	{
		for(int x=0; x<10; x++)
		{
			System.out.println(this.getName()+" run..."+x+" 当前毫秒"+System.currentTimeMillis());
			//getName可以获取当前线程名称,这里的this就相当于Thread.currentThread()
			//Thread.currentThread()返回的是当前执行的线程对象引用
		}
	}
}
class ThreadDemo
{
	public static void main(String[] args) 
	{
		Test t1 = new Test("线程A");
		Test t2 = new Test("线程B");
		t1.start();
		t2.start();
		Thread.currentThread().setName("主线程");//将当前线程的名称设为主线程
		for(int x=0; x<10; x++)
		{
			System.out.println(Thread.currentThread().getName()+"run"+x);
			//主线程中不能用this来调用getName获取名称
			//因为主函数是静态的,其随着类的加载而加载而加载,不存在对象,所以不能使用this
		}
	}
}
5、多线程实例

需求:简单的卖票程序,多个窗口同时卖票。

class TicketWindow implements Runnable
{
	private int ticket_num=100;//设置总票数
	public void run()
	{
		while(true)
		{
			if(ticket_num>0)
			{
				try{Thread.sleep(10);}catch(Exception e){}//让该线程休眠10毫秒,目的:为了显现出线程中的安全问题
				System.out.println(Thread.currentThread().getName()+"....sale : "+ ticket_num--);
			}
		}
	}
}

class TicketDemo
{
	public static void main(String[] args) 
	{
		//新建4个线程并调用start方法启动线程
		TicketWindow t = new TicketWindow();
		Thread t1=new Thread(t);
		Thread t2=new Thread(t);
		Thread t3=new Thread(t);
		Thread t4=new Thread(t);
		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");
		t4.setName("窗口4");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}



通过分析,发现程序可能会打印出0,-1,-2等错票, 多线程的运行出现了安全问题。

问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,
另一个线程参与进来执行。导致共享数据的错误。
解决办法:对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。

Java对于多线程的安全问题提供了专业的解决方式,这就引入了线程的同步。


三、线程的同步

由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。

Java语言为解决这种冲突提供了专门的机制,有效避免了同一个数据对象被多个线程同时访问,这就是线程同步。

1、同步代码块

(1)格式:

synchronized(对象)
{
需要被同步的代码

}

对象:如同锁
持有锁的线程可以在同步中执行。
没有锁的线程即使获取CPU的执行权,也进不去,因为没有获取锁。

(2)同步的前提:
1:必须要有两个或者两个以上的线程。
2:必须是多个线程使用同一个锁。
3:必须保证同步中只能有一个线程在运行。
(3)好处:解决了多线程的安全问题。

         弊端:多个线程需要判断锁,较为消耗资源。

例如公共卫生间问题:

公共卫生间的门关上并锁死,表示里面有人使用,外面的人进不来,只能等里面的人使用完出来才能进入;

公共卫生间的门开着,表示里面没有人,外面的人可以进来使用,进来一个人把门关上锁死后,外面的人就又进不来了。

实例:

class TicketWindow implements Runnable
{
	private int ticket_num=100;//设置总票数
	Object obj = new Object();
	public void run()
	{
		while(true)
		{
			//给访问共享数据的代码加锁,当有进程正在访问共享数据的时候,其他进程不能访问,保证了数据安全性。
			synchronized (obj)//obj对象标志位为0,线程不能运行同步块代码,为1,则可以运行。
			{
				if(ticket_num>0)
				{
					try{Thread.sleep(10);}catch(Exception e){}//让该线程休眠10毫秒
					System.out.println(Thread.currentThread().getName()+"....sale : "+ ticket_num--);
				}
			}
		}
	}
}

class TicketDemo
{
	public static void main(String[] args) 
	{
		//新建4个线程并调用start方法启动线程
		TicketWindow t = new TicketWindow();
		Thread t1=new Thread(t);
		Thread t2=new Thread(t);
		Thread t3=new Thread(t);
		Thread t4=new Thread(t);
		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");
		t4.setName("窗口4");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

2、同步函数

同步函数就是在方法前面修饰synchronized关键字的方法。

当某个对象调用了同步方法时,该对象上的其他同步方法必须等待该同步方法执行完毕后才能执行,必须将每个能访问共享资源的方法修饰为同步方法,否则会出错。

需求:
银行有一个金库。
有两个储户分别存300员,每次存100,存3次。
目的:该程序是否有安全问题,如果有,如何解决?

如何在程序中解决同步问题:
(1)明确哪些代码是多线程运行代码。
(2)明确共享数据。
(3)明确多线程运行代码中哪些语句是操作共享数据的。

class Bank
{
	private int sum;//银行储金总数
	public synchronized void add(int n)//同步方法
	{
			sum = sum + n;
			try{Thread.sleep(10);}catch(Exception e){}
			System.out.println("sum="+sum);
	}
}
class Custom implements Runnable
{
	private Bank b = new Bank();
	public void run()
	{		
		for(int x=0; x<3; x++)
		{
			b.add(100);//调用同步方法,其他线程必须等待该线程的同步方法执行完毕才能调用。
		}
	}
}
class  BankDemo
{
	public static void main(String[] args) 
	{
		Custom c = new Custom();
		//建立两个线程开始运行,即两个顾客开始存钱
		Thread t1 = new Thread(c);
		Thread t2 = new Thread(c);
		t1.start();
		t2.start();
	}
}

3、同步函数的锁是this

同步代码块中都有一个对象作为锁,可是同步函数没有指明的对象也能进行锁的操作。

这是因为同步函数都将当前所属的对象作为锁,即this。

class TicketWindow implements Runnable
{
	private int ticket_num=100;//设置总票数
	Object obj = new Object();
	public void run()
	{
		while(true)
		{
			sell();
		}
	}
	public synchronized void sell(){  //同步函数的锁是this
		if(ticket_num>0)
		{
			try{Thread.sleep(10);}catch(Exception e){}//让该线程休眠10毫秒
			System.out.println(Thread.currentThread().getName()+"....sale : "+ ticket_num--);
		}
    }     
}
class TicketDemo
{
	public static void main(String[] args) 
	{
		//新建4个线程并调用start方法启动线程
		TicketWindow t = new TicketWindow();
		Thread t1=new Thread(t);
		Thread t2=new Thread(t);
		Thread t3=new Thread(t);
		Thread t4=new Thread(t);
		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");
		t4.setName("窗口4");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

4、静态同步函数的锁是Class对象

如果同步函数被静态修饰后,使用的锁是什么呢?
通过实例验证,发现不再是this。因为静态方法中也不可以定义this。
静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象:类名.class  该对象的类型是Class
静态的同步方法,使用的锁是该方法所在类的字节码文件对象。 类名.class。

实例验证:

class Ticket implements Runnable
{
	private static  int tick = 100;
	boolean flag = true;
	public  void run()
	{
		if(flag)//如果标记flag为真,则线程运行同步代码块
		{
			while(true)
			{
				synchronized(Ticket.class)
				{
					if(tick>0)
					{
						try{Thread.sleep(10);}catch(Exception e){}
						System.out.println(Thread.currentThread().getName()+"....code : "+ tick--);
					}
				}
			}
		}
		else//如果标记flag为真,则线程运行同步函数
			while(true)
				sell();
	}
	public static synchronized void sell()//静态同步函数使用的锁是Class对象
	{
		if(tick>0)
		{
			try{Thread.sleep(10);}catch(Exception e){}
			System.out.println(Thread.currentThread().getName()+"....show.... : "+ tick--);
		}
	}
}
class  TicketDemo
{
	public static void main(String[] args) 
	{
		Ticket t = new Ticket();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t1.start();
		try{Thread.sleep(10);}catch(Exception e){}
		//让主线程在开启t1线程后休眠10毫秒,目的是让t1有运行的时间,防止两个线程只运行同步函数
		t.flag = false;//在t1线程运行同步代码块后,设置标记为flase,用来让线程访问同步函数
		t2.start();
	}
}
从实例得出:

两个线程同时在卖票,且run方法中使用了两个同步机制:同步代码块和静态同步函数。

若让同步代码块中使用一个对象锁,则代码运行结果中会出现卖出0票,表明不安全,就是线程同步中用的锁不一样。

若让同步代码块中使用当前class对象锁,则代码运行结果中不会出现卖出0票,表明安全,就是说静态同步函数使用的锁就是class对象锁。


5、单例设计模式-懒汉式

单例设计模式-懒汉式:对象是方法被调用时才初始化,也称为对象的延迟加载。
例:Single类进内存,对象还没有存在,只有调用了getInstance方法时,才建立对象。
class Single{
	private Single(){}
	private static Single s=null;
	public static  Single getInstance(){
		if(s==null){
			s=new Single();
			return s;
		}
	}//向外提供本类对象。
}
懒汉式会出现问题:
例如A程序和B程序,A程序判断完s==null,cpu切换到另一程序,A程序挂起;接着cpu开始执行B程序,B程序判断完s==null,cpu又切换到另一程序,B程序挂起。等到A程序恢复执行,则建立一个对象s,接下来当B程序开始执行,B又会进行创建对象s,由于这是单例设计,所以这里两个对象会冲突报错,产生安全问题。
这个问题可以用synchronized关键字(同步)来对程序加锁解决
class Single{
	private Single(){}
	private static Single s=null;
	public static  Single getInstance(){//或者在函数上修饰synchronized关键字
		//判断对象是否为空
		if(s==null)
		{
			//判断锁,无锁,则执行该同步代码块中代码,建立对象
			synchronized(Single.class)//用到的是class对象锁
			{
				//再次判断对象是否为空
				if(s==null){
					s=new Single();//新建Single类对象
					return s;//返回该对象
				}
			}
		}
	}//向外提供本类对象。
}


6、死锁

死锁是指发生在线程之间相互阻塞的现象,这种现象导致同步线程相互等待,以致每个线程都不能往下执行。

例如:两个人各持有一根筷子,想要吃饭,但吃饭需要两根筷子,所以两个人都想要对方的筷子,这时候就产生了死锁,谁也得不到所需的资源。

死锁:同步中嵌套同步。


死锁实例1:多窗口同时售票,这个程序会造成死锁。

死锁问题发生:

若线程1开始访问同步代码块,并把obj锁锁死,准备访问同步函数;

与此同时,线程2开始访问同步函数,并把class锁锁死,准备访问同步代码块;

紧接着,线程1发现同步函数被线程2锁死,不能运行,线程2发现同步代码块被线程1锁死,也不能运行;

同时两个线程都想访问对方所占用的资源,且都不放弃自己的资源,就发生了阻塞,产生了死锁现象。

class Ticket implements Runnable
{
	private  int tick = 1000;
	Object obj = new Object();
	boolean flag = true;
	public  void run()
	{
		if(flag)
		{
			while(true)
			{
				synchronized(obj)
				{
					sell();
				}
			}
		}
		else
			while(true)
				sell();
	}
	public synchronized void sell()//this
	{
		synchronized(obj)
		{
			if(tick>0)
			{
				try{Thread.sleep(10);}catch(Exception e){}
				System.out.println(Thread.currentThread().getName()+"....code : "+ tick--);
			}
		}
	}
}
class  DeadLockDemo
{
	public static void main(String[] args) 
	{
		Ticket t = new Ticket();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t1.start();
		try{Thread.sleep(10);}catch(Exception e){}
		t.flag = false;
		t2.start();
	}
}
死锁实例2:

class Test implements Runnable
{
	private boolean flag;
	Test(boolean flag)
	{
		this.flag = flag;
	}

	public void run()
	{
		if(flag)
		{
			while(true)
			{
				synchronized(MyLock.locka)
				{
					System.out.println(Thread.currentThread().getName()+"...if locka ");
					synchronized(MyLock.lockb)
					{
						System.out.println(Thread.currentThread().getName()+"..if lockb");					
					}
				}
			}
		}
		else
		{
			while(true)
			{
				synchronized(MyLock.lockb)
				{
					System.out.println(Thread.currentThread().getName()+"..else lockb");
					synchronized(MyLock.locka)
					{
						System.out.println(Thread.currentThread().getName()+".....else locka");
					}
				}
			}
		}
	}
}

//定义锁
class MyLock
{
	static Object locka = new Object();
	static Object lockb = new Object();
}

class  DeadLockDemo
{
	public static void main(String[] args) 
	{
		Thread t1 = new Thread(new Test(true));
		Thread t2 = new Thread(new Test(false));
		t1.start();
		t2.start();
	}
}

死锁问题发生:

线程1有a锁,线程2有b锁;线程1想要访问b锁,线程2想要访问a锁,双方还不放自己的锁,于是产生死锁。


四、线程操作

1、线程通信

(1)线程通信

多线程一个重要特点就是它们之间可以相互通信,线程通信是线程之间可以相互交流和等待,可以通过经常共享的数据使线程相互交流,也可以通过线程控制方法使线程之间相互等待。

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

(2)等待唤醒机制

线程通信可以通过线程控制方法使线程互相等待。

Object类提供了3个方法:wait()、notify()和notifyAll()。

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


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


wait()和sleep()的区别:wait() 释放资源,释放锁;sleep() 释放资源,不释放锁。

线程通信实例:

//资源
class Res
{
	String name;
	String sex;
	boolean flag = false;//默认资源标识为false,没有资源
}
//输入
class Input implements Runnable
{
	private Res r ;//资源锁
	Input(Res r)
	{
		this.r = r;
	}
	public void run()
	{
		int x = 0;
		while(true)
		{
			synchronized(r)//同步锁
			{
				if(r.flag)//若有资源,则调用wait等待输出者取走资源
					try{r.wait();}catch(Exception e){}
				//放入资源
				if(x==0)
				{
					r.name="小明";
					r.sex="男";
				}
				else
				{
					r.name="小丽";
					r.sex = "女";
				}
				x = (x+1)%2;//对输入的资源进行切换
				r.flag = true;//将资源标识设为true,表明资源有了
				r.notify();//唤醒输出线程,让其取走资源
			}
		}
	}
}
//输出
class Output implements Runnable
{
	private Res r ;//资源锁
	
	Output(Res r)
	{
		this.r = r;
	}
	public void run()
	{
		while(true)
		{
			synchronized(r)//同步锁
			{
				if(!r.flag)//若没有资源,则调用wait等待输入者放入资源
					try{r.wait();}catch(Exception e){}
				System.out.println(r.name+"...."+r.sex);//输出资源
				r.flag = false;//将资源标识设为flase,表明资源没有了
				r.notify();//唤醒输入线程,让其放入资源
			}
		}
	}
}
class  InputOutputDemo
{
	public static void main(String[] args) 
	{
		Res r = new Res();//建立资源对象
                
		Input in = new Input(r);
		Output out = new Output(r);
                //建立两个线程并开启线程,一个输入线程,一个输出线程
		new Thread(in).start();
		new Thread(out).start();
	}
}


(3)生产者与消费者

生产者和消费者问题:是一个典型的线程通信模型。

该问题和上文的输入输出同步问题相似,并对其进行优化,将同步代码块封装成同步函数。

代码:

//商品仓库
class Res
{
	private String name;
	private int num=1;//仓库商品编号
	private boolean flag = false;//仓库中是否有商品的标识,初始化为没有商品。
	
	public synchronized void put()//生产函数,同步函数
	{  
        if(flag)//若有商品,则调用wait等待消费者消费商品
            try{this.wait();}catch (Exception e){}  
        //生产产品
        this.name="商品"+num++;
        System.out.println(Thread.currentThread().getName()+"生产者生产:"+name);  
        flag = true;//将商品标识设为true,表明仓库中已有商品
        this.notify();//通知消费者消费商品
    } 
	
	public synchronized void get()//消费函数,同步函数
	{  
		if(!flag)//若没有商品,则调用wait等待生产者生产商品
			try{this.wait();}catch(Exception e){}
		//消费产品
		System.out.println(Thread.currentThread().getName()+"消费者消费:"+name);  
		flag = false;//将商品标识设为false,表明仓库中没商品
		this.notify();//通知生产者生产商品
    }
}
//生产者
class Producer implements Runnable
{
	private Res r ;
	Producer(Res r)
	{
		this.r = r;
	}
	public void run()
	{
			for(int i=0;i<100;i++){
				r.put();
			}			
	}
}
//消费者
class Customer implements Runnable
{
	private Res r ;
	
	Customer(Res r)
	{
		this.r = r;
	}
	public void run()
	{
		for(int i=0;i<100;i++){
			r.get();
		}	
	}
}
class  ProducerCustomerDemo
{
	public static void main(String[] args) 
	{
		Res r = new Res();

		new Thread(new Producer(r)).start();
		new Thread(new Customer(r)).start();
	}
}

上述是一个生产者线程和一个消费者线程的代码,如果使其开启两个生产者线程和两个消费者线程,则可能会出现异常情况。

出现的情况:
序号为线程执行顺序,F、T指商品标识
生产一次,消费两次的异常情况:

  

                                       F                                  T                       F                        F                                      T                                                 F
生产者1       (1)生产商品186,等待   
生产者2                                                                                               (4)生产商品187,等待
消费者1                                           (2)消费商品186,等待                                                    (5)消费商品187,等待
消费者2                                                                                  (3)等待                                                                                 (6)不再判断标识,仍然消费商品187,问题出现


生产两次,消费一次的异常情况:


                                        F                      T                     T                          F                         T                                                                        F
生产者1    (1)生产商品169,等待                                                                  (5)生产商品170,等待
生产者2                                           (2)等待                                                                                              (6)不再判断标识,继续生产商品171,导致商品170没有被消费           
消费者1                                                        (3)消费商品169,等待                 
消费者2                                                                                                (4)等待                         


下面解决这个问题并说明原因。

下面是两个生产者线程和两个消费者线程的代码:

//商品仓库
class Res
{
	private String name;
	private int num=1;//仓库商品编号
	private boolean flag = false;//仓库中是否有商品的标识,初始化为没有商品。
	
	public synchronized void put()//生产函数,同步函数
	{  
        while(flag)//若多次判断商品标识
            try{this.wait();}catch (Exception e){}  
        //生产产品
        this.name="商品"+num++;
        System.out.println(Thread.currentThread().getName()+"生产者生产:"+name);  
        flag = true;//将商品标识设为true,表明仓库中已有商品
        this.notifyAll();//唤醒全部线程
    } 
	
	public synchronized void get()//消费函数,同步函数
	{  
		while(!flag)//若没有商品,则调用wait等待生产者生产商品
			try{this.wait();}catch(Exception e){}
		//消费产品
		System.out.println(Thread.currentThread().getName()+"消费者消费:"+name);  
		flag = false;//将商品标识设为false,表明仓库中没商品
		this.notifyAll();
    }
}
//生产者
class Producer implements Runnable
{
	private Res r ;
	Producer(Res r)
	{
		this.r = r;
	}
	public void run()
	{
			for(int i=0;i<100;i++){
				r.put();
			}			
	}
}
//消费者
class Customer implements Runnable
{
	private Res r ;
	
	Customer(Res r)
	{
		this.r = r;
	}
	public void run()
	{
		for(int i=0;i<100;i++){
			r.get();
		}	
	}
}
class  ProducerCustomerDemo
{
	public static void main(String[] args) 
	{
		Res r = new Res();
		//两个生产线程生产
		new Thread(new Producer(r)).start();
		new Thread(new Producer(r)).start();
		//两个消费线程消费
		new Thread(new Customer(r)).start();
		new Thread(new Customer(r)).start();
	}
}
对于多个生产者和消费者:
为什么要定义while判断标记?
原因:让被唤醒的线程再一次判断标记。

为什么定义notifyAll?
因为需要唤醒对方线程。只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。


生产者消费者问题中怎么只唤醒对方线程呢?

JDK1.5 中提供了多线程升级解决方案。
将同步Synchronized替换成现实Lock操作。
将Object中的wait,notify notifyAll,替换了Condition对象。
该对象可以Lock锁 进行获取。
该示例中,实现了本方只唤醒对方操作。
Lock:替代了Synchronized
lock 
unlock
newCondition()

Condition:替代了Object wait notify notifyAll
await();
signal();
signalAll();

import java.util.concurrent.locks.*;  
//商品仓库
class Res
{
	private String name;
	private int num=1;//仓库商品编号
	private boolean flag = false;//仓库中是否有商品的标识,初始化为没有商品。
	 private Lock lock = new ReentrantLock();  
	 private Condition condition_put = lock.newCondition();   
	 private Condition condition_get= lock.newCondition(); 
	public void put()throws InterruptedException//生产函数
	{  
		lock.lock();//获取锁  
      try{  
          while(flag)//若多次判断商品标识
          	condition_put.await();  
          //生产产品
          this.name="商品"+num++;
          System.out.println(Thread.currentThread().getName()+"生产者生产:"+name);  
          flag = true;//将商品标识设为true,表明仓库中已有商品
          condition_get.signalAll();//唤醒消费线程
      }          
      finally{  
          lock.unlock();//一定要释放锁  
      }
  } 
	
	public void get()throws InterruptedException//消费函数
	{  
		lock.lock();//获取锁  
      try{  
          while(!flag)//若没有商品,则调用wait等待生产者生产商品
          	condition_get.await(); 
            //消费产品
  		  System.out.println(Thread.currentThread().getName()+"消费者消费:"+name);  
          flag = false;//将商品标识设为false,表明仓库中没商品
          condition_put.signalAll();//唤醒生产线程
      }          
      finally{  
          lock.unlock();//一定要释放锁  
      }
		
  }
}
//生产者
class Producer implements Runnable
{
	private Res r ;
	Producer(Res r)
	{
		this.r = r;
	}
	public void run()
	{
			for(int i=0;i<100;i++){
				try{  
	                r.put();
	            }catch (InterruptedException e){} 
			}			
	}
}
//消费者
class Customer implements Runnable
{
	private Res r ;
	
	Customer(Res r)
	{
		this.r = r;
	}
	public void run()
	{
		for(int i=0;i<100;i++){
			try{  
              r.get();
          }catch (InterruptedException e){} 
		}	
	}
}
class  ProducerCustomerDemo
{
	public static void main(String[] args) 
	{
		Res r = new Res();
		//两个生产线程生产
		new Thread(new Producer(r)).start();
		new Thread(new Producer(r)).start();
		//两个消费线程消费
		new Thread(new Customer(r)).start();
		new Thread(new Customer(r)).start();
	}
}
2、线程的控制

(1)停止线程

stop方法已经过时
如何停止线程?
只有一种方法,run方法结束。开启多线线程,运行代码通常是循环结构。
只要控制住循环,就可以让run方法结束,也就是线程结束。

特殊情况:
当线程处于冻结状态,就不会读取到标记,那么线程就不会结束。
当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。
强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。

一个例子:一个人(线程)正处于冻结状态(wait),当没有指定的方式(notify)让其苏醒,可以对这个人糊过去一个砖头( interrupt),这个人就醒了,恢复了活动(运行)。

Thread类提供该方法 interrupt();

class StopThread implements Runnable{
	private boolean flag = true;
	public synchronized void run(){
		while(flag){
			try{
				wait();
			}
			catch(InterruptedException e){
				System.out.println(Thread.currentThread().getName()+"....Exception");
				flag = false;
			}
			System.out.println(Thread.currentThread().getName()+"....run");
		}
	}
	public void changeFlag(){
		flag = false;
	}
}
class StopThreadDemo{
	public static void main(String[] args){
		StopThread st = new StopThread();
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(st);
		t1.start();
		t2.start();
		int num = 0;
		while(true){
			if(num++ ==60){
				t1.interrupt();
				t2.interrupt();
				break;
			}
			System.out.println(Thread.currentThread().getName()+"...."+num);
		}
		System.out.println("over");
	}
}

(2)守护线程

守护线程 setDaemon
特点:开启后和前台线程共同抢劫CPU的执行权运行

当所有的前台线程都结束后,后台线程会自动结束。

public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。

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

该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。

class StopThread implements Runnable
{
	private boolean flag =true;
	public  void run()
	{
		while(flag)
		{		
			System.out.println(Thread.currentThread().getName()+"....run");
		}
	}
	public void changeFlag()
	{
		flag = false;
	}
}
class  StopThreadDemo
{
	public static void main(String[] args) 
	{
		StopThread st = new StopThread();
		
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(st);


		t1.setDaemon(true);
		t2.setDaemon(true);
		t1.start();
		t2.start();

		int num = 0;

		while(true)
		{
			if(num++ == 60)
			{
				break;
			}
			System.out.println(Thread.currentThread().getName()+"......."+num);
		}
		System.out.println("over");
	}
}

(3)加入线程(join)

public final void join()
throws InterruptedException等待该线程终止。

当A线程执行到了B线程的join()方法时,A就会等待,等B线程都执行完后,A线程才执行。
join可以用来临时加入线程执行。

class Demo implements Runnable{
	public void run(){
		for (int x=0;x<=10 ;x++ ){
			System.out.println(Thread.currentThread().getName()+"...."+x);
		}
	}
}
class JoinDemo{
	public static void main(String[] arsg) throws Exception{
		Demo d = new Demo();
		Thread t1 = new Thread(d);
		Thread t2 = new Thread(d);
		t1.start();
		t1.join();//主线程执行到t1线程的join方法后,主线程停止,等到t1线程run方法执行结束,主线程才会运行。也就是t1线程加入了主线程。
		t2.start();
		
		for (int x=0;x<=10 ;x++ ){
			System.out.println("main...."+x);
		}
		System.out.println("over");
	}
}

3.、线程的优先级

yield() 暂停当前正在执行的线程对象,并执行其他线程。

setPriority(int newPriority) 更改线程的优先级。newPriority用1-10表示

java已经给出了常用的三个等级:
    MAX_PRIORITY 线程可以具有的最高优先级。最高是10
    MIN_PRIORITY 线程可以具有的最低优先级。最低是1
    NORM_PRIORITY 分配给线程的默认优先级。默认是5

toString():
返回该线程的字符串表示形式,包括线程名称、优先级和线程组。

class Demo implements Runnable
{
	public void run()
	{
		for(int x=0; x<10; x++)
		{
			System.out.println(Thread.currentThread().toString()+"....."+x);
			//Thread.yield();//暂停当前正在执行的线程对象,并执行其他线程
		}
	}
}
class  JoinDemo
{
	public static void main(String[] args) throws Exception
	{
		Demo d = new Demo();
		Thread t1 = new Thread(d);
		Thread t2 = new Thread(d);
		t1.start();
		
		t1.setPriority(Thread.MAX_PRIORITY);//使t1线程拥有最高的优先级,但t1并不一定先运行。

		t2.start();

		for(int x=0; x<10; x++)
		{
			System.out.println("main....."+x);
		}
	}
}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值