欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

多线程入门

程序员文章站 2023-01-11 22:39:24
本次主要内容,主要是初步了解线程,创建线程,使用一些简单的API,多线程的五种状态。 线程和进程 什么是线程?什么是进程?线程和进程的区别是什么?(面试常问) 用例子说明:我们打开电脑,同时打开qq,网易云音乐,word多个软件,在任务管理器中就可以看到这些就是进程,进程是正在执行中的程序,我们在q ......

本次主要内容,主要是初步了解线程,创建线程,使用一些简单的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阻塞,线程想得到一个被其他线程正在持有的锁。

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

多线程入门