Java多线程

一、基本概念

1、CPU核心数与线程数的关系

一般来说是1:1的关系 即1个核心对应1个线程,但我们在程序中可以创建多个线程的原因是由于CPU的时间片调度

2、CPU时间片轮转(RR调度)

把CPU的运行时间进行切片分别轮转到各个线程

3、进程和线程

进程:操作系统对资源分配的最小单位

线程:CPU调度的最小单位

进程>线程,线程不能单独存在,必须要依附于进程存在

线程数量限制:在操作系统层面Linux限制为1000,Windows限制为2000

4、并行和并发

(1)并行:同时执行(例如高速公路的4车道,并行数就是4)

(2) 并发:单位时间内的吞吐量(与并行的关键区别就在于时间限制),CPU的并发能力取决于CPU时间片的切换速度

二、多线程

1、线程的启动方式

Java中有三种线程启动的方式

(1)、继承Thread类

image

(2)、实现Runnable接口

image

(3)、实现Callable接口

image

实现Runnable和Callable接口的不同在于,实现Callable是允许有返回值的;以上三种创建线程的方式,最后都是通过Thread类进行开启,在Java中只有Thread类是线程的创建和实现类。

2、线程的结束方式

(1)、stop方式

image
stop方法被官方定义为弃用的;因为stop方法会强制退出线程,可能会导致线程中的其它资源未被正确释放等安全问题。

(2)、suspend方式

将线程挂起,同样也是被JDK弃用的;因为susbend方法不会释放锁,容易导致死锁发生

(3)、interrupt

Thread的成员方法,不会立刻导致线程退出,只会将线程的中断标志位置为true

(4)、interrupted

Thread的静态方法,返回boolean值,除具有isInterrupted正常功能外,还会重置中断标志位为false

(5)、isInterrupted

Thread的成员方法,判断线程是否被中断
一个线程退出的例子:
image
调用interrupt方法将线程标志位置为true,用isInterrupted检测当前的线程标志位;当然在实际开发当中也可以通过一些boolean标志位进行控制。

3、线程的生命周期、线程调度、等待唤醒、ThreadLocal相关

(1)生命周期

线程的五种基本状态:新建、就绪、运行、阻塞、死亡

image

当我们new一个线程并调用start方法时,该线程就从新建到就绪(Runnable)状态;当线程获取到CPU的时间片后进入到执行状态(Running);该线程调用yield方法或者时间片执行完毕后,从Running切换到Runnable状态;该线程调用wait(等待,释放锁)、sleep(休眠、不释放锁)、join(放弃执行让其他线程先执行)时会进入到阻塞(Blocked)状态;线程执行完毕后进入到死亡(Dead)状态。

(2)yield方法

使当前线程放弃时间片暂停执行,并执行其它线程;但由于CPU轮询速度较快,很可能马上又会轮询到当前线程,效果不明显

注:yield方法不会释放锁

(3)join方法

主要作用是线程的同步,使得线程从并行改为串行执行,例如线程A中执行线程B的join方法,表示线程B执行完成后才会继续执行线程A

eg:线程A、线程B、线程C三个线程,实现依次输出A、B、C中的内容

image
image

join方法的实现原理:
正常调用join方法实际上调用join(0)

public final void join() throws InterruptedException {
        join(0);
    }

join参数是一个delay时间,默认是0,但wait(0)不是等待0ms,而是一直等待。

void join():当前线程等该加入该线程后面,等待该线程终止。
void join(long millis):当前线程等待该线程终止的时间最长为 millis 毫秒。 如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度。
void join(long millis,int nanos):等待该线程终止的时间最长为 millis 毫秒 + nanos纳秒。如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度。

join的原理为当在线程A中调用了线程B的join方法,线程A会执行wait方法,释放锁并等待线程B的执行结束

public final void join(long millis) throws InterruptedException {
        synchronized(lock) {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
        //传入的是0 默认执行这个判断
            while (isAlive()) {//判断线程是否存活 native方法返回的boolean变量
                lock.wait(0);//执行wait方法 释放锁 并等待
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                lock.wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
        }
    }

(4)wait、notify、notifyAll方法

wait、notify、notifyAll被用于线程之间的协作,等待和唤醒;这几个方法都是Object而不是Thread的
注:为什么wait、notify、notifyAll要被设计成为Object下的方法而不是Thread的
wait、notify、notifyAll使用的前提必须在同步代码块中,但Synchronized中的锁可以为任意对象,因此wait notify notifyAll放在所有类的父类Object中,方便管理。

wait用于线程的等待,与sleep的区别在于wait会释放锁,sleep不会释放锁
notify和notifyAll用于线程的唤醒,不会释放锁;notify用于唤醒一个线程,notifyAll会通知所有线程

等待通知的标准范式
1)等待方
a 获取对象锁(同步)
b 检查条件 条件不满足 wait
c 条件满足 执行业务代码

2)通知方
a 获取对象的锁(同步)
b 修改条件
c 通知等待方

以一个生产者消费者为例:

package com.hzf.demo;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.LinkedBlockingQueue;

public class MainTest {
    public static void main(String[] args) throws InterruptedException,
            ExecutionException {
        LinkedBlockingQueue<Integer> linkedBolckingQueue = new LinkedBlockingQueue<>(
                10);
        new ConsumerThread(linkedBolckingQueue).start();
        new ProducterThread(linkedBolckingQueue, 10).start();
    }

    private static class ConsumerThread extends Thread {
        private LinkedBlockingQueue<Integer> mQueue;

        private ConsumerThread(LinkedBlockingQueue<Integer> queue) {
            this.mQueue = queue;
        }

        @Override
        public void run() {
            super.run();
            while (true) {
                //获取同步锁
                synchronized (mQueue) {
                    while (!mQueue.isEmpty()) {
                        System.out.println("消费了1个");
                        mQueue.remove();
                        mQueue.notify();
                        try {
                            sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    try {
                        mQueue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private static class ProducterThread extends Thread {
        private LinkedBlockingQueue<Integer> mQueue;
        private int mMaxSize;

        private ProducterThread(LinkedBlockingQueue<Integer> queue, int maxSize) {
            this.mQueue = queue;
            this.mMaxSize = maxSize;
        }

        @Override
        public void run() {
            super.run();
            while (true) {
                //获取同步锁
                synchronized (mQueue) {
                    while (mQueue.size() == mMaxSize) {
                        try {
                            mQueue.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    mQueue.add(1);
                    System.out.println("生产了1个");
                    // 如果生产量达到最大值 notify消费者消费
                    mQueue.notify();
                    try {
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

}

(5)ThreadLocal的使用

4、Java线程锁的简单介绍

Java锁分类.png

(1)可重入锁&不可重入锁

可重入锁:一个线程在外层方法中获取到了锁,进入内层方法后不需要再次获取锁
Synchronized和ReentrantLock都是可重入锁

(2)独享锁&共享锁

ReentrantLock是独享锁;ReentrantReadWriteLock是共享锁
独享锁也叫排它锁,指一个线程持有该锁之后,其它线程无法获取到该锁,持有锁的线程可以读、写数据
共享锁是指该锁可以被多个线程持有,以ReentrantReadWriteLock为例,当为读操作时,所有的线程都可以并发执行进行读操作,但无法修改数据;当为写操作时,所有其它的读写线程都会被排斥,无法进行读写操作。

(3)公平锁&非公平锁

ReentrantLock可以通过其构造方法指定当前锁为公平还是非公平锁


ReentrantLock.png

公平锁指先申请的线程先拿到锁,按照申请顺序获取锁
非公平锁不一定按照申请的顺序获取锁
非公平锁的有点在于减少唤起线程的开销,整体的吞吐效率高,但处于等待的线程有可能会被饿死

(4)乐观锁&悲观锁

乐观锁和悲观锁是一种广义上的概念,乐观锁认为自己在操作数据的同时没有其它线程在修改数据,仅仅在更新数据的时候去判断有没有别的线程更改过当前数据(CAS算法,Java的原子类递增等操作都是通过CAS实现的);悲观锁认为自己在使用数据的同时一定会有别的线程修改数据,因此在获取数据的时候要先加锁,确保数据不会被其它线程锁修改,Java的Synchronized和Lock的实现类都是悲观锁。

(5)偏向锁、轻量级锁、重量级锁

偏向锁:是指同一段代码被一个线程多次获取,那么这个线程可以自动获取到锁,降低获取锁的代价;
在大多数情况下,锁总是由同一线程多次获得,不存在多线程竞争,所以出现了偏向锁。其目标就是在只有一个线程执行同步代码块时能够提高性能。偏向锁在JDK 6及以后的JVM里是默认启用的。可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,关闭之后程序默认会进入轻量级锁状态。
轻量级锁:是指当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。
重量级锁:升级为重量级锁时,锁标志的状态值变为“10”,此时Mark Word中存储的是指向重量级锁的指针,此时等待锁的线程都会进入阻塞状态。

来自美团技术团队.png

一篇美团技术团队关于锁的讲解:https://tech.meituan.com/2018/11/15/java-lock.html

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

推荐阅读更多精彩内容

  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,957评论 1 18
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,454评论 1 15
  • 本文主要介绍线程的定义,创建,使用,停止,状态图和常用方法。主要用于概念扫盲和梳理。多进程是指操作系统能同时运行多...
    stoneyang94阅读 1,188评论 2 5
  • 林炳文Evankaka原创作品。转载自http://blog.csdn.net/evankaka 本文主要讲了ja...
    ccq_inori阅读 654评论 0 4
  • 大家都知道,虽然制度是一个让人反感的东西,但是如果没有制度,那么整个公司的运转很显然会如同一盘散沙。那么制度多了就...
    独木桥上的独行者阅读 227评论 0 0