一、什么是线程

学习线程,我们首先要了解什么是进程,它和进程有什么样的区别和联系?

找了很多资料,大部分都是在说:进程是资源分配的最小单位,线程是操作系统调度的最小单元,这样理解起来还是很抽象,我们先看一副图,我们在解释下它们的概念

拜托,学习并发编程之前请学习下线程!_等待状态

如图所示,我们直观上看到,进程之间是相互独立的,正是因为这个原因,所以进程间的数据很难共享,进程间的切换也比较耗费资源;一个进程可以包含多个线程,如线程1和线程2,所以同一进程间的线程能够进行数据共享,又因为线程上的代码的执行需要被记录下来,每个线程都拥有各自的计数器、堆栈和局部变量。

总结进程和线程的特点

  • 进程

一个进程包含多个线程

不同进程间的数据很难共享

进程的上下切换比线程消耗更多的计算机资源

进程间是互相独立的

  • 线程

线程依赖于进程

同一进程下的线程可以数据共享

线程的运行会影响进程

线程拥有自己的计数器。堆栈和局部变量等属性

二、线程的使用

创建线程有四种方式,分别为①继承Thread类创建线程②实现Runnable接口创建线程③使用Callable和Future创建线程④使用线程池。

因为这篇文主要是对线程做一些概念上的讲解,所以这里我们用前两种方式做参考,后两种方式,有兴趣的同学可以查阅一些资料进行学习。

  • 继承Thread类创建线程
Thread thread= new Thread(){
            @Override
            public void run () {
                //需要做的任务
            }
        };
thread.start();
  • 实现Runnable接口创建线程
//自定义Runnable
class MyRunnable implements Runnable{
    @Override
    public void run () {
        //需要处理的任务
    }
}
//创建Runnable对象
Runnable r = new MyRunnable();
//创建Thread对象
Thread t = new Thread(r);
//启动线程
t.start();

三、为什么要使用多线程

进程使得不同程序之间的可以运行,我们在网站看新闻不影响我们听歌;而线程是在应用程序的不同功能并发执行,我们利用微信在和朋友视频聊天的时候不会影响我们发文件给对方。这是我们很直观的感受多线程的好处,事实上,使用多线程有以下优点

  • 更好的利用资源

对于多核处理器的计算机,可以将多个线程分配到多个处理器上,这样就会减少程序的运行时间,能够更好的利用计算机的资源

  • 更快的提高程序的响应时间

对于一个应用程序,运行它需要耗费很长的时间,这是可以先响应窗口线程,一些加载功能可以放在后台线程上去做,这样有利于提升用户体验。

四、线程的优先级

在学习线程的优先级之前,我们需要了解线程的优先级是怎么定义的?

我们都了解线程的执行需要CPU分配时间片,当线程的时间片用完之后就会发生调度,并等待着下次分配,而分配时间片的大小决定着线程的优先级。

在Java程序中,线程的优先级是用数字表示的,优先级范围为[1,10],数字越大优先级也就越高,不在这个范围的优先级则会抛出异常,默认的优先级的是5,优先级高的线程会比优先级低的线程先执行,我们用一段代码来讲解下线程优先级的设置和不同优先级线程的执行顺序。

/**
 * @Author: Simon Lang
 * @Date: 2020/4/30 19:00
 */
public class ThreadPriority {
    public static void main(String[] args){
        //定义三个线程
        ThreadA A=new ThreadA();
        ThreadB B=new ThreadB();
        ThreadC C=new ThreadC();
        //设置线程优先级,C线程为默认优先级
        A.setPriority(1);
        B.setPriority(10);
        //打印线程的优先级
        System.out.println(A.getPriority());
        System.out.println(B.getPriority());
        System.out.println(C.getPriority());

        A.start();
        B.start();
        C.start();
    }
}
class ThreadA extends Thread{
    @Override
    public void run(){
        System.out.println("A线程");
    }
}
class ThreadB extends Thread{
    @Override
    public void run(){
        System.out.println("B线程");
    }
}
class ThreadC extends Thread{
    @Override
    public void run(){
        System.out.println("C线程");
    }
}

我们在程序中设定了优先级的顺序为:B线程>C线程>A线程,我们来打印下线程的优先级具体数值和顺序

拜托,学习并发编程之前请学习下线程!_ide_02

五、线程的状态

学习线程的状态前,我们先学习下进程有哪些状态,它和线程的状态有哪些区别?

1、进程的状态

拜托,学习并发编程之前请学习下线程!_创建线程_03

进程有5种状态

状态名称 说明
创建态 进程的初始状态,刚被创建出来,还没有执行
就绪态 进程具备运行条件,等待系统来分配时间片来运行
运行态 进程正在处理器执行
阻塞态 进程因为某一事情暂时不能运行,这是为了提高CPU利用率,需要先将其它进程资源分配到位,才能得到CPU的服务
终止态 进程被系统撤销

状态转换

创建态-->就绪态:完成了进程的创建,满足计算机性能的要求

就绪态-->运行态:进程被调度

运行态-->就绪态:分配的时间片被用完

运行态-->终止态:进程结束被系统撤销,或者发生错误

运行态-->阻塞态:等待某一事情的发生而发生阻塞

阻塞态--->就绪态:等待事情的操作完成

2、线程的状态

拜托,学习并发编程之前请学习下线程!_ide_04

线程有6种状态

状态名称 说明
创建态 线程被创建,还未调用start()方法
运行态 Java线程中将操作系统中的的就绪态和运行态两种状态称为运行态
阻塞态 线程阻塞于锁
等待态 线程进入等待状态,进入该状态的表示当前线程需要等待其它线程做出一些特定的动作,如通知或中断
超时等待态 不同于等待态,它可以在指定的时间自行返回
终止状态 表示线程已完成或被撤销。

线程间的转换

以java程序运行为例,线程被创建之后就会调用start()方法,线程执行wait()方法后,线程进入等待状态。进入等待状态的进程需要依靠其它线程的通知才能够返回到运行状态。超时等待状态是在等待状态的基础是增加了超时限制,也就是超时时间到达后将会返回运行状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入阻塞状态,当线程执行Runnable的run()方法之后将会进入到终止状态。

创建态--->运行态:调用start()方法

运行态--->等待态:调用wait()、join()、LockSupport.park()

等待态--->运行态:notify()、notifyAll()、LockSupport.unpark()

运行态--->超时等待:sleep()、wait()、join()、LockSupport.parkNanos()、LockSuuport.parkUntil()

超时等待--->运行态:notify()、notifyAll()、LockSupport.unpark()

运行态---->阻塞态:没哟获取到锁,等待进入synchronized方法(块)

阻塞态--->运行态:获取到锁

运行态--->终止态:执行完成

  • 等待和超时等待很多方法都是一样的,它们有什么区别呢?

等待态会首先获取对象的锁,然后判断条件,如果条件不满足则会调用对象的wait()方法,它就会放弃锁进入对象的等待队列中。

synchronized(对象){
    while(条件不满足) {
        对象.wait();
    }
    执行后续操作
}

这时NotifyThread会将等待状态的线程转移到同步队列中,此时线程就从等待状态变为阻塞态。

而等待超时就是为了解决不让等待态的线程转移到阻塞态,它在等待态的模式上增加了超时控制,即使方法执行时间过长,也不会永久阻塞调用者,会按照调用者的要求按时返回。

 

拜托,学习并发编程之前请学习下线程!_等待状态_05