java 网络编程connection timed out是什么意思_安琪拉教百里守约学并发编程之多线程基础...

《安琪拉与面试官二三事》系列文章
一个HashMap能跟面试官扯上半个小时

一个synchronized跟面试官扯了半个小时

《安琪拉教鲁班学算法》系列文章

安琪拉教鲁班学算法之动态规划

安琪拉教鲁班学算法之BFS和DFS

安琪拉教鲁班学算法之堆排序

《安琪拉教妲己学分布式》系列文章

安琪拉教妲己分布式限流

《安琪拉教百里守约学并发编程》系列文章

安琪拉教百里守约学并发编程之多线程基础

本文是来自读者群里@凯的建议,决定开一个并发编程的专栏,尊重读者安琪拉是认真的,为什么是教百里守约,也是因为读者群里@百里守约是安琪拉的忠实读者,每期必读,经常草丛里定点蹲安琪拉,然后抢板凳(放大招)。这期是《安琪拉教百里守约学并发线程》系列文章第一集多线程基础。
前言

并发编程应该是Java 后端工程必备的技能,在日常开发中用的好能提升系统吞吐量,提升业务逻辑执行效率,提高系统的响应性,简化程序结构,当然这把青龙偃月刀也不是随随便便就能耍的好,需要些内力。先放一张Java 并发工具包JUC的知识脑图,后面公众号【安琪拉的博客】《安琪拉教百里守约学并发线程》会按以下思维脑图详细介绍 JUC 的各部分组件实际使用场景以及组件特性:

1e2c2fbdcf926c43a08a59d800b14bcd.png
开场

百里守约:安琪拉,你熟悉线程(Thread)吗?和进程(Process)有什么区别?

安琪拉:熟悉啊!一个应用就是一个进程,一个进程可以包含多个线程,从操作系统层面看,同一个进程中的线程共享该进程的资源,例如内存空间和文件句柄。Linux 操作系统中线程是轻量级进程。

百里守约:在Java 中怎么创建一个线程呢?

安琪拉:线程的创建有2 种方式,如下,很多网上的文章还写了通过线程池的方式创建,其本质也是这二种中的一种:

  1. 继承 Thread 类;

  2. 实现 Runnable 接口;

百里守约:能不能用实际的代码举例说一下?

安琪拉:可以,如下所示:

25084e2c60898786a8b6848f58741b45.png

百里守约:如果我直接使用 new Seller("笔").run() 执行和start() 有什么区别?

安琪拉start() 方法是native 方法,JVM 会另起一个线程执行,而直接执行run() 方法是本地线程执行,我们可以使用示例程序对比一下,如下:

4085bbadbae3c22e26e231acc1cab7b6.png

看下控制台输出如下:

1当前线程:main 卖笔
2当前线程:Thread-1 卖笔

因为调用start() 方法后,JVM 会新建一个线程来执行run() 方法内容。

百里守约:我理解了,Thread 对象是Java 中普通的对象,和其他对象一样,只是在调用 start() 这个native 方法时变得不一样了,JVM 会根据Thread 对象来创建线程。

安琪拉:你说的非常对,new Thread() 创建Thread 对象时,JVM 还没有实际创造线程,调用start() 方法后JVM 才会通过 pthread_create 方法(Linux系统)创建线程。因此一定要将Thread 对象和真实的线程区分开。

百里守约:那JVM 又是如何创建线程的呢?

安琪拉:你这个问的有点深了,我可以大致讲讲,因为今天是基础篇,因此不展开聊,想深入了解的可以关注【安琪拉的博客】公众号,有源代码层的详细的讲解,先丢个源代码地址:Hotspot1.8 jvm.cpp,推荐先将本篇文章整体看完,然后回过头来再看实现原理。担心很多同学没学过c++,或者源码太多无从下嘴,后面会出一期JVM 创建线程源代码解析。

今天先丢个大致原理:Java 种新建的Thread 对象只是操作系统线程运行的载体,Thread类的作用主要有二点:

  • Thread 对象内的属性提供了创建新线程时所需要的线程描述信息,例如线程名、线程id、线程组、是否为守护线程;

  • Thread 对象内的方法提供了Java 程序可以跟操作系统线程打交道的手段,例如wait、sleep、join、interrupt等。

前面说到JVM new Thread对象时其实还没有真实创建线程,调用start() 方法时才开始正式创建。

百里守约:那线程是怎么从创建到执行,最后销毁的啊?

安琪拉:那你就要看Java 中线程的生命周期了,如下图所示:

9fe2a61fb2d1020fab03fdf0ec2b40f3.png
线程状态转换图

在 Thread 类中有个State 枚举类型标识线程状态,如下。

1public static enum State {
2  NEW,
3  RUNNABLE,
4  BLOCKED,
5  WAITING,
6  TIMED_WAITING,
7  TERMINATED;
8}

同时可以使用Thread.currentThread().getState()获取当前线程的状态。

解释一下每种状态:

  • New: 刚创建而未启动的线程就是这个状态。由于一个线程只能被启动一次,因此一个线程只可能有一次在这个状态。

  • Runnable:如上图,这个状态实际是个复合状态,包含二个子状态:Ready 和 Running。Ready是就绪状态,可以被JVM 线程调度器(Scheduler) 进行调度,如果是单核CPU,同一时刻只有一个线程处于Running 状态,可能有多个线程处于 Ready 状态,Running 表示当前线程正在被CPU 执行,在Java 中就是Thread 对象只 run() 方法正在被执行。当 yield() 方法被调用,或者线程时间片被用完,线程就会从 Running 状态转为 Ready 状态。另外有个小姿势点,CPU 的一个时间片时间是多久呢?这个展开来讲又可以单独写篇文章,这里只说一个结论:CPU时间片和主机时钟频率有关系,一般是10 ~ 20 ms。

  • Blocked:一个线程发生一个阻塞式I/0 (文件读写I/O, 网络读写I/O)时,或者试图获取其他线程持有的锁时,线程会进入此状态,例如:获取别的线程已经持有的 synchronized 修饰的对象锁。如果大家对synchronized 关键字感兴趣,可以看我这篇文章 一个synchronized跟面试官扯了半个小时,建议看完这篇再回过头看,顺便还可以点个赞。在Blocked 状态的线程不会占用CPU 资源,但是程序如果出现大量处于这个状态的线程,需要警惕了,可以考虑优化一下程序性能。

  • Waiting: 一个线程执行了Object.wait( )、 Thread.join( ) 、LockSupport.park( ) 后会进入这个状态,这个状态是处于无限等待状态,没有指定等待时间,可以和Timed_Waiting 对比,Timed_Waiting是有等待时间的。这个状态的线程如果要恢复到Runnable 状态需要通过别的线程调用Object.notify( )、Object.notifyAll( )、LockSupport.unpark( thread )。

  • Timed_Waiting:   带时间限制的Waiting。

  • Terminated:已经执行结束的线程处于此状态。Thread 的 run( ) 方法执行结束,或者由于异常而提前终止都会让线程处于这个状态。

百里守约:你刚才上面讲了wait( )、sleep( )、join( )、yield( ) 、notify()、notifyAll( ) 都是做什么的?什么区别?

安琪拉:这些方法都是线程控制方法,JAVA 通过这些方法跟它创建的操作系统线程进行交互,具体如下:

wait:线程等待,调用该方法会让线程进入 Waiting 状态,同时很重要的一点,线程会释放对象锁,所以wait 方法一般用在同步方法或同步代码块中;

sleep: 线程休眠,调用该方法会让线程进入Time_Waiting 状态,调sleep 方法需要传入一个参数标识线程需要休眠的时间;

yield:线程让步,yield 会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争CPU 时间片,一般来说,优先级高的线程有更大的可能性成功竞争到CPU 时间片,但不是绝对的,有的系统对优先级不敏感。

join:在当前线程中调用另一个线程的join 方法,则当前线程转为阻塞状态,等到另一线程执行结束,当前线程才会从阻塞状态变为就绪状态,等待CPU 的调度。写个代码一看就明白:

78635c34dbb19966837b180a0eaf7c40.png

控制台输出如下:
1主线程main 开始运行...
2子线程Thread-0 开始运行...
3子线程Thread-0 准备结束运行...
4主线程main 运行结束...

主线程调用threadA.join() 导致主线程等Thread-0 线程执行结束才开始继续执行。

join() 函数的内部实现如下:

2547c17df04730a82d98df576e511a1b.png

为了便于大家理解,我画了图(一言不合就上图),大家对照着代码和图看,上面代码主要有二个线程,主线程和 ThreadA 线程,主线程创建ThreadA并启动ThreadA线程,然后调用threadA.join() 会导致主线程阻塞,直到ThreadA 线程执行结束 isActive 变为 false,主线程恢复继续执行。

fd830caa73eaad654ae3e2ef70d167e2.png
join()
  • interrupt:线程中断,调用interrupt 方法中断一个线程,是希望给这个线程一个通知信号,会改变线程内部的一个中断标识位,线程本身并不会因为中断而改变状态(如阻塞、终止等)。调用interrupt 方法有二种情况:

  1. 如果当前线程正处于 Running 状态,interrupt( ) 只会改变中断标识位,不会真的中断正在运行的线程;

  2. 如果线程当前处于 Timed_Waiting 状态,interrupt( ) 会让线程抛出 InterruptedException。

    所以我们在编写多线程程序时,优雅关闭线程需要同时处理这二种情况,常规写法是:

2d4dd48878e0fb93b4d502fc190898a8.png

notify:notify方法和wait方法一样,也是Object 类中的方法,notify方法用于唤醒在此对象监视器上等待的单个线程,如果有多个线程在此对象监视器上等待,选择其中一个进行唤醒。另外要注意一点的是,当前线程唤醒等待线程后不会立即释放锁,而是当前线程执行结束才会释放锁,因此被唤醒的线程不是说唤醒之后立即就可以开始执行,而是要等到唤醒的线程执行结束,获得对象锁之后开始执行。上代码吧。

f9d5faffd7f762217d9a628406c3ff96.png

控制台输出:

 1Thread-A 进入状态 running...
2Thread-A 进入状态 waiting...
3Thread-B 进入状态 running...
4Thread-B 进入状态 time_waiting...
5Thread-B 进入状态 running...
6Thread-B 进入状态 time_waiting...
7Thread-B 进入状态 running...
8Thread-B 执行完毕, 进入状态 terminated...
9Thread-A 进入状态 running...
10Thread-A 执行完毕, 进入状态 terminated...

可以看到B 线程调用 lock.notify() 之后A 线程没有立即开始执行,而是等到B 线程执行结束后才开始执行,所以lock.notify() 唤醒 A 线程只是让 A 线程进入预备执行的状态,而不是直接进 Running 状态,B 线程调 notify 没有立即释放对象锁。

鉴于篇幅原因,此篇也是基础篇,知识部分就到此为止,接下来是一些常规的线程面试题。

第一题:关闭线程的方式有哪几种?哪种方式最可取?(美团一面面试题)
  1. 使用退出标识位;

    1public class ThreadSafe extends Thread { 
    2 public volatile boolean exit = false;
    3 public void run() { 
    4   while (!exit){
    5         //do something 
    6   }
    7 }
    8}
  2. 调用 interrupt 方法,这种是最可取的,但是要考虑到处理二种情况;

  3. stop 方法,这种属于强行终止,非常危险。就像直接给线程断电,调用thread.stop() 方法时,会释放子线程持有的所有锁,这种突然的释放可能会导致数据不一致,因此不推荐使用这种方式终止线程。

第二题:很多面试会问wait 和sleep 的区别?(比心一面面试题)

主要有以下3点:

  1. sleep 方法让线程进入 Timed_Waiting 状态,sleep 方法必须传入时间参数,会让当前线程挂起一段时间,过了这个时间会恢复到runnable 状态(取决于系统计时器和调度程序的精度和准确性)。而wait 方法会让当前线程进入Waiting 状态,会一直阻塞,直到别的线程调用 notify 或者 notifyAll 方法唤醒。

  2. wait 是Object 类中的方法,sleep 是Thread 类中的方法,理解这点很重要,wait方法跟对象绑定的,调用wait方法会释放wait 关联的对象锁;

  3. 如果在同步代码块,当前线程持有锁,执行到wait 方法会释放对象锁,sleep 只是单纯休眠,不会释放锁;

上面那个notify 的代码可以拿来巩固一下:

f9d5faffd7f762217d9a628406c3ff96.png

这里我建议大家先不急着看控制台输出,根据自己经验猜测一下输出应该是怎样的,然后对比输出,这样对比能看是否有偏差。另外我建议大家有条件,把本篇文章的示例程序拷贝到本地,实际看下运行。

控制台输出如下:

 1Thread-A 进入状态 running...
2Thread-A 进入状态 waiting...
3Thread-B 进入状态 running...
4Thread-B 进入状态 time_waiting...
5Thread-B 进入状态 running...
6Thread-B 进入状态 time_waiting...
7Thread-B 进入状态 running...
8Thread-B 执行完毕, 进入状态 terminated...
9Thread-A 进入状态 running...
10Thread-A 执行完毕, 进入状态 terminated...
第三题:手写一个死锁的例子?(美团二面面试题)

b0ccb58e11c3e1a911d507cedafedae1.png

第四题:写一个通过线程wait / notify通信的生产者消费者代码?(声网四面面试题)

253ac93d83927dbb3910f01affc74a37.png

控制台输出

 1生产第: 1杯冰沙...
2生产第: 2杯冰沙...
3生产第: 3杯冰沙...
4生产第: 4杯冰沙...
5生产第: 5杯冰沙...
6吧台满了,冰沙放不下 生产者 线程等待,当前吧台冰沙数: 5
7消费第: 1杯冰沙...
8消费第: 2杯冰沙...
9消费第: 3杯冰沙...
10消费第: 4杯冰沙...
11消费第: 5杯冰沙...
12吧台空的,没有冰沙 消费者 消费者线程等待,当前吧台冰沙数: 0
13生产第: 6杯冰沙...
14生产第: 7杯冰沙...
15生产第: 8杯冰沙...
16生产第: 9杯冰沙...
17生产第: 10杯冰沙...
18吧台满了,冰沙放不下 生产者 线程等待,当前吧台冰沙数: 5
19消费第: 6杯冰沙...
20消费第: 7杯冰沙...

后面几期会分别讲以下内容,顺序还没定,大概会按照读者群的反馈来。

  • 线程上下文切换、JAVA锁

  • 线程池实战、Fork / Join

  • 并发工具 CyclicBarrier、CountDownLatch、Semaphore的实际使用场景

  • synchronized、volatile、Atomic*** 涉及的原子性、内存可见性和指令重排序原理

  • AQS、ReentrantLock原理、以及和synchronized区别、CAS原理

  • 阻塞队列、线程调度
    欢迎关注Wx 公众号【安琪拉的博客】查看后续内容更新

参考:How to work with wait(), notify() and notifyAll() in Java?

ceb2598608793e7f8190e25afd1731af.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值