展会信息港展会大全

Android开发中的多线程 Java构建线程类几种常用的方法
来源:互联网   发布日期:2016-01-14 09:14:37   浏览:2219次  

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

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,从而结束这个线程。

注意:

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

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

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

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

1.关于线程休眠问题

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

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

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

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

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,当单击 结束计时 按钮时,停止计时。

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, "losttime " + timer);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

});

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

}

}

程序打包运行结果出现了异常

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

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

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

/* 线程体是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, "losttime " + timer);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

});

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

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

主线程的职责是显示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, "losttime " + 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, "losttime " + 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

AiLab云推荐
展开

热门栏目HotCates

Copyright © 2010-2024 AiLab Team. 人工智能实验室 版权所有    关于我们 | 联系我们 | 广告服务 | 公司动态 | 免责声明 | 隐私条款 | 工作机会 | 展会港