JAVA并发编程(二):线程知识


1. 线程和进程

  • 进程是代码在数据集合上的一次运行活动, 是系统进行资源分配和调度的基本单位。
  • 线程则是进程的一个执行路径, 一个进程中至少有一个线程,进程中的线程共享进程的资源。线程是CPU 分配的基本单位。
  • Java中,多个线程共享进程的堆和方法区资源,每个线程有自己的程序计数器和栈区域。
  • 程序计数器
    (1) 记录了该线程让出CPU时的执行地址,待再次分配到时间片时线程就可以从计数器指定的地址继续执行。
    (2) 只有执行的是Java 代码时记录的才是下一条指令的地址,而对于native方法记录的是undefined 地址。
  • 栈资源
    (1) 存储该线程的局部变量
    (2) 存放线程的调用栈帧

  • (1) 堆里面主要存放使用new 操作创建的对象实例
    (2) 堆是被进程中的所有线程共享的
  • 方法区用来存放JVM加载的类、常量及静态变量等信息,是线程共享的。

2. 线程创建

  • 继承Thread 类并重写run方法
    (1) 创建完thread对象后该线程并没有被启动执行,调用了start 方法后才真正启动了线程。
    (2) start 方法后线程处于就绪状态,等待获取CPU 资源。
    (3) 在run()方法内可以使用this获取当前线程。
  //创建线程
  MyThread thread= new MyThread();
  // 启动线程
  thread .start();
  • 实现Runnable接口的run 方法
    (1) 多个线程可共用一个task 代码逻辑。
    (2) 在实现接口的同时可以继承其他类。
  RunableTask task =new RunableTask();
  new Thread(task).start() ;
  new Thread(task).start() ;
  • 使用FutureTask 方式
    (1) 实现Callable 接口的call()方法。
    (2) 可以拿到任务的返回结果。
  static class CallerTask implements Callable<String>{
    @Override
    public String call() throws Exception{}
  }
  //创建异步任务
  FutureTask<String> futureTask =new FutureTask<>(new CallerTask()) ;
  //启动线程
  new Thread(futureTask).start () ;
  try {
    //等待任务执行完毕,并返回结果
    String result = futureTask.get ();
  } catch (ExecutionException e) {}

3. 线程通知与等待

  • wait()函数
    当一个线程调用一个共享变量的wait()方法时,该调用线程会被阻塞挂起,直到发生以下情况才返回:
    (1) 其他线程调用了该共享对象的notify()或者notifyAll()方法;
    (2) 其他线程调用了该线程的interrupt()方法,该线程抛出InterruptedException异常返回。

    调用wait()方法的线程需要先获取该对象的监视器锁,否则会抛出IllegalMonitorStateException异常
    通过以下方式获取监视器锁
    (1) 执行synchronized 同步代码块时, 使用该共享变量作为参数。
    (2) 调用该共享变量的方法,并且该方法使用了synchronized 修饰。


4. 线程睡眠

  • sleep方法:
    (1) 当一个执行中的线程调用了Thread 的sleep方法后,会暂时让出指定时间的执行权,不参与CPU的调度
    (2) 该线程拥有的监视器资源,比如锁还是持有不会让出。
    (3) 睡眠时间到了后该线程处于就绪状态,等待获取cpu资源。
    (4) 在睡眠期间其他线程调用了该线程的interrupt方法中断了该线程,则该线程会在调用sleep方法的地方抛出IntermptedException异常而返回。
  • yield方法:
    (1) 线程调用yield 方法时,是在暗示线程调度器当前线程请求让出自己的CPU 使用,让线程调度器现在就可以进行下一轮的线程调度,但是线程调度器可以无条件忽略这个暗示。
    (2) 调用yield 方法时后,当前线程会让出CPU 使用权,然后处于就绪状态,线程调度器下一次调度时就有可能调度到当前线程执行。

5. 线程中断

  • Java 中的线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接终止该线程的执行, 而是被中断的线程根据中断状态自行处理。

  • interrupt方法:中断线程
    (1) 线程B可以调用线程A的interrupt方法来设置线程A 的中断标志为true并立即返回。
    (2) 如果线程A 因为调用了wait 系列函数、join 方法或者sleep 方法而被阻塞挂起,会在调用这些方法的地方抛出InterruptedException 异常而返回。

  • 线程上下文切换时机有:
    (1) 当前线程的CPU 时间片使用完处于就绪状态时
    (2) 当前线程被其他线程中断时


6. 线程死锁

  • 死锁是指两个或两个以上的线程在执行过程中,因争夺对方已经持有的资源而造成的互相等待的现象。

  • 死锁的产生必须具备四个条件:
    互斥条件:该资源同时只由一个线程占用。
    请求并持有条件: 指一个线程己经持有了至少一个资源, 但又提出了新的资源请求,而新资源己被其他线程占有,所以当前线程会被阻塞,但阻塞的同时并不释放自己已经获取的资源。
    不可剥夺条件: 指线程获取到的资源在自己使用完之前不能被其他线程抢占。
    环路等待条件:发生死锁时, 必然存在一个线程-资源的环形链。

  • 避免死锁:
    (1) 只有请求并持有和环路等待条件是可以被破坏的。
    (2) 造成死锁的原因其实和申请资源的顺序有很大关系, 使用资源申请的有序性原则就可以避免死锁。
    (3) 资源申请的有序性:假如线程A 和线程B 都需要资源1, 2, 3, ... , n 时,对资源进行排序,线程A 和线程B 只有在获取了资源n-1 时才能去获取资源n 。


6. 守护线程与用户线程

  • Java 中的线程分为两类,daemon 线程(守护线程〉和user 线程(用户线程)。
  • 守护线程
    (1) 在JVM启动的同时,启动了好多守护线程, 比如垃圾回收线程
    (2) 守护线程是否结束并不影响JVM的退出,只要用户线程都退出了,就会终止JVM进程。如果你希望在主线程结束后JVM进程马上结束,那么在创建线程时可以将其设置为守护线程。
    (3) 设置守护线程: thread.setDaemon(true) ;
    (4) main线程运行结束后, JVM会自动启动一个叫作DestroyJava VM 的线程, 该线程会等待所有用户线程结束后终止JVM进程。
    (5) 在默认情况下, tomcat的接受线程和处理线程都是守护线程, 这意味着当tomcat收到shutdown 命令后并且没有其他用户线程存在的情况下tomcat 进程会马上消亡,而不会等待处理线程处理完当前的请求。

7. ThreadLocal

  • 提供了线程本地变量,如果创建了一个ThreadLocal 变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。
  • 使用
//创建ThreadLocal 变量
ThreadLocal<String> localVariable = new ThreadLocal<> () ;
//在线程1中
localVariable.set("threadOne local variable");
//在线程2中
localVariable.set("threadTwo local variable");
//在某个线程中获取变量
localVariable.get()
  • 原理
    (1) 在每个线程内部都有一个名为threadLocals 的成员变量, 该变量的类型为HashMap,用于存储线程本地变量。key 为我们定义的ThreadLocal变量的this引用, value 则为我们使用set 方法设置的值。
    (2) ThreadLocal只是相当于一个工具类,它通过set 方法把value 值放入调用线程的threadLocals 里面并存放起来, 当调用线程调用它的get 方法时,再从当前线程的threadLocals 变量里面将其拿出来使用。
    (3) 为了防止内存溢出,在使用完后,记得通过ThreadLocal的remove方法移除变量。
    (4) 同一个ThreadLocal 变量在父线程中被设置值后, 在子线程中是获取不到的。

  • InheritableThreadLocal: 让子线程能访问到父线程中的值
    (1) InheritabIeThreadLocal 继承了ThreadLocal,set和get操作的是该线程的成员变量inheritableThreadLocals。
    (2) 在创建线程时,会判断当前线程(父线程)中的inheritableThreadLocals变量是否为空,如果不为空,会把父线程的inheritableThreadLocals 成员变量的值复制到子线程的inheritableThreadLocals变量中。这样子线程就拥有了一份父线程的可继承的变量的副本。
    (3) 父线程和子线程的相互影响关系可以查看https://blog.csdn.net/v123411739/article/details/79117430
    对于可变对象:父线程初始化, 因为Thread Construct浅拷贝, 共用索引, 子线程修改父线程跟着变; 父线程不初始化, 子线程初始化, 无Thread Construct浅拷贝, 子线程和父线程都是单独引用, 不同对象, 子线程修改父线程不跟着变。
    对于不可变对象:不可变对象由于每次都是新对象, 所以无论父线程初始化与否,子线程和父线程都互不影响。

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