经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
多线程入门
来源:cnblogs  作者:自带锋芒  时间:2018/10/29 10:04:20  对本文有异议

本次主要内容,主要是初步了解线程,创建线程,使用一些简单的API,多线程的五种状态。

 

线程和进程

什么是线程?什么是进程?线程和进程的区别是什么?(面试常问)

用例子说明:我们打开电脑,同时打开qq,网易云音乐,word多个软件,在任务管理器中就可以看到这些就是进程,进程是正在执行中的程序,我们在qq中,既可以给好友发信息,发文件,也可以接收信息,这些就是这个进程中的线程,线程是正在独立运行的一条执行路径,进程是线程的集合,一个操作系统可以有多个进程,一个进程有多个线程。任何一个进程都有一个主线程,在Java中是main线程。

为什么使用多线程?

百度网盘下载,我们可以同时下载多个文件,这多个文件是交给线程处理的,并且线程之间是互不影响的,不会因为一个出问题了,影响到其他文件的下载,提高程序的效率。

 

如何创建多线程

有几种方式?共有5种

在这里先介绍三种:

1、继承Thread类

2、实现Runnable接口

3、使用匿名内部类(可以算作一种)

另外两种后面深入再讲:

1、callable

2、使用线程池创建线程

 

01继承Thread类

  1. class CreateThread1 extends Thread {
  2. @Override
  3. public void run() {
  4. for (int i = 0; i < 20; i++) {
  5. System.out.println("run i:" + i);
  6. }
  7. }
  8. }
  9. public class Thread_Demo1 {
  10. public static void main(String[] args) {
  11. // 调用线程
  12. CreateThread1 ct1 = new CreateThread1();
  13. // 启动线程
  14. ct1.start();
  15. for (int i = 0; i < 30; i++) {
  16. System.out.println("main i:" + i);//打印交替执行
  17. }
  18. }
  19. }

 

继承Thread类创建线程,重写run方法,调用线程就是创建线程对象。怎么启动线程?不是直接对象名.run(),而是调用start方法来启动线程,如果调用run,就成了简单的调用一个普通的run方法。一旦开启线程以后,代码的执行顺序不会在按照从上往下的顺序执行。执行顺序具有随机性。上面的代码运行一次的部分结果为:

  1. main i:0
    main i:1
    run i:0
    main i:2
    run i:1
    main i:3
    run i:2
    main i:4
    run i:3
    run i:4
    main i:5

main是主线程,很容易的看出,多线程执行顺序不是从上而下的。

 

 

上图明显看出线程和多线程之间的区别,如果每个任务完成需要10秒,那么单线程下需要20秒,多线程下只需要10秒,单线程下如果任务1执行过程中出错了,那么整个程序就不会继续执行,多线程下互不影响。

这里,我们引出同步和异步的概念,同步就是代码从上往下顺序执行,是单线程的,异步就不是顺序执行,多线程,并且线程之间互不影响。

 

02实现Runnable接口

  1. class CreateThread2 implements Runnable{
  2. @Override
  3. public void run() {
  4. for (int i = 0; i < 20; i++) {
  5. System.out.println("run i:" + i);
  6. }
  7. }
  8. }
  9. public class Thread_Demo2 {
  10. public static void main(String[] args) {
  11. // 调用线程
  12. CreateThread2 ct2 = new CreateThread2();
  13. Thread t = new Thread(ct2);//别名 new Thread(ct2,"别名");
  14. // 启动线程
  15. t.start();
  16. for (int i = 0; i < 30; i++) {
  17. System.out.println("main i:" + i);//打印交替执行
  18. }
  19. }
  20. }

 

源码中,Thread类是实现了Runnable接口的,我们需要先创建实现Runnable的对象,然后传给Thread,启动线程。

对于这两种方法,是继承好还是实现接口好?

实现接口好,Java是单继承,多实现的面向对象编程语言,并且后面的开发都是面向接口编程,有利于代码的扩展与维护。

 

03匿名内部类创建线程

  1. public class Thread_Demo3 {
  2. public static void main(String[] args) {
  3. Thread t = new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. for (int i = 0; i < 30; i++) {
  7. System.out.println("run i:"+i);
  8. }
  9. }
  10. });
  11. t.start();
  12. for (int i = 0; i < 30; i++) {
  13. System.out.println("main i:" + i);//打印交替执行
  14. }
  15. }
  16. }

 

简单的API

1、start()    启动线程

2、currentThread()    获取当前线程对象

3、getID()    获取当前线程ID  Thread-编号,编号从0开始

4、getName()    获取当前线程名称

5、sleep()    线程休眠

6、stop()    停止线程(已过时,不建议使用)

这些大部分都是静态方法,可以直接Thread.进行调用。

  1. class CreateT extends Thread {
  2. @Override
  3. public void run() {
  4. for (int i = 0; i < 20; i++) {
  5. try {
  6. Thread.sleep(1000);//休眠1000毫秒也就是1秒
  7. } catch (InterruptedException e) {
  8. // TODO Auto-generated catch block
  9. e.printStackTrace();
  10. }
  11. System.out.println("线程ID:" + this.getId() + "子线程 i:" + i+"子线程name:"+this.getName());// 拿到线程ID,唯一,不会重复
  12. //Thread.currentThread()获取当前线程
  13. if(i==5){
  14. //Thread.currentThread().stop();不安全,不建议使用,强制停止
  15. }
  16. }
  17. }
  18. }
  19. public class ThreadAPI {
  20. public static void main(String[] args) {
  21. // 获取主线程ID
  22. System.out.println("主线程ID:" + Thread.currentThread().getId()+"主线程name:"+Thread.currentThread().getName());
  23. for (int i = 0; i < 3; i++) {
  24. CreateT t = new CreateT();
  25. t.start();
  26. }
  27. CreateT t = new CreateT();
  28. t.start();
  29. }
  30. }

 

 守护线程和非守护线程

守护线程就是和main线程相关的,特征就是和主线程一起销毁,比如gc线程。

非守护线程特征就是和main线程互不影响,比如用户自己创建的用户线程,主线程停止掉,不会影响用户线程。

 

下面这个例子,主线程执行完毕后,不会影响到子线程,子线程依然在执行。

  1. public class Demo4 {
  2. public static void main(String[] args) {
  3. Thread t = new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. for (int i = 0; i < 20; i++){
  7. System.out.println("子线程 i:"+i);
  8. }
  9. }
  10. });
  11. t.start();
  12. for (int i = 0; i < 5; i++) {
  13. System.out.println("主线程 i:"+i);
  14. }
  15. System.out.println("主线程执行完毕");
  16. }
  17. }
  1.  

使用下面这个方法:

setDaemon(true);//设置该线程为守护线程,和main线程一起挂掉

  1. public class Demo4 {
  2. public static void main(String[] args) {
  3. Thread t = new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. for (int i = 0; i < 20; i++){
  7. System.out.println("子线程 i:"+i);
  8. }
  9. }
  10. });
  11. t.setDaemon(true);
  12. t.start();
  13. for (int i = 0; i < 5; i++) {
  14. System.out.println("主线程 i:"+i);
  15. }
  16. System.out.println("主线程执行完毕");
  17. }
  18. }

 

一次的执行结果

  1. 主线程 i:0
    子线程 i:0
    主线程 i:1
    主线程 i:2
    主线程 i:3
    主线程 i:4
    主线程执行完毕

主线程执行完毕后,子线程不会执行完毕,而是会随着主线程的停止就停止了。

join

join的作用就是让其他线程变为等待。

一个A线程,一个B线程,A线程调用B线程的join方法,那么A等待B执行完毕后,释放CPU执行权,再继续执行。

有这样一个题目:T1,T2,T3三个线程,怎么保证T2在T1完成后执行,T3在T2完成后执行???

在T3的run方法中调用T2的join方法,在T2的run方法中调用T1的join方法

  1. public class Join_Test {
  2. public static void main(String[] args) {
  3. Thread T1 = new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. for (int i = 0; i < 5; i++){
  7. System.out.println("T1");
  8. }
  9. }
  10. });
  11. T1.start();
  12. Thread T2 = new Thread(new Runnable() {
  13. @Override
  14. public void run() {
  15. try {
  16. T1.join();
  17. } catch (InterruptedException e) {
  18. // TODO Auto-generated catch block
  19. e.printStackTrace();
  20. }
  21. for (int i = 0; i < 5; i++){
  22. System.out.println("T2");
  23. }
  24. }
  25. });
  26. T2.start();
  27. Thread T3 = new Thread(new Runnable() {
  28. @Override
  29. public void run() {
  30. try {
  31. T2.join();
  32. } catch (InterruptedException e) {
  33. // TODO Auto-generated catch block
  34. e.printStackTrace();
  35. }
  36. for (int i = 0; i < 5; i++){
  37. System.out.println("T3");
  38. }
  39. }
  40. });
  41. T3.start();
  42. }
  43. }

  1. 线程的几种状态

线程共有5种状态。

新建状态,就绪状态,运行状态,阻塞状态,死亡状态。

 

新建状态:就是new创建一个线程,线程还没有开始运行。

就绪状态:新建状态的线程不会自动执行,要想执行线程,需要调用start方法启动线程,start线程创建线程运行的系统资源,并调度线程运行run方法,start方法返回后,就处于了就绪状态。就绪状态的线程不会立即运行run方法,需要获得CPU时间片后,才会运行。

运行状态:获取CPU时间片,执行run方法。

阻塞状态:线程运行时,可能会有好多原因进入该状态,比如调用sleep方法休眠,I/O阻塞,线程想得到一个被其他线程正在持有的锁。

死亡状态:正常退出而死亡,非正常退出而死亡(一个未捕获的异常终止了该线程)。

 

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号