You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
main 主线程开始运行
main 主线程运行结束
Thread-0 线程开始运行
Thread-1 线程开始运行
E running 0
F running 0
E running 1
F running 1
E running 2
F running 2
E running 3
F running 3
E running 4
F running 4
Thread-0 线程运行结束
Thread-1 线程运行结束
main 主线程开始运行
Thread-0 线程开始运行
E running 0
Thread-1 线程开始运行
F running 0
E running 1
F running 1
E running 2
F running 2
E running 3
F running 3
E running 4
F running 4
Thread-0 线程运行结束
Thread-1 线程运行结束
main 主线程运行结束
可以说并发是Java开发工程师面试时最常见的面试题了,关于并发的书籍也有很多,比如Java并发编程实战,Java并发编程艺术等。下面的几篇文章目的用最浅显的内容来解释并发的基础内容、并发的三大特性、线程池、并发容器、锁机制、相关源码分析等内容。并且会在每一篇章最后总结出常见的面试题。
进程?线程?
这可以说是操作系统中的一块问题,也是Java并发编程的开始。首先来看看基本概念:
如果实在无法理解,可以去看看阮一峰老师的解释:
一个比较有趣的进程,线程的解释
二者区别:
二者相同点:
执行阶段:创建,就绪,运行,阻塞,终止。
主线程
通常运行一个java程序,就会有一个线程立即运行,这个线程就是主线程。
尽管主线程在程序启动时自动创建,但它可以由一个Thread对象控制。为此,你必须调用方法currentThread()获得它的一个引用,currentThread()是Thread类的公有的静态成员。它的通常形式如下:
该方法返回一个调用它的线程的引用。一旦你获得主线程的引用,你就可以像控制其他线程那样控制主线程。
输出如下:
我们输出的线程显示顺序依次是[线程名称,优先级,组的名称]。主线程名称默认为main,优先级为5,main为组的名称。
我们可以用setName()设置线程的名称,用getName()获取线程的名称。
运行结果如下:
在start()方法后,并不是立即执行多线程代码,而是使线程进入可运行状态(Runnable),何时运行由操作系统决定。由于代码执行时乱序的,所以我们要加上线程机制。
Runnable接口
运行结果:
在启动线程时,需要先通过Thread类的构造方法Thread类的构造方法Thread构造出对象,然后调用Thread对象的start()方法来运行多线程代码。
注意Thread和Runnable之间代码具有细微的差别,但是二者背后的差距很大。
Thread类和Runnable接口区别
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
线程状态转化图
新建(new):新创建了一个线程对象。
可运行(runnable):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
运行(running):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
阻塞(block):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
(三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
常用函数
sleep()
最简单的一个函数,作用是在指定的毫秒数内瓤当前正在执行的线程进入休眠,也就是进入阻塞状态。另外它没有对象锁这个概念。
join()
join是Thread类的一个方法,启动线程后直接调用,即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。
在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。
不加join。
看如下示例(不加join函数的情况):
运行结果如下:
我们想让主线程最后执行完,下面再看看加上join()的情况:
运行结果如下:
yield()
作用是暂停当前的执行的线程对象,开始执行其他线程。
需要注意的是yield()是让当前运行状态进入到可运行状态,而非阻塞状态,实际情况下,我们并无法保证yield()完美的完成让步目的,即使当前线程让步了,根据优先级,该线程还可能继续被选中,继续执行该线程。由于可控性差的原因,建议尽量少用yield()函数。
sleep()和yield()的区别:
sleep()和yield()的区别):sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程
另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。
wait()¬ify()
wait()和notify()必须和synchronized一起使用,二者针对对象进行操作。wait()必须在同步代码块中调用,调用wait()的线程会等待,知道其他线程调用了notify()方法之后,该线程才会被唤醒。被唤醒并不意味着立即获得对象锁,需要等待两件事的发生:
运行结果如下:
其他函数
总结
这一块基本都是Java入门书籍中都会出现的知识点,当然真的是入门。其中,Thread类和Runnable接口之间的区别是面试最常见的题目。线程同步部分在后面的章节结合synchronized一起叙述。下面一个章节将结合Java并发编程实战这本书来谈谈并发三大特性的相关机制和volatile的相关基础。
The text was updated successfully, but these errors were encountered: