java的多线程用法编程总结_java

一、进程与线程

1、进程是什么?

狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。

广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

2、线程是什么?

线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

3、进程和线程的区别?

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。

进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。

线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

简言之,线程与进程的区别就是:

(1)一个程序至少有一个进程,一个进程至少有一个线程;
(2) 线程的划分尺度小于进程,使得多线程程序的并发性高。
(3)进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
(4)线程在执行过程中与进程是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
(5)从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。

这就是进程和线程的重要区别。

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

Java线程具有五种基本状态:

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

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

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

(4)阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

①等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

②同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

③其他阻塞 : 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

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

三、Java多线程的实现

在Java中,如果要实现多线程的程序,那么必须依靠一个线程的主体类(好比主类的概念一样,表示一个线程的主类),但是这个线程的主体类在定义的时候需要有一些特殊的要求,这个类可以继承Thread类或实现Runnable接口来完成定义。

1、继承Thread类实现多线程

java.lang.Thread是一个负责线程操作的类,任何的类继承了Thread类就可以成为一个线程的主类。既然是主类,必须有它的使用方法,而线程启动的主方法需要覆写Thread类中的run()方法才可以。

定义一个线程的主体类:

class MyThread extends Thread { // 线程的主体类
 private String title;

 public MyThread(String title) {
 this.title = title;
 }

 @Override
 public void run() { // 线程的主方法
 for (int x = 0; x < 10; x++) {
  System.out.println(this.title + "运行,x = " + x);
 }
 }
}

现在已经有了线程类,并且里面也存在了相应的操作方法,那么就应该产生对象并调用里面的方法,于是编写出了下的程序:

public class TestDemo {
 public static void main(String[] args) {
 MyThread mt1 = new MyThread("线程A");
 MyThread mt2 = new MyThread("线程B");
 MyThread mt3 = new MyThread("线程C");
 mt1.run();
 mt2.run();
 mt3.run();
 }

 运行结果:

线程A运行,x = 0
线程A运行,x = 1
线程A运行,x = 2
线程A运行,x = 3
线程A运行,x = 4
线程A运行,x = 5
线程A运行,x = 6
线程A运行,x = 7
线程A运行,x = 8
线程A运行,x = 9
线程B运行,x = 0
线程B运行,x = 1
线程B运行,x = 2
线程B运行,x = 3
线程B运行,x = 4
线程B运行,x = 5
线程B运行,x = 6
线程B运行,x = 7
线程B运行,x = 8
线程B运行,x = 9
线程C运行,x = 0
线程C运行,x = 1
线程C运行,x = 2
线程C运行,x = 3
线程C运行,x = 4
线程C运行,x = 5
线程C运行,x = 6
线程C运行,x = 7
线程C运行,x = 8
线程C运行,x = 9

我们发现:以上的操作并没有真正的启动多线程,因为多个线程彼此之间的执行一定是交替的方式运行,而此时是顺序执行,每一个对象的代码执行完之后才向下继续执行。

如果要想在程序之中真正的启动多线程,必须依靠Thread类的一个方法:public void start(),表示真正启动多线程,调用此方法后会间接调用run()方法:

public class TestDemo {
 public static void main(String[] args) {
 MyThread mt1 = new MyThread("线程A");
 MyThread mt2 = new MyThread("线程B");
 MyThread mt3 = new MyThread("线程C");
 mt1.start();
 mt2.start();
 mt3.start();
 }

}

 运行结果:

线程C运行,x = 0
线程A运行,x = 0
线程B运行,x = 0
线程A运行,x = 1
线程C运行,x = 1
线程A运行,x = 2
线程B运行,x = 1
线程A运行,x = 3
线程A运行,x = 4
线程A运行,x = 5
线程C运行,x = 2
线程C运行,x = 3
线程C运行,x = 4
线程C运行,x = 5
线程C运行,x = 6
线程C运行,x = 7
线程C运行,x = 8
线程C运行,x = 9
线程A运行,x = 6
线程A运行,x = 7
线程A运行,x = 8
线程A运行,x = 9
线程B运行,x = 2
线程B运行,x = 3
线程B运行,x = 4
线程B运行,x = 5
线程B运行,x = 6
线程B运行,x = 7
线程B运行,x = 8
线程B运行,x = 9

此时可以发现:多个线程之间彼此交替执行,但每次的执行结果是不一样的。通过以上的代码就可以得出结论:要想启动线程必须依靠Thread类的start()方法执行,线程启动之后会默认调用了run()方法。

在调用start()方法之后,发生了一系列复杂的事情:
(1)启动新的执行线程(具有新的调用栈);
(2)该线程从新状态转移到可运行状态;
(3)当该线程获得机会执行时,其目标run()方法将运行。
注意:对Java来说,run()方法没有任何特别之处。像main()方法一样,它只是新线程知道调用的方法名称(和签名)。因此,在Runnable上或者Thread上调用run方法是合法的,但并不启动新的线程。

说明:为什么线程启动的时候必须调用start()而不是直接调用run()?

我们发现,在调用了start()之后,实际上它执行的还是覆写后的run()方法,那为什么不直接调用run()方法呢?为了解释此问题,下面打开Thread类的源代码,观察一下start()方法的定义:

 public synchronized void start() {
 if (threadStatus != 0)
  throw new IllegalThreadStateException();
 group.add(this);
 boolean started = false;
 try {
  start0();
  started = true;
 } finally {
  try {
  if (!started) {
   group.threadStartFailed(this);
  }
  } catch (Throwable ignore) {
  }
 }
 }
 private native void start0();

 打开此方法的源代码首先可以发现:方法会抛出一个“IllegalThreadStateException”异常。一般来讲,如果一个方法中使用了throw抛出一个异常对象,那么这个异常应该使用try…catch捕获,或者是方法的声明上使用throws抛出,但是这块都没有,为什么呢?因为这个异常类是属于运行时异常(RuntimeException)的子类:

java.lang.Object
   |- java.lang.Throwable
     |- java.lang.Exception
       |- java.lang.RuntimeException
         |- java.lang.IllegalArgumentException
           |- java.lang.IllegalThreadStateException

当一个线程对象被重复启动之后会抛出此异常,即:一个线程对象只能启动一次。

在start()方法之中有一个最为关键的部分就是start0()方法,而且这个方法上使用了一个native关键字的定义。

native关键字指的是Java本地接口调用(Java Native Interface),即:是使用Java调用本机操作系统的函数功能完成一些特殊的操作,而这样的代码开发在Java之中几乎很少出现,因为Java的最大特点是可移植性,如果一个程序只能在固定的操作系统上使用,那么可移植性就将彻底的丧失,所以,此操作一般不用。

多线程的实现一定需要操作系统的支持,那么以上的start0()方法实际上就和抽象方法很类似没有方法体,而这个方法体交给JVM去实现,即:在windows下的JVM可能使用A方法实现了start0(),而在Linux下的JVM可能使用了B方法实现了start0(),但是在调用的时候并不会去关心具体是何方式实现了start0()方法,只会关心最终的操作结果,交给JVM去匹配了不同的操作系统。

所以在多线程操作之中,使用start()方法启动多线程的操作是需要进行操作系统函数调用的。

2、实现Runnable接口实现多线程

使用Thread类的确是可以方便的进行多线程的实现,但是这种方式最大的缺点就是单继承的问题。为此,在java之中也可以利用Runnable接口来实现多线程。这个接口的定义如下:

public interface Runnable {
 public void run();
}

 通过Runnable接口实现多线程:

class MyThread implements Runnable { // 线程的主体类
 private String title;

 public MyThread(String title) {
 this.title = title;
 }

 @Override
 public void run() { // 线程的主方法
 for (int x = 0; x < 10; x++) {
  System.out.println(this.title + "运行,x = " + x);
 }
 }
}

 这和之前继承Thread类的方式区别不大,但是有一个优点就是避免了单继承局限。

不过问题来了。之前说过,如果要启动多线程,需要依靠Thread类的start()方法完成,之前继承Thread类的时候可以将此方法直接继承过来使用,但现在实现的是Runable接口,没有这个方法可以继承了,怎么办?

要解决这个问题,还是需要依靠Thread类完成。在Thread类中定义了一个构造方法,接收Runnable接口对象:

public Thread(Runnable target);

利用Thread类启动多线程:

public class TestDemo {
 public static void main(String[] args) throws Exception {
 MyThread mt1 = new MyThread("线程A");
 MyThread mt2 = new MyThread("线程B");
 MyThread mt3 = new MyThread("线程C");
 new Thread(mt1).start();
 new Thread(mt2).start();
 new Thread(mt3).start();
 }
}

运行结果:

线程A运行,x = 0
线程B运行,x = 0
线程B运行,x = 1
线程C运行,x = 0
线程B运行,x = 2
线程A运行,x = 1
线程B运行,x = 3
线程C运行,x = 1
线程C运行,x = 2
线程B运行,x = 4
线程B运行,x = 5
线程A运行,x = 2
线程A运行,x = 3
线程A运行,x = 4
线程A运行,x = 5
线程A运行,x = 6
线程A运行,x = 7
线程A运行,x = 8
线程A运行,x = 9
线程B运行,x = 6
线程B运行,x = 7
线程B运行,x = 8
线程B运行,x = 9
线程C运行,x = 3
线程C运行,x = 4
线程C运行,x = 5
线程C运行,x = 6
线程C运行,x = 7
线程C运行,x = 8
线程C运行,x = 9

此时,不但实现了多线程的启动,而且没有了单继承局限。

3、实现多线程的第三种方法:.使用Callable接口实现多线程

使用Runnable接口实现的多线程可以避免单继承局限,但是有一个问题,Runnable接口里面的run()方法不能返回操作结果。为了解决这个问题,提供了一个新的接口:Callable接口(java.util.concurrent.Callable)。

public interface Callable<V>{
 public V call() throws Exception;
}

执行完Callable接口中的call()方法会返回一个结果,这个返回结果的类型由Callable接口上的泛型决定。

实现Callable接口来实现多线程的具体操作是:
创建Callable接口的实现类,并实现clall()方法;然后使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程。

定义一个线程主体类:

import java.util.concurrent.Callable;

class MyThread implements Callable<String>{

 private int ticket = 10;
 @Override
 public String call() throws Exception {
 for(int i = 0 ; i < 20 ; i++){
  if(this.ticket > 0){
  System.out.println("卖票,剩余票数为"+ this.ticket --);
  }
 }
 return "票已卖光";
 }

}

Thread类没有直接支持Callable接口。而在JDK1.5之后,提供了一个类:

java.util.concurrent.FutureTask<V>

这个类主要负责Callable接口对象操作。其定义结构如下:

public class FutureTask<V>
extends Object
implements RunnableFurture<V>

而RunnableFurture这个接口又有如下定义:

public interface RunnableFurture<V>
extends Runnable,Future<V>

在FutureTask 类里面定义有如下构造方法:

public FutureTask(Callable<V> callable)

现在,终于可以通过FutureTask类来接收Callable接口对象了,接收的目的是为了取得call()方法的返回结果。

从上面分析我们可以发现:
FutureTask类可以接收Callable接口对象,而FutureTask类实现了RunnableFurture接口,RunnableFurture接口又继承了Runnable接口。

于是,我们可以这样来启动多线程:

public class TestDemo {
 public static void main(String[] args) throws Exception {
 MyThread mt1 = new MyThread();
 MyThread mt2 = new MyThread();

 FutureTask<String> task1 = new FutureTask<String>(mt1);//取得call()方法返回结果
 FutureTask<String> task2 = new FutureTask<String>(mt2);//取得call()方法返回结果

 //FutureTask是Runnable接口的子类,可以使用Thread类的构造来接收task对象
 new Thread(task1).start();
 new Thread(task2).start();

 //多线程执行完毕后,可以使用FutureTask的父接口Future中的get()方法取得执行结果
 System.out.println("线程1的返回结果:"+task1.get());
 System.out.println("线程2的返回结果:"+task2.get());
 }
}

运行结果:

卖票,剩余票数为10
卖票,剩余票数为10
卖票,剩余票数为9
卖票,剩余票数为8
卖票,剩余票数为7
卖票,剩余票数为9
卖票,剩余票数为6
卖票,剩余票数为8
卖票,剩余票数为5
卖票,剩余票数为7
卖票,剩余票数为4
卖票,剩余票数为6
卖票,剩余票数为3
卖票,剩余票数为5
卖票,剩余票数为2
卖票,剩余票数为4
卖票,剩余票数为1
卖票,剩余票数为3
卖票,剩余票数为2
卖票,剩余票数为1
线程1的返回结果:票已卖光
线程2的返回结果:票已卖光

小结:

上述讲解了三种实现多线程的方式,对于线程的启动而言,都是调用线程对象的start()方法,需要特别注意的是:不能对同一线程对象两次调用start()方法。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索多线程编程
java多线程
java多线程编程实例、多线程编程 java、java多线程编程题目、java多线程socket编程、java中多线程编程案例,以便于您获取更多的相关知识。

时间: 2024-10-02 02:06:48

java的多线程用法编程总结_java的相关文章

Java RandomAccessFile的用法详解_java

RandomAccessFile RandomAccessFile是用来访问那些保存数据记录的文件的,你就可以用seek( )方法来访问记录,并进行读写了.这些记录的大小不必相同:但是其大小和位置必须是可知的.但是该类仅限于操作文件. RandomAccessFile不属于InputStream和OutputStream类系的.实际上,除了实现DataInput和 DataOutput接口之外(DataInputStream和DataOutputStream也实现了这两个接口),它和这两个类系毫

Java中线程休眠编程实例_java

import java.awt.*; import java.util.*; import javax.swing.*; public class SleepMethodTest extends JFrame { /** * */ private static final long serialVersionUID = 1L; private Thread t; // 定义颜色数组 private static Color[] color = { Color.BLACK, Color.BLUE,

java 实现多线程的方法总结_java

java 实现多线程的三种方法 在java中,有三种方法可以实现多线程.第一种方法:继承Thread类,重写run函数.第二种方法:实现Runnable接口,重写run函数.第三种方法:实现Callable接口,重写call函数.本文章将通过实例讲解这三种方法如何实现多线程.需要的可以参考一下.  (1)继承Thread类,重写run函数. class xx extends Thread{ public void run(){ Thread.sleep(1000) //线程休眠1000毫秒,sl

Java BigDecimal类用法详解_java

1.引言 借用<Effactive Java>这本书中的话,float和double类型的主要设计目标是为了科学计算和工程计算.他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的.然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合.但是,商业计算往往要求结果精确,这时候BigDecimal就派上大用场啦. 2.BigDecimal简介 BigDecimal 由任意精度的整数非标度值 和32 位的整数标度 (scale) 组成.如果为零或正数

java equals函数用法详解_java

equals函数在基类object中已经定义,源码如下 复制代码 代码如下: public boolean equals(Object obj) { return (this == obj); } 从源码中可以看出默认的equals()方法与"=="是一致的,都是比较的对象的引用,而非对象值(这里与我们常识中equals()用于对象的比较是相饽的,原因是java中的大多数类都重写了equals()方法,下面已String类举例,String类equals()方法源码如下:) [java

Java Thread多线程详解及用法解析_java

最全面的java多线程用法解析,如果你对Java的多线程机制并没有深入的研究,那么本文可以帮助你更透彻地理解Java多线程的原理以及使用方法. 1.创建线程 在Java中创建线程有两种方法:使用Thread类和使用Runnable接口.在使用Runnable接口时需要建立一个Thread实例.因此,无论是通过Thread类还是Runnable接口建立线程,都必须建立Thread类或它的子类的实例.Thread构造函数: public Thread( ); public Thread(Runnab

Java 多线程学习详细总结_java

目录(?)[-] 一扩展javalangThread类 二实现javalangRunnable接口 三Thread和Runnable的区别 四线程状态转换 五线程调度 六常用函数说明 使用方式 为什么要用join方法 七常见线程名词解释 八线程同步 九线程数据传递      本文主要讲了java中多线程的使用方法.线程同步.线程数据传递.线程状态及相应的一些线程函数用法.概述等. 首先讲一下进程和线程的区别: 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个

Java 中Map 的用法详解_java

Map简介 将键映射到值的对象.一个映射不能包含重复的键:每个键最多只能映射到一个值.此接口取代 Dictionary 类,后者完全是一个抽象类,而不是一个接口. Map 接口提供三种collection 视图,允许以键集.值集或键-值映射关系集的形式查看某个映射的内容.映射顺序 定义为迭代器在映射的 collection 视图上返回其元素的顺序.某些映射实现可明确保证其顺序,如 TreeMap 类:另一些映射实现则不保证顺序,如HashMap 类. 注:将可变对象用作映射键时必须格外小心.当对

Java中线程用法总结_java

本文实例总结了Java中线程用法.分享给大家供大家参考.具体分析如下: 1.线程是基本调度单元.共享进程的资源,如内存和文件句柄.但有自己的pc(程序计数器),stack(线程栈)及本地变量 2.线程的优势: a) 充分利用多处理器 b) 可以简化模型.特定任务给特定线程.如servlets及rmi等框架. c) 对异步事件的简单处理.如socket,nio使用更复杂.而现在的操作系统支持更大数量的线程. d) 界面的更佳响应 3.内部锁:synchronized块.互斥.可重入(reentra