一.线程的生命周期及五种基本状态

关于Java中线程的生命周期,首先看一下以下这张较为经典的图:

Java线程具有五中基本状态

新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

就绪状态(Runnable):当调用线程对象的start()方法(t.start();)。线程即进入就绪状态。处于就绪状态的线程,仅仅是说明此线程已经做好了准备。随时等待CPU调度运行。并非说运行了t.start()此线程马上就会运行;

执行状态(Running):当CPU開始调度处于就绪状态的线程时。此时线程才得以真正执行,即进入到执行状态。注:就绪状态是进入到执行状态的唯一入口,也就是说,线程要想进入执行状态执行。首先必须处于就绪状态中;

堵塞状态(Blocked):处于执行状态中的线程因为某种原因,临时放弃对CPU的使用权。停止执行。此时进入堵塞状态。直到其进入到就绪状态,才 有机会再次被CPU调用以进入到执行状态。

依据堵塞产生的原因不同。堵塞状态又能够分为三种:

1.等待堵塞:执行状态中的线程执行wait()方法。使本线程进入到等待堵塞状态。

2.同步堵塞 -- 线程在获取synchronized同步锁失败(由于锁被其他线程所占用),它会进入同步堵塞状态;

3.其它堵塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到堵塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完成时。线程又一次转入就绪状态。

【疑问】:锁的持有问题

死亡状态(Dead):线程运行完了或者因异常退出了run()方法,该线程结束生命周期。

二. Java多线程的创建及启动

Java中线程的创建常见有如三种基本形式

1.继承Thread类。重写该类的run()方法。

 1 class MyThread extends Thread {
2
3 private int i = 0;
4
5 @Override
6 public void run() {
7 for (i = 0; i < 100; i++) {
8 System.out.println(Thread.currentThread().getName() + " " + i);
9 }
10 }
11 }

 1 public class ThreadTest {
2
3 public static void main(String[] args) {
4 for (int i = 0; i < 100; i++) {
5 System.out.println(Thread.currentThread().getName() + " " + i);
6 if (i == 30) {
7 Thread myThread1 = new MyThread(); // 创建一个新的线程 myThread1 此线程进入新建状态
8 Thread myThread2 = new MyThread(); // 创建一个新的线程 myThread2 此线程进入新建状态
9 myThread1.start(); // 调用start()方法使得线程进入就绪状态
10 myThread2.start(); // 调用start()方法使得线程进入就绪状态
11 }
12 }
13 }
14 }

如上所看到的,继承Thread类,通过重写run()方法定义了一个新的线程类MyThread,当中run()方法的方法体代表了线程须要完毕的任务。称之为线程运行体。当创建此线程类对象时一个新的线程得以创建。并进入到线程新建状态。

通过调用线程对象引用的start()方法,使得该线程进入到就绪状态,此时此线程并不一定会立即得以运行,这取决于CPU调度时机。

2.实现Runnable接口,并重写该接口的run()方法。该run()方法相同是线程运行体,创建Runnable实现类的实例,并以此实例作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象。

 1 class MyRunnable implements Runnable {
2 private int i = 0;
3
4 @Override
5 public void run() {
6 for (i = 0; i < 100; i++) {
7 System.out.println(Thread.currentThread().getName() + " " + i);
8 }
9 }
10 }
 1 public class ThreadTest {
2
3 public static void main(String[] args) {
4 for (int i = 0; i < 100; i++) {
5 System.out.println(Thread.currentThread().getName() + " " + i);
6 if (i == 30) {
7 Runnable myRunnable = new MyRunnable(); // 创建一个Runnable实现类的对象
8 Thread thread1 = new Thread(myRunnable); // 将myRunnable作为Thread target创建新的线程
9 Thread thread2 = new Thread(myRunnable);
10 thread1.start(); // 调用start()方法使得线程进入就绪状态
11 thread2.start();
12 }
13 }
14 }
15 }

相信以上两种创建新线程的方式大家都非常熟悉了。那么Thread和Runnable之间究竟是什么关系呢?我们首先来看一下以下这个样例。

1 public interface Runnable {
2
3 public abstract void run();
4
5 }

我们看一下Thread类中对Runnable接口中run()方法的实现:

  @Override
public void run() {
if (target != null) {
target.run();
}
}

也就是说,当运行到Thread类中的run()方法时,会首先推断target是否存在。存在则运行target中的run()方法,也就是实现了Runnable接口并重写了run()方法的类中的run()方法。可是上述给到的列子中,因为多态的存在。根本就没有运行到Thread类中的run()方法,而是直接先运行了运行时类型即MyThread类中的run()方法。

3.使用Callable和Future接口创建线程。详细是创建Callable接口的实现类,并实现clall()方法。

并使用FutureTask类来包装Callable实现类的对象。且以此FutureTask对象作为Thread对象的target来创建线程。

看着好像有点复杂,直接来看一个样例就清晰了。

 1 public class ThreadTest {
2
3 public static void main(String[] args) {
4
5 Callable<Integer> myCallable = new MyCallable(); // 创建MyCallable对象
6 FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask来包装MyCallable对象
7
8 for (int i = 0; i < 100; i++) {
9 System.out.println(Thread.currentThread().getName() + " " + i);
10 if (i == 30) {
11 Thread thread = new Thread(ft); //FutureTask对象作为Thread对象的target创建新的线程
12 thread.start(); //线程进入到就绪状态
13 }
14 }
15
16 System.out.println("主线程for循环运行完成..");
17
18 try {
19 int sum = ft.get(); //取得新创建的新线程中的call()方法返回的结果
20 System.out.println("sum = " + sum);
21 } catch (InterruptedException e) {
22 e.printStackTrace();
23 } catch (ExecutionException e) {
24 e.printStackTrace();
25 }
26
27 }
28 }
29
30
31 class MyCallable implements Callable<Integer> {
32 private int i = 0;
33
34 // 与run()方法不同的是。call()方法具有返回值
35 @Override
36 public Integer call() {
37 int sum = 0;
38 for (; i < 100; i++) {
39 System.out.println(Thread.currentThread().getName() + " " + i);
40 sum += i;
41 }
42 return sum;
43 }
44
45 }

首先,我们发现。在实现Callable接口中。此时不再是run()方法了,而是call()方法,此call()方法作为线程运行体,同一时候还具有返回值。在创建新的线程时,是通过FutureTask来包装MyCallable对象,同一时候作为了Thread对象的target。

那么看下FutureTask类的定义:

1 public class FutureTask<V> implements RunnableFuture<V> {
2
3 //....
4
5 }
1 public interface RunnableFuture<V> extends Runnable, Future<V> {
2
3 void run();
4
5 }

于是。我们发现FutureTask类实际上是同一时候实现了Runnable和Future接口,由此才使得其具有Future和Runnable双重特性。通过Runnable特性,能够作为Thread对象的target。而Future特性,使得其能够取得新创建线程中的call()方法的返回值。

运行下此程序。我们发现sum = 4950永远都是最后输出的。

而“主线程for循环运行完成..”则非常可能是在子线程循环中间输出。由CPU的线程调度机制,我们知道,“主线程for循环运行完成..”的输出时机是没有不论什么问题的,那么为什么sum =4950会永远最后输出呢?

原因在于通过ft.get()方法获取子线程call()方法的返回值时。当子线程此方法还未运行完成,ft.get()方法会一直堵塞,直到call()方法运行完成才干取到返回值。

上述主要解说了三种常见的线程创建方式,对于线程的启动而言。都是调用线程对象的start()方法。须要特别注意的是:不能对同一线程对象两次调用start()方法

Java多线程(1) 创建的更多相关文章

  1. java多线程-概念&创建启动&中断&守护线程&优先级&线程状态(多线程编程之一)

    今天开始就来总结一下Java多线程的基础知识点,下面是本篇的主要内容(大部分知识点参考java核心技术卷1): 1.什么是线程以及多线程与进程的区别 2.多线程的创建与启动 3.中断线程和守护线程以及 ...

  2. Java多线程的创建(一)

    方法一:继承Thread类实现 1.创建一个类A,并继承Thread类 2.重写A的run()方法 3.创建A的实例对象b,即创建了线程对象 4.使用b调用start()方法:启动线程(会自动调用ru ...

  3. Java多线程的创建与简单使用

    一.线程的基本概念 什么是线程:Thread 进程内部的一个执行单元,它是程序中一个单一的顺序控制流程. 线程又被称为轻量级进程(lightweight process) 如果在一个进程中同时运行了多 ...

  4. java多线程之创建线程的4种方式及Future

    Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例.Java可以用四种方式来创建线程: 继承Thread创建线程 实现Runnable接口创建线程 实现callab ...

  5. Java多线程的创建(二)

    前言: 虽然java的API中说创建多线程的方式只有两种(There are two ways to create a new thread of execution),分别是继承Thread类创建和 ...

  6. java多线程-线程创建

    Java 线程类也是一个 object 类,它的实例都继承自 java.lang.Thread 或其子类. 可以用如下方式用 java 中创建一个线程,执行该线程可以调用该线程的 start()方法: ...

  7. Java多线程-----匿名内部类创建线程

       1.继承Thread类创建线程 package com.practise.createthread; public class AnonymousThread { public static v ...

  8. Java多线程——之一创建线程的四种方法

    1.实现Runnable接口,重载run(),无返回值 package thread; public class ThreadRunnable implements Runnable { public ...

  9. Java多线程之创建线程的三种方式比较

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6560057.html  一:继承Thread类创建线程 1:继承Thread类定义线程子类: 2:重写run( ...

随机推荐

  1. linux_jdk_mysql_tomcat

    1 linux下安装jdk的步骤: 0. 查找原有的jdk: rpm -qa | grep java 删除原有的jdk: rpm -e --nodeps java-1.7.0-openjdk-1.7. ...

  2. Tracing mysqld Using DTrace

    http://dev.mysql.com/doc/refman/5.6/en/dba-dtrace-server.html MySQL 5.6 Reference Manual -> 5 MyS ...

  3. c#分页工具类,完美实现List分页

    using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace Proje ...

  4. 多个rs485设备怎样跟上位机通讯?

    http://bbs.hcbbs.com/thread-819457-1-1.html 多个rs485设备怎样跟上位机通讯? [复制链接] |关注本帖     fdemeng 签到天数: 1228 天 ...

  5. window.open("url?param="+paramvalue) 服务端 乱码问题解决

    window.open("url?param="+paramvalue)传递参数出现乱码,在客房端显示是正常的,可是到服务端就是乱码. 1. 利用一个js在客户端转码的函数,esc ...

  6. 如何优化JAVA代码

    通过使用一些辅助性工具来找到程序中的瓶颈,然后就可以对瓶颈部分的代码进行优化.一般有两种方案:即优化代码或更改设计方法.我们一般会选择后者,因为不去调用以下代码要比调用一些优化的代码更能提高程序的性能 ...

  7. cocos编译Android版本号问题总结

    今天编译cocos2d-x项目到Android平台遇到编译不通过的问题,编译错误提示是一堆乱码. 主要原因有: 1.文件编码格式错误 或 换行符格式错误,改动方法为,在VS2012里面选择 文件-&g ...

  8. IOS tableview 横向滚动

    1. UITableView 设置 CGRect tableViewRect = CGRectMake(0.0, 0.0, 50.0, 320.0);self.tableView = [[UITabl ...

  9. Asp.Net中自以为是的Encode

    Asp.Net 引擎可能是不错,但是它把程序员想的太笨,会自以为是做很多自动的 Encode 和 Decode,以下文举例: 如果客户端我们 post 了如下的数据, 但是你实际得到的是: 也就是说, ...

  10. 完全理解Gson(1):简单入门

    GSON是Google开发的Java API,用于转换Java对象和Json对象.本文讨论并提供了使用API的简单代码示例.更多关于GSON的API可以访问:http://sites.google.c ...