• 多线程入门


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

     

    线程和进程

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

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

    为什么使用多线程?

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

     

    如何创建多线程

    有几种方式?共有5种

    在这里先介绍三种:

    1、继承Thread类

    2、实现Runnable接口

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

    另外两种后面深入再讲:

    1、callable

    2、使用线程池创建线程

    01继承Thread类

    class CreateThread1 extends Thread {
     @Override
     public void run() {
       for (int i = 0; i < 20; i++) {
         System.out.println("run i:" + i);
       }
     }
    }
    public class Thread_Demo1 {
    
     public static void main(String[] args) {
       // 调用线程
       CreateThread1 ct1 = new CreateThread1();
       // 启动线程
       ct1.start();
       for (int i = 0; i < 30; i++) {
         System.out.println("main i:" + i);//打印交替执行
       }
     }
    }

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

    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接口

    class CreateThread2 implements Runnable{
     @Override
     public void run() {
       for (int i = 0; i < 20; i++) {
         System.out.println("run i:" + i);
       }
     }
    }
    
    public class Thread_Demo2 {
    
     public static void main(String[] args) {
       // 调用线程
       CreateThread2 ct2 = new CreateThread2();
       Thread t = new Thread(ct2);//别名 new Thread(ct2,"别名");
       // 启动线程
       t.start();
       for (int i = 0; i < 30; i++) {
         System.out.println("main i:" + i);//打印交替执行
       }
     }
    
    }

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

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

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

    03匿名内部类创建线程

    public class Thread_Demo3 {
    
     public static void main(String[] args) {
       Thread t = new Thread(new Runnable() {
         
         @Override
         public void run() {
           for (int i = 0; i < 30; i++) {
             System.out.println("run i:"+i);
    
           }
         }
       });
       t.start();
       for (int i = 0; i < 30; i++) {
         System.out.println("main i:" + i);//打印交替执行
       }
       
     }
    
    }

    简单的API

    1、start()    启动线程

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

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

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

    5、sleep()    线程休眠

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

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

    class CreateT extends Thread {
     @Override
     public void run() {
       for (int i = 0; i < 20; i++) {
         try {
           Thread.sleep(1000);//休眠1000毫秒也就是1秒
         } catch (InterruptedException e) {
           // TODO Auto-generated catch block
           e.printStackTrace();
         }
         System.out.println("线程ID:" + this.getId() + "子线程 i:" + i+"子线程name:"+this.getName());// 拿到线程ID,唯一,不会重复
          //Thread.currentThread()获取当前线程
         if(i==5){
           //Thread.currentThread().stop();不安全,不建议使用,强制停止
         }
       }
     }
    }
    
    public class ThreadAPI {
    
     public static void main(String[] args) {
       // 获取主线程ID
       System.out.println("主线程ID:" + Thread.currentThread().getId()+"主线程name:"+Thread.currentThread().getName());
        for (int i = 0; i < 3; i++) {
         CreateT t = new CreateT();
         t.start();
       }
       
       CreateT t = new CreateT();
       t.start();
       
    
     }
    
    }

     守护线程和非守护线程

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

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

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

    public class Demo4 {
     
     public static void main(String[] args) {
       Thread t = new Thread(new Runnable() {
         @Override
         public void run() {
           for (int i = 0; i < 20; i++){
           System.out.println("子线程 i:"+i);
           }
         }
       });
       
       t.start();
       for (int i = 0; i < 5; i++) {
         System.out.println("主线程 i:"+i);
       }
       System.out.println("主线程执行完毕");
     }
    
    }
     

    使用下面这个方法:

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

    public class Demo4 {
    
    public static void main(String[] args) {
      Thread t = new Thread(new Runnable() {
        @Override
        public void run() {
          for (int i = 0; i < 20; i++){
          System.out.println("子线程 i:"+i);
          }
        }
      });
      t.setDaemon(true);
      t.start();
      for (int i = 0; i < 5; i++) {
        System.out.println("主线程 i:"+i);
      }
      System.out.println("主线程执行完毕");
    }
    
    }

    一次的执行结果

    主线程 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方法

    public class Join_Test {
     public static void main(String[] args) {
       Thread T1 = new Thread(new Runnable() {
         @Override
         public void run() {
           for (int i = 0; i < 5; i++){
             System.out.println("T1");
           }
         }
       });
       T1.start();
       Thread T2 = new Thread(new Runnable() {
         @Override
         public void run() {
           try {
             T1.join();
           } catch (InterruptedException e) {
             // TODO Auto-generated catch block
             e.printStackTrace();
           }
           for (int i = 0; i < 5; i++){
             System.out.println("T2");
           }
         }
       });
       T2.start();
       Thread T3 = new Thread(new Runnable() {
         @Override
         public void run() {
           try {
             T2.join();
           } catch (InterruptedException e) {
             // TODO Auto-generated catch block
             e.printStackTrace();
           }
           for (int i = 0; i < 5; i++){
             System.out.println("T3");
           }
         }
       });
       T3.start();
     }
    }

    线程的几种状态

    线程共有5种状态。

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

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

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

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

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

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

  • 相关阅读:
    linux命令行下命令参数前的一横(-)和两横(--)的区别
    sql的集合操作
    二叉树的遍历
    linux网络编程中阻塞和非阻塞socket的区别
    Python 信号量
    python中文件的复制
    Linux网络服务器epoll模型的socket通讯的实现(一)
    Linux启动提示“unexpected inconsistency;RUN fsck MANUALLY”
    Linux用户级线程和内核级线程区别
    nodejs的cs模式聊天客户端和服务器实现
  • 原文地址:https://www.cnblogs.com/javatalk/p/9866052.html
Copyright © 2020-2023  润新知