走进Java世界中的线程
1.进程、线程与任务
进程是程序运行的实例,一个简单的java程序如下所示:
import java.util.Date;
public class SimlpeJavaApp {
public static void main(String[] args) throws Exception
{
while (true)
{
System.out.println(new Date());
Thread.sleep(1000);
}
}
}
进程是程序向操作系统申请资源的最小单位,线程则是进程中可独立执行的最小单位。一个进程可以包含多个线程,同一个进程中的所有线程共享该进程中的资源。
2.Java线程API简介
2.1线程的创建、运行及启动
在Java平台中创建一个线程就是创建一个Thread类的实例。线程的任务处理逻辑Thread类的run实例方法中直接实现或者通过该方法进行调用,因此run方法为线程的任务处理逻辑入口。
Thread类的start方法的作用是启动相应的线程。启动一个线程的使之是请求Java虚拟机运行相应的线程,而这个线程具体何时能够运行是由线程调度器决定的。
Thread类的两个常用构造器是Thread()和Thread(Runnable target)。
下面以创建一个处理任务为打印一行欢迎信息的简单线程为例:
以定义Thread类子类的方式创建线程
import java.lang.String;
public class WelcomeApp {
public static void main(String[] args)
{
Thread welcomeThread = new WelcomeThread();
welcomeThread.start();
System.out.printf("1.Welcome!I'm %s.%n",Thread.currentThread().getName());
}
}
class WelcomeThread extends Thread
{
@Override
public void run()
{
System.out.printf("2.Welcome!I'm %s.%n",Thread.currentThread().getName());
}
}
其输出为
1.Welcome!I’m main.
2.Welcome!I’m Thread-0.
以创建Runnable接口实例的方式创建线程
public class WelcomeApp1 {
public static void main(String[] agrs)
{
Thread welcomeThread = new Thread(new WelcomeTask());
welcomeThread.start();
System.out.printf("1.Welcome!I'm %s.%n",Thread.currentThread().getName());
}
}
class WelcomeTask implements Runnable{
@Override
public void run()
{
System.out.printf("2.Welcome!I'm %s.%n",Thread.currentThread().getName());
}
}
运行结果为
1.Welcome!I’m main.
2.Welcome!I’m Thread-0.
在多次运行之后,运行结果也可能为
2.Welcome!I’m Thread-0.
1.Welcome!I’m main.、
无论采用什么方法创建线程,当线程的run方法执行结束,相应线程也就结束了。线程属于一次性用品,无法调用一个已结束线程的start方法使其重新运行。
Java平台中任意一段代码总是由确定的线程负责执行的,这个线程相应的称为这段代码的执行线程。同一段代码可以被多个线程执行。而当前线程则是相对的,即该娘层次上的当前线程。
线程的run方法总是由虚拟机调用的,虽然也可以在代码中直接调用,但是直接调用的结果一般都不理想。
如以下代码块所示,应用代码直接调用线程run方法(应避免这么做)
public class WelcomeApp2 {
public static void main(String [] args)
{
Thread welcomeThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.printf("2.Welcome!I'm %s.%n",Thread.currentThread().getName());
}
});
welcomeThread.start();
welcomeThread.run();
System.out.printf("1.Welcome!I'm %s.%n",Thread.currentThread().getName());
}
}
其运行结果为
2.Welcome!I’m main.
1.Welcome!I’m main.
2.Welcome!I’m Thread-0.
可以看出直接调用run()的当前线程仍然为main
2.2 Runnable接口
Runnable接口只定义了一个方法,该方法的声明如下:
public void run()
Thread对Runnable接口的实现如下:
public void run()
{
if(target != null){
target.run();
}
}
实例变量target的类型为Runnable。
如下代码中显示了线程的两种创建方式的区别
import java.util.Random;
public class ThreadCreationCmp {
public static void main(String[] args)
{
Thread t;
CountingTask ct = new CountingTask();
final int numberOfProceeesors = Runtime.getRuntime().availableProcessors();
for (int i = 0; i < 2 * numberOfProceeesors; i++)
{
t = new Thread(ct);
t.start();
}
for (int i = 0; i < 2 * numberOfProceeesors; i++)
{
t = new CountingThread();
t.start();
}
}
static class Counter{
private int count = 0;
public void increment()
{
count++;
}
public int value()
{
return count;
}
}
static class CountingTask implements Runnable{
private Counter counter = new Counter();
@Override
public void run()
{
try {
for (int i = 0;i<100;i++)
{
doSomething();
counter.increment();
}
System.out.println("CountingTask:"+counter.value());
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
}
private void doSomething() throws Exception
{
int a = new Random().nextInt(80);
Thread.currentThread().sleep(a);
}
}
static class CountingThread extends Thread{
private Counter counter = new Counter();
@Override
public void run()
{
try {
for (int i=0;i<100;i++)
{
doSomething();
counter.increment();
}
System.out.println("CountingThread:"+counter.value());
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
}
private void doSomething() throws Exception
{
int a = new Random().nextInt(80);
Thread.currentThread().sleep(a);
}
}
}
在本人的主机上,CountThread始终为100,而CountTask为小于2400的不定值。这是因为多个线程共享了一个CountingTask实例,导致产生竞态问题。
2.3线程属性
线程的属性包括线程的编号(ID),名称(Name),线程类别(Daemon)和优先级(Priority)。
线程属性中除了编号(ID)为只读外,其他均为可读写属性。通过名称属性,可以区别不同的线程,有助于定位线程的问题。Java线程优先级的本职是给线程调度器的提示信息,其并不能保证线程按照优先级运行。按照线程是否会阻止虚拟机的正常停止,可以区分为用户线程和守护线程。用户线程会阻止虚拟机的停止,只有用户线程运行结束Java虚拟机才会正常停止。而守护线程不会影响Java虚拟机的正常停止。
2.4Thread的常用方法
currentThread()返回当前线程。
run()实现任务处理逻辑。
start()启动相应线程。
join()等待相应线程运行结束。如果线程A调用线程B的join方法,那么线程A的运行就会暂停。直到线程B运行结束。
yield()使当前线程放弃对处理器的占用。不过该方法被调用之后当前线程仍然可能继续运行。相当于对操作系统说“我现在不急,如果别人需要处理器资源就先给他用吧。当然如果没有别人要用,我也不介意继续占用。”
sleep(long millis),使当前线程休眠指定时间。
一个使用sleep函数实现的建议倒计时器如下所示:
public class SimpleTimer {
private static int count;
public static void main(String[] args)
{
count = args.length>1? Integer.valueOf(args[0]): 60;
int remaining;
while (true)
{
remaining = countDown();
if(0 == remaining)
{
break;
}
else {
System.out.println("Remaining"+ count + " second(s)");
}
try {
Thread.sleep(1000);
}
catch (InterruptedException e)
{
System.out.println(e.getMessage());
}
}
}
private static int countDown()
{
return count--;
}
}
3.线程的层次关系
Java中的线程不是孤立的。假设线程A创建了线程B,那么习惯上我们称线程B为线程A的子线程,线程A为线程B的父线程。
Java虚拟机创建的main线程负责执行Java程序的图库方法main方法,因此main方法直接创建的都是main的子线程,而这些子线程又创建了其他线程。
Java平台中,一个线程是否是守护线程取决于其父线程,父线程是用户线程则子线程默认用户线程,反之亦然。一个线程的默认优先级也与父线程相同。
4.线程的生命周期状态
线程的生命周期包含以下几种:
NEW:一个已创建而未启动的线程处于该状态。
RUNNABLE:它包含两个子状态,分别为READY和RUNNING。前者表示处于该状态的线程可以被线程调度器进行调度而使之处于RUNNING状态。后者在表示线程正在运行。执行Thread.yield()的线程,状态可能从RUNNING变成READY。处于READY状态的线程也被称为活跃线程。
BLOCKED:一个线程发起阻塞式I/O操作后或者申请一个其他线程持有的独占资源后,会处于该状态(阻塞状态)。
WAITING:一个线程执行了如Object.wait()之类的特定方法就会处于该状态等待其他线程执行一些特殊操作。
TIMED_WAITING:和WAITING状态类似,区别在于处于该状态的线程并非无限制等待其他线程的操作,而是带有时间限制的等待。
TERMINATED:已经执行结束的线程处于该状态。
5.多线程编程的简单运用实例
一个简单的多线程编程运用实例如下所示。该实例要实现这样一个功能:根据指定url下载一批文件。
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.Scanner;
public class FileDownloaderApp {
public static void main(String[] args) {
Thread downloaderThread = null;
Scanner in = new Scanner(System.in);
int num = in.nextInt();
String[] urls = new String[num];
for (int i = 0; i < num;i++)
{
urls[i] = in.nextLine();
}
for (String url : urls) {
// 创建文件下载器线程
downloaderThread = new Thread(new FileDownloader(url));
// 启动文件下载器线程
downloaderThread.start();
}
}
// 文件下载器
static class FileDownloader implements Runnable {
private final String fileURL;
public FileDownloader(String fileURL) {
this.fileURL = fileURL;
}
@Override
public void run() {
System.out.println("Downloading from " + fileURL);
String fileBaseName = fileURL.substring(fileURL.lastIndexOf('/') + 1);
try {
URL url = new URL(fileURL);
String localFileName = System.getProperty("java.io.tmpdir")
+ "/viscent-"
+ fileBaseName;
System.out.println("Saving to: " + localFileName);
downloadFile(url, new FileOutputStream(
localFileName), 1024);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Done downloading from " + fileURL);
}
// 从指定的URL下载文件,并将其保存到指定的输出流中
private void downloadFile(URL url, OutputStream outputStream, int bufSize)
throws MalformedURLException, IOException {
// 建立HTTP连接
final HttpURLConnection httpConn = (HttpURLConnection) url
.openConnection();
httpConn.setRequestMethod("GET");
ReadableByteChannel inChannel = null;
WritableByteChannel outChannel = null;
try {
// 获取HTTP响应码
int responseCode = httpConn.getResponseCode();
// HTTP响应非正常:响应码不为2开头
if (2 != responseCode / 100) {
throw new IOException("Error: HTTP " + responseCode);
}
if (0 == httpConn.getContentLength()) {
System.out.println("Nothing to be downloaded " + fileURL);
return;
}
inChannel = Channels
.newChannel(new BufferedInputStream(httpConn.getInputStream()));
outChannel = Channels
.newChannel(new BufferedOutputStream(outputStream));
ByteBuffer buf = ByteBuffer.allocate(bufSize);
while (-1 != inChannel.read(buf)) {
buf.flip();
outChannel.write(buf);
buf.clear();
}
} finally {
// 关闭指定的Channel以及HttpURLConnection
inChannel.close();
outChannel.close();
httpConn.disconnect();
}
}// downloadFile结束
}// FileDownloader结束
}
6.多线程编程的优势和风险
多线程编程的优势:
提高系统吞吐率:多线程使得一个进程中可以有多高并发操作。
提高响应性:多线程编程的情况下,一个慢的操作并不会导致页面卡住而影响用户的其他操作。
充分利用多核资源:目前多核处理器越来越普遍。多线程编程有助于充分利用多核资源。
最小化系统资源的使用:多线程可以共享其进程所申请的资源,以此来节约系统资源的使用。
简化程序结构:多线程可以简化程序结构
多线程编程的风险:
线程安全问题:多个线程共享数据时,如果未采用相应措施,可能产生数据一致性问题。
线程活性问题:代码编写的不当可能导致线程一直处于某个状态而毫无进展。如产生死锁会一直等待其他线程释放资源而处于阻塞状态。
上下文切换:处理器从执行一个线程到执行另一个线程的时候操作系统所做的一个动作被称为上下文切换。上下文切换增加了系统的消耗,不利于系统的吞吐率。
可靠性:多线程一方面利于可靠性,如某个线程意外终止了不会影响其他线程的工作。另一方面,线程是进程的一个组件,如果某个进程意外终止,则其中所有线程都会无法运行。但从可靠性来看,某些情况下可多进程多线程方式而非单进程多线程方式。
本文介绍了Java多线程编程的基础知识,包括进程与线程的概念,Java线程API的使用,如Thread类和Runnable接口,线程的生命周期状态,以及多线程编程的优势和风险。强调了线程安全问题和上下文切换对系统性能的影响。
3万+

被折叠的 条评论
为什么被折叠?



