Android开发中的多线程编程技术

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介:



Android开发中的多线程编程技术


  【IT168技术】多线程这个令人生畏的“洪水猛兽”,很多人谈起多线程都心存畏惧。在Android开发过程中,多线程真的很难吗?多线程程序的“麻烦”源于它很抽象、与单线程程序运行模式不同,但只要掌握了它们的区别,编写多线程程序就会很容易了。下面让我们集中精力开始学习吧!

  多线程案例——计时器

  我在给我的学生讲多线程的时候都会举一个计时器的案例,因为计时器案例是多线程的经典应用。

  这个案例中,屏幕启动之后,进入如图8-1所示的界面。

  屏幕上有一个文本框用于显示逝去的时间,此外还有一个“停止计时”按钮。案例的用例图如图8-2所示。

  

  ▲图8-1 计时器界面

  

  ▲图8-2 计时器用例图

  能够在屏幕上“实时地显示”时间的流逝,单线程程序是无法实现的,必须要多线程程序才可以实现,即便有些计算机语言可以通过封装好的类实现这一功能,但从本质上讲这些封装好的类就是封装了一个线程。

  综上所述,完成本案例用到的知识及技术如下:

  1)进程和线程的概念;

  2)Java中的线程,在Java中创建线程的方式;

  3)Android中的线程,包括:Message、Handler、Looper和HandlerThread等概念。

  线程究竟是什么?在Windows操作系统出现之前,个人计算机上的操作系统都是单任务系统,只有在大型计算机上才具有多任务和分时设计。Windows、Linux操作系统的出现,把原本只在大型计算机才具有的优点,带到了个人计算机系统中。

  进程概念

  一般可以在同一时间内执行多个程序的操作系统都有进程的概念。一个进程就是一个执行中的程序,而每一个进程都有自己独立的一块内存空间、一组系统资源。在进程的概念中,每一个进程的内部数据和状态都是完全独立的。在Windows操作系统下我们可以通过〈Ctrl+Alt+Del〉组合键查看进程,在UNIX和Linux操作系统下是通过PS命令查看进程的。打开Windows当前运行的进程,如图8-3所示。

  Android开发中的多线程编程技术

  ▲图8-3 Windows操作系统进程

  在Windows操作系统中一个进程就是一个exe或dll程序,它们相互独立,互相也可以通信,在Android操作系统中进程间的通信应用也是很多的。

  线程概念

  多线程指的是在单个程序中可以同时运行多个不同的线程,执行不同的任务。多线程意味着一个程序的多行语句可以看上去几乎在同一时间内同时运行。

  线程与进程相似,是一段完成某个特定功能的代码,是程序中单个顺序的流控制。但与进程不同的是,同类的多个线程共享一块内存空间和一组系统资源,所以系统在各个线程之间切换时,资源占用要比进程小得多,正因如此,线程也被称为轻量级进程。一个进程中可以包含多个线程。图8-4所示是计时器程序进程和线程之间的关系,主线程负责管理子线程,即子线程的启动、挂起、停止等操作。

  Android开发中的多线程编程技术

  ▲图8-4 进程和线程关系


  Java中的线程

  Java的线程类是java.lang.Thread类。当生成一个Thread类的对象之后,一个新的线程就产生了。Java中每个线程都是通过某个特定Thread对象的方法run()来完成其操作的,方法run( )称为线程体。

  下面是构建线程类几种常用的方法:

  public Thread()

  public Thread(Runnable target)

  public Thread(Runnable target, String name)

  public Thread(String name)

  参数target是一个实现Runnable接口的实例,它的作用是实现线程体的run()方法。目标target可为null,表示由本身实例来执行线程。name参数指定线程名字,但没有指定的构造方法,线程的名字是JVM分配的,例如JVM指定为thread-1、thread-2等名字。

  1、Java中的实现线程体方式1

  在Java中有两种方法实现线程体:一是继承线程类Thread,二是实现接口Runnable。下面我们先看看继承线程类Thread方式。

  如果采用第1种方式,它继承线程类Thread并重写其中的方法 run(),在初始化这个类实例的时候,目标target可为null,表示由本实例来执行线程体。由于Java只支持单重继承,用这种方法定义的类不能再继承其他父类,例如代码清单8-1,完整代码请参考chapter8_1工程中chapter8_1代码部分。

  【代码清单8-1】

public  class chapter8_1 extends Thread {

    
boolean  isRunning  =   true ;

    
int   timer   =   0 ;

    
/**
     
*  线程体代码
     
*/
    @Override
    
public  void run() {
        
while  (isRunning) {
            try {
                Thread.currentThread().sleep(
1000 );
                
timer ++ ;
                System.out.println(
" 逝去了  " + timer + "  秒 " );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    
public  static void main( String [] args) {

        chapter8_1 t1 
=   new  chapter8_1();

        t1.start();
        System.out.println(
" 计时器启动... " );
        BufferedReader br 
=   new  BufferedReader( new  InputStreamReader(System.in));
        try {
            
String  line  =  br.readLine();
            
if  (line.equalsIgnoreCase( " 1 " )) {
                t1.isRunning 
=   false ;
                
/* t1.stop(); */
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    }

  在main主方法中通过new chapter8_1()创建子线程,并通过t1.start()方法启动子线程,main主方法所在线程为主线程,主线程负责管理其他的子线程。本例进程、主线程和子线程之间的关系如图8-5所示。

  子线程启动之后就开始调用run()方法,run()是一个线程体,我们在子线程中处理事情就是在这里编写代码实现的。本案例中子线程要做的事情就是:休眠1s,计时器加1,再反复执行。Thread.currentThread().sleep(1000)就是休眠1s。

  为了能够停止线程,我们在主线程中增加了一个标识,通过在控制台输入一个字符

  “1”来改变该标识t1.isRunning = false,从而结束这个线程。

Java中的线程
▲图8-5 线程间关系图

  注意:

  事实上线程中有一个stop()方法也可以停止线程,但是由于这种方法会产生线程死锁问题,所以在新版JDK中已经废止了,它的替代解决方法就是增加标识,就是我们在本例中采用的方案。

  很多人觉得线程难理解,主要有两个问题:

  线程休眠,既然线程已经休眠了,程序的运行速度还能提高吗?

  线程体一般都进行死循环,既然线程死循环,程序就应该死掉了,就会没有反应。

  1.关于线程休眠问题

  对线程休眠问题头痛的读者,其实还是在用单线程的思维模式考虑问题,多数情况下我们的PC都是单CPU的,某个时间点只能有一个线程运行。所谓多线程就是多个线程交替执行就好像同时运行似的。因此,休眠当前线程可以交出CPU控制权,让其他的线程有机会运行,多个线程之间只有交替运行效率才是最高的,这就像我们开车过十字路口,只有我等等,让你先过,你再等等让他先过,才能保证最高效率,否则就会造成交通系统崩溃,对线程情况也是一样的。因此,多线程中线程的休眠是程序运行的最有效方式。

  2.关于线程体死循环问题

  在单线程中如果是死循环,程序应就会死掉,没有反应,但是多线程中线程体(run方法)中的死循环,可以保证线程一直运行,如果不循环线程,则运行一次就停止了。在上面的例子中线程体运行死循环,可以保证线程一直运行,每次运行都休眠1s,然后唤醒,再然后把时间信息输出到控制台。所以,线程体死循环是保证子线程一直运行的前提。由于是子线程它不会堵塞主线程,就不会感觉到程序死掉了。但是需要注意的是有时我们确实执行一次线程体,就不需要循环了。

  程序运行后开始启动线程,线程启动后就计算逝去的时间,每过1s将结果输出到控制台。当输入1字符后线程停止,程序终止。如图8-6所示。

Java中的线程
▲图8-6 运行显示图


  Java中的实现线程体方式2

  上面介绍继承Thread方式实现线程体,下面介绍另一种方式,这种方式是提供一个实现接口Runnable的类作为一个线程的目标对象,构造线程时有两个带有Runnable target参数的构造方法:

  Thread(Runnable target);

  Thread(Runnable target, String name)。

  其中的target就是线程目标对象了,它是一个实现Runnable的类,在构造Thread类时候把目标对象(实现Runnable的类)传递给这个线程实例,由该目标对象(实现Runnable的类)提供线程体run()方法。这时候实现接口Runnable的类仍然可以继承其他父类。

  请参看代码清单8-2,这是一个Java AWT的窗体应用程序,完整代码请参考chapter8_2工程中chapter8_2_1代码部分。

  【代码清单8-2】

public  class chapter8_2_1 extends Frame implements ActionListener, Runnable {

    
private  Label label;
    
private  Button button1;
    
private  Thread clockThread;
    
private   boolean  isRunning  =   false ;
    
private   int   timer   =   0 ;

    
public  chapter8_2_1() {
        button1 
=   new  Button( " 结束计时 " );
        label 
=   new  Label( " 计时器启动... " );
        button1.addActionListener(this);
        setLayout(
new  BorderLayout());
        add(button1, 
" North " );
        add(label, 
" Center " );
        setSize(
320 480 );
        setVisible(
true );

        clockThread 
=   new  Thread(this);
        
/*  线程体是Clock对象本身,线程名字为 " Clock "   */
        clockThread.start(); 
/*  启动线程  */
        isRunning 
=   true ;
    }

    @Override
    
public  void actionPerformed(ActionEvent event) {
        isRunning 
=   false ;
    }

    @Override

    
public  void run() {
        
while  (isRunning) {
            try {
                Thread.currentThread().sleep(
1000 );
                
timer ++ ;
                label.setText(
" 逝去了  "   +   timer   +   "  秒 " );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    
public  static void main( String  args[]) {
        chapter8_2_1 a 
=   new  chapter8_2_1();
    }

}

  其中关于Java AWT知识本书就不在这里介绍了,有兴趣的读者可以自己看看相关书籍。在本例中构建AWT窗体的应用程序方式是继承Frame类。采用第1种方式——继承方式实现线程体是不可以的,因为Java是单继承的,这个类不能既继承Frame又继承Thread。应该采用第2种方式——实现Runnable接口方式。Runnable接口也有一个run()方法,它是实现线程体方法,其代码处理与上一节是一样。需要注意的是,在第2种方法中,创建了一个Thread成员变量clockThread,才用构造方法new Thread(this)创建一个线程对象,其中创建线程使用的构造方法是Thread(Runnable target),其中的this就是代表本实例,它是一个实现了Runnable接口的实现类。

  程序运行结果如图8-7所示,屏幕开始加载的时候线程启动开始计算时间,1s更新一次UI,当单击“结束计时”按钮时,停止计时。


▲图8-7 运行结果图


  Java中的实现线程体方式3

  实现线程体方式3是实现线程体方式2的变种,本质上还是实现线程体方式2,但是在Android应用开发中经常采用第3种方式。下面我们看第3种方式的计时器代码清单8-3,完整代码请参考chapter8_2工程中 chapter8_2_2代码部分。

  【代码清单8-3】

public  class chapter8_2_2 extends Frame implements ActionListener {

    
private  Label label;
    
private  Button button1;
    
private  Thread clockThread;

    
private   boolean  isRunning  =   false ;
    
private   int   timer   =   0 ;

    
public  chapter8_2_2() {
        button1 
=   new  Button( " 结束计时 " );
        label 
=   new  Label( " 计时器启动... " );
        button1.addActionListener(this);
        setLayout(
new  BorderLayout());
        add(button1, 
" North " );
        add(label, 
" Center " );
        setSize(
320 480 );
        setVisible(
true );

        
/*  线程体是Clock对象本身,线程名字为 " Clock "   */
        clockThread 
=   new  Thread( new  Runnable() {
            @Override
            
public  void run() {
                
while  (isRunning) {
                    try {
                        Thread.currentThread().sleep(
1000 );
                        
timer ++ ;
                        label.setText(
" 逝去了  "   +   timer   +   "  秒 " );
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        clockThread.start(); 
/*  启动线程  */
        isRunning 
=   true ;
    }

    @Override
    
public  void actionPerformed(ActionEvent event) {
        isRunning 
=   false ;
    }

    
public  static void main( String  args[]) {
        chapter8_2_2 a 
=   new  chapter8_2_2();
    }

    }

  与第2种方式比较,我们发现Frame类不再实现Runnable接口了,而是在实例化Thread类的时候,定义了一个实现Runnable接口的匿名内部类:

clockThread  =   new  Thread( new  Runnable() {
            @Override
            
public  void run() {
                
while  (isRunning) {
                    try {
                        Thread.currentThread().sleep(
1000 );
                        
timer ++ ;
                        label.setText(
" 逝去了  "   +   timer   +   "  秒 " );
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
    });

  有关Java多线程的内容还有很多,例如线程优先级、线程同步等,由于这些内容与本书关系不是很紧密,所以不再介绍了,有关其他的线程知识可以参考Java方面的书籍。接下来介绍一下Android中的线程。


  Android中的线程

  在Android平台中多线程应用很广泛,在UI更新、游戏开发和耗时处理(网络通信等)等方面都需要多线程。Android线程涉及的技术有:Handler;Message;MessageQueue;Looper;HandlerThread。

  Android线程应用中的问题与分析

  为了介绍这些概念,我们把计时器的案例移植到Android系统上,按照在Frame方式修改之后的代码清单8-4,完整代码请参考chapter8_3工程中 chapter8_3代码部分。

  【代码清单8-4】

public  class chapter8_3 extends Activity {

    
private   String  TAG  =   " chapter8_3 " ;
    
private  Button btnEnd;
    
private  TextView labelTimer;
    
private  Thread clockThread;
    
private   boolean  isRunning  =   true ;
    
private   int   timer   =   0 ;

    @Override
    
public  void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        btnEnd 
=  (Button) findViewById(R.id.btnEnd);
        btnEnd.setOnClickListener(
new  OnClickListener() {

            @Override
            
public  void onClick(View v) {
                isRunning 
=   false ;
            }
        });

        labelTimer 
=  (TextView) findViewById(R.id.labelTimer);

        
/*  线程体是Clock对象本身,线程名字为 " Clock "   */
        clockThread 
=   new  Thread( new  Runnable() {
            @Override
            
public  void run() {
                
while  (isRunning) {
                    try {
                        Thread.currentThread().sleep(
1000 );
                        
timer ++ ;
                        labelTimer.setText(
" 逝去了  "   +   timer   +   "  秒 " );
                        
Log .d(TAG,  " lost  time  "   +   timer );
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        });

        clockThread.start(); 
/*  启动线程  */

    }
    }

  程序打包运行结果出现了异常,如图8-8所示。

Android线程应用中的问题与分析
▲图8-8 运行结果异常图

  我们打开LogCat窗口,出错日志信息如图8-9所示。

Android线程应用中的问题与分析
▲图8-9 出错日志

  系统抛出的异常信息是“Only the original thread that created a view hierarchy can touch its views”,在Android中更新UI处理必须由创建它的线程更新,而不能在其他线程中更新。上面的错误原因就在于此。

  现在分析一下上面的案例,在上面的程序中有两个线程:一个主线程和一个子线程,它们的职责如图8-10所示。

  由于labelTimer是一个UI控件,它是在主线程中创建的,但是它却在子线程中被更新了,更新操作在clockThread线程的run()方法中实现,代码如下:

Android线程应用中的问题与分析
▲图8-10 线程职责

/*  线程体是Clock对象本身,线程名字为 " Clock "   */
clockThread 
=   new  Thread( new  Runnable() {
    @Override
    
public  void run() {
        
while  (isRunning) {
            try {
                Thread.currentThread().sleep(
1000 );
                
timer ++ ;
                labelTimer.setText(
" 逝去了  "   +   timer   +   "  秒 " );
                
Log .d(TAG,  " lost  time  "   +   timer );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
});

  这样的处理违背了Android多线程编程规则,系统会抛出异常“Only the original thread that created a view hierarchy can touch its views”。

  要解决这个问题,就要明确主线程和子线程的职责。主线程的职责是创建、显示和更新UI控件、处理UI事件、启动子线程、停止子线程;子线程的职责是计算逝去的时间和向主线程发出更新UI消息,而不是直接更新UI。它们的职责如图8-11所示。

Android线程应用中的问题与分析
▲图8-11 线程职责

  主线程的职责是显示UI控件、处理UI事件、启动子线程、停止子线程和更新UI,子线程的职责是计算逝去的时间和向主线程发出更新UI消息。但是新的问题又出现了:子线程和主线程如何发送消息、如何通信呢?

  在Android中,线程有两个对象—消息(Message)和消息队列(MessageQueue)可以实现线程间的通信。下面再看看修改之后的代码清单8-5,完整代码请参考chapter8_4工程中chapter8_4代码部分。

  【代码清单8-5】

public  class chapter8_4 extends Activity {

    
private   String  TAG  =   " chapter8_3 " ;
    
private  Button btnEnd;
    
private  TextView labelTimer;
    
private  Thread clockThread;
    
private   boolean  isRunning  =   true ;
    
private  Handler handler;

    @Override
    
public  void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        btnEnd 
=  (Button) findViewById(R.id.btnEnd);
        btnEnd.setOnClickListener(
new  OnClickListener() {

            @Override
            
public  void onClick(View v) {
                isRunning 
=   false ;
            }
        });

        handler 
=   new  Handler() {

            @Override
            
public  void handleMessage(Message msg) {
                switch (msg.what) {
                
case   0 :
                    labelTimer.setText(
" 逝去了  "   +  msg.obj  +   "  秒 " );
                }
            }

        };

        labelTimer 
=  (TextView) findViewById(R.id.labelTimer);

        
/*  线程体是Clock对象本身,线程名字为 " Clock "   */
        clockThread 
=   new  Thread( new  Runnable() {
            @Override

            
public  void run() {
                
int   timer   =   0 ;
                
while  (isRunning) {
                    try {
                        Thread.currentThread().sleep(
1000 );
                        
timer ++ ;
                        
/*  labelTimer.setText( " 逝去了  "   +   timer   +   "  秒 " );  */
                        Message msg 
=   new  Message();
                        msg.obj 
=   timer ;
                        msg.what 
=   0 ;
                        handler.sendMessage(msg);
                        
Log .d(TAG,  " lost  time  "   +   timer );
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        clockThread.start(); 
/*  启动线程  */

    }

  有的时候为了将Android代码变得更加紧凑,把线程的创建和启动编写在一条语句中,如下面chapter8_5的代码片段。代码清单8-6所示,完整代码请参考chapter8_5工程中 chapter8_5代码部分。

  【代码清单8-6】

new  Thread() {
        @Override
        
public  void run() {
            
int   timer   =   0 ;
            
while  (isRunning) {
                ry {
                    Thread.currentThread().sleep(
1000 );
                    
timer ++ ;
                    
/  labelTimer.setText( " 逝去了  "   +   timer   +   "  秒 " );
                    Message msg 
=   new  Message();
                    msg.obj 
=   timer ;
                    msg.what 
=   0 ;
                    handler.sendMessage(msg);
                    
Log .d(TAG,  " lost  time  "   +   timer );
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }.start();

  chapter8_5代码看起来有些糊涂吧?chapter8_4和chapter8_5创建线程的区别是:chapter8_4采用Thread(Runnable target)构造方法创建一个线程,需要提供一个Runnable接口对象,需要提供的参数是实现了Runnable接口的匿名内部类对象。chapter8_5采用Thread()构造方法创建一个线程,在这里采用了简便的编程方法,直接新建一个Thread类,同时重写run()方法。

  chapter8_5编程方法虽然晦涩难懂,而且违背了Java编程规范,程序结构也比较混乱,但却是Android习惯写法,这主要源于Android对于减少字节码的追求。究竟这两种方式在性能上有多少差别呢?诚实地讲我没有做过测试和求证,在我看来就上面的程序而言它们之间不会有太大差别,由于本书要尽可能遵守Java编程规范和Android的编程习惯,因此本书中两种编程方式都会采用,如果给大家带来不便敬请谅解。

  运行模拟器结果如图8-1所示,加载屏幕后马上开始计时,也可以单击“停止计时”按钮来停止计时。



Android开发中的多线程编程技术

2011年10月03日00:05  it168网站原创 作者:关东升 赵志荣 编辑: 景保玉  我要评论(5)
标签:  Android , 移动开发

  【IT168技术】多线程这个令人生畏的“洪水猛兽”,很多人谈起多线程都心存畏惧。在Android开发过程中,多线程真的很难吗?多线程程序的“麻烦”源于它很抽象、与单线程程序运行模式不同,但只要掌握了它们的区别,编写多线程程序就会很容易了。下面让我们集中精力开始学习吧!

  多线程案例——计时器

  我在给我的学生讲多线程的时候都会举一个计时器的案例,因为计时器案例是多线程的经典应用。

  这个案例中,屏幕启动之后,进入如图8-1所示的界面。

  屏幕上有一个文本框用于显示逝去的时间,此外还有一个“停止计时”按钮。案例的用例图如图8-2所示。

  

  ▲图8-1 计时器界面

  

  ▲图8-2 计时器用例图

  能够在屏幕上“实时地显示”时间的流逝,单线程程序是无法实现的,必须要多线程程序才可以实现,即便有些计算机语言可以通过封装好的类实现这一功能,但从本质上讲这些封装好的类就是封装了一个线程。

  综上所述,完成本案例用到的知识及技术如下:

  1)进程和线程的概念;

  2)Java中的线程,在Java中创建线程的方式;

  3)Android中的线程,包括:Message、Handler、Looper和HandlerThread等概念。

  线程究竟是什么?在Windows操作系统出现之前,个人计算机上的操作系统都是单任务系统,只有在大型计算机上才具有多任务和分时设计。Windows、Linux操作系统的出现,把原本只在大型计算机才具有的优点,带到了个人计算机系统中。

  进程概念

  一般可以在同一时间内执行多个程序的操作系统都有进程的概念。一个进程就是一个执行中的程序,而每一个进程都有自己独立的一块内存空间、一组系统资源。在进程的概念中,每一个进程的内部数据和状态都是完全独立的。在Windows操作系统下我们可以通过〈Ctrl+Alt+Del〉组合键查看进程,在UNIX和Linux操作系统下是通过PS命令查看进程的。打开Windows当前运行的进程,如图8-3所示。

  Android开发中的多线程编程技术

  ▲图8-3 Windows操作系统进程

  在Windows操作系统中一个进程就是一个exe或dll程序,它们相互独立,互相也可以通信,在Android操作系统中进程间的通信应用也是很多的。

  线程概念

  多线程指的是在单个程序中可以同时运行多个不同的线程,执行不同的任务。多线程意味着一个程序的多行语句可以看上去几乎在同一时间内同时运行。

  线程与进程相似,是一段完成某个特定功能的代码,是程序中单个顺序的流控制。但与进程不同的是,同类的多个线程共享一块内存空间和一组系统资源,所以系统在各个线程之间切换时,资源占用要比进程小得多,正因如此,线程也被称为轻量级进程。一个进程中可以包含多个线程。图8-4所示是计时器程序进程和线程之间的关系,主线程负责管理子线程,即子线程的启动、挂起、停止等操作。

  Android开发中的多线程编程技术

  ▲图8-4 进程和线程关系


  Java中的线程

  Java的线程类是java.lang.Thread类。当生成一个Thread类的对象之后,一个新的线程就产生了。Java中每个线程都是通过某个特定Thread对象的方法run()来完成其操作的,方法run( )称为线程体。

  下面是构建线程类几种常用的方法:

  public Thread()

  public Thread(Runnable target)

  public Thread(Runnable target, String name)

  public Thread(String name)

  参数target是一个实现Runnable接口的实例,它的作用是实现线程体的run()方法。目标target可为null,表示由本身实例来执行线程。name参数指定线程名字,但没有指定的构造方法,线程的名字是JVM分配的,例如JVM指定为thread-1、thread-2等名字。

  1、Java中的实现线程体方式1

  在Java中有两种方法实现线程体:一是继承线程类Thread,二是实现接口Runnable。下面我们先看看继承线程类Thread方式。

  如果采用第1种方式,它继承线程类Thread并重写其中的方法 run(),在初始化这个类实例的时候,目标target可为null,表示由本实例来执行线程体。由于Java只支持单重继承,用这种方法定义的类不能再继承其他父类,例如代码清单8-1,完整代码请参考chapter8_1工程中chapter8_1代码部分。

  【代码清单8-1】

public  class chapter8_1 extends Thread {

    
boolean  isRunning  =   true ;

    
int   timer   =   0 ;

    
/**
     
*  线程体代码
     
*/
    @Override
    
public  void run() {
        
while  (isRunning) {
            try {
                Thread.currentThread().sleep(
1000 );
                
timer ++ ;
                System.out.println(
" 逝去了  " + timer + "  秒 " );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    
public  static void main( String [] args) {

        chapter8_1 t1 
=   new  chapter8_1();

        t1.start();
        System.out.println(
" 计时器启动... " );
        BufferedReader br 
=   new  BufferedReader( new  InputStreamReader(System.in));
        try {
            
String  line  =  br.readLine();
            
if  (line.equalsIgnoreCase( " 1 " )) {
                t1.isRunning 
=   false ;
                
/* t1.stop(); */
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    }

  在main主方法中通过new chapter8_1()创建子线程,并通过t1.start()方法启动子线程,main主方法所在线程为主线程,主线程负责管理其他的子线程。本例进程、主线程和子线程之间的关系如图8-5所示。

  子线程启动之后就开始调用run()方法,run()是一个线程体,我们在子线程中处理事情就是在这里编写代码实现的。本案例中子线程要做的事情就是:休眠1s,计时器加1,再反复执行。Thread.currentThread().sleep(1000)就是休眠1s。

  为了能够停止线程,我们在主线程中增加了一个标识,通过在控制台输入一个字符

  “1”来改变该标识t1.isRunning = false,从而结束这个线程。

Java中的线程
▲图8-5 线程间关系图

  注意:

  事实上线程中有一个stop()方法也可以停止线程,但是由于这种方法会产生线程死锁问题,所以在新版JDK中已经废止了,它的替代解决方法就是增加标识,就是我们在本例中采用的方案。

  很多人觉得线程难理解,主要有两个问题:

  线程休眠,既然线程已经休眠了,程序的运行速度还能提高吗?

  线程体一般都进行死循环,既然线程死循环,程序就应该死掉了,就会没有反应。

  1.关于线程休眠问题

  对线程休眠问题头痛的读者,其实还是在用单线程的思维模式考虑问题,多数情况下我们的PC都是单CPU的,某个时间点只能有一个线程运行。所谓多线程就是多个线程交替执行就好像同时运行似的。因此,休眠当前线程可以交出CPU控制权,让其他的线程有机会运行,多个线程之间只有交替运行效率才是最高的,这就像我们开车过十字路口,只有我等等,让你先过,你再等等让他先过,才能保证最高效率,否则就会造成交通系统崩溃,对线程情况也是一样的。因此,多线程中线程的休眠是程序运行的最有效方式。

  2.关于线程体死循环问题

  在单线程中如果是死循环,程序应就会死掉,没有反应,但是多线程中线程体(run方法)中的死循环,可以保证线程一直运行,如果不循环线程,则运行一次就停止了。在上面的例子中线程体运行死循环,可以保证线程一直运行,每次运行都休眠1s,然后唤醒,再然后把时间信息输出到控制台。所以,线程体死循环是保证子线程一直运行的前提。由于是子线程它不会堵塞主线程,就不会感觉到程序死掉了。但是需要注意的是有时我们确实执行一次线程体,就不需要循环了。

  程序运行后开始启动线程,线程启动后就计算逝去的时间,每过1s将结果输出到控制台。当输入1字符后线程停止,程序终止。如图8-6所示。

Java中的线程
▲图8-6 运行显示图


  Java中的实现线程体方式2

  上面介绍继承Thread方式实现线程体,下面介绍另一种方式,这种方式是提供一个实现接口Runnable的类作为一个线程的目标对象,构造线程时有两个带有Runnable target参数的构造方法:

  Thread(Runnable target);

  Thread(Runnable target, String name)。

  其中的target就是线程目标对象了,它是一个实现Runnable的类,在构造Thread类时候把目标对象(实现Runnable的类)传递给这个线程实例,由该目标对象(实现Runnable的类)提供线程体run()方法。这时候实现接口Runnable的类仍然可以继承其他父类。

  请参看代码清单8-2,这是一个Java AWT的窗体应用程序,完整代码请参考chapter8_2工程中chapter8_2_1代码部分。

  【代码清单8-2】

public  class chapter8_2_1 extends Frame implements ActionListener, Runnable {

    
private  Label label;
    
private  Button button1;
    
private  Thread clockThread;
    
private   boolean  isRunning  =   false ;
    
private   int   timer   =   0 ;

    
public  chapter8_2_1() {
        button1 
=   new  Button( " 结束计时 " );
        label 
=   new  Label( " 计时器启动... " );
        button1.addActionListener(this);
        setLayout(
new  BorderLayout());
        add(button1, 
" North " );
        add(label, 
" Center " );
        setSize(
320 480 );
        setVisible(
true );

        clockThread 
=   new  Thread(this);
        
/*  线程体是Clock对象本身,线程名字为 " Clock "   */
        clockThread.start(); 
/*  启动线程  */
        isRunning 
=   true ;
    }

    @Override
    
public  void actionPerformed(ActionEvent event) {
        isRunning 
=   false ;
    }

    @Override

    
public  void run() {
        
while  (isRunning) {
            try {
                Thread.currentThread().sleep(
1000 );
                
timer ++ ;
                label.setText(
" 逝去了  "   +   timer   +   "  秒 " );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    
public  static void main( String  args[]) {
        chapter8_2_1 a 
=   new  chapter8_2_1();
    }

}

  其中关于Java AWT知识本书就不在这里介绍了,有兴趣的读者可以自己看看相关书籍。在本例中构建AWT窗体的应用程序方式是继承Frame类。采用第1种方式——继承方式实现线程体是不可以的,因为Java是单继承的,这个类不能既继承Frame又继承Thread。应该采用第2种方式——实现Runnable接口方式。Runnable接口也有一个run()方法,它是实现线程体方法,其代码处理与上一节是一样。需要注意的是,在第2种方法中,创建了一个Thread成员变量clockThread,才用构造方法new Thread(this)创建一个线程对象,其中创建线程使用的构造方法是Thread(Runnable target),其中的this就是代表本实例,它是一个实现了Runnable接口的实现类。

  程序运行结果如图8-7所示,屏幕开始加载的时候线程启动开始计算时间,1s更新一次UI,当单击“结束计时”按钮时,停止计时。


▲图8-7 运行结果图


  Java中的实现线程体方式3

  实现线程体方式3是实现线程体方式2的变种,本质上还是实现线程体方式2,但是在Android应用开发中经常采用第3种方式。下面我们看第3种方式的计时器代码清单8-3,完整代码请参考chapter8_2工程中 chapter8_2_2代码部分。

  【代码清单8-3】

public  class chapter8_2_2 extends Frame implements ActionListener {

    
private  Label label;
    
private  Button button1;
    
private  Thread clockThread;

    
private   boolean  isRunning  =   false ;
    
private   int   timer   =   0 ;

    
public  chapter8_2_2() {
        button1 
=   new  Button( " 结束计时 " );
        label 
=   new  Label( " 计时器启动... " );
        button1.addActionListener(this);
        setLayout(
new  BorderLayout());
        add(button1, 
" North " );
        add(label, 
" Center " );
        setSize(
320 480 );
        setVisible(
true );

        
/*  线程体是Clock对象本身,线程名字为 " Clock "   */
        clockThread 
=   new  Thread( new  Runnable() {
            @Override
            
public  void run() {
                
while  (isRunning) {
                    try {
                        Thread.currentThread().sleep(
1000 );
                        
timer ++ ;
                        label.setText(
" 逝去了  "   +   timer   +   "  秒 " );
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        clockThread.start(); 
/*  启动线程  */
        isRunning 
=   true ;
    }

    @Override
    
public  void actionPerformed(ActionEvent event) {
        isRunning 
=   false ;
    }

    
public  static void main( String  args[]) {
        chapter8_2_2 a 
=   new  chapter8_2_2();
    }

    }

  与第2种方式比较,我们发现Frame类不再实现Runnable接口了,而是在实例化Thread类的时候,定义了一个实现Runnable接口的匿名内部类:

clockThread  =   new  Thread( new  Runnable() {
            @Override
            
public  void run() {
                
while  (isRunning) {
                    try {
                        Thread.currentThread().sleep(
1000 );
                        
timer ++ ;
                        label.setText(
" 逝去了  "   +   timer   +   "  秒 " );
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
    });

  有关Java多线程的内容还有很多,例如线程优先级、线程同步等,由于这些内容与本书关系不是很紧密,所以不再介绍了,有关其他的线程知识可以参考Java方面的书籍。接下来介绍一下Android中的线程。


  Android中的线程

  在Android平台中多线程应用很广泛,在UI更新、游戏开发和耗时处理(网络通信等)等方面都需要多线程。Android线程涉及的技术有:Handler;Message;MessageQueue;Looper;HandlerThread。

  Android线程应用中的问题与分析

  为了介绍这些概念,我们把计时器的案例移植到Android系统上,按照在Frame方式修改之后的代码清单8-4,完整代码请参考chapter8_3工程中 chapter8_3代码部分。

  【代码清单8-4】

public  class chapter8_3 extends Activity {

    
private   String  TAG  =   " chapter8_3 " ;
    
private  Button btnEnd;
    
private  TextView labelTimer;
    
private  Thread clockThread;
    
private   boolean  isRunning  =   true ;
    
private   int   timer   =   0 ;

    @Override
    
public  void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        btnEnd 
=  (Button) findViewById(R.id.btnEnd);
        btnEnd.setOnClickListener(
new  OnClickListener() {

            @Override
            
public  void onClick(View v) {
                isRunning 
=   false ;
            }
        });

        labelTimer 
=  (TextView) findViewById(R.id.labelTimer);

        
/*  线程体是Clock对象本身,线程名字为 " Clock "   */
        clockThread 
=   new  Thread( new  Runnable() {
            @Override
            
public  void run() {
                
while  (isRunning) {
                    try {
                        Thread.currentThread().sleep(
1000 );
                        
timer ++ ;
                        labelTimer.setText(
" 逝去了  "   +   timer   +   "  秒 " );
                        
Log .d(TAG,  " lost  time  "   +   timer );
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        });

        clockThread.start(); 
/*  启动线程  */

    }
    }

  程序打包运行结果出现了异常,如图8-8所示。

Android线程应用中的问题与分析
▲图8-8 运行结果异常图

  我们打开LogCat窗口,出错日志信息如图8-9所示。

Android线程应用中的问题与分析
▲图8-9 出错日志

  系统抛出的异常信息是“Only the original thread that created a view hierarchy can touch its views”,在Android中更新UI处理必须由创建它的线程更新,而不能在其他线程中更新。上面的错误原因就在于此。

  现在分析一下上面的案例,在上面的程序中有两个线程:一个主线程和一个子线程,它们的职责如图8-10所示。

  由于labelTimer是一个UI控件,它是在主线程中创建的,但是它却在子线程中被更新了,更新操作在clockThread线程的run()方法中实现,代码如下:

Android线程应用中的问题与分析
▲图8-10 线程职责

/*  线程体是Clock对象本身,线程名字为 " Clock "   */
clockThread 
=   new  Thread( new  Runnable() {
    @Override
    
public  void run() {
        
while  (isRunning) {
            try {
                Thread.currentThread().sleep(
1000 );
                
timer ++ ;
                labelTimer.setText(
" 逝去了  "   +   timer   +   "  秒 " );
                
Log .d(TAG,  " lost  time  "   +   timer );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
});

  这样的处理违背了Android多线程编程规则,系统会抛出异常“Only the original thread that created a view hierarchy can touch its views”。

  要解决这个问题,就要明确主线程和子线程的职责。主线程的职责是创建、显示和更新UI控件、处理UI事件、启动子线程、停止子线程;子线程的职责是计算逝去的时间和向主线程发出更新UI消息,而不是直接更新UI。它们的职责如图8-11所示。

Android线程应用中的问题与分析
▲图8-11 线程职责

  主线程的职责是显示UI控件、处理UI事件、启动子线程、停止子线程和更新UI,子线程的职责是计算逝去的时间和向主线程发出更新UI消息。但是新的问题又出现了:子线程和主线程如何发送消息、如何通信呢?

  在Android中,线程有两个对象—消息(Message)和消息队列(MessageQueue)可以实现线程间的通信。下面再看看修改之后的代码清单8-5,完整代码请参考chapter8_4工程中chapter8_4代码部分。

  【代码清单8-5】

public  class chapter8_4 extends Activity {

    
private   String  TAG  =   " chapter8_3 " ;
    
private  Button btnEnd;
    
private  TextView labelTimer;
    
private  Thread clockThread;
    
private   boolean  isRunning  =   true ;
    
private  Handler handler;

    @Override
    
public  void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        btnEnd 
=  (Button) findViewById(R.id.btnEnd);
        btnEnd.setOnClickListener(
new  OnClickListener() {

            @Override
            
public  void onClick(View v) {
                isRunning 
=   false ;
            }
        });

        handler 
=   new  Handler() {

            @Override
            
public  void handleMessage(Message msg) {
                switch (msg.what) {
                
case   0 :
                    labelTimer.setText(
" 逝去了  "   +  msg.obj  +   "  秒 " );
                }
            }

        };

        labelTimer 
=  (TextView) findViewById(R.id.labelTimer);

        
/*  线程体是Clock对象本身,线程名字为 " Clock "   */
        clockThread 
=   new  Thread( new  Runnable() {
            @Override

            
public  void run() {
                
int   timer   =   0 ;
                
while  (isRunning) {
                    try {
                        Thread.currentThread().sleep(
1000 );
                        
timer ++ ;
                        
/*  labelTimer.setText( " 逝去了  "   +   timer   +   "  秒 " );  */
                        Message msg 
=   new  Message();
                        msg.obj 
=   timer ;
                        msg.what 
=   0 ;
                        handler.sendMessage(msg);
                        
Log .d(TAG,  " lost  time  "   +   timer );
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        clockThread.start(); 
/*  启动线程  */

    }

  有的时候为了将Android代码变得更加紧凑,把线程的创建和启动编写在一条语句中,如下面chapter8_5的代码片段。代码清单8-6所示,完整代码请参考chapter8_5工程中 chapter8_5代码部分。

  【代码清单8-6】

new  Thread() {
        @Override
        
public  void run() {
            
int   timer   =   0 ;
            
while  (isRunning) {
                ry {
                    Thread.currentThread().sleep(
1000 );
                    
timer ++ ;
                    
/  labelTimer.setText( " 逝去了  "   +   timer   +   "  秒 " );
                    Message msg 
=   new  Message();
                    msg.obj 
=   timer ;
                    msg.what 
=   0 ;
                    handler.sendMessage(msg);
                    
Log .d(TAG,  " lost  time  "   +   timer );
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }.start();

  chapter8_5代码看起来有些糊涂吧?chapter8_4和chapter8_5创建线程的区别是:chapter8_4采用Thread(Runnable target)构造方法创建一个线程,需要提供一个Runnable接口对象,需要提供的参数是实现了Runnable接口的匿名内部类对象。chapter8_5采用Thread()构造方法创建一个线程,在这里采用了简便的编程方法,直接新建一个Thread类,同时重写run()方法。

  chapter8_5编程方法虽然晦涩难懂,而且违背了Java编程规范,程序结构也比较混乱,但却是Android习惯写法,这主要源于Android对于减少字节码的追求。究竟这两种方式在性能上有多少差别呢?诚实地讲我没有做过测试和求证,在我看来就上面的程序而言它们之间不会有太大差别,由于本书要尽可能遵守Java编程规范和Android的编程习惯,因此本书中两种编程方式都会采用,如果给大家带来不便敬请谅解。

  运行模拟器结果如图8-1所示,加载屏幕后马上开始计时,也可以单击“停止计时”按钮来停止计时。


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
2月前
|
安全 Android开发 iOS开发
Android vs. iOS:构建生态差异与技术较量的深度剖析###
本文深入探讨了Android与iOS两大移动操作系统在构建生态系统上的差异,揭示了它们各自的技术优势及面临的挑战。通过对比分析两者的开放性、用户体验、安全性及市场策略,本文旨在揭示这些差异如何塑造了当今智能手机市场的竞争格局,为开发者和用户提供决策参考。 ###
|
2月前
|
安全 Android开发 iOS开发
安卓与iOS的较量:技术深度对比
【10月更文挑战第18天】 在智能手机操作系统领域,安卓和iOS无疑是两大巨头。本文将深入探讨这两种系统的技术特点、优势以及它们之间的主要差异,帮助读者更好地理解这两个平台的独特之处。
48 0
|
4天前
|
API Android开发 iOS开发
深入探索Android与iOS的多线程编程差异
在移动应用开发领域,多线程编程是提高应用性能和响应性的关键。本文将对比分析Android和iOS两大平台在多线程处理上的不同实现机制,探讨它们各自的优势与局限性,并通过实例展示如何在这两个平台上进行有效的多线程编程。通过深入了解这些差异,开发者可以更好地选择适合自己项目需求的技术和策略,从而优化应用的性能和用户体验。
|
25天前
|
安全 搜索推荐 Android开发
揭秘安卓与iOS系统的差异:技术深度对比
【10月更文挑战第27天】 本文深入探讨了安卓(Android)与iOS两大移动操作系统的技术特点和用户体验差异。通过对比两者的系统架构、应用生态、用户界面、安全性等方面,揭示了为何这两种系统能够在市场中各占一席之地,并为用户提供不同的选择。文章旨在为读者提供一个全面的视角,理解两种系统的优势与局限,从而更好地根据自己的需求做出选择。
66 2
|
27天前
|
安全 搜索推荐 Android开发
揭秘iOS与安卓系统的差异:一场技术与哲学的较量
在智能手机的世界里,iOS和Android无疑是两大巨头,它们不仅定义了操作系统的标准,也深刻影响了全球数亿用户的日常生活。本文旨在探讨这两个平台在设计理念、用户体验、生态系统及安全性等方面的本质区别,揭示它们背后的技术哲学和市场策略。通过对比分析,我们将发现,选择iOS或Android,不仅仅是选择一个操作系统,更是选择了一种生活方式和技术信仰。
|
2月前
|
安全 Android开发 iOS开发
iOS与安卓:技术生态的双雄争霸
在当今数字化时代,智能手机操作系统的竞争愈发激烈。iOS和安卓作为两大主流平台,各自拥有独特的技术优势和市场地位。本文将从技术架构、用户体验、安全性以及开发者支持四个方面,深入探讨iOS与安卓之间的差异,并分析它们如何塑造了今天的移动技术生态。无论是追求极致体验的苹果用户,还是享受开放自由的安卓粉丝,了解这两大系统的内在逻辑对于把握未来趋势至关重要。
|
2月前
|
安全 搜索推荐 Android开发
揭秘iOS与Android系统的差异:一场技术与哲学的较量
在当今数字化时代,智能手机操作系统的选择成为了用户个性化表达和技术偏好的重要标志。iOS和Android,作为市场上两大主流操作系统,它们之间的竞争不仅仅是技术的比拼,更是设计理念、用户体验和生态系统构建的全面较量。本文将深入探讨iOS与Android在系统架构、应用生态、用户界面及安全性等方面的本质区别,揭示这两种系统背后的哲学思想和市场策略,帮助读者更全面地理解两者的优劣,从而做出更适合自己的选择。
|
2月前
|
安全 Android开发 iOS开发
安卓vs iOS:探索两种操作系统的独特魅力与技术深度###
【10月更文挑战第16天】 本文旨在深入浅出地探讨安卓(Android)与iOS这两种主流移动操作系统的特色、优势及背后的技术理念。通过对比分析,揭示它们各自如何塑造了移动互联网的生态,并为用户提供丰富多彩的智能体验。无论您是科技爱好者还是普通用户,都能从这篇文章中感受到技术创新带来的无限可能。 ###
55 2
|
2月前
|
机器学习/深度学习 人工智能 Android开发
安卓与iOS:技术演进的双城记
【10月更文挑战第16天】 在移动操作系统的世界里,安卓和iOS无疑是两个最重要的玩家。它们各自代表了不同的技术理念和市场策略,塑造了全球数亿用户的移动体验。本文将深入探讨这两个平台的发展历程、技术特点以及它们如何影响了我们的数字生活,旨在为读者提供一个全面而深入的视角,理解这两个操作系统背后的哲学和未来趋势。
33 2
|
2月前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
51 4