并发编程之线程简介

一、何为线程?

1、线程的定义

线程的定义:程序中的一个顺序控制流程,也是CPU中的最小调度单位。

从定义来看,可能很多同学觉得有点晦涩难懂,OK,我这里举个例子就明白了,其实在我们最开始学习java的时候就写过Hello World程序对吧,在一个main方法里面定义一条打印语句,然后执行main方法就能完成打印,那同学们有没有想过,到底是由什么玩意儿来执行的main方法呢?其实就是有我们的线程来执行的,在我们java中,任何代码的执行都是由线程来完成的,那线程追溯到计算机底层其实又是由CPU来完成的。OK,到这里就明确了,计算机执行任何程序都是由CPU来完成的,而CPU真正执行的是一个个的线程,然后由线程去完成相应代码的执行。

从计算机中反应到现实中,每个线程就好比每个不同的人,每个人都自己的独立完成工作的能力,线程也可以这么理解,以后在我的课堂中,人就是线程,线程就是人。

2、进程的定义

进程的定义:运行中的应用程序。

没有运行的应用程序只是一堆静态代码,这里可以通过查看计算机进程验证

3、进程和线程的关系

进程和线程的关系:一个进程由多个线程构成。

二、线程的关系户

1、线程与CPU核心数的关系

如果线程数小于或者等于CPU核心数的时候,则CPU能同时运行,如果,大于的时候,CPU只能通过上下文切换的方式轮转交替执行线程。

2、守护线程和本地线程

1)两种Java中的线程

守护线程(Daemon)和用户线程(User)。任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon(boolean);true则把该线程设置为守护线程,反之则为用户线程。Thread.setDaemon()必须在Thread.start()之前调用,否则运行时会抛出异常。

2)守护线程和用户线程的区别

唯一的区别是判断虚拟机(JVM)何时离开,Daemon是为其他线程提供服务,如果全部的User Thread已经撤离,Daemon 没有可服务的线程,JVM撤离。也可以理解为守护线程是JVM自动创建的线程(但不一定),用户线程是程序创建的线程;比如JVM的垃圾回收线程是一个守护线程,当所有线程已经撤离,不再产生垃圾,守护线程自然就没事可干了,当垃圾回收线程是Java虚拟机上仅剩的线程时,Java虚拟机会自动离开。

三、线程的作用

说的官方一点就是线程的使用能够提高CPU的效率,说的直白一点就是人多了,干一件事就快了,干多件事也快了,反正就是人多干活快。

举个例子,如果现在你要往数据库里面插入1千万条数据(暂时先不考虑数据库的性能问题)然后你一个线程一条条插可能要10分钟,但是如果你是10个线程同时执行,可能就只要少于1分钟就能搞定。

虽然线程很好用,但是并不是线程越多越好,就看人一样的,虽然人多力量大,但是人太多了,地球妈妈也扛不住啊,所以线程太多了,CPU扛不住,为什么扛不住呢?看上面讲的核心数。

四、线程的使用

普通回答:jdk提供四种创建线程的方式,下面介绍这四种

1、通过继承Thread类

这里可以带着同学们进到底层start0()方法,然后解释一下start方法是怎么调用的。

  • 自定义Girl类,然后继承Thread类
  • 重写run方法(这个方法里面的逻辑是具体要干的活,比如这个女孩要生小孩)
  • 创建Girl类的对象,Girl girl= new Girl();
  • 调用对象的start方法,girl.start();

2、通过实现Runnable接口

Runnable实际上不能理解成线程,应该理解成任务更加准确,既然是任务,它就不能自己去执行,必须得通过线程(人)去执行

  • 自定义MyTask类,然后实现Runnable接口
  • 重写run方法(这里也是具体要干的活)
  • 创建MyTask类的对象,MyTask task = new MyTask();注意,我们现在创建的只是一个任务,不能指望任务自己去完成自己,所以还得通过下一步的操作
  • 创建一个基本的线程,Thread ren = new Thread(task);
  • 调用线程的start方法,ren.start()

3、通过实现Callable接口

  • 自定义MyCallable类,然后实现Callable接口
  • 重写call方法(这里也是具体要干的活)
  • 创建MyCallable类的对象,MyCallable calla= new MyCallable();这里也是一个任务,也只能通过线程去执行,但是它只能通过线程池去做
  • 创建一个线程池,ExecutorService executorService = Executors.newFixedThreadPool(3);executorService.submit(calla);

它与Runnable的区别在于有无返回值,和能否抛异常

4、线程池

高手回答:jdk只有一种创建线程的方式,因为不论是上面四种中的哪一种方式,其实最终都是通过Thread去创建

顶手回答:jdk没有创建线程的能力,因为最终走到底层会发现,其实JVM是调用了底层操作系统的方法,由操作系统去真正创建线程了

五、线程的生命周期

线程从出生到死亡,它走过了哪些阶段:

  • 1、new,初始化状态;(创建类的这个过程还是能算是前期准备哈,线程这个人还没出生,这个时候不能算,类的创建阶段还只能算备孕阶段,只有通过new关键字创建了对象,才能算开始)
  • 2、runnable,运行状态
    • start,就绪状态,这个状态还没有真正执行,只是告诉CPU,我准备好了,你可以来让我干活了。
    • running,运行中状态,拿到CPU给的工作牌,开始干活
  • 3、timed_waiting,限期等待
    • sleep(10)/wait(10)/parkNanos(10)
  • 4、waiting,无限期等待状态
    • wait()/park()
  • 5、blocked,阻塞状态
    • 在用锁的情况下(synchronized),没有获取到锁的线程进入blocked状态。限期和无限期等待也可以称为阻塞状态。
  • 6、terminated:终止状态,执行完run方法之后,自动被回收。

六、线程启动和停止

  • 1、start方法启动
  • 2、终止:
    • run方法执行完了之后自动终止
    • Thread.stop(不建议使用,对程序不友好,如果线程任务正在执行的话,调用stop会强制是的线程中断)
    • Thread.interrupt(),这个是友好的中断。
      • interrupt()作用:更改线程的中断标记,唤醒处于阻塞状态下的线程。会抛出InterruptedException,线程自己决定要不要结束,异常会触发复位
      • interrupted():获取线程的中断标记,并且执行复位操作
      • isInterrupted():获取线程的中断标记,但是不会进行复位操作

代码举例:

public class InterruptDemo02 implements Runnable {
    @Override
    public void run() {
        while(!Thread.currentThread().isInterrupted()){ //false
            try {
                TimeUnit.SECONDS.sleep(20);
                System.out.println("1231");
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println(Thread.currentThread().isInterrupted());
                //可以不做处理,
                //继续中断 ->
                Thread.currentThread().interrupt(); //再次中断
                //抛出异常。。
            }
           // Thread.currentThread().interrupted();  //再次复位
            System.out.println(Thread.currentThread().isInterrupted());
        }
        System.out.println("processor End");
    }
    
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(new InterruptDemo02());
        t1.start();
        TimeUnit.SECONDS.sleep(5);
        // Thread.sleep(1000);
        t1.interrupt(); //有作用 true
    }
}

发表评论