Java 线程 —— 基础篇

一、操作系统中线程和进程的概念

现在的操作系统是多任务操作系统。多线程是实现多任务的一种方式。

进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。

线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如Java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。

"同时"执行是人的感觉,在线程之间实际上轮换执行。

 
二、Java中的线程

在Java中,“线程”指两件不同的事情:

1、java.lang.Thread类的一个实例;
2、线程的执行。

 

使用java.lang.Thread类或者java.lang.Runnable接口编写代码来定义、实例化和启动新线程。

一个Thread类实例只是一个对象,像Java中的任何其他对象一样,具有变量和方法,生死于堆上。

Java中,每个线程都有一个调用栈,即使不在程序中创建任何新的线程,线程也在后台运行着。

一个Java应用总是从main()方法开始运行,mian()方法运行在一个线程内,它被称为主线程。

一旦创建一个新的线程,就产生一个新的调用栈。

 

线程总体分两类:用户线程守候线程

当所有用户线程执行完毕的时候,JVM自动关闭。但是守候线程却不独立于JVM,守候线程一般是由操作系统或者用户自己创建的。

 

三、线程的生命周期及五种基本状态

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


 


上图中基本上囊括了Java中多线程各重要知识点。掌握了上图中的各知识点,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 线程的创建和启动

1、定义线程

1)、扩展java.lang.Thread类。

此类中有个run()方法,应该注意其用法:

[java] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. public void run()  

如果该线程是使用独立的Runnable运行对象构造的,则调用该Runnable对象的run方法;否则,该方法不执行任何操作并返回。

Thread的子类应该重写该方法

 

2)、实现java.lang.Runnable接口。

[java] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. void run()  

使用实现接口Runnable的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的run方法。

方法run的常规协定是,它可能执行任何所需的操作

 

2、实例化线程

1)、如果是扩展java.lang.Thread类的线程,则直接new即可

2)、如果是实现了java.lang.Runnable接口的类,则用Thread的构造方法

[java] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. Thread(Runnable target)   
  2. Thread(Runnable target, String name)   
  3. Thread(ThreadGroup group, Runnable target)   
  4. Thread(ThreadGroup group, Runnable target, String name)   
  5. Thread(ThreadGroup group, Runnable target, String name, long stackSize)   

 

3、启动线程

       在线程的Thread对象上调用start()方法,而不是run()或者别的方法。

 在调用start()方法之前:线程处于新状态中,新状态指有一个Thread对象,但还没有一个真正的线程。在调用start()方法之后:发生了一系列复杂的事情:

启动新的执行线程(具有新的调用栈);

该线程从新状态转移到可运行状态;

当该线程获得机会执行时,其目标run()方法将运行。

      注意:对Java来说,run()方法没有任何特别之处。像main()方法一样,它只是新线程知道调用的方法名称(和签名)。因此,在Runnable上或者Thread上调用run方法是合法的。但并不启动新的线程。

 

4、实例解析

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

[java] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. package cn.com.qiang.threaddemo;  
  2.   
  3. public class ThreadTest {  
  4.     public static void main(String[] args) {  
  5.     for(int i = 0; i < 10; i++){  
  6.         System.out.println(Thread.currentThread().getName()+" "+i);  
  7.         if(i == 3){  
  8.             Thread myThread1 = new MyThread();// 创建一个新的线程  myThread1  此线程进入新建状态  
  9.             Thread myThread2 = new MyThread();// 创建一个新的线程  myThread1  此线程进入新建状态  
  10.             myThread1.start();                // 调用start()方法使得线程进入就绪状态  
  11.             myThread2.start();                // 调用start()方法使得线程进入就绪状态  
  12.         }  
  13.     }  
  14. }  
  15. }  
  16.   
  17. class MyThread extends Thread{  
  18.     private int i = 0;  
  19.       
  20.     public void run(){  
  21.         for(i = 0; i < 10; i++){  
  22.             System.out.println(Thread.currentThread().getName()+" "+i);  
  23.         }  
  24.     }  
  25. }  

执行结果如下:

[java] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. main 0  
  2. main 1  
  3. main 2  
  4. main 3  
  5. Thread-0 0  
  6. Thread-0 1  
  7. Thread-0 2  
  8. Thread-0 3  
  9. main 4  
  10. main 5  
  11. main 6  
  12. main 7  
  13. main 8  
  14. main 9  
  15. Thread-0 4  
  16. Thread-0 5  
  17. Thread-0 6  
  18. Thread-1 0  
  19. Thread-1 1  
  20. Thread-1 2  
  21. Thread-1 3  
  22. Thread-1 4  
  23. Thread-1 5  
  24. Thread-1 6  
  25. Thread-1 7  
  26. Thread-1 8  
  27. Thread-1 9  
  28. Thread-0 7  
  29. Thread-0 8  
  30. Thread-0 9  

如上所示,继承Thread类,通过重写run()方法定义了一个新的线程类MyThread,其中run()方法的方法体代表了线程需要完成的任务,称之为线程执行体。当创建此线程类对象时一个新的线程得以创建,并进入到线程新建状态。通过调用线程对象引用的start()方法,使得该线程进入到就绪状态,此时此线程并不一定会马上得以执行,这取决于CPU调度时机。

 

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

[java] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. package cn.com.qiang.threaddemo;  
  2.   
  3. public class TreadTest2 {  
  4.       
  5.     public static void main(String[] args) {  
  6.         for(int i = 0; i < 10; i++){  
  7.             System.out.println(Thread.currentThread().getName()+" "+i);  
  8.             if(i == 3){  
  9.                 Runnable myRunnable = new MyRunnable();//创建一个Runnable实现类的对象  
  10.                 Thread thread1 = new Thread(myRunnable);//将myRunnable作为Thread target创建新的线程  
  11.                 Thread thread2 = new Thread(myRunnable);  
  12.                 thread1.start();//调用start()方法使得线程进入就绪状态  
  13.                 thread2.start();  
  14.             }  
  15.         }  
  16.     }  
  17. }  
  18.   
  19. class MyRunnable implements Runnable{  
  20.     private int i = 0;  
  21.       
  22.     public void run(){  
  23.         for(i = 0; i < 10; i++){  
  24.             System.out.println(Thread.currentThread().getName()+" "+i);  
  25.         }  
  26.     }  
  27. }  

执行结果如下:

[java] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. main 0  
  2. main 1  
  3. main 2  
  4. main 3  
  5. main 4  
  6. main 5  
  7. Thread-0 0  
  8. Thread-0 1  
  9. Thread-1 0  
  10. main 6  
  11. main 7  
  12. main 8  
  13. main 9  
  14. Thread-0 1  
  15. Thread-0 3  
  16. Thread-1 3  
  17. Thread-1 4  
  18. Thread-1 5  
  19. Thread-1 7  
  20. Thread-1 8  
  21. Thread-1 9  
  22. Thread-0 6  


3)二者之间的关系是什么样的呢,下面看个例子:

[java] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. package cn.com.qiang.threaddemo;  
  2.   
  3. public class ThreadTest3 {  
  4.     public static void main(String[] args) {  
  5.         for(int i = 0; i < 10; i++){  
  6.             System.out.println(Thread.currentThread().getName()+" "+i);  
  7.             if(i == 3){  
  8.                 Runnable myRunnable2 = new MyRunnable2();  
  9.                 Thread thread = new MyThread2(myRunnable2);  
  10.                 thread.start();  
  11.                   
  12.             }  
  13.         }  
  14.     }  
  15. }  
  16.   
  17. class MyRunnable2 implements Runnable{  
  18.     private int i = 0;  
  19.       
  20.     public void run(){  
  21.         System.out.println("in MyRunnable run!");  
  22.         for(i = 0; i < 10; i++){  
  23.             System.out.println(Thread.currentThread().getName()+" "+i);  
  24.         }  
  25.     }  
  26. }  
  27.   
  28. class MyThread2 extends Thread{  
  29.       
  30.     private int i = 0;  
  31.       
  32.     public MyThread2(Runnable MyRunnable2) {  
  33.         super(MyRunnable2);  
  34.     }  
  35.       
  36.     public void run(){  
  37.         System.out.println("in MyThread run");  
  38.         for(i = 0; i< 10; i++){  
  39.             System.out.println(Thread.currentThread().getName()+" "+i);  
  40.         }  
  41.     }  
  42. }  

执行结果如下:

[java] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. main 0  
  2. main 1  
  3. main 2  
  4. main 3  
  5. main 4  
  6. in MyThread run  
  7. main 5  
  8. Thread-0 0  
  9. main 6  
  10. Thread-0 1  
  11. Thread-0 2  
  12. Thread-0 3  
  13. Thread-0 4  
  14. Thread-0 5  
  15. Thread-0 6  
  16. Thread-0 7  
  17. Thread-0 8  
  18. Thread-0 9  
  19. main 7  
  20. main 8  
  21. main 9  

同样的,与实现Runnable接口创建线程方式相似,不同的地方在于

[java] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. Thread thread = new MyThread(myRunnable);  

那么这种方式可以顺利创建出一个新的线程么?答案是肯定的。至于此时的线程执行体到底是MyRunnable接口中的run()方法还是MyThread类中的run()方法呢?通过输出我们知道线程执行体是MyThread类中的run()方法。其实原因很简单,因为Thread类本身也是实现了Runnable接口,而run()方法最先是在Runnable接口中定义的方法。

[java] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. public interface Runnable {  
  2.       
  3.      public abstract void run();     
  4. }  

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

[java] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. public void run() {  
  3.     if (target != null) {  
  4.         target.run();  
  5.     }  
  6. }  

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

 

总结:两种线程创建方式的比较

1、使用Runnable接口

1)可以将CPU,代码和数据分开,形成清晰的模型;

2)还可以从其他类继承;

3)保持程序风格的一致性;

 

2、直接继承 Thread 类

1)不能够再从其他类继承;

2)编写简单,可以直接操纵线程,无需使用Thread.currentThread(). 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/401980.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Java并发性和多线程】线程安全及不可变性

2019独角兽企业重金招聘Python工程师标准>>> 本文为转载学习 原文链接&#xff1a;http://tutorials.jenkov.com/java-concurrency/thread-safety-and-immutability.html 译文链接&#xff1a;http://ifeve.com/thread-safety-and-immutability/ 当多个线程同时访问…

Java 高级—— IO 基础

一、File 类 先看一下File 类的定义 [java] view plaincopy public class File extends Object implements Serizliable Comparable<File> 从定义看&#xff0c;File类是Object的直接子类&#xff0c;同时它继承了Comparable接口可以进行数组的排序。 File类的操作包括…

安装 SharePoint 2013 Foundation

一、Foundation版本的区别Foundation版本的区别见附件&#xff08;英文&#xff09;。官网下载地址 http://www.microsoft.com/zh-cn/download/details.aspx?id35488二、安装必备软件三、独立安装模式1. 启动安装向导2. 接受软件许可条款3. 选择服务器类型和数据位置4. 结束安…

Java 异常处理机制

异常处理是程序设计中一个非常重要的方面&#xff0c;也是程序设计的一大难点&#xff0c;从C开始&#xff0c;你也许已经知道如何用if...else...来控制异常了&#xff0c;也许是自发的&#xff0c;然而这种控制异常痛苦&#xff0c;同一个异常或者错误如果多个地方出现&#x…

架构师未来性的基础:简单性

作者&#xff1a;高焕堂&#xff0c;misoo.twqq.com 首页&#xff1a;Backee e架构师未来性的基础&#xff1a;简单性 Apple公司创始人乔布斯(Steve Jobs)曾说到&#xff1a;“简单比复杂更难&#xff0c;你必须努力让你的想法变得清晰…

Android 基础—— 对Context的理解与使用技巧

一、Context 基础概念 1、什么是Context 1) Context是一个抽象类&#xff0c;其通用实现在ContextImpl类中。 2) Context&#xff1a;是一个访问application环境全局信息的接口&#xff0c;通过它可以访问application的资源和相关的类&#xff0c;其主要功能如下&a…

Android 四大组件 —— 广播(广播机制解析)

在网络通信中&#xff0c;一个IP网络范围中最大的IP 地址是被保留作为广播地址来使用的。比如某个网络的IP 范围是192.168.0.XXX&#xff0c;子网掩码是255.255.255.0&#xff0c;那么这个网络的广播地址就是192.168.0.255。广播数据包会被发送到同一网络上的所有端口&#xff…

Android 基础 —— 活动的生存周期

一、返回栈 Android 中的活动是可以层叠的。我们每启动一个新的活动&#xff0c;就会覆盖在原活动之上&#xff0c;然后点击Back 键会销毁最上面的活动&#xff0c;下面的一个活动就会重新显示出来。 其实Android 是使用任务&#xff08;Task&#xff09;来管理活动的&#xff…

产品经理做市场调研和数据分析的方法

产品经理&#xff0c;你对用户的需求了解多少呢&#xff1f;你知道用户想要什么样的产品吗&#xff1f;你想知道用户将会如何看待你的产品吗&#xff1f;你想知道你设计的产品在用户中的口碑如何吗&#xff1f; 是 的。每一个产品经理都希望在产品开始立项设计前&#xff0c;得…

Android 基础 —— 活动的启动模式

活动的启动模式来说应该是个全新的概念&#xff0c;在实际项目中我们应该根据特定的需求为每个活动指定恰当的启动模式。启动模式一共有四种&#xff0c;分别是standard、singleTop、singleTask 和singleInstance &#xff0c; 可以在AndroidManifest.xml 中通过给<activity…

Android 四大组件 —— 服务

一、服务是什么 服务&#xff08;Service&#xff09;是Android 中实现程序后台运行的解决方案&#xff0c;它非常适合用于去执行那些不需要和用户交互而且还要求长期运行的任务。服务的运行不依赖于任何用户界面&#xff0c;即使当程序被切换到后台&#xff0c;或者用户打开了…

Highlighting System

Highlighting System 法线贴图漫反射着色器 Unity论坛&#xff1a;http://forum.unity3d.com/threads/143043-Highlighting-System-Released 需要条件 Requires Unity 3.5.6 or higher.This package requires Unity Pro, as it uses post-processing!Now supports Unity iOS Pr…

Android 基础 —— 模拟实现拨打电话功能

前面已经学习活动的显示跳转及隐式跳转&#xff0c;也学习 TextView 及 Button 两个控件的使用&#xff0c;下面我们来学习 EditText的使用&#xff1a; EditText 是程序用于和用户进行交互的另一个重要控件&#xff0c;它允许用户在控件里输入和编辑内容&#xff0c;并可以在程…

Android 四大组件 —— 活动(活动的隐式跳转)

上一篇我们讨论了活动的显示跳转&#xff0c;现在来学习活动的隐式跳转 相比于显式Intent&#xff0c;隐式Intent 则含蓄了许多&#xff0c;它并不明确指出我们想要启动哪一个活动&#xff0c;而是指定了一系列更为抽象的action 和category 等信息&#xff0c;然后交由系统去分…

ALAssetsLibrary-代码操作iOS相册资源

2019独角兽企业重金招聘Python工程师标准>>> 在iOS中&#xff0c;我们调用摄像头和选择相册中的资源&#xff0c;我们可以使用&#xff1a;UIImagePickerController类来完成。 当然&#xff0c;我们也可以不使用UI的形式来访问iOS设备的相册资源。 那就是使用&#…

Android 四大组件 —— 活动(使用Intent 实现活动的显示跳转)

一、什么是Intent&#xff1f;Intent的中文意思是目的。在Android中也是“目的”的意思。就是我们要去哪里&#xff0c;从这个activity要前往另一个Activity就需要用到Intent。 下面是 Intent 两个最基本的函数&#xff1a; 1、定义一个Intent [java] view plaincopy Intent in…

Android 项目在Eclipse中的目录结构

Android工程目录 如果使用Eclipse插件ADT开发应用程序&#xff0c;必须要熟悉工程的目录结构&#xff0c;清楚各个目录下面放置的是什么东西。Android工程主要的目录有&#xff1a;src、bin、gen、res等。 不同的Android平台目录结构是不同的&#xff0c;下面以Android 2.3.…

Exynos4412 Uboot 编译工具 —— 交叉工具链 arm-linux-gcc 的安装

一、什么是交叉编译&#xff1f; 在开发主机运行编译器编译内核、应用程序。内核和程序在目标机上运行&#xff0c;这个编译过程被称为交叉编译。编译器运行在开发主机&#xff08;通常是X86体系的PC机&#xff09;上&#xff0c;编译出的的代码是目标机体系结构的&#xff0c;…

Linux学习之CentOS(一)--CentOS6.4环境搭建

一、前言作为一个想从事j2ee后台开发的程序猿&#xff0c;linux系统怎能不学呢&#xff1f;所以&#xff0c;这几天自己准备学习一下linux操作系统。废话不多说&#xff0c;直奔主题。要学linux开发&#xff0c;首先得要安装linux系统吧&#xff0c;这里我在选择之前也挺迷茫&a…

内联函数 —— C 中关键字 inline 用法解析

一、什么是内联函数 在C语言中&#xff0c;如果一些函数被频繁调用&#xff0c;不断地有函数入栈&#xff0c;即函数栈&#xff0c;会造成栈空间或栈内存的大量消耗。 为了解决这个问题&#xff0c;特别的引入了inline修饰符&#xff0c;表示为内联函数。 栈空间就是指放置程式…