Java多线程总结

多线程基础概念介绍

进程
是程序或任务的执行的过程,具有动态性;
它持有资源(共享内存,共享文件)和线程
如:打开QQ、搜狗输入法软件时,我们启动了两个进程。

线程
系统中最小的执行单元。 比如一个软件里边的各种任务就是线程;
同一进程中有多个线程;
线程共享进程的资源。

多线程
一个进程中可以开启多条线程,多条线程可以并行(同时)执行不同的任务;
多线程技术可以提高程序的执行效率。

线程创建的两种方式比较

  • 继承Thread类
public class ExtendThread {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.start();
//      mt.start();
        MyThread mt2 = new MyThread();
        mt2.start();
    }

}

class MyThread extends Thread{
    
    @Override
    public void run(){
        System.out.println("使用extends创建线程!!");
    }
}

  • 实现Runnable接口
public class ImplementThread {
    public static void main(String[] args) {
        Mythread2 mt = new Mythread2();
        Thread th = new Thread(mt);
        th.start();
        Thread th2 = new Thread(mt);
        th2.start();
    }
}

class Mythread2 implements Runnable{

    @Override
    public void run() {
        System.out.println("使用implements创建线程!");
    }
    
}

注意事项:同一线程的start()方法不能重复调用
start()方法相关源码如下:

public synchronized void start() {
        if (threadStatus != 0)  //start()方法不能被重复调用,否则会抛出异常
            throw new IllegalThreadStateException();
        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);  //加入到线程组中
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
}
private native void start0();

附:Thread常用方法介绍

Thread常用方法

两种实现方式对比:

  • Runnable方式可以避免Thread方式由于Java单继承特性带来的缺陷

  • Runnable方式实现可以被多个线程共享,适合于多个线程处理同一资源的情况

线程的生命周期

下图是线程生命周期的详细状态转换图(注意:不能从阻塞状态直接到运行状态)


线程的生命周期

下面将对每一个状态进行简单的介绍

  • 创建
    新建一个线程对象,如Thread td = new Thread();

  • 就绪
    创建线程对象之后,调用start()方法(注意:此时线程只是进入了线程队列,等待获取CPU服务,具备了运行条件,但不一定已经开始运行了

  • 运行
    处于就绪状态的线程,一旦获取CPU资源,就会进入运行状态,开始执行run()方法里面的逻辑

  • 阻塞
    一个正在执行的线程在某些情况下,由于某种原因而暂时让出了CPU资源,暂停了自己的执行,便进入了阻塞状态。如:调用了sleep()方法

  • 终止
    线程的run()方法执行完毕,或者线程调用了stop()方法(现在已经不推荐使用这种方式),线程便进入了终止状态

附:更加详细的线程状态转换图

更加详细的线程状态转换图

守护线程介绍

Java线程有两类

  • 用户线程
    运行在前台,执行具体的任务。如:用户的主线程,连接网络的子线程等

  • 守护线程
    运行在后台,为其他前台线程服务。
    特点:
    一旦所有用户线程都结束运行,守护线程也会随JVM一起结束工作
    应用:
    数据库连接池中的监测线程
    JVM虚拟机启动后的监测线程
    常见:
    垃圾回收线程
    使用方法:
    调用Thread类的setDaemon(true)方法来设置当前线程为守护线程
    注意事项:
    ①setDaemon(true)必须在start()方法之前调用,否则会抛出异常IllegalThreadStateException
    ②在守护线程中产生的新线程也是守护线程
    ③不是所有的任务都可已分配给守护线程来执行,如:读写操作或计算逻辑

内存可见性

原子性
跟数据库事务的原子性概念差不多,即一个操作(有可能包含有多个子操作)要么全部执行(生效),要么全部都不执行(都不生效)。

可见性
一个线程对共享变量值的修改,能够及时地被其他线程看到。

共享变量
如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。

Java内存模型(JMM)
描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层细节。

Java内存模型

注意:

  • 所有的变量都存储在主内存中
  • 每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(即主内存中共享变量的副本)

共享变量可见性实现原理

共享变量可见性实现原理

线程1对共享变量的修改想要被线程2及时看到,必须经过以下两个步骤:

  • 把工作内存1中更新过的共享变量刷新到主内存中
  • 将主内存中最新的共享变量的值更新到工作内存2中

Java语言层面实现可见性的两种方式

  • synchronized
    synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程A每次运行到这个方法时,都要检查有没有其它正在用这个方法的线程B(或者C D等),有的话要等正在使用这个方法的线程B(或者C D)运行完这个方法后再运行此线程A,如果没有其他线程正在用这个方法则线程A可以运行这个方法。

线程执行互斥代码的过程:
①获得互斥锁
②清空工作内存
③从主内存拷贝变量的最新副本到工作内存
④执行代码
⑤将更改后的共享变量的值刷新到主内存
⑥释放互斥锁

synchronized 关键字的两种用法
synchronized 方法
通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:

public synchronized void accessVal(int newVal);

synchronized 块
通过 synchronized关键字来声明synchronized 块。语法如下:

synchronized(syncObject)
{  
      //允许访问控制的代码
}

对synchronized (this)的一些理解(this指的是调用这个方法的对象)

  1. 当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
  2. 然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块
  3. 尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
  4. 第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
  5. 以上规则对其它对象锁同样适用。

注意:
线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存重新读取最新的值
线程解锁时,必须把共享变量的最新值刷新到主内存中

  • volatile
    能够保证volatile变量的可见性;不能保证volatile变量复合操作的原子性

实现方式:
通过加入内存屏障和禁止重排序优化实现
①对volatile变量执行写操作时,会在写操作后加入一条store屏障指令
②对volatile变量执行读操作时,会在读操作前加入一条load屏障指令

线程写volatile变量的过程
①改变线程工作内存中volatile变量副本的值
②将改变后的副本的值从工作内存刷新到主内存
线程读volatile变量的过程:
①从主内存中读取volatile变量的最新值到线程的工作内存中
②从工作内存中读取volatile变量的副本的值

适用场合:
①对变量的写操作不依赖其当前值
不满足:number++、count = count+5
满足:boolean变量,记录温度变化的变量

②变量没有包含在具有其它变量的不变式中
不满足:不变式 low < up

两种实现方式比较

  • volatile不用加锁,比synchronized更加轻量级,不会阻塞线程;
  • 从内存可见性分析,volatile读操作相当于加锁,volatile写操作相当于解锁;
  • synchronized既能保证可见性,又能够保证原子性;而volatile只能保证可见性,不能够保证原子性

参考资料

[1]Java总结篇系列:Java多线程(一)
[2]深入理解java中的synchronized关键字
[3]java中synchronized的用法详解

补充问题

1.Thread类的yield方法作用
yield()应该做的是让当前运行线程回到可运行状态,只允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

注意:sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。而yield()方法只能让同优先级的线程有执行的机会。

2.为什么每个线程要使用工作内存,而不直接访问主内存
①为了减少各线程对主内存的频繁访问,各线程只需要访问自己的工作内存即可以获取共享变量的副本值,将访问压力分摊到各线程的工作内存中,主内存只需要按照一定的规则同步共享变量的值到各线程的工作内存。
注意:
“synchronized” — 保证在块开始时都同步主内存的值到工作内存,而块结束时将变量同步回主内存

3.happens-before原则

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

推荐阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,182评论 11 349
  • 二、共享受限资源 有了并发就可以同时做多件事情了。但是,两个或多个线程彼此互相干涉的问题也就出现了。如果不防范这种...
    端木轩阅读 495评论 0 0
  • 一、认识多任务、多进程、单线程、多线程 要认识多线程就要从操作系统的原理说起。 以前古老的DOS操作系统(V 6....
    GT921阅读 1,011评论 0 3
  • [cp]在这超现实的时代 青涩的爱情无处不在 没有世俗没有背叛 只有每晚通话的爱恋 时间一点一点过 他们也只有享受...
    听说那个梦很遥远阅读 118评论 0 0
  • 前言:会员卡的线上营销方案不仅仅在推广会员卡这一个服务,其中还必须有品牌推广和拉新客户、活跃老客户一并进行,才可以...
    宇蓝酱阅读 2,511评论 0 0