博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
多线程上
阅读量:6028 次
发布时间:2019-06-20

本文共 6802 字,大约阅读时间需要 22 分钟。

 单线程的程序只有一个顺序流;而多线程的程序则可以包括多个顺序执行流,并且多个顺序流之间互不干扰。就像单线程程序如同只雇佣了一个服务员的餐厅,他只有做完一件事情后才可以做下面一件事情;而多线程程序则是雇佣了多名服务员的餐厅,他们可以同时进行着多件事情。

  JAVA多线程编程的相关知识:创建、启动线程、控制线程、以及多线程的同步操作。

1.概述:

 进程是指正在运行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或叫一个执行单元。

 线程是指进程中能够独立执行的控制单元。线程控制着进程的执行。一个进程可以同时运行多个不同的线程。 

 两者的区别:

  一个程序运行后至少有一个进程,一个进程里可以包含一个或多个线程。

  每个进程都需要操作系统为其分配独立的内存地址空间,而同一进程中的所有线程在同一块地址空间中工作。这些线程可以共享进程的状态和资源。 

2.创建和启动线程:

 创建线程有两种方式:

 1、继承 java.lang.Thread 类。

 2、实现Runnable 接口。

 

  2.1 继承Thread 类创建线程类:

  步骤如下 :

    1、定义Thread 类的子类,并重写该线程的run() 方法。

  2、创建Thread 类的子类的实例,即创建线程对象。

  3、调用线程的start方法来启动该线程。 

1 //定义Thread 类的子类 2 class ThreadText extends Thread {  3 //    重写run()方法 4     public void run() 5     { 6         for (int a = 0 ; a <10 ; a ++) 7         { 8             System.out.println( currentThread().getName() + ":" + a);  9         }10     }11 }12 public class StratThread {13     14     public static void main(String args[])15     {16 //        创建Thread 类的子类的实例17         ThreadText t = new ThreadText() ;18 //        调用线程的start()方法来启动该线程19         t.start() ; 20         21 //         主线程调用用户线程的对象run()方法。        22         t.run() ; 23     }24 }

  为什么要重写Thread 类的run() 方法?

  Thread 类定义了一个功能,就是用于存储线程要运行的代码,该存储功能就是 run() 方法。即 该run() 方法的方法体就是代表了线程需要完成的任务。所以,我们也把run() 方法称为线程执行体。

 为什么要调用 start() 方法来启动线程,而不是run() 方法?

 因为调用start() 方法来启动线程,系统会把run() 方法当成线程执行体来处理,而如果直接调用线程对象的run() 方法,则系统会把线程对象当成一个普通的对象,而run() 方法也是一个普通方法,而不是线程执行体。

 在上面的代码中第19行和第22行分别调用了start() 和 run() 方法。通过运行的结果如下: 

main:0Thread-0:0main:1Thread-0:1main:2Thread-0:2main:3Thread-0:3main:4Thread-0:4main:5Thread-0:5main:6Thread-0:6main:7Thread-0:7Thread-0:8Thread-0:9main:8main:9

  通过运行结果可以看到两个线程交替运行:t 和 main 线程(当运行JAVA程序时,JVM 首先会创建并启动主线程,主线程从main() 开始运行)。所以,当调用 Thread 类的子类调用start() 方法是启动该线程;而调用run() 方法时则只是让主线程运行其run() 方法中的代码,并没有启动新的线程。

  注意:局部变量在每一个线程中都是独立的一份。

  在上面程序第8行用还用到了Thread 类的两个方法:

  > static Thread currentThread () :  该方法是Thread 类的静态方法,该方法返回当前正在执行的线程对象。

  > String getName() : 该方法是Thread 的实例方法,该方法返回调用该方法的线程的名字。

  2.2 实现Runnable 接口创建线程类:

    1、定义 Runnable 接口的实现类,并重写该接口的run方法。

  2、创建 Runnable 实现类的实例,并以此实例作为Thread 的target 来创建Thread 对象,该Thread 对象才是真正的线程对象。

  3、调用线程对象的 start() 方法来启动该线程。

  

1 //定义Runnable 接口的实现类 2 class RunnableText implements Runnable 3 { 4 //    重写接口的run方法。 5     public void run() 6     { 7         for (int a = 0 ; a <10 ; a ++) 8         { 9             System.out.println( Thread.currentThread().getName() + ":" + a); 10         }11     }12 }13 14 public class ThreadTextRunnable  { 15 16     public static void main(String args[])17     {18 //        创建Runnable 实现类的实例19         RunnableText t = new RunnableText() ; 20         21 //        通过new Thread(Runnable target ) 创建新线程22         Thread thread = new Thread(t) ; 23         Thread thread1 = new Thread(t) ;24 25 //        启动线程。26         thread.start() ; 27         thread1.start() ; 28     }29 }

  为什么要将Runnable 实现类的实例传递给Thread 的构造函数?

  因为,自定的run() 方法所属是 Runnable 接口的实现类的实例。所以,要让线程去指定对象的run() 方法,就必须明确该 run() 方法所属的对象。

  

 2.3 两种方式的区别:

    2.3.1 采用实现 Runnable 接口方式的多线程

      1、线程类只是实现了 Runnable 接口。所以,还可以继承其他的类。

      2、在这种方式下,可以多个线程共享同一个target 对象,所以适合多个相同线程处理同一分资源的情况。

    2.3.2 采用继承 Thread 类方式的多线程

      1、因为线程继承了 Thread 类,所以不能继承其他父类。

3.线程的运行状态

  当线程被创建并启动后,并不是已启动就进入执行状态,也不是一直处于执行状态。在线程的生命周期中要经历如下集中状态:新建、就绪、运行、阻塞、和死亡五种状态。

  3.1 新建(New)和就绪(Runnable) 状态:

  当程序使用 new 关键字创建一个线程之后,该线程就处于新建状态,这时候它仅仅由JVM为其分配了内存。

  当线程对象调用 start() 方法之后,该线程就处于就绪状态。这个状态的线程处于有运行资格,却没有运行权利。如何有运行权利则取决于JVM里线程调度器。

  3.2 就绪(Runnable)、运行(Running)和阻塞(Blocked)状态:

  如果就绪状态获得了运行权利,则开始执行 run() 方法 的线程执行体,则该线程处于运行状态。

  当一条线程开始运行时,他不可能一直处于运行状态(除非它的线程执行体足够短,瞬间就执行结束了)。所以当其他的线程抢到CPU的执行权利时,运行状态则重新进入就绪状态。但是当运行状态的线程发生如下情况时,则会进入阻塞状态(放弃所占用的资源,即没有执行资格):

  1、线程调用sleep 方法。当sleep(time)  的时间到了 ,线程又会进入就绪状态。

  2、线程试图获得一个同步监视器,但该同步监视器正在被其他线程所持有。

  3、线程在等待某个通知(notify、notifyAll),即被wait 挂起。

    3.3 线程死亡(Dead)

  线程结束后就处于死亡状态:

  1、run() 方法执行完成,线程正常结束。

  2、线程异常。

  3、直接调用该线程的stop() 方法来结束该线程。

 

  注意:不要对处于死亡状态的线程调用 start() 方法,程序只能对新建状态的线程调用 start() 方法。同时对新建状态的线程两次调用start() 方法也是错误的。

 

4.控制线程

  4.1 join 线程

  当A 线程执行到了 B 线程的join() 方法时,A 线程就会等待,等 B 线程执行完,A 才会执行。即A 线程 等待B 线程终止。

  

1 // 定义Runnable 接口的实现类 2 public class JoinText implements Runnable {  3 //    重写run() 方法 4     public void run() 5     { 6         for ( int i = 0; i <= 10  ; ++ i) 7             System.out.println(Thread.currentThread().getName()+ "..." + i); 8     } 9     public static void main(String args[]) throws InterruptedException10     {11 //        创建Runnable 接口实现类的实例12         JoinText t = new JoinText()  ;13 //        通过 Thread(Runnable target) 创建新线程14         Thread thread = new Thread(t)  ;15         Thread thread1 = new Thread(t) ; 16 //        启动线程17         thread.start() ; 18 //        只有等thread 线程执行结束,main 线程才会向下执行;19         thread.join() ;20         System.out.println("thread1 线程将要启动");21         thread1.start() ; 22     }23 }

 

  在上面程序中,thread 线程调用了join() 方法。所以main 线程会等 thread 线程执行结束才会向下执行,所以运行的结果如下: 

Thread-0...0Thread-0...1Thread-0...2Thread-0...3Thread-0...4Thread-0...5Thread-0...6Thread-0...7Thread-0...8Thread-0...9Thread-0...10thread1 线程将要启动Thread-1...0Thread-1...1Thread-1...2Thread-1...3Thread-1...4Thread-1...5Thread-1...6Thread-1...7Thread-1...8Thread-1...9Thread-1...10

  join 的方法有三种重载形式:

  void join() : 等待该线程终止。

  void join(long millis) : 等待该线程中指的时间最长为 millis 毫秒。

  void join(long millis , int nanos) 等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。

 

  4.2 守护线程(后台线程)

  Daemon Thread ,JVM 的垃圾回收机制就是典型的 后台线程。

  调用 Thread 对象 setDaemon(true) 方法可以指定线程设置成后台线程。该方法必须在启动线程钱调用,否则会引发 IllegalThreadStateException 异常。

  后台线程特点:如果所有前台线程都死亡,后台线程会自动死亡。 

1 public class DaemonText implements Runnable {  2     public void run() 3     { 4         for (int i = 0 ; i < 1000 ; i ++) 5         { 6             System.out.println(Thread.currentThread().getName()+"..."+i); 7         } 8     } 9     public static void main(String args[])10     { 11         DaemonText t = new DaemonText()  ; 12         Thread thread = new Thread(t) ; 13 14 //        将次线程设置为后台线程15         thread.setDaemon(true) ; 16 //        启动后台线程17         thread.start() ; 18         19         for(int i = 0 ; i < 10; i ++)20         {21             System.out.println(Thread.currentThread().getName()+"......"+i); 22         }23 //        程序执行到此,前台线程结束。24 //        后台线程也随之结束。25     }26 }

  因为thread 线程被设置成了 后台线程。所以,当主线程运行完后 ,JVM 会主动退出,因而后台线程也被结束。

 

  4.3 线程睡眠:sleep 

  当当前线程调用 sleep 方法进入阻塞状态后,在其sleep 时间段内,该线程不会获得执行机会,即使系统中没有其他线程了,直到sleep 的时间到了。所以,调用sleep 能让线程暂短暂停。

  4.4 线程让步:yield

  和sleep 类似,也可以让当前执行的线程暂停,但他不会让线程进入阻塞状态。而是,取消当前线程的执行权,使当前线程进入就绪状态。就是相当于让系统的线程调度器重新调度一次。

  4.5 改变线程的优先级

  每个线程执行时都具有一定的优先级,优先级高的则获得多的运行机会,优先级低的则获得较少的运行机会。

  Thread 提供了 setPriority(int newPriority) 和 getPriority() 方法来设置和返回指定线程的优先级。设置优先级的整数在1~10之间,也可以使用Thread 类的三个静态常量:

  > MAX_PRIORITY : 其值是 10 

  > MIN_PRIORITY : 其值是 1

  > NORM_PRIORITY: 其值是 5 

转载于:https://www.cnblogs.com/shenweibk/articles/3034208.html

你可能感兴趣的文章
文本编程
查看>>
乔布斯走了。你还期待苹果吗?
查看>>
优先级
查看>>
Tomcat与Web服务器、应用服务器的关系
查看>>
用DFS实现全排列 & 八皇后问题
查看>>
深度学习博客
查看>>
Android总结篇系列:Android Service
查看>>
Android dumpsys命令的使用
查看>>
Linux Kernel系列一:开篇和Kernel启动概要
查看>>
BZOJ 2756: [SCOI2012]奇怪的游戏 网络流/二分
查看>>
master + worker模式的node多核解决框架——node-cluster
查看>>
Android如何实现超级棒的沉浸式体验
查看>>
使用node打造自己的命令行工具方法教程
查看>>
Express代理中间件问题与解决方案
查看>>
||和&&返回什么?
查看>>
linux在文件中查找指定字符串,然后根据查找结果来做进一步的处理
查看>>
在Oracle中删除所有强制性外键约束
查看>>
dhcp
查看>>
【R】R语言使用命令行参数 - [编程技巧(Program Skill)]
查看>>
经典算法题每日演练——第二题 五家共井
查看>>