多线程基础

线程和进程的基本概念

一、进程和线程

进程

进程:指在系统中正在运行的一个应用程序。

每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间。

比如同时打开QQ、Xcode,系统就会分别启动2个进程

通过“活动监视器”可以查看Mac系统中所开启的进程

进程.png

线程

线程:一个进程想要执行任务,必须得有线程(每个进程至少有一个线程)

线程是进程的基本执行单元,一个进程(程序)的所有任务都在线程中执行

比如使用酷狗播放音乐、使用迅雷下载电影,都需要在线程中执行

线程.png

线程的串行

一个线程中任务的执行是串行的。如果要在一个线程中执行多个任务,那么只能一个一个按照顺序去执行。在同一时间里,一个线程只能执行一个任务。

在一个线程中下载3个文件(分别是A、B、C)

多线程下载.png

二、多线程

多线程:一个进程中可以开启多个线程。每条线城可以并行(同时)执行不同的任务。

进程 -> 车间,线城 -> 车间工人

同时开启3条线程分别下载3个文件(A、B、C)

并发和并行.png

多线程原理

同一时间,CPU只能处理一条线程,只有一条线程在工作(执行),多线程并发(同时)进行,其实是CPU快速地在多条线程之间调度(切换),如果CPU调度线程的时间足够快,就造成了多线程并发访问的假象。

思考:如果线程非常多,会发生什么情况?

CPU在N多线程之间调度,CPU会累死,消耗大量CPU资源。每条线程被调度执行的频次会降低(线程执行的效率降低)

多线程的优点

  • 能适当提高程序执行效率
  • 能适当提高资源利用率(CPU、内存利用率)

多线程的缺点

  • 开启线程需要占用一定的内存空间(默认情况下下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量内存空间,降低程序性能。
  • 线程越多,CPU在调度线程上的开销就越大
  • 程序设计更加复杂:比如线程之间的通信,多线程的数据共享

并发与并行

并行:多个CPU实例或多台机器同时执行一段处理逻辑,是真正的同时

并发:通过CPU调度算法,让用户看上去同时执行,实际上从CPU操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源网网产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。

线程安全:经常用来描述一段代码。指在并发情况下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只关注系统的内存,CPU是不是够用即可。反过来,线程不安全就意味着线程的调度顺序会影响最终结果。

如不加事务的转账代码

void transferMoney(User from, User to, float amount){
  to.setMoney(to.getBalance() + amount);
  from.setMoney(from.getBalance() - amount);
}

同步:Java中的同步指的是人为控制和调度,保证共享资源的多线程访问完成线程安全,来保证结果的准确。如上面的代码简单加上@synchronized关键字。在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能。

三、线程的状态

线程状态转换.jpeg
  1. 新建状态(NEW):新创建一个线程对象。

  2. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的 start() 方法,该状态的线程位于可运行线程池中,等待获取CPU的使用权。

  3. 运行状态(Running):就绪状态的线程获取了CPU,执行代码程序。

  4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

    阻塞分三种:

    • 等待阻塞:运行到线程执行了 wait() 方法,JVM会把该线程放入等待池中。
    • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别到线程占用,则JVM会把该线程放入锁池中。
    • 其他阻塞:运行到线程执行sleep()或join()方法,或者发出I/O请求时,JVM会把线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时或者I/O处理完毕时,线程重新转入就绪状态。
  5. 死亡状态(Dead):线程执行完了或者因为异常退出了run()方法,该线程结束生命周期。

四、创建线程的三种方式

  1. 继承Thread类
  2. 实现Runnable接口
  3. 使用Callable和Future

继承Thread类

  1. 定义Thread类的子类,并重写run方法,该方法的方法体是需要线程完成的任务
  2. 创建Thread子类的实例,也就创建了线程的对象
  3. 启动线程,即是调用线程的start()方法。

注意:线程的start()和run()方法都可以被线程实例调用,只有调用start()方法才是开启线程,拥有线程的特性,而只调用run()方法,相当于普通方法。

public class MyThread extends Thread{//继承Thread类
  public void run(){
    //重写run方法
  }

}
public class Main {
  public static void main(String[] args){
    new MyThread().start();//创建并启动线程
  }
}

实现Runnable接口

  1. 定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体
  2. 创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象
  3. 第三部依然是通过调用线程对象的start()方法来启动线程
public class MyThread2 implements Runnable {//实现Runnable接口
  public void run(){
    //重写run方法
  }
}
public class Main {
  public static void main(String[] args){
    //创建并启动线程
    MyThread2 myThread=new MyThread2();
    Thread thread=new Thread(myThread);
    thread().start();
    //或者    new Thread(new MyThread2()).start();
  }

}

使用Callable和Future

和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。

  1. call()方法可以有返回值
  2. call()方法可以声明抛出异常

Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。在Future接口里定义了几个公共方法来控制它关联的Callable任务。

  • boolean cancel(boolean mayInterruptIfRunning):视图取消该Future里面关联的Callable任务
  • V get():返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值
  • V get(long timeout,TimeUnit unit):返回Callable里call()方法的返回值,最多阻塞timeout时间,经过指定时间没有返回抛出TimeoutException
  • boolean isDone():若Callable任务完成,返回True
  • boolean isCancelled():如果在Callable任务正常完成前被取消,返回True

创建病启动有返回值的线程

  1. 创建Callable接口的实现类,并实现call()方法,然后创建该类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)
  2. 使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
  3. 使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
  4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
public class Main {
  public static void main(String[] args){
   MyThread3 th=new MyThread3();
   //使用Lambda表达式创建Callable对象
    //使用FutureTask类来包装Callable对象
   FutureTask<Integer> future=new FutureTask<Integer>(
    (Callable<Integer>)()->{
      return 5;
    }
    );
   new Thread(task,"有返回值的线程").start();//实质上还是以Callable对象来创建并启动线程
    try{
    System.out.println("子线程的返回值:"+future.get());//get()方法会阻塞,直到子线程执行结束才返回
    }catch(Exception e){
    ex.printStackTrace();
   }
  }
}

三种创建线程方式的对比

实现Runnable和实现Callable接口的方式基本相同,不过Callable执行call()方法有返回值,Runnable执行run()方法无返回值,因此可以把这两种方式归为一种这种方式与继承Thread类的方法之间的差别如下:

1、线程只是实现Runnable或实现Callable接口,还可以继承其他类。

2、这种方式下,多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。

3、但是编程稍微复杂,如果需要访问当前线程,必须调用Thread.currentThread()方法。

4、继承Thread类的线程类不能再继承其他父类(Java单继承决定)。

注:一般推荐采用实现接口的方式来创建多线程

参考:

https://www.cnblogs.com/3s540/p/7172146.html

https://www.cnblogs.com/yxt9322yxt/p/4804026.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容

  • 多线程 多线程概念 进程:正在进行中的程序 线程:是进程中一个负责程序执行的控制单元(执行路径)一个进行中可以有多...
    聽見下雨的_聲音阅读 198评论 0 0
  • 线程与进程 线程:进程中负责程序执行的执行单元 线程本身依靠程序进行运行线程是程序中的顺序控制流,只能使用分配给程...
    CHSmile阅读 212评论 0 0
  • 你真的了解线程吗?创建线程的常用方式有哪些?为什么不能重复调用 Start 方法?什么是单继承的局限?生产者与消费...
    极速24号阅读 429评论 0 0
  • 简介 在接触多线程之前,在我们程序中在任意时刻都只能执行一个步骤,称之为单线程。在单线程开发的程序中所有的程序路径...
    mghio阅读 125评论 0 0
  • 1. 场景困惑: 在主线程中开启一个线程t1 , 那么我如何能够获取这个线程t1的执行状态:是否开始执行?是...
    _Danniel_阅读 309评论 0 0