多线程的学习从一些概念开始,进程和线程,并发与并行,同步与异步,高并发。编程
几乎全部的操做系统都支持同时运行期多个任务,全部运行中的任务一般就是一个进程,进程是处于运行过程当中的程序,进程是操做系统进行资源分配和调度的一个独立单位。多线程
进程有三个以下特征:架构
独立性:进程是系统中独立存在的实体,它能够拥有本身独立的资源,每个进程都拥有本身私有的地址空间。在没有通过进程自己容许的状况下,一个用户进程不能够直接访问其余进程的地址空间。并发
动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中加入了时间的概念,进程具备本身的生命周期和各类不一样的状态,这些概念在程序中部是不具有的。异步
并发性:多个进程能够在单个处理器上并发执行,多个进程之间不会互相影响。分布式
线程是进程的组成部分,一个进程能够拥有多个线程,而线程必须有一个父进程,线程能够有本身的堆栈、本身的程序计数器和本身的局部变量,但不拥有系统资源。好比使用QQ时,咱们能够同事传文件,发送图片,聊天,这就是多个线程在进行。ide
线程能够完成必定的任务,线程可以独立运行的,它不知道有其余线程的存在,线程的执行是抢占式的,当前线程随时可能被挂起。高并发
总之:一个程序运行后至少有一个进程,一个进程里能够有多个线程,但至少要有一个线程。学习
并发和并行是比较容易混淆的概念,他们都表示两个或者多个任务一块儿执行,但并发侧重多个任务交替执行,同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具备多个进程同时执行的效果。而并行确实真正的同时执行,有多条指令在多个处理器上同时执行,并行的前提条件就是多核CPU。this
同步和异步一般用来形容一次方法调用。同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。异步方法调用更像一个消息传递,一旦开始,方法调用就会当即返回,调用者能够继续后续的操做。
高并发通常是指在短期内遇到大量操做请求,很是具备表明性的场景是秒杀活动与抢票,高并发是互联网分布式系统架构设计中必须考虑的因素之一,高并发相关经常使用的一些指标有响应时间(Response Time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户数等。
多线程在这里只是在同/异步角度上解决高并发问题的其中的一个方法手段,是在同一时刻利用计算机闲置资源的一种方式
线程在程序中是独立的、并发的执行流,拥有独立的内存单元,多个线程共享父进程里的所有资源,线程共享的环境有进程的代码段,进程的公有数据等,利用这些共享数据,线程很容易实现相互之间的通讯,能够提升程序的运行效率。
多线程的好处主要有:
进程之间不能共享内存,但线程之间共享内存很是容易。
系统建立进程时须要给进程从新分配系统资源,但建立线程代价小得多,因此使用多线程实现多任务并发比多进程效率高
Java语言内置了多线程功能支持。
上面讲了多线程的一些概念,都有些抽象,下面将学习如何使用多线程,建立多线程的方式有三种。
继承Thread建立并启动多线程有三个步骤:
定义类并继承Thread,重写run()方法,run()方法中为须要多线程执行的任务。
建立该类的实例,即建立了线程对象。
调用实例的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并非连续的,说明他们并不共享数据。
实现Runnable接口建立并启动多线程也有如下步骤:
定义类并继承Runnable接口,重写run()方法,run()方法中为须要多线程执行的任务。
建立该类的实例,并以此实例做为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(); } } } }
Callable是Runnable的增长版,主要是接口中的call()方法能够有返回值,而且能够申明抛出异常,使用Callable建立的步骤以下:
定义类并继承Callable接口,重写call()方法,run()方法中为须要多线程执行的任务。
建立类实例,使用FutureTask来包装对象实例,
使用FutureTask对象做为Thread的target来建立多线程,并启动线程。
调用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类两种,这两种都各有优缺点。
继承接口实现:
基础Thread类:
优势:编程简单
缺点:不能继承其余类
线程状态是线程中很是重要的一个概念,然而我看过不少资料,线程的状态理解有不少种方式,不少人将其分为五个基本状态:新建、就绪、运行、阻塞、死亡,但在状态枚举中并非这五个状态,我不知道是什么缘由(有大神能够解答更好),只能按照枚举中的状态根据本身的理解。
初始(NEW):新建立了一个线程对象,但尚未调用start()方法,并且就算调用了改方法也不表明状态当即改变。
运行(RUNNABLE):在运行的状态确定就处于RUNNABLE状态。
阻塞(BLOCKED):表示线程阻塞,或者说线程已经被挂起了。
等待(WAITING):进入该状态的线程须要等待其余线程作出一些特定动做(通知或中断)。
超时等待(TIMED_WAITING):该状态不一样于WAITING,它能够在指定的时间后自行返回。
终止(TERMINATED):表示该线程已经执行完毕。
状态流程图以下:
理解:初始状态很好理解,这个时候其实还不能被称为一个线程,由于他还没被启动,当调用start()方法后,线程正式启动,可是也不表明当即就改变了状态。
运行状态中其实包含两种状态,运行中(RUNING)和就绪(READY)。
就绪状态表示你有资格运行,只要CPU还未调度到你,就处于就绪状态,有几个状态会是线程状态编程就绪状态
调用线程的start()方法。
当前线程sleep()方法结束,其余线程join()结束,等待用户输入完毕,某个线程拿到对象锁。
当前线程时间片用完了,调用当前线程的yield()方法。
锁池里的线程拿到对象锁后。
运行中(RUNING)状态比较好理解,线程调度程序选择了当前线程做。
阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。
等待状态是指线程没有被CPU分配执行时间,须要等待,这种等待是须要被显示的唤醒,不然会无限等待下去。
超时等待状态是这如今没有被CPU分配执行时间,须要等待,不过这种等待不须要被显示的唤醒,会设置必定的时间后zi懂唤醒。
死亡状态也很好理解,说明线程方法被执行完成,或者出错了,线程一旦进入这个状态就表明完全的结束