【Java高级】Java多线程及实战脱坑指南

本文为原创文章,转载请注明出处
查看[Java]系列内容请点击:https://www.jianshu.com/nb/45938443

1.线程与进程的区别

简单理解,线程比进程更加轻量级,一个进程可以包含多个线程,实际上,甚至可以认为在Java程序运行的过程中,一个JVM只启动一个JVM进程,其他的程序运行都是以线程的方式存在。

进程之间无法共享变量,进程之间共享数据往往通过第三方的文件、数据库等进行共享;线程之间可以互相共享变量。

由于线程之间会互相共享变量,所以会出现变量的使用存在冲突的问题,使用不当的时候会造成一定程度上的死锁现象。

2.线程数量设置为多少合适?

这个一般是要看我们CPU的数量以及线程的类型,一般来说,线程的数量不应该比CPU数量多太多,太多没有意义,反而会造成CPU的频繁切换带来更大的开销。

而对于线程类型,我们主要分为CPU密集型(即计算密集型)和I/O密集型。

2.1.CPU密集型线程

对于CPU密集型线程,一般经验值设置线程数量为:

Z = N × ( X + Y ) ÷ X , 其中,N=CPU核数,X=单个线程需要的计算时间,Y=线程需要等待的时间

比如,对于4核CPU,单个线程计算时间为10,等待时间为4,那么Z=5.6,所以应该将线程数设置为6

2.2.I/O密集型线程

这类任务一般需要根据不同的I/O任务来进行设置,比如如果对于需要频繁随机读取机械硬盘数据的线程,线程数一般不宜设置过大,因为过大会导致系统的磁盘寻道时间过多,降低性能。如果是对于访问远程数据等,可以在网络带宽允许的情况下多开几个线程,从而实现并发访问。

3.线程的状态

线程具有6种状态,线程可以在不同的状态之间切换,分别如下:

  • 1、New新建状态:当我们创建了一个线程,线程还未开始的时候是新建状态。
  • 2、Runnable可运行状态:当调用线程的start()方法以后,线程就处于可运行状态,注意,可运行状态的线程可能在运行也可能不在运行,主要需要看有没有获得CPU的时间片。
  • 3、Blocked阻塞状态:线程等待获得线程锁,就处于这个状态,获得锁后重新回到Runnable状态。
  • 4、Waiting等待状态:等待线程被通知的状态,比如调用了Thread.join或者Object.wait()等方法,都会等待某个事件结束,然后给当前线程发通知,唤醒当前线程,当前线程被唤醒后处于Runnable状态。
  • 5、Timed waiting计时等待状态:调用了Thread.sleep()的线程处于计时等待状态,等待计时结束,结束后可以转回Runnable状态。
  • 6、Terminated终止状态:终止状态,线程终止。

还有一点需要说明的是,调用Thread.yield()方法可以让当前线程让出运行权,但是一般让出后也不能保证其他线程能继续运行,也完全有可能是当前线程继续运行,所以这个方法一般不用。

还有,在早期的Java版本中可以强制终止一个线程,现在已经被废除。
可以调用线程的interrupt方法来请求终止线程,但是线程是否响应请求是由线程内部决定的,而且这个方法会抛出异常,所以在实际使用的过程中,自己定义一个线程标志位,自己通过其他方式请求中断线程而不使用interrupt方法会让代码可读性更高一些。所以interrupt方法在实际使用过程中也尽量不要使用。

4.线程的底层同步机制

假设大家都已经清楚了线程的资源竞争的概念,如果有不清楚,请看我以前的一些文章:https://www.jianshu.com/p/55a13cf06194

在介绍我们常用的synchronized关键字之前,我们先来介绍其底层的实现原理。

4.1.重入锁和读写锁

对于需要竞争的资源,底层使用JVM的锁来进行资源的锁定,在java.util.concurrent.locks包中有一系列关于锁的类:

Lock lock = new ReentrantLock(); // 如果是对象级别的锁,lock就需要定义成对象的字段
lock.lock(); // 从这里开始,开始加互斥锁,其他企图获得lock锁的程序被迫转入【阻塞】状态
// do sth ... 临界区代码
lock.unlock(); // 锁释放,允许其他线程继续获得锁,让其他线程继续竞争锁

除了ReentrantLock锁以外,还有好几种其他的锁:

  • ReentrantLock implements Lock:重入锁,具有持有锁计数,被锁住的代码可以调用其他的具有相同锁的代码。
  • ReentrantReadWriteLock implements ReadWriteLock:读写锁,读写分别各一个锁,可实现读锁和写锁互不影响,具体的读锁和写锁依然是Lock实现。

4.2.条件变量(条件对象)

当线程进入到某个临界区之后,发现当前的条件不满足,一般来说处理办法是直接释放锁然后结束线程,但是如果要求线程继续等待,等待到满足条件再执行的话,就需要使用条件变量来实现了:

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
while(!YourCondition) condition.await();  // 从调用await开始该线程转入【阻塞】状态,并等待其他线程调用signalAll()
// do sth ... 临界区代码
condition.signalAll(); // 唤醒其他所有等待该条件的线程,重新竞争lock锁,并通过while循环重新检测条件
lock.unlock(); // 锁释放,允许其他线程继续获得锁,让其他线程继续竞争锁

如上面注释所说,while循环用来检测条件,条件不满足就会自动阻塞,并等待其他线程唤醒,唤醒后重新获得锁并重新检测条件,如此往复。

当没有线程唤醒阻塞的线程时,就会出现死锁现象。

5.synchronized关键字

为了避免手动加锁解锁的过程,Java提供了synchronized关键字来实现自动加锁。synchronized关键字可以加在一段代码块上、一个方法上等。Java从最底层的Object类开始,就内置了一个默认的锁(通过JNI实现),根据关键字位置加的不同,所获取的锁也不同:

synchronized(object){...} // 获取object对象的锁,持有该锁的所有代码都不能同时执行
public synchronized void f(){...} // 获取该对象的锁,该对象内所有拥有该锁的代码都不能同时执行
public static synchronized void f(){...} // 获取该类的Class对象锁,所有具有该所的代码不能同时执行

例如,我们看下面代码:

public class MainTest {

    public static void main(String[] args) throws Exception {
        MainTest test = new MainTest();

        // test1与test2持有的是test对象锁,所以test1获取锁期间,test2不能执行
        new Thread(test::test1).start();
        new Thread(test::test2).start();

        // staticTest1与staticTest2持有的是Class<MainTest>对象锁,所以staticTest1获取锁期间,staticTest2不能执行
        new Thread(MainTest::staticTest1).start();
        new Thread(MainTest::staticTest2).start();
    }

    public synchronized void test1() {
        for (int i = 0; i < 5; i++) {
            System.out.println("test1");
            sleep(1000);
        }
    }

    public synchronized void test2() {
        for (int i = 0; i < 5; i++) {
            System.out.println("test2");
            sleep(1000);
        }
    }

    public static synchronized void staticTest1() {
        for (int i = 0; i < 5; i++) {
            System.out.println("staticTest1");
            sleep(1000);
        }
    }

    public static synchronized void staticTest2() {
        for (int i = 0; i < 5; i++) {
            System.out.println("staticTest2");
            sleep(1000);
        }
    }

    public static void sleep(long time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出结果:

test1
staticTest1
test1
staticTest1
test1
staticTest1
staticTest1
test1
test1
staticTest1
test2
staticTest2
staticTest2
test2
staticTest2
test2
staticTest2
test2
staticTest2
test2

知道了以上内容之后,请自主实现一个需求:

已知一段代码的运行时间很长,所需耗费的CPU非常大,如果线程数量过多的话,会造成电脑卡机等问题,现在想让这段代码最多只允许x个线程运行,即并发量最高为x,如何实现呢?

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