3y多线程

一 .什么是多线程

1.1 进程

window上任务管理器中显示的都是进程;
定义:程序的一次执行,资源分配和调度的独立单位,但是线程实现多处理非常耗费CPU资源,所以引入线程代替进程的部分调度功能,作为调度和分派的基本单位。
同一个进程内又可以执行多个任务,每个任务都可以看为一个线程

总结

  • 进程是资源分配的基本单位;
  • 线程是资源调度的基本单位;执行、
    就绪、阻塞;派生、阻塞、激活、调度、结束;
  • 多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈
    image.png

    堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
    线程状态.png

    多线程的存在,不是提⾼程序的执⾏速度。其实是为了提⾼应⽤程序的使⽤率,程序的
    执⾏其实都是在抢CPU的资源, CPU的执⾏权。多个进程是在抢这个资源, ⽽其中的某⼀个进程如果执
    ⾏路径⽐较多,就会有更⾼的⼏率抢到CPU的执⾏权
1.2 并行域并发
多核多进程.png
1.3 java实现多线程

基于Thread类:

image.png

创建线程的两种方式,一种是继承Thread类并重写run方法,然后这个类实例.start启动线程;
另一种是实现Runnable或Callable接口重写run方法,new Thread(p).start()
image.png

package Threey;

/**
 * @author : liulinzhi
 * @date: 2020/09/17/19:34
 * @description:
 */
public class ThreadDemo {
    public static class MyRunnable implements Runnable {
        @Override
        public void run() {
            for (int x = 0; x < 100; x++) {
                System.out.println(x);
            }
        }
    }
    public static class MyThread extends Thread {
        @Override
        public void run() {
            for (int x = 0; x < 200; x++) {
                System.out.println(x);
            }
        }
    }

    public static void main(String[] args) {
// 创建两个线程对象
//        MyThread my1 = new MyThread();
//        MyThread my2 = new MyThread();
//        my1.start();
//        my2.start();

        // 创建MyRunnable类的对象
        MyRunnable my = new MyRunnable();
        Thread t1 = new Thread(my);
        Thread t2 = new Thread(my);
        t1.start();
        t2.start();
    }
}

1.4 细节

run()和start()区别:

  • run方法:封装被线程执行的代码,直接调用就是个普通方法;
  • start方法:首先启动线程,然后由JVM调用run方法

JVM启动是多线程:因为至少启动main和垃圾回收线程

一般我们用实现Runnable接口:

  • 避免单继承限制
  • 将并发运行任务和运行机制解耦

二. Thread类解析

2.1 设置线程名

查看线程名:Thread.currentThread().getName();
下面我们查看源码,看默认的名字是怎么取得?

首先进入Thread()无参构造
public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
可以看到默认名字是"Thread-" + nextThreadNum()

而
private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }

private static int threadInitNumber;
这个变量表示线程初始化得数量
也就是Thread-i

我们可以用实现Runnable接口得对象和自定义线程名字来创建线程
public Thread(Runnable target, String name) {
        init(null, target, name, 0);
    }

也可以用setName修改名字
public final synchronized void setName(String name) {
        checkAccess();
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;
        if (threadStatus != 0) {
            setNativeName(name);
        }
    }
2.2 守护线程

守护线程为其他线程服务,比如垃圾回收线程;
特点:

  • 别的用户线程执行完毕,虚拟机会退出,守护线程也就被停止
  • 也就是说没有服务对象就没必要继续运行了

1)线程启动前可以用setDaemon(boolean on)来设置当前线程为守护线程
2)守护线程不要访问共享资源,因为它随时会挂
3)守护线程中产生得新线程也是守护线程

2.3 优先级线程

优先级高==获取CPU时间片概率高,并不意味者必定先执行

java默认初始优先级是5,共有10个优先级
/**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;


可以用setPriority
public final void setPriority(int newPriority) {
        ThreadGroup g;
        checkAccess();
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
            throw new IllegalArgumentException();
        }
        if((g = getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            setPriority0(priority = newPriority);
        }
    }
2.4 线程得生命周期
  • sleep方法:让线程进入计时等待状态,时间到了进入就绪态而不是运行态
    sleep.png
  • yield方法:让别的线程限制性,但不是必定会让出CPU


    yield.png
  • join方法:等待该线程执行完毕后才执行别的线程

三 .使用多线程需要注意的问题

3.1 使用多线程遇到的问题
  • 线程安全问题:因为多个线程操作共享变量或一些组合操作很可能出问题;
  • 性能问题:比如无脑添加同步锁,很可能会造成效率低,或是死锁问题
3.2 解决问题
  • 不设置共享变量
  • 使用final修饰
  • 加锁
  • JDK提供的线程安全类:比如AtomicLong满足原子性,ConcurrentHashMap等

3.3 原子性和可见性

3.3.1原子性(某个不可分割的操作)

比如自增操作:读取主存值,寄存器中+1,赋回变量——三步;就会产生原子性问题;

  • 用AtomicXxx包解决:


    AtomicXxx.png
3.3.2 可见性(修改对所有线程可见)

多线程环境下:当某个线程让变脸修改,其他线程都会知道,

  • 解决:volatile,保证可见性和防止指令重排序
  • 原理:缓存一致性思路:当CPU写数据时,如果发现操作的变量时共享变量,即其他线程的工作内存也存在该变量,于是会发信号通知其他CPU该变量的内存地址无效。当其他线程需要使用这个变量时,如内存地址失效,那么它们会在主存中重新读取该值。
  • 不保证原子性:volatile保证了读写一致性。但是当线程2已经使用旧值完成了运算指令,且将要回写到内存时*,是不能保证原子性的。
    image.png
  • 为什么普通变量不能保证可见性:
    Java内存模型通常在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值,依赖主内存作为传递媒介的方式实现可见性的。无论是普通变量还是volatile变量都是如此,普通变量和volatile变量的唯一区别就是:volatile的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此volatile保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。

四 .synchronized锁和lock锁

4.1 synchronized锁

  • 互斥锁:一次只允许一个线程进入被锁代码块
  • 每个对象都有一个内置锁,而synchronized就是用对象的内置锁来把代码块锁住
4.1.1 原理

主要分为同步方法和同步代码块:


两种.png

分别由monitorenter和monitorexit指令实现同步代码块;由ACC_SYNCHRONIZED实现同步方法:


同步代码块实现.png
4.1.2 使用
  • 修饰普通方法(锁类对象)
  • 修饰代码块(锁this),也可以自定义用哪个对象来锁
  • 修饰静态方法(类锁).class

这里要区分一下类锁和对象锁:分别获取二者的线程不冲突

不冲突,不互斥.png

4.1.3 重入锁
  • 比如父类有一个同步方法,子类也有一个同步方法:此时一个线程进入子类的同步方法,而这个子类同步方法中调用了父类的同步方法——显然实例对象的锁还没有解开,但调用父类的同步方法不需要一把锁,因为锁的持有者是线程,此线程已经由对象锁了,所以需要的时候直接开锁进去
4.1.4 释放锁的时机
  1. 方法(代码块)执行完毕后自动释放锁
  2. 当一个线程执行的代码出现异常时,锁自动释放

4.2 Lock锁

  • 特性:


    特性.png

synchoronized和lock如何选择?

JDK1.6之后对synchoronized做了各种优化,优化操作:适应⾃旋锁,锁消除,锁粗化,轻量级锁,偏向锁,所以二者性能差别不大,所以可以常用

4.3 公平锁:按照线程发出请求的顺序获取锁;

非公平锁:可以“插队”

  • lock和synchronized默认是非公平锁;

4.4 总结

  • synchronized好⽤,简单,性能不差
  • 没有使⽤到Lock显式锁的特性就不要使⽤Lock锁了。

五 .AQS

JUC(java util concurrent)下的locks包中有


第二个就是常说的AQS抽象类.png
  • 我们的ReentrantLock\ReentrantReadWriteLock都是基于AQS实现的;

5.1 特性:

  • AQS其实就是⼀个可以给我们实现锁的框架
  • 内部实现的关键是: 先进先出的队列、 state状态
  • 定义了内部类ConditionObject
  • 拥有两种线程模式
    独占模式
    共享模式
  • 在LOCK包中的相关锁(常⽤的有ReentrantLock、 ReadWriteLock)都是基于AQS来构建
  • ⼀般我们叫AQS为同步器

5.2 深入

六 .ReentrantLock和ReentrantReadWriteLock

七 线程池

八 死锁

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