`

Java中的线程(转)

    博客分类:
  • java
 
阅读更多

Java语法总结 - 线程

来自:http://hi.baidu.com/wangcf0224/blog/item/bb5e533877b7abfb3b87ce2b.html

一提到线程好像是件很麻烦很复杂的事,事实上确实如此,涉及到线程的编程是很讲究技巧的。这就需要我们变换思维方式,了解线程机制的比较通用的技巧,写出 高效的、不依赖于某个JVM实现的程序来。毕竟仅仅就Java而言,各个虚拟机的实现是不同的。学习线程时,最令我印象深刻的就是那种不确定性、没有保障 性,各个线程的运行完全是以不可预料的方式和速度推进,有的一个程序运行了N次,其结果差异性很大。


1、什么是线程?线程是彼此互相独立的、能独立运行的子任务,并且每个线程都有自己的调用栈。所谓的多任务是通过周期性地将CPU时间片切换到不同的子任 务,虽然从微观上看来,单核的CPU上同时只运行一个子任务,但是从宏观来看,每个子任务似乎是同时连续运行的。(但是JAVA的线程不是按时间片分配 的,在本文的最后引用了一段网友翻译的JAVA原著中对线程的理解。)

2、在java中,线程指两个不同的内容:一是java.lang.Thread类的一个对象;另外也可以指线程的执行。线程对象和其他的对象一样,在堆上创建、运行、死亡。但不同之处是线程的执行是一个轻量级的进程,有它自己的调用栈。
可以这样想,每个调用栈都对应一个线程,每个线程又对应一个调用栈。
我们运行java程序时有一个入口函数main()函数,它对应的线程被称为主线程。一个新线程一旦被创建,就产生一个新调用栈,从原主线程中脱离,也就是与主线程并发执行。


4、当提到线程时,很少是有保障的。我们必须了解到什么是有保障的操作,什么是无保障的操作,以便设计的程序在各种jvm上都能很好地工作。比如,在某些jvm实现中,把java线程映射为本地操作系统的线程。这是java核心的一部分。

5、线程的创建。
创建线程有两种方式:
A、继承java.lang.Thread类。
   class ThreadTest extends Thread{
        public void run() {
           System.out.println ("someting run here!");
        }
        public void run(String s){
           System.out.println ("string in run is " + s);
        }
        public static void main (String[] args) {
           ThreadTest tt = new ThreadTest();
           tt.start();
           tt.run("it won't auto run!");
        }
   }

输出的结果比较有趣:
string in run is it won't auto run!
someting run here!
注意输出的顺序:好像与我们想象的顺序相反了!为什么呢?
一旦调用start()方法,必须给JVM点时间,让它配置进程。而在它配置完成之前,重载的run(String s)方法被调用了,结果反而先输出了“string in run is it won't auto run!”,这时tt线程完成了配置,输出了“someting run here!”。
这个结论是比较容易验证的:
修改上面的程序,在tt.start();后面加上语句for (int i = 0; i<10000; i++); 这样主线程开始执行运算量比较大的for循环了,只有执行完for循环才能运行后面的tt.run("it won't auto run!");语句。此时,tt线程和主线程并行执行了,已经有足够的时间完成线程的配置!因此先到一步!修改后的程序运行结果如下:
someting run here!
string in run is it won't auto run!
注意:这种输出结果的顺序是没有保障的!不要依赖这种结论!

没有参数的run()方法是自动被调用的,而带参数的run()是被重载的,必须显式调用。
这种方式的限制是:这种方式很简单,但不是个好的方案。如果继承了Thread类,那么就不能继承其他的类了,java是单继承结构的,应该把继承的机会留给别的类。除非因为你有线程特有的更多的操作。
Thread类中有许多管理线程的方法,包括创建、启动和暂停它们。所有的操作都是从run()方法开始,并且在run()方法内编写需要在独立线程内执行的代码。run()方法可以调用其他方法,但是执行的线程总是通过调用run()。

B、实现java.lang.Runnable接口。
   class ThreadTest implements Runnable {
        public void run() {
           System.out.println ("someting run here");
        }
        public static void main (String[] args) {
           ThreadTest tt = new ThreadTest();
       Thread t1 = new Thread(tt);
       Thread t2 = new Thread(tt);
       t1.start();
       t2.start();
           //new Thread(tt).start();
        }
   }

比第一种方法复杂一点,为了使代码被独立的线程运行,还需要一个Thread对象。这样就把线程相关的代码和线程要执行的代码分离开来。

另一种方式是:参数形式的匿名内部类创建方式,也是比较常见的。
   class ThreadTest{
       public static void main (String[] args) {
           Thread t = new Thread(new Runnable(){
               public void run(){
                   System.out.println ("anonymous thread");
               }
           });  
          
           t.start();
        }
   }
如果你对此方式的声明不感冒,请参看本人总结的内部类。

第一种方式使用无参构造函数创建线程,则当线程开始工作时,它将调用自己的run()方法。
第二种方式使用带参数的构造函数创建线程,因为你要告诉这个新线程使用你的run()方法,而不是它自己的。
如上例,可以把一个目标赋给多个线程,这意味着几个执行线程将运行完全相同的作业。

6、什么时候线程是活的?
在调用start()方法开始执行线程之前,线程的状态还不是活的。测试程序如下:
   class ThreadTest implements Runnable {
        public void run() {
           System.out.println ("someting run here");
        }
        public static void main (String[] args) {
           ThreadTest tt = new ThreadTest();
           Thread t1 = new Thread(tt);
           System.out.println (t1.isAlive());
           t1.start();
           System.out.println (t1.isAlive());
        }
   }

结果输出:
false
true
isAlive方法是确定一个线程是否已经启动,而且还没完成run()方法内代码的最好方法。

7、启动新线程。
线程的启动要调用start()方法,只有这样才能创建新的调用栈。而直接调用run()方法的话,就不会创建新的调用栈,也就不会创建新的线程,run()方法就与普通的方法没什么两样了!

8、给线程起个有意义的名字。
没有该线程命名的话,线程会有一个默认的名字,格式是:“Thread-”加上线程的序号,如:Thread-0
这看起来可读性不好,不能从名字分辨出该线程具有什么功能。下面是给线程命名的方式。
第一种:用setName()函数
第二种:选用带线程命名的构造器
   class ThreadTest implements Runnable{
       public void run(){
           System.out.println (Thread.currentThread().getName());
       }
       public static void main (String[] args) {
       ThreadTest tt = new ThreadTest();   
       //Thread t = new Thread (tt,"eat apple");
       Thread t = new Thread (tt);
       t.setName("eat apple");
       t.start();
        }
   }

9、“没有保障”的多线程的运行。下面的代码可能令人印象深刻。
   class ThreadTest implements Runnable{
       public void run(){
           System.out.println (Thread.currentThread().getName());
       }
       public static void main (String[] args) {
           ThreadTest tt = new ThreadTest();
           Thread[] ts =new Thread[10];
      
           for (int i =0; i < ts.length; i++)
               ts[i] = new Thread(tt);
              
           for (Thread t : ts)
               t.start();
        }
   }
在我的电脑上运行的结果是:
Thread-0
Thread-1
Thread-3
Thread-5
Thread-2
Thread-7
Thread-4
Thread-9
Thread-6
Thread-8
而且每次运行的结果都是不同的!继续引用前面的话,一旦涉及到线程,其运行多半是没有保障。这个保障是指线程的运行完全是由调度程序控制的,我们没法控制它的执行顺序,持续时间也没有保障,有着不可预料的结果。


10、线程的状态。
A、新状态。
实例化Thread对象,但没有调用start()方法时的状态。
ThreadTest tt = new ThreadTest();   
或者Thread t = new Thread (tt);
此时虽然创建了Thread对象,如前所述,但是它们不是活的,不能通过isAlive()测试。

B、就绪状态。
线程有资格运行,但调度程序还没有把它选为运行线程所处的状态。也就是具备了运行的条件,一旦被选中马上就能运行。
也是调用start()方法后但没运行的状态。此时虽然没在运行,但是被认为是活的,能通过isAlive()测试。而且在线程运行之后、或者被阻塞、等待或者睡眠状态回来之后,线程首先进入就绪状态。

C、运行状态。
从就绪状态池(注意不是队列,是池)中选择一个为当前执行进程时,该线程所处的状态。

D、等待、阻塞、睡眠状态。
这三种状态有一个共同点:线程依然是活的,但是缺少运行的条件,一旦具备了条就就可以转为就绪状态(不能直接转为运行状态)。另外,suspend()和stop()方法已经被废弃了,比较危险,不要再用了。

E、死亡状态。
一个线程的run()方法运行结束,那么该线程完成其历史使命,它的栈结构将解散,也就是死亡了。但是它仍然是一个Thread对象,我们仍可以引用它,就像其他对象一样!它也不会被垃圾回收器回收了,因为对该对象的引用仍然存在。
如此说来,即使run()方法运行结束线程也没有死啊!事实是,一旦线程死去,它就永远不能重新启动了,也就是说,不能再用start()方法让它运行起来!如果强来的话会抛出IllegalThreadStateException异常。如:
t.start();
t.start();
放弃吧,人工呼吸或者心脏起搏器都无济于事……线程也属于一次性用品。

11、阻止线程运行。
A、睡眠。sleep()方法
让线程睡眠的理由很多,比如:认为该线程运行得太快,需要减缓一下,以便和其他线程协调;查询当时的股票价格,每睡5分钟查询一次,可以节省带宽,而且即时性要求也不那么高。
用Thread的静态方法可以实现Thread.sleep(5*60*1000); 睡上5分钟吧。sleep的参数是毫秒。但是要注意sleep()方法会抛出检查异常InterruptedException,对于检查异常,我们要么声明,要么使用处理程序。
   try {
       Thread.sleep(20000);
   }
   catch (InterruptedException ie) {
       ie.printStackTrace();
   }
既然有了sleep()方法,我们是不是可以控制线程的执行顺序了!每个线程执行完毕都睡上一觉?这样就能控制线程的运行顺序了,下面是书上的一个例子:
   class ThreadTest implements Runnable{
       public void run(){
           for (int i = 1; i<4; i++){
               System.out.println (Thread.currentThread().getName());
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException ie) { }
           }
       }
       public static void main (String[] args) {
           ThreadTest tt = new ThreadTest();
           Thread t0 = new Thread(tt,"Thread 0");
           Thread t1 = new Thread(tt,"Thread 1");
           Thread t2 = new Thread(tt,"Thread 2");
           t0.start();
           t1.start();
           t2.start();          
        }
   }

并且给出了结果:
Thread 0
Thread 1
Thread 2
Thread 0
Thread 1
Thread 2
Thread 0
Thread 1
Thread 2
也就是Thread 0 Thread 1 Thread 2 按照这个顺序交替出现,作者指出虽然结果和我们预料的似乎相同,但是这个结果是不可靠的。果然被我的双核电脑验证了:
Thread 0
Thread 1
Thread 2
Thread 2
Thread 0
Thread 1
Thread 1
Thread 0
Thread 2
看来线程真的很不可靠啊。但是尽管如此,sleep()方法仍然是保证所有线程都有运行机会的最好方法。至少它保证了一个线程进入运行之后不会一直到运行完位置。

时间的精确性。再强调一下,线程醒来之后不会进入运行状态,而是进入就绪状态。因此sleep()中指定的时间不是线程不运行的精确时间!不能依赖 sleep()方法提供十分精确的定时。我们可以看到很多应用程序用sleep()作为定时器,而且没什么不好的,确实如此,但是我们一定要知道 sleep()不能保证线程醒来就能马上进入运行状态,是不精确的。

sleep()方法是一个静态的方法,它所指的是当前正在执行的线程休眠一个毫秒数。看到某些书上的 Thread.currentThread().sleep(1000); ,其实是不必要的。Thread.sleep(1000);就可以了。类似于getName()方法不是静态方法,它必须针对具体某个线程对象,这时用取 得当前线程的方法Thread.currentThread().getName();

B、线程优先级和让步。
线程的优先级。在大多数jvm实现中调度程序使用基于线程优先级的抢先调度机制。如果一个线程进入可运行状态,并且它比池中的任何其他线程和当前运行的进程的具有更高的优先级,则优先级较低的线程进入可运行状态,最高优先级的线程被选择去执行。

于是就有了这样的结论:当前运行线程的优先级通常不会比池中任何线程的优先级低。但是并不是所有的jvm的调度都这样,因此一定不能依赖于线程优先级来保 证程序的正确操作,这仍然是没有保障的,要把线程优先级用作一种提高程序效率的方法,并且这种方法也不能依赖优先级的操作。

另外一个没有保障的操作是:当前运行的线程与池中的线程,或者池中的线程具有相同的优先级时,JVM的调度实现会选择它喜欢的线程。也许是选择一个去运行,直至其完成;或者用分配时间片的方式,为每个线程提供均等的机会。

优先级用正整数设置,通常为1-10,JVM从不会改变一个线程的优先级。默认情况下,优先级是5。Thread类具有三个定义线程优先级范围的静态最终 常量:Thread.MIN_PRIORITY (为1) Thread.NORM_PRIORITY (为5) Thread.MAX_PRIORITY (为10)

静态Thread.yield()方法。
它的作用是让当前运行的线程回到可运行状态,以便让具有同等优先级的其他线程运行。用yield()方法的目的是让同等优先级的线程能适当地轮转。但是,并不能保证达到此效果!因为,即使当前变成可运行状态,可是还有可能再次被JVM选中!也就是连任。

非静态join()方法。
让一个线程加入到另一个线程的尾部。让B线程加入A线程,意味着在A线程运行完成之前,B线程不会进入可运行状态。
   Thread t = new Thread();
   t.start();
   t.join;
这段代码的意思是取得当前的线程,把它加入到t线程的尾部,等t线程运行完毕之后,原线程继续运行。书中的例子在我的电脑里效果很糟糕,看不出什么效果来。也许是CPU太快了,而且是双核的;也许是JDK1.6的原因?

12、没总结完。线程这部分很重要,内容也很多,看太快容易消化不良,偶要慢慢地消化掉……

 

分享到:
评论

相关推荐

    java多线程Demo

    Java线程有10个优先级(MIN_PRIORITY, NORM_PRIORITY, MAX_PRIORITY),默认优先级是NORM_PRIORITY。但是,线程优先级并不保证绝对的执行顺序,操作系统调度策略可能影响实际执行顺序。 7. join()方法: 一个线程...

    Java的线程和Java AppletJava的线程和Java AppletJava的线程和Java Applet

    5. **线程状态**:Java线程有五种基本状态:新建、可运行、运行、阻塞和死亡。线程的状态转换反映了其生命周期的不同阶段。 6. **线程同步**:为了避免线程间的冲突,Java提供了多种同步机制,如`synchronized`...

    Java多线程知识点总结

    Java多线程是Java编程语言中一个非常重要的概念,它允许开发者在一个程序中创建多个执行线程并行运行,以提高程序的执行效率和响应速度。在Java中,线程的生命周期包含五个基本状态,分别是新建状态(New)、就绪...

    Java多线程设计模式上传文件

    Java多线程设计模式上传文件Java多线程设计模式上传文件Java多线程设计模式上传文件Java多线程设计模式上传文件Java多线程设计模式上传文件Java多线程设计模式上传文件Java多线程设计模式上传文件Java多线程设计模式...

    JAVA单线程多线程

    ### JAVA中的单线程与多线程概念解析 #### 单线程的理解 在Java编程环境中,单线程指的是程序执行过程中只有一个线程在运行。这意味着任何时刻只能执行一个任务,上一个任务完成后才会进行下一个任务。单线程模型...

    java多线程经典案例

    本案例将深入探讨Java多线程中的关键知识点,包括线程同步、线程通信和线程阻塞。 线程同步是为了防止多个线程同时访问共享资源,导致数据不一致。Java提供了多种同步机制,如synchronized关键字、Lock接口...

    java 多线程操作数据库

    由于数据库连接是一种昂贵的资源,应避免在每个线程中重复创建连接,通常采用连接池的方式进行管理。 3. **数据插入操作**:代码示例展示了如何使用预编译语句(PreparedStatement)执行数据插入操作。预编译语句...

    Java 模拟线程并发

    Java 模拟线程并发是编程领域中的一个重要概念,尤其在多核处理器和高并发应用中,理解并熟练掌握线程并发技术对于提升程序性能至关重要。在Java中,线程并发可以通过多种方式实现,包括继承Thread类、实现Runnable...

    java多线程的讲解和实战

    Java多线程是Java编程中的重要概念,尤其在如今的多核处理器环境下,理解并熟练掌握多线程技术对于提高程序性能和响应速度至关重要。本资料详细讲解了Java多线程的原理,并提供了丰富的实战代码,非常适合Java初学者...

    java多线程读取文件

    Java多线程读大文件 java多线程写文件:多线程往队列中写入数据

    Java多线程机制(讲述java里面与多线程有关的函数)

    Java多线程机制是Java编程中至关重要的一部分,它允许程序同时执行多个任务,提升应用程序的效率和响应性。以下是对各个知识点的详细说明: 9.1 Java中的线程: Java程序中的线程是在操作系统级别的线程基础上进行...

    java中线程的应用例子

    4. **线程状态**:Java线程有五种状态:新建、可运行、运行、阻塞和死亡。了解这些状态可以帮助开发者更好地控制和调试线程。 5. **线程优先级**:每个线程都有一个优先级,Java中从1(`MIN_PRIORITY`)到10(`MAX_...

    java多线程ppt

    java多线程PPT 多线程基本概念 创建线程的方式 线程的挂起与唤醒 多线程问题

    线程 JAVA java线程 java线程第3版 java线程第2版第3版合集

    java线程第二版中英文 java线程第二版中英文 线程并不是新的概念:许多操作系统和语言都支持它们。在Java出现以前,似乎人人都在谈论线程,却很少有人使用它。用线程编程是技巧性很强的且不可移植。 而在Java中却...

    java多线程查询数据库

    在Java编程中,多线程查询数据库是一种常见的优化策略,特别是在处理大数据量或者需要并行执行多个查询时。本文将详细探讨如何利用Java的多线程技术和线程池来实现并发查询数据库,以及相关的文件`BatchDataUtil....

    java 多线程并发实例

    在Java编程中,多线程并发是提升程序执行效率、充分利用多核处理器资源的重要手段。本文将基于"java 多线程并发实例"这个主题,深入探讨Java中的多线程并发概念及其应用。 首先,我们要了解Java中的线程。线程是...

    java多线程处理数据库数据

    但是,直接在一个线程中频繁地进行数据库交互可能会导致性能瓶颈,特别是当处理大数据量时。因此,使用多线程可以将任务分解,让每个线程处理一部分数据,从而提高效率。 为了实现这个目标,我们可以使用`...

    java多线程实现大批量数据导入源码

    在Java编程中,多线程技术是处理大数据批量导入或导出的重要手段。它能有效提升程序执行效率,尤其在数据库操作这样的I/O密集型任务中。本项目以"java多线程实现大批量数据导入源码"为题,旨在通过多线程策略将大量...

    java多线程分页查询

    在Java中,可以通过继承`Thread`类或者实现`Runnable`接口来创建线程。 ##### 3. 多线程分页查询方案设计 针对数据量较大的场景,本文介绍的多线程分页查询方案主要包含以下几个步骤: - **步骤1:计算总记录数**...

    JAVA多线程练习题答案。

    在本文中,我们将对 JAVA 多线程练习题的答案进行详细的解释和分析。这些题目涵盖了 JAVA 多线程编程的基本概念和技术,包括线程的生命周期、线程同步、线程状态、线程优先级、线程安全等方面。 一、单项选择题 在...

Global site tag (gtag.js) - Google Analytics