黑马程序员技术交流社区

标题: 多线程总结 [打印本页]

作者: 黄玉昆    时间: 2013-2-16 14:37
标题: 多线程总结
本帖最后由 黄玉昆 于 2013-2-16 14:38 编辑

一、概述:
1、线程是什么呢?
我们先来说一说比较熟悉的进程吧,之后就比较容易理解线程了。所谓进程,就是一个正在执行(进行)中的程序。每一个进程的执行都有一个执行顺序,或者说是一个控制单元。简单来说,就是你做一件事所要进行的一套流程。线程,就是进程中的一个独立的控制单元;也就是说,线程是爱控制着进程的执行。一个进程至少有一个线程,并且线程的出现使得程序要有效率。打个比方说,在仓库搬运货物,一个人搬运和五个人搬运效率是不一样的,搬运货物的整个程序,就是进程;每一个人搬运货物的过程,就是线程。
2、java中的线程:
在java中,JVM虚拟机启动时,会有一个进程为java.exe,该程序中至少有一个线程负责java程序的执行;而且该程序运行的代码存在于main方法中,该线程称之为主线程。其实,JVM启动时不止有一个线程(主线程),由于java是具有垃圾回收机制的,所以,在进程中,还有负责垃圾回收机制的线程。
3、多线程的意义:
透过上面的例子,可以看出,多线程有两方面的意义:
1)提高效率。   2)清除垃圾,解决内存不足的问题。
二、自定义线程:
线程有如此的好处,那要如何才能通过代码自定义一个线程呢?其实,线程是通过系统创建和分配的,java是不能独立创建线程的;但是,java是可以通过调用系统,来实现对进程的创建和分配的。java作为一种面向对象的编程语言,是可以将任何事物描述为对象,从而进行操作的,进程也不例外。我们通过查阅API文档,知道java提供了对线程这类事物的描述,即Thread类。创建新执行线程有两种方法:
一)创建线程方式一:继承Thread类。
1、步骤:
第一、定义类继承Thread。
第二、复写Thread类中的run方法。
第三、调用线程的start方法。分配并启动该子类的实例。
         start方法的作用:启动线程,并调用run方法。
  1. <font face="Arial" size="1">class Demo extends Thread
  2. {
  3.         public void run()
  4.         {
  5.                 for (int i=0;i<60;i++)
  6.                         System.out.println(Thread.currentThread().getName() + "demo run---" + i);
  7.         }
  8. }
  9. class Test2
  10. {
  11.         public static void main(String[] args)
  12.         {
  13.                 Demo d1 = new Demo();//创建一个对象就创建好了一个线程
  14.                 Demo d2 = new Demo();
  15.                 d1.start();//开启线程并执行run方法
  16.                 d2.start();
  17.                 for (int i=0;i<60;i++)
  18.                         System.out.println("Hello World!---" + i);
  19.         }
  20. }</font>
复制代码
2、运行特点:
A.并发性:我们看到的程序(或线程)并发执行,其实是一种假象。有一点需要明确:;在某一时刻,只有一个程序在运行(多核除外),此时cpu是在进行快速的切换,以达到看上去是同时运行的效果。由于切换时间是非常短的,所以我们可以认为是在并发进行。
B.随机性:在运行时,每次的结果不同。由于多个线程都在获取cpu的执行权,cpu执行到哪个线程,哪个线程就会执行。可以将多线程运行的行为形象的称为互相抢夺cpu的执行权。这就是多线程的特点,随机性。执行到哪个程序并不确定。
3、覆盖run方法的原因:
1)Thread类用于描述线程。该类定义了一个功能:用于存储线程要运行的代码,该存储功能即为run方法。也就是说,Thread类中的run方法用于存储线程要运行的代码,就如同main方法存放的代码一样。
2)复写run的目的:将自定义代码存储在run方法中,让线程运行要执行的代码。直接调用run,就是对象在调用方法。调用start(),开启线程并执行该线程的run方法。如果直接调用run方法,只是将线程创建了,但未运行。
二)创建线程方式二:实现Runnable接口
1、步骤:
第一、定义类实现Runnable接口。
第二、覆盖Runnable接口中的run方法。
第三、通过Thread类建立线程对象。要运行几个线程,就创建几个对象。
第四、将Runnable接口的子类对象作为参数传递给Thread类的构造函数。
第五、调用Thread类的start方法开启线程,并调用Runnable接口子类的run方法。
  1. <font size="1" face="Arial">//多个窗口同时卖票
  2. class Ticket implements Runnable
  3. {
  4.         private int tic = 20;
  5.         public void run()
  6.         {
  7.                 while(true)
  8.                 {
  9.                         if (tic > 0)
  10.                                 System.out.println(Thread.currentThread().getName() + "sale:" + tic--);
  11.                 }
  12.         }
  13. }

  14. class  TicketDemo
  15. {
  16.         public static void main(String[] args)
  17.         {
  18.                 Ticket t = new Ticket();
  19.                 Thread t1 = new Thread(t);//创建一个线程
  20.                 Thread t2 = new Thread(t);//创建一个线程
  21.                 Thread t3 = new Thread(t);//创建一个线程
  22.                 Thread t4 = new Thread(t);//创建一个线程
  23.                 t1.start();
  24.                 t2.start();
  25.                 t3.start();
  26.                 t4.start();
  27.         }
  28. }
  29. </font>
复制代码
2、说明:
A.步骤2覆盖run方法:将线程要运行的代码存放在该run方法中。
B.步骤4:为何将Runnable接口的子类对象传给Thread构造函数。因为自定义的run方法所属对象为Runnable接口的子类对象,所以让线程指定对象的run方法,就必须明确该run方法所属的对象。
三)实现方式与继承方式有何区别:
1、实现方式:避免了单继承的局限性。
            在定义线程时,建议使用实现方式。
2区别:
继承Thread:线程代码存放在Thread子类的run方法中。
实现Runnable:线程代码存在接口的子类run方法中。
需要注意的是:局部变量在每一个线程中都独有一份。
四)Thread类中的一些方法简介:
1、获取线程名称:getName()
每个线程都有自己默认的名称,获取格式:对象.getName();打印后,显示为:Thread-编号(从0开始),也就是说,线程一为:Thread-0,线程二为:Thread-1。也可以获取当前线程对象的名称,通过currentThread().getName()。如上面方式二的结果为


file:///C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\msohtml1\01\clip_image002.jpg
2、设置线程名称:setName()或构造函数
可以通过setName()设置线程名称,或者通过含有参数的构造函数直接显式初始化线程的名称,如Test(Stringname)。

结果.png (792 Bytes, 下载次数: 34)

结果.png

作者: 黄玉昆    时间: 2013-2-16 14:42
三、线程的运行状态
线程运行状态可用如图示二说明:
需要说明的是:
A.阻塞状态:具备运行资格,但是没有执行权,必须等到cpu的执行权,才转到运行状态,。
B.冻结状态:放弃了cpu的执行资格,cpu不会将执行权分配给这个状态下的线程,必须被唤醒后,此线程要先转换到阻塞状态,等待cpu的执行权后,才有机会被执行到。
四、多线程的安全问题:
在那个简单的卖票小程序中,发现打印出了0、-1、-2等错票,也就是说这样的多线程在运行的时候是存在一定的安全问题的。
为什么会出现这种安全问题呢?
原因是当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还未执行完,另一线程就参与进来执行了,导致共享数据发生错误。以也就是说,由于cpu的快速切换,当执行线程一时,tic为1了,执行到if (tic > 0)的时候,cpu就可能将执行权给了线程二,那么线程一就停在这条语句了,tic还没减1,仍为1;线程二也判断if (tic> 0)是符合的,也停在这,以此类推。当cpu再次执行线程一的时候,打印的是1号,执行线程二的时候,是2号票,以此类推,就出现了错票的结果。其实就是多条语句被共享了,如果是一条语句,是不会出现此种情况的。
那么该如何解决呢?
对于多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可参与执行,就不会出现问题了。Java对于多线程的安全问题,提供了专业的解决方式,即同步代码块,可操作共享数据。
1、同步代码块
格式:
  1. <font size="1" face="Arial">synchronized(对象)//对象称为锁旗标
  2. {
  3.         需要被同步的代码
  4. }</font>
复制代码
其中的对象如同锁,持有锁的线程可在同步中执行,没有锁的线程,即使获得cpu的执行权,也进不去,因为没有获取锁,是进不去代码块中执行共享数据语句的。
1)同步的前提:
A.必须有两个或两个以上的线程
B.必须保证同步的线程使用同一个锁。必须保证同步中只能有一个线程在运行。
好处与弊端:解决了多线程的安全问题。多个线程需要判断锁,较为消耗资源。
示例:
  1. <font size="1" face="Arial">class Ticket implements Runnable
  2. {
  3.         private int tic = 100;
  4.         Object obj = new Object();
  5.         public void run()
  6.         {
  7.                 while(true)
  8.                 {
  9.                         synchronized(obj)//任意的一个对象
  10.                         {
  11.                                 //此两句为共享语句
  12.                                 if (tic > 0)
  13.                                         System.out.println(Thread.currentThread().getName() + "sale:" + tic--);
  14.                         }       
  15.                 }
  16.         }
  17. }

  18. class  TicketDemo
  19. {
  20.         public static void main(String[] args)
  21.         {
  22.                 Ticket t = new Ticket();
  23.                 Thread t1 = new Thread(t,"1");//创建第一个线程
  24.                 Thread t2 = new Thread(t,"2");//创建第二个线程
  25.                 //开启线程
  26.                 t1.start();
  27.                 t2.start();
  28.         }
  29. }</font>
复制代码
2、同步函数
同步函数就是将修饰符synchronized放在返回类型的前面,下面通过同步函数给出多线程安全问题的具体解决方案:
1)目的:判断程序中是否有安全问题,若有,该如何解决。
2)解决:第一、明确哪些代码是多线程的运行代码
                 第二、明确共享数据
                 第三、明确多线程运行代码中,哪些语句是操作共享数据的。
示例:
  1. <font size="1" face="Arial">class Bank
  2. {
  3.         private int sum;//共享数据
  4.         //run中调用了add,所以其也为多线程运行代码
  5.         public synchronized void add(int n)//同步函数,用synchronized修饰
  6.         {
  7.                 //这有两句操作,是操作共享数据的
  8.                 sum += n;
  9.                         System.out.println("sum" + sum);
  10.         }
  11. }

  12. class Cus implements Runnable
  13. {
  14.         private Bank b = new Bank();//共享数据
  15.         //多线程运行代码run
  16.         public void run()
  17.         {
  18.                 for (int i=0;i<3;i++)
  19.                 {
  20.                         b.add(100);//一句,不会分开执行,所以没问题
  21.                 }
  22.         }
  23. }

  24. class BankDemo
  25. {
  26.         public static void main(String[] args)
  27.         {
  28.                 Cus c = new Cus();
  29.                 Thread t1 = new Thread(c);
  30.                 Thread t2 = new Thread(c);
  31.                 t1.start();
  32.                 t2.start();
  33.         }
  34. }</font>
复制代码

作者: 黄玉昆    时间: 2013-2-16 14:42
本帖最后由 黄玉昆 于 2013-2-17 21:18 编辑

五、同步函数中的锁:
1、非静态同步函数中的锁---> this
函数需被对象调用,那么函数都有一个所属的对象引用,就是this,因此同步函数使用的锁为this。测验如下:
  1. <font size="1" face="Arial">class Ticket implements Runnable
  2. {
  3. private int tic = 100;
  4. boolean flog = true;
  5. public void run()
  6. {
  7. if (flog)
  8. {
  9. //线程一执行
  10. while(true)
  11. {
  12. //如果对象为obj,则是两个锁,是不安全的;换成this,为一个锁,会安全很多
  13. synchronized(this)
  14. {
  15. if (tic > 0)
  16. System.out.println(Thread.currentThread().getName() + "--cobe--:" + tic--);
  17. }
  18. }
  19. }
  20. //线程二执行
  21. else
  22. while(true)
  23. show();
  24. }
  25. public synchronized void show()
  26. {
  27. if (tic > 0)
  28. System.out.println(Thread.currentThread().getName() + "----show-----:" + tic--);
  29. }
  30. }

  31. class ThisLockDemo
  32. {
  33. public static void main(String[] args)
  34. {
  35. Ticket t = new Ticket();
  36. Thread t1 = new Thread(t);//创建一个线程
  37. Thread t2 = new Thread(t);//创建一个线程
  38. t1.start();
  39. t.flog = false;//开启线程一,即关闭if,让线程二执行else中语句
  40. t2.start();
  41. }
  42. }</font>
复制代码
让线程一执行打印cobe的语句,让线程二执行打印show的语句。如果对象换位另一个对象obj,那将是两个锁,因为在主函数中创建了一个对象即Ticket t = new Ticket();,线程会共享这个对象调用的run方法中的数据,所以都是这个t对象在调用,那么,其中的对象应为this;否则就破坏了同步的前提,就会出现安全问题。
2、静态同步函数中的锁:
如果同步函数被静态修饰后,经验证,使用的锁不是this了,因为静态方法中不可定义this,所以,这个锁不再是this了。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象:类名.class;该对象的类型是Class。
所以静态的同步方法使用的锁是该方法所在类的字节码文件对象,即类名.class。
示例:
  1. <font size="1" face="Arial">class Ticket implements Runnable
  2. {
  3. //私有变量,共享数据
  4. private static int tic = 100;
  5. boolean flog = true;
  6. public void run()
  7. {
  8. //线程一执行
  9. if (flog)
  10. {
  11. while(true)
  12. {
  13. synchronized(Ticket.class)//不再是this了,是Ticket.class
  14. {
  15. if (tic > 0)
  16. System.out.println(Thread.currentThread().getName() + "--obj--:" + tic--);
  17. }
  18. }
  19. }
  20. //线程二执行
  21. else
  22. while(true)
  23. show();
  24. }
  25. public static synchronized void show()
  26. {
  27. if (tic > 0)
  28. System.out.println(Thread.currentThread().getName() + "----show-----:" + tic--);
  29. }
  30. }

  31. class StaticLockDemo
  32. {
  33. public static void main(String[] args)
  34. {
  35. Ticket t = new Ticket();
  36. Thread t1 = new Thread(t);//创建第一个线程
  37. Thread t2 = new Thread(t);//创建第二个线程
  38. t1.start();
  39. t.flog = false;
  40. t2.start();
  41. }
  42. }
  43. </font>
复制代码
在之前,也提到过关于多线程的安全问题的相关知识,就是在单例设计模式中的懒汉式中,用到了锁的机制。
具体请看单例设计模式中的内容:http://blog.csdn.net/shengfeixiang/article/details/8567567

下面还有关于多线程通信的总结,继续补上:
作者: 李培根    时间: 2013-2-16 14:45
支持了,总结的很全面
作者: 黄玉昆    时间: 2013-2-16 14:46
李培根 发表于 2013-2-16 14:45
支持了,总结的很全面

其实很多都是毕老师总结的内容,自己的理解很少啦,以后有新的理解会继续加入的。
作者: jonn    时间: 2013-2-16 16:55
{:soso_e144:} 哪能不能告诉我线程与栈有撒联系?
作者: 黄玉昆    时间: 2013-2-16 17:43
赵文 发表于 2013-2-16 16:55
哪能不能告诉我线程与栈有撒联系?

你可以想一想主函数(主线程)和栈有啥联系。线程是在使用着各种数据,对于局部变量(在栈内存中),每个线程运行时都各有自己的一份,而对于共享数据,很明显了,就是大家共用的,如我总结中的卖票的例子,tic就是共享的。你可以自己把图画出来,你就明白了
作者: jonn    时间: 2013-2-16 18:04
黄玉昆 发表于 2013-2-16 17:43
你可以想一想主函数(主线程)和栈有啥联系。线程是在使用着各种数据,对于局部变量(在栈内存中),每个 ...

:L...,整个程序处理逻辑过程都是有栈区来完成,而堆区存储对象的数据,不管main线程,还是何时运行启动的垃圾回收线程,与jvm底层交互实现完全交给栈区来处理,classLoader 加载 后,校验字节码,方法区查找 任何对象,根据栈区的对象引用值,java的解释器去查找对应的堆区对象数据,也查找多线程执行程序,每一个线程其实对应栈区一个单独的栈线程,每个线程包含的局部变量,方法返回值等存放在栈区,若多线程实现通信,不管是偏对锁还是优化多线程的轻量级锁,共享资源对象信息全在堆区,堆区的数据就是被栈区所共享。
作者: 黄玉昆    时间: 2013-2-16 18:16
赵文 发表于 2013-2-16 18:04
...,整个程序处理逻辑过程都是有栈区来完成,而堆区存储对象的数据,不管main线程,还是何时运行启动 ...

感觉讲的很好,可是我没太懂,对于多线程间的交互,以及各种锁,我还没有相关接触,以后我会慢慢补充相关知识的。谢谢你
作者: 黄玉昆    时间: 2013-2-17 21:24
六、多线程间的通信:
多线程间通信是线程之间进行交互的方式,简单说就是存储资源和获取资源。比如说仓库中的货物,有进货的,有出货的。还比如生产者和消费者的例子。这些都可以作为线程通信的实例。那么如何更好地实现通信呢?先看下面的代码:
  1. <font size="1" face="Arial">import java.util.concurrent.locks.*;  
  2.   
  3. class ProducerConsumerDemo{  
  4.     public static void main(String[] args){  
  5.         Resouse r = new Resouse();  
  6.         Producer p = new Producer(r);  
  7.         Consumer c = new Consumer(r);  
  8.         Thread t1 = new Thread(p);  
  9.         Thread t2 = new Thread(c);  
  10.         Thread t3 = new Thread(p);  
  11.         Thread t4 = new Thread(c);  
  12.         t1.start();  
  13.         t2.start();  
  14.         t3.start();  
  15.         t4.start();  
  16.     }  
  17. }  
  18.   
  19. class Resouse{  
  20.     private String name;  
  21.     private int count = 1;  
  22.     private boolean flag =  false;   
  23.     private Lock lock = new ReentrantLock();  
  24.     private Condition condition_P = lock.newCondition();  
  25.     private Condition condition_C = lock.newCondition();  
  26. //要唤醒全部,否则都可能处于冻结状态,那么程序就会停止。这和死锁有区别的。  
  27.     public void set(String name)throws InterruptedException{  
  28.         lock.lock();  
  29.         try{  
  30.             while(flag)//循环判断,防止都冻结状态  
  31.                 condition.await();  
  32.             this.name = name + "--" + count++;  
  33.             System.out.println(Thread.currentThread().getName() + "..生成者--" + this.name);  
  34.             flag = true;  
  35.             condition_C.signal();  
  36.         }finally{  
  37.             lock.unlock();//释放锁的机制一定要执行  
  38.         }         
  39.     }  
  40.     public void out()throws InterruptedException{  
  41.         lock.lock();  
  42.         try{  
  43.             while(!flag)//循环判断,防止都冻结状态  
  44.                 condition.await();  
  45.             System.out.println(Thread.currentThread().getName() + "..消费者." + this.name);  
  46.             flag = false;  
  47.             condition_P.signal();//唤醒全部  
  48.         }finally{  
  49.             lock.unlock();  
  50.         }  
  51.     }  
  52. }  
  53.   
  54. class Producer implements Runnable{  
  55.     private Resouse r;  
  56.     Producer(Resouse r){  
  57.         this.r = r;  
  58.     }  
  59.     public void run(){  
  60.         while(true){  
  61.             try{  
  62.                 r.set("--商品--");  
  63.             }catch (InterruptedException e){}  
  64.         }  
  65.     }  
  66. }  
  67.   
  68. class Consumer implements Runnable{  
  69.     private Resouse r;  
  70.     Consumer(Resouse r){  
  71.         this.r = r;  
  72.     }  
  73.     public void run(){  
  74.         while(true){  
  75.             try{</span><span style="font-family: Arial; ">//上面抛出了异常</span><span style="font-family:Arial;font-size:12px;">  
  76.                 r.out();  
  77.             }catch (InterruptedException e){}  
  78.         }  
  79.     }  
  80. }  
  81. </font>
复制代码
一)等待唤醒机制:
1、显式锁机制和等待唤醒机制:
JDK 1.5中,提供了改进synchronized的升级解决方案。将同步synchronized替换为显式的Lock操作,将Object中的waitnotifynotifyAll替换成Condition对象,该对象可对Lock锁进行获取。这就实现了本方唤醒对方的操作。在这里说明几点:
1)、对于waitnotifynotifyAll这些方法都是用在同步中,也就是等待唤醒机制,这是因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。
2)、而这些方法都定义在Object中,是因为这些方法操作同步中的线程时,都必须表示自己所操作的线程的锁,就是说,等待和唤醒的必须是同一把锁。不可对不同锁中的线程进行唤醒。所以这就使得程序是不良的,因此,通过对锁机制的改良,使得程序得到优化。
3)、等待唤醒机制中,等待的线程处于冻结状态,是被放在线程池中,线程池中的线程已经放弃了执行资格,需要被唤醒后,才有被执行的资格。
2、对于上面的程序,有两点要说明:
1)、为何定义while判断标记:
原因是让被唤醒的线程再判断一次。
避免未经判断,线程不知是否应该执行,就执行本方的上一个已经执行的语句。如果用if,消费者在等着,两个生成着一起判断完flag后,cpu切换到其中一个如t1,另一个t3wait,当t1唤醒冻结中的一个,是t3(因为它先被冻结的,就会先被唤醒),所以t3未经判断,又生产了一个。而没消费。
2)这里使用的是signal方法,而不是signalAll方法。是因为通过Condition的两个对象,分别唤醒对方,这就体现了Lock锁机制的灵活性。可以通过Contidition对象调用Lock接口中的方法,就可以保证多线程间通信的流畅性了。



作者: 黄玉昆    时间: 2013-2-17 21:24
二)Thread类中的方法简介:
在这简单介绍几个Thread中的方法:
1、停止线程:
java 1.5之后,就不再使用stop方法停止线程了。那么该如何停止线程呢?只有一种方法,就是让run方法结束。
开启多线程运行,运行代码通常为循环结构,只要控制住循环,就可以让run方法结束,也就可以使线程结束。
注:
特殊情况:当线程处于冻结状态,就不会读取标记,那么线程就不会结束。如下:
  1. <font size="1" face="Arial">class StopThread implements Runnable{
  2. private boolean flag = true;
  3. public synchronized void run(){
  4. while (flag){
  5. try{
  6. wait();
  7. }catch (InterruptedException e) {
  8. System.out.println(Thread.currentThread().getName() + "----Exception");
  9. flag = false;
  10. }
  11. System.out.println(Thread.currentThread().getName() + "----run");
  12. }
  13. }
  14. public void changeFlag(){
  15. flag = false;
  16. }
  17. }

  18. class StopThreadDemo{
  19. public static void main(String[] args) {
  20. StopThread st = new StopThread();

  21. Thread t1 = new Thread(st);
  22. Thread t2 = new Thread(st);

  23. t1.start();
  24. t2.start();

  25. int n = 0;
  26. while (true){
  27. if (n++ == 60){
  28. st.changeFlag();
  29. break;
  30. }
  31. System.out.println("Hello World!");
  32. }
  33. }
  34. }</font>
复制代码
这时,当没有指定的方式让冻结的线程回复打破运行状态时,就需要对冻结进行清除。强制让线程回复到运行状态来,这样就可以操作标记让线程结束。
Thread类中提供了此种方法:interrupt()。此方法是为了让线程中断,但是并没有结束运行,让线程恢复到运行状态,再判断标记从而停止循环,run方法结束,线程结束。
  1. <font size="1" face="Arial">class StopThread implements Runnable{
  2. private boolean flag = true;
  3. public synchronized void run(){
  4. while (flag){
  5. try{
  6. wait();
  7. }catch (InterruptedException e){
  8. System.out.println(Thread.currentThread().getName() + "----Exception");
  9. flag = false;
  10. }
  11. System.out.println(Thread.currentThread().getName() + "----run");
  12. }
  13. }
  14. }

  15. class StopThreadDemo{
  16. public static void main(String[] args){
  17. StopThread st = new StopThread();
  18. Thread t1 = new Thread(st);
  19. Thread t2 = new Thread(st);
  20. t1.start();
  21. t2.start();
  22. int n = 0;
  23. while (true){
  24. if (n++ == 60){
  25. t1.interrupt();
  26. t2.interrupt();
  27. break;
  28. }
  29. System.out.println("Hello World!");
  30. }
  31. }
  32. } </font>
复制代码
2、守护线程:---setDaemon()
可将一个线程标记为守护线程,直接调用这个方法。此方法需要在启动前调用守护线程在这个线程结束后,会自动结束,则Jvm虚拟机也结束运行。
  1.   <font size="1" face="Arial">........

  2. //守护线程(后台线程),在启动前调用。后台线程自动结束
  3. t1.setDaemon(true);
  4. t2.setDaemon(true);
  5. t1.start();
  6. t2.start();

  7. .........</font>
复制代码
3、临时加入线程:--join()
特点:当A线程执行到B线程方法时,A线程就会等待,B线程都执行完,A才会执行。join可用来临时加入线程执行。
  1. <font size="1" face="Arial">class Demo implements Runnable{
  2. public void run(){
  3. for(int x=0;x<90;x++){
  4. System.out.println(Thread.currentThread().getName() + "----run" + x);
  5. }
  6. }
  7. }

  8. class JoinDemo{
  9. public static void main(String[] args)throws Exception{
  10. Demo d = new Demo();
  11. Thread t1 = new Thread(d);
  12. Thread t2 = new Thread(d);
  13. t1.start();
  14. t2.start();
  15. t1.join();//等t1执行完了,主线程才从冻结状态恢复,和t2抢执行权。t2执不执行完都无所谓。
  16. int n = 0;
  17. for(int x=0;x<80;x++){
  18. System.out.println(Thread.currentThread().getName() + "----main" + x);
  19. }
  20. System.out.println("Over");
  21. }
  22. }
  23. </font>
复制代码
4、优先级:
setPriority()
Thread中,存在着1~10这十个执行级别,最高的是 MAX_PRIORITY 10,最低是 MIN_PRIORITY 1,默认优先级是 NORM_PRIORITY 5;但是并不是优先级越高,就会一直执行这个线程,只是说会优先执行到这个线程,此后还是有其他线程会和此线程抢夺cpu执行权的。
优先级是可以设定的,可通过setPriority()设定,如:setPriority(Thread.MAX_PRIORITY)设优先级为最大。
yield()
此方法可暂停当前线程,而执行其他线程。通过这个方法,可稍微减少线程执行频率,达到线程都有机会平均被执行的效果。如下:
  1. <font size="1" face="Arial">class Demo implements Runnable{
  2. public void run(){
  3. for(int x=0;x<90;x++){
  4. System.out.println(Thread.currentThread().toString() + "----run" + x);
  5. Thread.yield();//稍微减少线程执行频率。可达到线程都有机会达到平均运行的效果
  6. }
  7. }
  8. }

  9. class YieldDemo{
  10. public static void main(String[] args)throws Exception{
  11. Demo d = new Demo();
  12. Thread t1 = new Thread(d);
  13. Thread t2 = new Thread(d);
  14. t1.start();
  15. t1.setPriority(Thread.MAX_PRIORITY);//设置线程优先级最大
  16. t2.start();
  17. System.out.println("Over");
  18. }
  19. }</font>
复制代码
对于多线程的知识,还需要慢慢积累,毕竟线程通信可以提高程序运行的效率,这样就可以让程序得到很大的优化。期待新知识······








欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2