黑马程序员---多线程

-----------android培训java培训、java学习型技术博客、期待与您交流!---------


多线程


      要理解多线程就要先了解线程,了解线程就要先了解进程,下面跟着我的博客学习多线程吧

      

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

 

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

                  

                  只要进程中有一个线程在执行,进程就不会结束,一个进程中至少有一个线程。

 

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


                      而且这个线程运行的代码存在于main方法中。该线程称之为主线程。JVM启动除了执行一个主线程,


                      还有负责垃圾回收机制的线程。像这种在一个进程中有多个线程执行的方式,就叫做多线程。

 

      多线程存在的意义


                  多线程的出现能让程序出现同时运行效果(原来没学多线程的时候我以为程序是同时运行的,


                  学完之后才发现不是,等一下我会讲解CPU的运行原理)可以提高程序执行效率。


                  例如:在java.exe进程执行主线程时,如果程序代码特别多,在堆内存中产生了很多对象。


                              而对象调用完后,就成了垃圾。如果垃圾过多可能发生内存溢出的情况,如果只有一个线程工作的话,程序的执行将会很低效。


                              如果有另一个线程帮助处理的话,比如垃圾回收机制线程来帮助回收垃圾的话,程序的运行将变得更有效率。

 

      创建线程有两种方式

                        

            继承方式和实现方式,继承就是继承Thread类,实现就是实现Runnable接口,我们先来看继承方式。

             

            继承方式:


                  通过对API的查找,java已经提供了对线程这类事物的描述,就是Thrad类


                  创建线程的第一种方式:继承Thread类。


                        1.   定义类继承Thread


                        2.   复写Thread类中的run方法。


                              目的:将线程要执行的代码储存在run方法中,让程序运行。


                        3.   创建类的实例对象,就相当于创建了一个线程。


                        4.   调用线程的start方法,该方法有两个作用


                              a.   启动线程


                              b.   调用run方法


                              start:调用到底层让控制单元去执行的动作。


                              run:封装线程要运行的代码

 

                    注意:调用线程的start方法是开启线程并执行该线程的run方法。


                                而直接调用run方法仅仅是对象调用方法,而线程创建了,并没有运行

 

                  为什么要覆盖run方法呢?


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


                        也就是说Thread类中的run方法,用于存储线程要运行的代码。

                                                

                  下面我们来看一个使用继承方法,创建线程的实例

/* 
创建两个线程,和主线程交替运行。 
*/  
      
//定义一个ThreadA类继承Thread
class ThreadA extends Thread  
{   
    ThreadA(String name)  
    {  
		//使用super方法,访问父类构造函数
        super(name);    
    }  
    //复写run方法写入要执行的语句
    public void run()  
    {  
        for(int x=0;x<80;x++)  
		//通过Thread.currentThread方法获得当前执行线程对象的引用
		//再用getName方法获得这个正在运行线程的名称,以示区分
        System.out.println(Thread.currentThread().getName()+"..run..."+x);  
    }  
}  
      
class  ThreadTest  
{  
    public static void main(String[] args)   
    {  
		//创建ThreadA的匿名对象,并传入name参数,开启自定义的第一个线程
        new ThreadA("线程11111111————————————").start(); 

		//创建ThreadA的匿名对象,并传入name参数,开启自定义的第一个线程
        new ThreadA("线程22222*******").start();
      
        //编写主线程要执行的代码 
        for(int x=0;x<170;x++)  
			System.out.println("Hello World!");  
    }  
}  

                        运行结果



 

                  注:Thread.currentThread()== this 这种获取线程对象的方法不建议使用,因为this不一定通用。


                          直接用Thread.currentThread()就好,因为它是标准通用的。

 

                  为什么每次运行的结果都不一样呢?


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


                        明确点的说就是在某个时刻只能一个程序在运行(多核除外),CPU在做着快速的切换,以达到看上去是同时运行的效果,


                        我们可以形象的把多线程的运行形容为在互相抢夺CPU的执行权,


                                   这就是多线程的一个特性:随机性。(谁抢到谁执行,至于执行多长时间,CPU说了算)

 

            创建线程的第二种方式,实现方式


                  使用继承方式有一个弊端,那就是如果该类本来就继承了其他父类,就无法通过Thread类来创建线程了。


                  这样就有了第二种创建线程的方式:实现Runnable接口,并复写run方法


                        1.   定义类实现Runnable接口


                        2.   覆盖Runna接口中的run方法,将线程要执行的代码存放在run方法中。


                        3.   通过Thread类创建线程对象。


                        4.   将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。


                              为什么要将Runnable接口的子类对象传给Thread的构造函数呢?


                                    因为,自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run方法,


                                    就必须明确该run方法的所属对象。


                        5.   调用Thread类的start方法,开启线程,start会自动调用 Runnable接口子类的run方法。

 

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


                  实例程序

/*
使用实现Runnable接口的方法,创建线程

现在想买好鞋太难了,都得排号,因为鞋太少价钱又被炒上去了,100个号可能就为了抽一双鞋
现在我就来模拟一个拿号系统,四个人发,就相当于4个线程。
*/
class Shoes implements Runnable//extends Thread
{
	//有100个号
	private  int shoesNumber = 100;

	//复写run方法
	public void run()
	{
		while(true)
		{	
			if(shoesNumber>0)
			{
				//输出第几个人发出去的号,并且每卖出去一个号减一
				System.out.println(Thread.currentThread().getName()+"...发号 "+ shoesNumber--);
			}
		}
	}
}
class  ShoesDemo
{
	public static void main(String[] args) 
	{
		//创建Runnable接口的子类对象
		Shoes s = new Shoes();

		//将Runnable接口的子类对象作为参数传递给Thread类的构造函数,创建线程
		Thread t1 = new Thread(s);
		Thread t2 = new Thread(s);
		Thread t3 = new Thread(s);
		Thread t4 = new Thread(s);

		//启动线程
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

                        运行结果



      

                        在这个程序的运行结果中出现了Thread-0这类的名称,通过查看源代码我们知道这是通过获取线程名得来的,


                              原来线程有自己的默认名称:Thread—编号(编号从0开始)

 

            实现方法和继承方法有什么区别呢?


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


                        两种方式的区别:线程代码存放的位置不一样。


                                    继承Thread:线程代码存放在Thread子类的run方法中。


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


      线程的状态


           线程的几种状态分别为


                  被创建:等待被start方法被调用启动。


                  运行状态:具有执行资格和执行权。


                  临时状态(阻塞状态):具备执行资格,但是没有执行权。


                  冻结状态:有两个状态


                                      a.   睡眠:sleep(time)方法


                                      b.   等待:wait()方法。放弃了执行资格。


                                      当sleep方法时间到或者调用到notify()方法时,获得执行资格,变为临时状态。


                                      变为临时状态后,如果CPU空闲再执行。


                  消亡状态:run()方法结束,或者遇到stop()方法。


            总结:没有执行资格是冻结状态,有执行资格没有执行权是阻塞状态,执行权执行资格都有是运行状态。


            线程的状态图示


       

      线程安全问题

                                                         

                  刚才那个卖鞋的程序,最后一个号不是1,而是别的数字。当然因为程序运行结果都不一样,不一定是哪个数字,反正不是1。

                  

                       虽然这不是一个安全性的问题,但是如果真的在做项目的过程中,出现一点小问题都是很可怕的。


                       而且如果我们在卖鞋程序的输出语句前加入:


                       try{Thread.sleep(10);}catch(Exception e){}(因为sleep可能会发生异常,所以要try catch处理)代码,让线程睡一会儿,


                       运行出的结果可能会发生如下图所示的情况


                       会出现0,-1,-2的号,这就表明多线程出现了很严重的安全问题,我们必须要解决




                   

                  问题的原因


                        当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,


                        另一个线程就参与进来执行,导致了共享数据的错误。

 

                   解决办法


                            对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。

 

                  在java中对于多线程的安全问题提供了专业的解决方式——synchronized(同步)


                        同步有两种方式。


                              1.   同步代码块。


                              2.   同步函数。


                              都是利用synchronized关键字实现的

                                                

                        先来介绍同步代码块


                              格式:


                                          Synchronized(对象){


                                                      需要被同步的代码;


                                          }

                                    

                              判断那些代码需要同步,就看那些语句在操作共享数据。

        

                        同步可以解决安全问题的原因就在对象上。对象如同锁,持有锁的线程可以在同步中执行,


                              没有持有锁的线程即使获取了CPU的执行权也进不去,因为没有获取锁。

                                  

                        下面我们就给卖鞋那号的程序加上同步代码块,再来看运行结果


                              给程序加入同步代码块

/*
使用实现Runnable接口的方法,创建线程

现在想买好鞋太难了,都得排号,因为鞋太少价钱又被炒上去了,100个号可能就为了抽一双鞋
现在我就来模拟一个拿号系统,四个人发,就相当于4个线程。
*/
class Shoes implements Runnable//extends Thread
{
	//有1000个号
	private  int shoesNumber = 1000;
	//创建一个对象,作为锁
	Object obj = new Object();

	//复写run方法
	public void run()
	{
		while(true)
		{	
			//创建同步代码块obj对象为锁,把操作共享数据的代码放入同步代码块中
			synchronized(obj)
			{
				if(shoesNumber>0)
				{
					try{Thread.sleep(10);}catch(Exception e){}
					//输出第几个人发出去的号,并且每卖出去一个号减一
					System.out.println(Thread.currentThread().getName()+"...发号 "+ shoesNumber--);
				}
			}
		}
	}
}
class  ShoesDemo
{
	public static void main(String[] args) 
	{
		//创建Runnable接口的子类对象
		Shoes s = new Shoes();

		//将Runnable接口的子类对象作为参数传递给Thread类的构造函数,创建线程
		Thread t1 = new Thread(s);
		Thread t2 = new Thread(s);
		Thread t3 = new Thread(s);
		Thread t4 = new Thread(s);

		//启动线程
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

                                    运行结果

      


        

                        现在就可以一直卖到最后一张票了,在截图中只能看到Thread-3线程在运行,是因为我的电脑是双核的,比较不容易看到线程交替。


                              虽然鞋号数已经调到了1000,还是看不出来,所以只截了卖到最后一张的截图,望老师见谅,嘿嘿。

                           

                  同步函数


                        格式


                              把synchronized加在函数上public后即可。

 

                        同步函数用的是哪一个锁呢?


                              函数需要被对象调用,那么函数都有一个所属对象的引用,就是this。所以同步函数的锁是this。

 

                        再来看刚才卖鞋的程序用同步代码块来使它安全。

/*
使用实现Runnable接口的方法,创建线程

现在想买好鞋太难了,都得排号,因为鞋太少价钱又被炒上去了,100个号可能就为了抽一双鞋
现在我就来模拟一个拿号系统,四个人发,就相当于4个线程。
*/
class Shoes implements Runnable//extends Thread
{
	//有100个号
	private  int shoesNumber = 1000;

	public void run()//复写run方法
	{
		//调用同步方法
		show();
	}
	//将sysnchronized关键字加入函数中
	public synchronized void show()
	{
		while(true)
		{	
			if(shoesNumber>0)
			{
				try{Thread.sleep(10);}catch(Exception e){}
				//输出第几个人发出去的号,并且每卖出去一个号减一
				System.out.println(Thread.currentThread().getName()+"...发号 "+ shoesNumber--);
			}
		}
	}
}
class  ShoesDemo2
{
	public static void main(String[] args) 
	{
		//创建Runnable接口的子类对象
		Shoes s = new Shoes();

		//将Runnable接口的子类对象作为参数传递给Thread类的构造函数
		Thread t1 = new Thread(s);//创建线程;
		Thread t2 = new Thread(s);
		Thread t3 = new Thread(s);
		Thread t4 = new Thread(s);

		t1.start();//启动线程
		t2.start();
		t3.start();
		t4.start();
	}
}

                            运行结果




                  同步的前提


                        1.   必须有两个以上的线程。


                        2.   必须是多个线程使用同一个锁。


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


                  同步的好处和弊端


                        好处:解决了多线程的安全问题


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

 

                  如何寻找线程中的安全问题


                        1.   明确哪些代码是多线程运行代码。


                        2.   明确共享数据(一般成员都是,对象也是)


                        3.   明确多线程运行代码中那些语句是操作共享数据的。


                  静态函数的同步方式


                        如果同步函数被静态修饰后,使用的锁是什么呢?


                              通过验证发现,不是this,因为静态方法中不可以定义this,静态进内存时,内存中没有本类对象。


                              但是一定有该类对应的字节码文件对象。类名.class。该对象的类型是class,


                              静态的同步方法,使用的锁是该方法所在类的字节码文件对象。类名.class

                           

                  同步的经典懒汉式加上同步


/*
加入了同步的懒汉单例设计模式
*/
class  Single
{
	//定义私有,静态的类的引用,类加载时引用就存在,而且不能被外界访问
	private static Single s = null;

	//定义私有的构造函数,让用户不能创建对象
	private Single(){}

	//定义获取对象方法,类名可以直接调用,所以是静态的
	public static void getInstance()
	{
		//判断对象的引用是否指向null,这里加了双重判断。
		//在多线程并发的情况下,每个线程要创建对象都要判断一下锁,效率比较低,为了提高效率加入双重判断。
		//如果第一个线程已经成功的获取了实例对象,Single类的引用就不指向空了,就不用,每次都判断锁了
		if(s == null)
		{
			//使用同步代码块,让线程同步,锁为Single的字节码
			synchronized(Single.class)
			{
				//这里面才是真正需要被同步的代码
				//如果Single类的引用指向null
				if(s == null)
					//创建Single的实例对象,并指向s引用型变量
					s = new Single();
			}
		}
		//返回类的引用
		return s;
	}
}
      

      死锁


            在同步中嵌套同步时可能出现死锁现象

      

            用程序来体现死锁:面试题

/*
这是一个死锁程序
这个程序中会出现两个线程。
通过给构造函数传入不同标记的方式,用if else代码块控制线程运行的代码。

一号线程:A锁的同步代码块套着B锁的同步代码块
二号线程:B锁的同步代码块套着A锁的同步代码块
会出现死锁的情况
*/

class Test implements Runnable
{
	private boolean flag;

	//建立对象时接收boolean类型的数据
	Test(boolean flag)
	{
		this.flag = flag;
	}

	//复写run方法
	public void run()
	{
		if(flag)
		{
			while(true)
			{
				synchronized(MyLock.lockA)//A锁
				{
					System.out.println(Thread.currentThread().getName()+"...运行if代码块 lockA");
					synchronized(MyLock.lockB)//B锁
					{
						System.out.println(Thread.currentThread().getName()+"..运行if代码块 lockB");					
					}
				}
			}
		}
		else
		{
			while(true)
			{
				synchronized(MyLock.lockB)//B锁
				{
					System.out.println(Thread.currentThread().getName()+"..运行else代码块 lockB");
					synchronized(MyLock.lockA)//A锁
					{
						System.out.println(Thread.currentThread().getName()+".....运行else代码块 lockA");
					}
				}
			}
		}
	}
}

//创建一个类用于放锁
class MyLock
{
	static Object lockA = new Object();
	static Object lockB = new Object();
}

class  DeadLockTest
{
	public static void main(String[] args) 
	{
		//创建线程,并把标记作为参数传入实现Runnable接口的类的匿名对象中
		Thread t1 = new Thread(new Test(true));
		Thread t2 = new Thread(new Test(false));

		//开启线程
		t1.start();
		t2.start();
	}
}

                  结果:程序卡主,不能继续运行



                                              

      线程间的通信


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

            

            下面我们来看一个线程间通信的例子

/*
这是一个线程之间通信的例子

两个线程
一个往资源里面存东西,一个从资源里面取东西。存一个取一个
*/
//定义一个手机类
class Phone	
{
	//手机品牌
	String brand;
	//手机耐久度
	String durable;		
	//标记
	boolean flag = false;
}

//创建往资源里存东西的类实现Runnable端口
class Input implements Runnable
{
	private Phone p ;

	//Input类的构造函数接收Phone对象
	Input(Phone p)	
	{
		this.p = p;
	}
	//复写run方法
	public void run()
	{
		//定义标记,为了交替打印用
		int x = 0;
		//定义循环
		while(true)
		{
			//同步代码块,手机对象为锁
			synchronized(p)
			{
				//判断手机类中的标记
				if(p.flag)
					//使线程等待
					try{p.wait();}catch(Exception e){}
				//给Phone对象赋值
				if(x==0)
				{
					p.brand="苹果";
					p.durable="耐久度不好,容易碎屏";
				}
				else
				{
					p.brand="诺基亚";
					p.durable="耐久度好,能砸核桃";
				}
				//控制交替打印
				x = (x+1)%2;
				//改变手机标记
				p.flag = true;
				//唤醒等待的第一个线程,因为等下我只会创建两个线程,所以使用notify()就好
				//自己睡了,唤醒对方。
				p.notify();
			}
		}
	}
}

//创建从资源里往外拿东西的类实现Runnable接口
class Output implements Runnable
{
	private Phone p ;
	//参数接收Phone类对象
	Output(Phone p)
	{
		this.p = p;
	}
	//复写run方法
	public void run()
	{
		//定义循环
		while(true)
		{
			//同步代码块,Phone对象为锁
			synchronized(p)
			{
				//判断标记,与Input类判断相反
				if(!p.flag)
					//使线程等待
					try{p.wait();}catch(Exception e){}
				//获取P对象的值并打印
				System.out.println(p.durable+"...."+p.durable);
				//改变Phone类的标记
				p.flag = false;
				//唤醒等待的第一个线程。
				p.notify();
			}
		}
	}
}

class  InputOutputDemo
{
	public static void main(String[] args) 
	{
		//创建手机对象
		Phone p = new Phone();

		//创建读取资源和存入资源的对象,并将手机对象作为实际参数传入构造函数中
		Input in = new Input(p);
		Output out = new Output(p);

		//将Runnable的子类对象作为参数传入Thread的构造函数中,创建线程并启动线程。
		new Thread(in).start();
		new Thread(out).start();
	}
}

                  运行结果




 

            下面有几个小知识


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


                        为什么这些操作线程的方法要定义在Object类中呢?


                              因为这些方法在操作同步线程时,都必须要标识他们所操作的线程持有的锁。


                              只有同一个锁上的被等待线程,可以被同一个锁notify()唤醒,不可以对不同锁中的线程进行等待唤醒。


                              也就是说,等待和唤醒必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object中。


                  2.   wait(),sleep(time)有什么区别?


                        wait():释放CPU执行权,释放锁。


                        sleep():释放CPU执行权,不释放锁。


                  3.   为什么要定义notifyAll()?


                        因为在需要唤醒对方线程时,如果只使用notify(),会容易出现只唤醒本方线程的情况,导致程序所有的线程都等待。


                        因为notify()只通知队列中的第一个线程,而notifAll()通知的是等待队列中的所有线程

        

      JDK1.5中提供了多线程的升级解决方案


            将同步synchronized替换成显示的Lock操作,将Object中的wait() ; notify() ; notifyAll()替换成condition对象,该对象可以通过Lock锁进行获取。

            

            多线程的升级解决方案示例


import java.util.concurrent.locks.*;
/*
想想一个好玩儿一点儿的例子,想不出来啊,就用毕老师讲课的商品例子吧,我尽力了!

创建四个线程。
两个生产者,生产一次就等待消费一次
两个消费者,等待生产者生产一次就消费一次

*/

//资源类
class Resource
{
	private String name;
	private int count = 1;
	//标记
	private boolean flag = false;
	//多态,父类引用指向子类对象
	private Lock lock = new ReentrantLock();
	
	//创建两个Condition对象,用于控制等待或唤醒本方与对方线程
	private Condition condition_pro = lock.newCondition();
	private Condition condition_con = lock.newCondition();
	
	//生产方法
	public  void set(String name)throws InterruptedException
	{
		lock.lock();	//l锁
		try
		{
			while(flag)	//重复判断标识,确认是否生产
				condition_pro.await();	//本方线程等待
			this.name = name+"...."+count++;//生产
			
			//打印生产信息
			System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
			flag = true;//控制标记
			condition_con.signal(); //唤醒对方线程
		}
		finally
		{
			lock.unlock();//释放锁的动作一定要执行。
		}
	}
	//消费方法
	public  void out()throws InterruptedException
	{
		lock.lock();//锁
		try
		{
			while(!flag)	//重复判断标识,确认是否可以消费
				condition_con.await();	//本方线程等待

			//打印消费信息
			System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);
			flag = false;	//控制标记
			condition_pro.signal();//唤醒对方线程
		}
		finally
		{
			lock.unlock();	//释放锁
		}
		
	}
}

//生产者线程
class Producer implements Runnable
{
	private Resource res;

	Producer(Resource res)
	{
		this.res = res;
	}
	public void run()	//复写run方法
	{
		while(true)
		{
			try
			{
				//调用生产商品方法
				res.set("+商品+");
			}
			catch (InterruptedException e)
			{
			}
			
		}
	}
}

//消费者线程
class Consumer implements Runnable
{
	private Resource res;

	Consumer(Resource res)
	{
		this.res = res;
	}
	public void run()//复写run方法
	{
		while(true)
		{
			try
			{
				//调用消费方法
				res.out();
			}
			catch (InterruptedException e)
			{
			}
		}
	}
}
class ProducerConsumerDemo 
{
	public static void main(String[] args) 
	{
		//创建资源对象
		Resource r = new Resource();
		
		//创建生产者线程和消费者线程对象,并把资源对象作为参数传入
		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);

		//创建两个生产者线程,连个消费者线程,并启用
		new Thread(pro).start();
		new Thread(pro).start();
		new Thread(con).start();
		new Thread(con).start();
	}
}

                  运行结果



        

      停止线程


            JDK1.5版本之前可以用stop方法来停止线程,但是1.5版本更新以后stop方法过时了。

                  

            那么要如何停止线程呢?


                  只有一种,run()方法结束。开启多线程运行,运行代码通常是循环结构,只要控制住循环,就可以让run方法结束,也就是线程结束。

 

                  如下面的代码:


                        public  voidrun() { 


                                    while(flag) {    


                                            System.out.println(Thread.currentThread().getName()+"-------run"); 


                                   } 


                          }


                        使循环条件为一个变量,等程序运行一段时间后,只要在主函数,或者除了这个线程中的其他线程中


                        将false赋给标记flag,run方法就会结束,线程也会停止。

 

                        但是会出现特殊状况:当线程处于冻结状态,就不会读取到标记,那么线程就不会结束。


                              当没有指定的方式让冻结恢复到运行状态时,就需要对冻结进行清除,强制让线程恢复到运行状态中来


                              这样就可以操作标记,让线程结束。Thread类中提供了该方法 interrupt()

                              

                              特出情况示例

/*
停止线程的特殊情况
线程处于冻结状态,无法读取到标记,所以我们要这么做!
*/

//创建一个线程,就是等一下要被冻结,唤醒,再结束的线程(怎么那么可怜)
class StopThread implements Runnable  
{  
	//定义标记
    private boolean flag =true;  
	//复写run方法
    public  void run()  
    {  
		//将标记作为循环条件 
        while(flag)	 
        {  
            System.out.println(Thread.currentThread().getName()+"....我运行啦~");  
        }  
    } 
	
	//改变标记的方法
    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)  
             {  
				 //当主函数线程运行并且num正好到60的时候,将自定义线程从冻结状态唤醒
                 t1.interrupt();
                 t2.interrupt();  
				 
				 //调用改变循环标记的方法,让循环停止
                 st.changeFlag();
				 //跳出while循环
                 break;  
             }  
			 //主函数线程输出的语句
             System.out.println(Thread.currentThread().getName()+"......."+num);  
         }  
		 //结束
         System.out.println("over");  
     }  
}  


                                    运行结果



                        让主函数线程代码执行完时,冻结的线程执行结束。

        

      什么时候写多线程?


            当某些代码需要同时被执行时,就需要用单独的线程进行封装。

 

      多线程扩展知识


            1.   join():(抢夺CPU执行权)当A线程执行到了B线程的join()方法时,A就会等待,B执行完,A才会执行,join()可以用来临时加入线程执行。


            2.   setPriority():用来设置优先级:


                  MAX_PRIORITY  最高优先级10


                  MIN_PRIORITY   最低优先级1


                  NORM_PRIORITY  分配给线程的默认优先级5


                  优先级就是抢资源的频率,越大抢到CPU执行权的机会就越大


            3.   yield():暂停当前正在执行的线程对象,并执行其他线程。临时释放,稍微减缓线程的运行,而且能达到平均运行效果。

 

 


 

谢谢大家的观看~!

-----------android培训java培训、java学习型技术博客、期待与您交流!---------

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值