多线程与高并发(一)多线程入门

1、基础概念

多线程的学习从一些概念开始,进程和线程,并发与并行,同步与异步,高并发。编程

1.1 进程与线程

几乎全部的操做系统都支持同时运行期多个任务,全部运行中的任务一般就是一个进程,进程是处于运行过程当中的程序,进程是操做系统进行资源分配和调度的一个独立单位。多线程

进程有三个以下特征:架构

  • 独立性:进程是系统中独立存在的实体,它能够拥有本身独立的资源,每个进程都拥有本身私有的地址空间。在没有通过进程自己容许的状况下,一个用户进程不能够直接访问其余进程的地址空间。并发

  • 动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中加入了时间的概念,进程具备本身的生命周期和各类不一样的状态,这些概念在程序中部是不具有的。异步

  • 并发性:多个进程能够在单个处理器上并发执行,多个进程之间不会互相影响。分布式

线程是进程的组成部分,一个进程能够拥有多个线程,而线程必须有一个父进程,线程能够有本身的堆栈、本身的程序计数器和本身的局部变量,但不拥有系统资源。好比使用QQ时,咱们能够同事传文件,发送图片,聊天,这就是多个线程在进行。ide

线程能够完成必定的任务,线程可以独立运行的,它不知道有其余线程的存在,线程的执行是抢占式的,当前线程随时可能被挂起。高并发

总之:一个程序运行后至少有一个进程,一个进程里能够有多个线程,但至少要有一个线程。学习

1.2 并发和并行

并发和并行是比较容易混淆的概念,他们都表示两个或者多个任务一块儿执行,但并发侧重多个任务交替执行,同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具备多个进程同时执行的效果。而并行确实真正的同时执行,有多条指令在多个处理器上同时执行,并行的前提条件就是多核CPU。this

1.3 同步和异步

同步和异步一般用来形容一次方法调用。同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。异步方法调用更像一个消息传递,一旦开始,方法调用就会当即返回,调用者能够继续后续的操做。

1.4 高并发

高并发通常是指在短期内遇到大量操做请求,很是具备表明性的场景是秒杀活动与抢票,高并发是互联网分布式系统架构设计中必须考虑的因素之一,高并发相关经常使用的一些指标有响应时间(Response Time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户数等。

多线程在这里只是在同/异步角度上解决高并发问题的其中的一个方法手段,是在同一时刻利用计算机闲置资源的一种方式

1.5 多线程的好处

线程在程序中是独立的、并发的执行流,拥有独立的内存单元,多个线程共享父进程里的所有资源,线程共享的环境有进程的代码段,进程的公有数据等,利用这些共享数据,线程很容易实现相互之间的通讯,能够提升程序的运行效率。

多线程的好处主要有:

  • 进程之间不能共享内存,但线程之间共享内存很是容易。

  • 系统建立进程时须要给进程从新分配系统资源,但建立线程代价小得多,因此使用多线程实现多任务并发比多进程效率高

  • Java语言内置了多线程功能支持。

2、使用多线程

上面讲了多线程的一些概念,都有些抽象,下面将学习如何使用多线程,建立多线程的方式有三种。

2.1 继承Thread类建立

继承Thread建立并启动多线程有三个步骤:

  1. 定义类并继承Thread,重写run()方法,run()方法中为须要多线程执行的任务。

  2. 建立该类的实例,即建立了线程对象。

  3. 调用实例的start()方法启动线程。

public class FirstThread extends Thread {

    private int i=0;
    public void run() {
        for (; i < 100; i++) {
            //获取当前线程名称
            System.out.println(this.getName() + " " + i);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            //Thread的静态方法currentThread,获取当前线程
            System.out.println(Thread.currentThread().getName());
            if (i == 20) {
                //建立线程并启动
                new FirstThread().start();
                new FirstThread().start();
            }

        }
    }
}

运行结果能够看到两个线程的i并非连续的,说明他们并不共享数据。

2.2 实现Runnable接口

实现Runnable接口建立并启动多线程也有如下步骤:

  1. 定义类并继承Runnable接口,重写run()方法,run()方法中为须要多线程执行的任务。

  2. 建立该类的实例,并以此实例做为target为参数来建立Thread对象,这个Thread对象才是真正的多线程对象。

public class SecondThread implements Runnable {
    private int i = 0;
    
    @Override
    public void run() {
        for (; i < 100; i++) {
            //此时想要获取到多线程对象,只能使用Thread.currentThread()方法
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            //Thread的静态方法currentThread,获取当前线程
            System.out.println(Thread.currentThread().getName());
            if (i == 20) {
                //建立线程并启动
                SecondThread secondThread=new SecondThread();
                new Thread(secondThread,"线程一").start();
                new Thread(secondThread,"线程二").start();
            }

        }
    }
}

2.3 使用Callable和Future

Callable是Runnable的增长版,主要是接口中的call()方法能够有返回值,而且能够申明抛出异常,使用Callable建立的步骤以下:

  1. 定义类并继承Callable接口,重写call()方法,run()方法中为须要多线程执行的任务。

  2. 建立类实例,使用FutureTask来包装对象实例,

  3. 使用FutureTask对象做为Thread的target来建立多线程,并启动线程。

  4. 调用FutureTask对象的get()方法来获取子线程结束后的返回值。

public class ThirdThread {

    public static void main(String[] args) {
        //使用lambda表达式
        FutureTask<Integer> task = new FutureTask<>((Callable<Integer>) () -> {
            int i = 0;
            for (; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "的循环变量i的值:" + i);
            }
            return i;
        });
        for (int i = 0; i < 100; i++) {
            //Thread的静态方法currentThread,获取当前线程
            System.out.println(Thread.currentThread().getName());
            if (i == 20) {
                //建立线程并启动
                new Thread(task, "有返回值的线程").start();
            }
        }
        try {
            System.out.println("线程的返回值:" + task.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

这里使用了lambda表达式,不使用表达式的方式也很简单,能够去源码中查看。Callable与Runnable方式基本相同,只不过增长了返回值且可容许声明抛出异常。

使用三种方式均可以建立线程,且方式也相对简单,大致分为实现接口和实现Thread类两种,这两种都各有优缺点。

继承接口实现:

  • 优势:除了继承接口以外,还能够继承其余类。这种方式多个线程共享一个target对象,能够处理用于共同资源的状况。
  • 缺点:编程稍微复杂一些,而且没有直接获取当前线程对象的方式,必须使用Thread.currentThread()方式。

基础Thread类:

  • 优势:编程简单

  • 缺点:不能继承其余类

3、多线程的生命周期

线程状态是线程中很是重要的一个概念,然而我看过不少资料,线程的状态理解有不少种方式,不少人将其分为五个基本状态:新建、就绪、运行、阻塞、死亡,但在状态枚举中并非这五个状态,我不知道是什么缘由(有大神能够解答更好),只能按照枚举中的状态根据本身的理解。

  1. 初始(NEW):新建立了一个线程对象,但尚未调用start()方法,并且就算调用了改方法也不表明状态当即改变。

  2. 运行(RUNNABLE):在运行的状态确定就处于RUNNABLE状态。

  3. 阻塞(BLOCKED):表示线程阻塞,或者说线程已经被挂起了。

  4. 等待(WAITING):进入该状态的线程须要等待其余线程作出一些特定动做(通知或中断)。

  5. 超时等待(TIMED_WAITING):该状态不一样于WAITING,它能够在指定的时间后自行返回。

  6. 终止(TERMINATED):表示该线程已经执行完毕。

状态流程图以下:

理解:初始状态很好理解,这个时候其实还不能被称为一个线程,由于他还没被启动,当调用start()方法后,线程正式启动,可是也不表明当即就改变了状态。

运行状态中其实包含两种状态,运行中(RUNING)就绪(READY)

就绪状态表示你有资格运行,只要CPU还未调度到你,就处于就绪状态,有几个状态会是线程状态编程就绪状态

  • 调用线程的start()方法。

  • 当前线程sleep()方法结束,其余线程join()结束,等待用户输入完毕,某个线程拿到对象锁。

  • 当前线程时间片用完了,调用当前线程的yield()方法。

  • 锁池里的线程拿到对象锁后。

运行中(RUNING)状态比较好理解,线程调度程序选择了当前线程做。

阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。

等待状态是指线程没有被CPU分配执行时间,须要等待,这种等待是须要被显示的唤醒,不然会无限等待下去。

超时等待状态是这如今没有被CPU分配执行时间,须要等待,不过这种等待不须要被显示的唤醒,会设置必定的时间后zi懂唤醒。

死亡状态也很好理解,说明线程方法被执行完成,或者出错了,线程一旦进入这个状态就表明完全的结束

相关文章
相关标签/搜索