Java 并发编程系列文章

Java 并发基础——线程安全性

Java 并发编程——Callable+Future+FutureTask

java 并发编程——Thread 源码重新学习

java并发编程——通过ReentrantLock,Condition实现银行存取款

Java并发编程——BlockingQueue

Java 并发编程——Executor框架和线程池原理


对于程序员来说 Thread应该都不会陌生,这里再深入的去学习一下里面的很多借口

Thread的声明如下:

class Thread implements Runnable

Runnable 接口是个什么鬼?

public
interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}

当开启一个线程的时候,run()方法就会被执行在新开启的线程。

ThreadGroup

在了解Thread之前先了解一下ThreadGroup,结构其实很简单如下图(就是一个线程组,同时可以包含其它线程组。对于线程租的操作都是对里面线程和子线程组的操作,而子线程组需要进行递归的操作):

当创建了好几个线程的时候,很多线程的工作任务是类似或者一致的,这样我们就可以使用ThreadGroup来管理它。

优点:方便统一管理,线程组可以进行复制,快速定位到一个线程,统一进行异常设置等。

大概看了里面的接口设计,里面的操作有一个特点:在同步块中将子线程组拷贝到临时变量中然后再在同步块之外进行递归的操作。(这样设计的优点和目的:读写分离,防止多线程操作导致崩溃,类似于CopyOnWriteArrayList)

  public void list() {
list(System.out, 0);
}
void list(PrintStream out, int indent) {
int ngroupsSnapshot;
ThreadGroup[] groupsSnapshot;
synchronized (this) {
for (int j = 0 ; j < indent ; j++) {
out.print("");
}
out.println(this);
indent += 4;
for (int i = 0 ; i < nthreads ; i++) {
for (int j = 0 ; j < indent ; j++) {
out.print("");
}
out.println(threads[i]);
}
ngroupsSnapshot = ngroups;
if (groups != null) {

groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);

        } else {
groupsSnapshot = null;
}
}

//同步块中将 groups数组拷贝到临时变量 groupsSnapshot中,再在同步块之外对其进行递归操作。

    for (int i = 0 ; i < ngroupsSnapshot ; i++) {
groupsSnapshot[i].list(out, indent);
}
}

Thread的构造函数


基本属性

  • name:线程名称,可以重复,若没有指定会自动生成。
  • id:线程ID,一个正long值,创建线程时指定,终生不变,线程终结时ID可以复用。
  • priority:线程优先级,取值为1到10,线程优先级越高,执行的可能越大,若运行环境不支持优先级分10级,如只支持5级,那么设置5和设置6有可能是一样的。
  • state:线程状态,Thread.State枚举类型,有NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED 5种。
  • ThreadGroup:所属线程组,一个线程必然有所属线程组。 RUNNABLE
  • UncaughtEThreadGroupxceptionHandler:未捕获异常时的处理器,默认没有,线程出现错误后会立即终止当前线程运行,并打印错误。

无参构造函数

 public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}

设置Thread的name的构造函数

public Thread(String name) {
init(null, null, name, 0);
}

通过Runnable接口初始化一个Thread

public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}

上面几种Thread的初始化方式我们应该并不陌生,下面介绍用ThreadGroup(创建的线程的同时,将其添加到某个线程组)来初始化一个Thread:

public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}

线程的状态及其切换过程


Thread源码中其实只有上图中的六种,因为合并了Running和Runnable状态,统称为Runnable。调用yield()函数的时候,其实就是由Running变为Runnable状态,而此时在Thread源码中都是Runnable状态。

sleep()


对外提供两个重载版本:

//参数为毫秒

public static void sleep(long millis) throws InterruptedException

//第一参数为毫秒,第二个参数为纳秒

public static void sleep(long millis, int nanos) throws InterruptedException

sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。

  但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。看下面这个例子就清楚了:

public class Test {

    private int i = 10;
private Object object = new Object(); public static void main(String[] args) throws IOException {
Test test = new Test();
MyThread thread1 = test.new MyThread();
MyThread thread2 = test.new MyThread();
thread1.start();
thread2.start();
} class MyThread extends Thread{
@Override
public void run() {
synchronized (object) {
i++;
System.out.println("i:"+i);
try {
System.out.println("线程"+Thread.currentThread().getName()+"进入睡眠状态");
Thread.currentThread().sleep(10000);
} catch (InterruptedException e) {
// TODO: handle exception
}
System.out.println("线程"+Thread.currentThread().getName()+"睡眠结束");
i++;
System.out.println("i:"+i);
}
}
}
}

执行结果如下:

  从上面输出结果可以看出,当Thread-0进入睡眠状态之后,Thread-1并没有去执行具体的任务。只有当Thread-0执行完之后,此时Thread-0释放了对象锁,Thread-1才开始执行。

  注意,如果调用了sleep方法,必须捕获InterruptedException异常或者将该异常向上层抛出。当线程睡眠时间满后,不一定会立即得到执行,因为此时可能CPU正在执行其他的任务。所以说调用sleep方法相当于让线程进入阻塞状态。

在thread1睡眠过程中获取thread1/thread2状态结果:thread1.state=TIMED_WAITING    thread2.state=BLOCKED

下面这段代码是Thread源码中的实现,前面部分是 参数异常检查,如果参数有异常会抛出IllegalArgumentException异常。后面一部分主要是通过while循环控制sleep的结束(其实还是调用native的sleep方法)。

代码里面  获取当前线程的lock,并在 sleep时不能让其它线程访问。(原因不是很清楚)

Object lock = currentThread().lock;

public static void sleep(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("millis < 0: " + millis);
}
if (nanos < 0) {
throw new IllegalArgumentException("nanos < 0: " + nanos);
}
if (nanos > 999999) {
throw new IllegalArgumentException("nanos > 999999: " + nanos);
} // The JLS 3rd edition, section 17.9 says: "...sleep for zero
// time...need not have observable effects."
if (millis == 0 && nanos == 0) {
// ...but we still have to handle being interrupted.
if (Thread.interrupted()) {
throw new InterruptedException();
}
return;
} long start = System.nanoTime();
long duration = (millis * NANOS_PER_MILLI) + nanos; // NANOS_PER_MILLI 值为 1000 Object lock = currentThread().lock; // Wait may return early, so loop until sleep duration passes.
synchronized (lock) {
while (true) {
sleep(lock, millis, nanos); // 调用底层native sleep方法 long now = System.nanoTime();
long elapsed = now - start; // 当 sleep time 没有达到指定的时间间隔时继续调用 native 的sleep方法,
if (elapsed >= duration) {
break; // sleep 中断的条件
} duration -= elapsed;
start = now;
millis = duration / NANOS_PER_MILLI;
nanos = (int) (duration % NANOS_PER_MILLI);
}
}
}

yield()


将Cpu让给其它线程优先执行,自己进入等待执行(Runnable)状态。yield函数没有设置等待执行的时间,一切听从cpu的调度,当没有其它线程抢占cpu时,当前线程又会被cpu调度进入Running状态。它跟sleep方法类似,同样不会释放锁

当增加yield()执行下面这段代码和没有yield()时的区别在于:当增加yield()函数后循环执行到149时将cpu的使用权让给了另一个线程执行,知道另一个线程执行完毕再从149自增打印输出。

public class SelfTest {

    public static class ReadThread extends Thread {
int i = 0;
public void run() {
while(i<300 ){
System. out.println("******* "+Thread.currentThread().getId()+“ **********: "+i++);
if(150==i){
Thread. yield();
}
}
System. out.println(number+" currentThread: "+Thread.currentThread());
}
}
public static void main(String [] args) {
new ReadThread().start();
new ReadThread().start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

Thread源码中并没有看到六种状态切换的代码,相必都在c/c++层去做了。

currentThread()

public static native Thread currentThread();

该方法是用来获取当前线程,实现都是在native层做的,无法看到代码。

interrupt()


interrupt,顾名思义,即中断的意思。单独调用interrupt方法可以使得处于阻塞状态的线程抛出一个异常,也就说,它可以用来中断一个正处于阻塞状态的线程;另外,通过interrupt方法和isInterrupted()方法来停止正在运行的线程。

下面看一个例子:

public class Test {

    public static void main(String[] args) throws IOException  {
Test test = new Test();
MyThread thread = test.new MyThread();
thread.start();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) { }
thread.interrupt();
} class MyThread extends Thread{
@Override
public void run() {
try {
System.out.println("进入睡眠状态");
Thread.currentThread().sleep(10000);
System.out.println("睡眠完毕");
} catch (InterruptedException e) {
System.out.println("得到中断异常");
}
System.out.println("run方法执行完毕");
}
}
}

输出结果:

从这里可以看出,通过interrupt方法可以中断处于阻塞状态的线程。那么能不能中断处于非阻塞状态的线程呢?看下面这个例子:

public class Test {

    public static void main(String[] args) throws IOException  {
Test test = new Test();
MyThread thread = test.new MyThread();
thread.start();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) { }
thread.interrupt();
} class MyThread extends Thread{
@Override
public void run() {
int i = 0;
while(i<Integer.MAX_VALUE){
System.out.println(i+" while循环");
i++;
}
}
}
}

运行该程序会发现,while循环会一直运行直到变量i的值超出Integer.MAX_VALUE。所以说直接调用interrupt方法不能中断正在运行中的线程。

  但是如果配合isInterrupted()能够中断正在运行的线程,因为调用interrupt方法相当于将中断标志位置为true,那么可以通过调用isInterrupted()判断中断标志是否被置位来中断线程的执行。比如下面这段代码:

public class Test {

    public static void main(String[] args) throws IOException  {
Test test = new Test();
MyThread thread = test.new MyThread();
thread.start();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) { }
thread.interrupt();
} class MyThread extends Thread{
@Override
public void run() {
int i = 0;
while(!isInterrupted() && i<Integer.MAX_VALUE){
System.out.println(i+" while循环");
i++;
}
}
}
}

运行会发现,打印若干个值之后,while循环就停止打印了。

  但是一般情况下不建议通过这种方式来中断线程,一般会在MyThread类中增加一个属性 isStop来标志是否结束while循环,然后再在while循环中判断isStop的值。

class MyThread extends Thread{
private volatile boolean isStop = false;
@Override
public void run() {
int i = 0;
while(!isStop){
i++;
}
} public void setStop(boolean stop){
this.isStop = stop;
}
}

那么就可以在外面通过调用setStop方法来终止while循环。

wait()/notify()


public class WaitNotifyCase {
public static void main(String[] args) {
final Object lock = new Object(); new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread A is waiting to get lock");
synchronized (lock) {
try {
System.out.println("thread A get lock");
TimeUnit.SECONDS.sleep(1);
System.out.println("thread A do wait method");
lock.wait();
System.out.println("wait end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start(); try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread B is waiting to get lock");
synchronized (lock) {
System.out.println("thread B get lock");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.notify();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread B do notify method");
}
}
}).start();
}
}

执行结果:

thread A get lock
thread A do wait method
thread B is waiting to get lock
thread B get lock
thread B do notify method
wait end

多线程共享资源时,通过共享资源的 wait()和notify()进行通信,上例子中

1、 A线程首先持有了资源lock,并进入了Synchronized同步块

2、A 调用 共享资源的wait()方法,当前线程状态进入TIMED_WAITING状态,同时释放资源锁。wait()后面的代码只有当该线程被唤醒的时候才回去执行(notify())

3、B线程获取到资源锁,进入同步块,并调用了资源额notify()方法,唤醒了A线程(注意:此时线程B并没有停止执行,而去执行A线程,而是等B线程执行完之后A线程才能被真正唤醒)

疑问: 问什么调用wait() 和 notify()方法之前必须 将资源放在Synchronized块中?

参看 Object源码注释或者其它资料就会发现,这两个方法执行之前必须获取到资源(实例中的lock对象)的monitor。将资源方到同步块中可以保证当前线程获取到资源的monitor

当调用资源的wait方法时,将该线程放到资源的等待集合中,并放弃资源的同步请求(放弃同步锁)

This method causes the current thread (call it <var>T</var>) to
* place itself in the wait set for this object and then to relinquish
* any and all synchronization claims on this object.

notify()方法调用时,会去 wait set中任意选择一个线程进行唤醒

Wakes up a single thread that is waiting on this object's monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation

当调用notifyAll()方法时则会唤醒wait set中的所有线程

备注: 当thread A调用lock.wait()时,线程进入到WAITING状态而不是blocked状态。

join()


很多情况下 当 A线程需要等待B线程执行完之后才能去执行,对于这种场景我们就可以用到 join()函数了。

class BThread extends Thread {
public BThread() {
super("[BThread] Thread");
};
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " start.");
try {
for (int i = 0; i < 5; i++) {
System.out.println(threadName + " loop at " + i);
Thread.sleep(1000);
}
System.out.println(threadName + " end.");
} catch (Exception e) {
System.out.println("Exception from " + threadName + ".run");
}
}
}
class AThread extends Thread {
BThread bt;
public AThread(BThread bt) {
super("[AThread] Thread");
this.bt = bt;
}
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " start.");
try {
bt.join();
System.out.println(threadName + " end.");
} catch (Exception e) {
System.out.println("Exception from " + threadName + ".run");
}
}
}
public class TestDemo {
public static void main(String[] args) {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " start.");
BThread bt = new BThread();
AThread at = new AThread(bt);
try {
bt.start();
Thread.sleep(2000);
at.start();
at.join();
} catch (Exception e) {
System.out.println("Exception from main");
}
System.out.println(threadName + " end!");
}
}

执行结果如下:

main start.    //主线程起动,因为调用了at.join(),要等到at结束了,此线程才能向下执行。
[BThread] Thread start.
[BThread] Thread loop at 0
[BThread] Thread loop at 1
[AThread] Thread start. //线程at启动,因为调用bt.join(),等到bt结束了才向下执行。
[BThread] Thread loop at 2
[BThread] Thread loop at 3
[BThread] Thread loop at 4
[BThread] Thread end.
[AThread] Thread end. // 线程AThread在bt.join();阻塞处起动,向下继续执行的结果
main end! //线程AThread结束,此线程在at.join();阻塞处起动,向下继续执行的结果。

线程B先执行,当A执行后,调用B线程的join()方法,表示等待B的run()方法执行结束之后才会继续往下执行。

suspend()/stop()/resume()是已经过时的方法,这里就不再介绍,可能是因为他们容易造成死锁,所以不建议使用。

Thread 里面的 contextClassLoader 还没有搞懂,参考https://blog.csdn.net/zhoudaxia/article/details/35897057

一篇里面去讲整个Thread实在是太紧凑,很多深一点东西没有深入了解,后续再补充吧。

参考:

http://www.cnblogs.com/dolphin0520/p/3920357.html

https://blog.csdn.net/evankaka/article/details/51627380

https://blog.csdn.net/zhangzeyuaaa/article/details/53718734

wait()/notify() 详解:   https://www.jianshu.com/p/f4454164c017

join() 方法详解:  https://blog.csdn.net/sinat_29384657/article/details/52228578

java Thread 接口学习的更多相关文章

  1. java基础知识回顾之java Thread类学习(八)--java.util.concurrent.locks(JDK1.5)与synchronized异同讲解

    看API文档介绍几个方法:  JDK1.5中提供了多线程的升级解决方案: 特点: 1.将同步synchronized显示的替换成Lock                    2.接口Conditio ...

  2. java基础知识回顾之java Thread类学习(三)--java线程实现常见的两种方式实现好处:

    总结:实现Runnable接口比继承Thread类更有优势: 1.因为java只能单继承,实现Runnable接口可以避免单继承的局限性 2.继承Thread类,多个线程不能处理或者共享同一个资源,但 ...

  3. java基础知识回顾之java Thread类学习(十)--线程的状态以及转化使用的方法介绍

       线程的概述:         线程是程序的多个执行路径,执行调度的单位,依托于进程存在.线程不仅可以共享进程的内存,而且还拥有一个属于自己的内存空间,这段内存空间叫做线程栈,是建立线程的时候由系 ...

  4. java基础知识回顾之java Thread类学习(八)--java多线程通信等待唤醒机制经典应用(生产者消费者)

     *java多线程--等待唤醒机制:经典的体现"生产者和消费者模型 *对于此模型,应该明确以下几点: *1.生产者仅仅在仓库未满的时候生产,仓库满了则停止生产. *2.消费者仅仅在有产品的时 ...

  5. java基础知识回顾之java Thread类学习(四)--java多线程安全问题(锁)

    上一节售票系统中我们发现,打印出了错票,0,-1,出现了多线程安全问题.我们分析为什么会发生多线程安全问题? 看下面线程的主要代码: @Override public void run() { // ...

  6. Java Comparator接口学习笔记

    Comparator是一个泛型函数式接口,T表示待比较对象的类型: @FunctionalInterface public interface Comparator<T> { } 本文将主 ...

  7. java基础知识回顾之java Thread类学习(十二)-- 线程中断

    官方文档翻译: 如果本线程是处于阻塞状态:调用线程的wait(), wait(long)或wait(long, int)会让它进入等待(阻塞)状态,或者调用线程的join(), join(long), ...

  8. java基础知识回顾之java Thread类学习(十一)--join方法的理解

    以下面例子说明下面的源码:main 线程 和 A线程,A线程是main线程创建并且启动的,main线程优先级比较高,正在执行:这个时候main线程调用A.join()之后,main线程一直等待,直到A ...

  9. java基础知识回顾之java Thread类学习(九)--wait和notify区别

    wait和sleep区别:  相同点:调用wait,sleep方法都可以是线程进入阻塞状态,让出cpu的执行权. 不同点:1.sleep必须指定时间,但是wait方法可以指定时间,也可以不指定时间. ...

随机推荐

  1. python3 爬虫开发 学习总结一

    virtualenv 安装虚拟环境的   pip install  virtualenv安装慢的话,可以指定源    pip install  -i  源地址  xxx  就可以安装xxxvirtua ...

  2. Java注解学习笔记

    我们平常写Java代码,对其中的注解并不是很陌生,比如说写继承关系的时候经常用到@Override来修饰方法.但是@Override是用来做什么的,为什么写继承方法的时候要加上它,不加行不行.如果对J ...

  3. Konckout开发实例:简单的表单提交页面

    <!doctype html> <html > <head> <meta http-equiv="Content-Type" conten ...

  4. [poj3461]Oulipo_KMP

    Oulipo poj-3461 题目大意:给你两个字符串s和p,问s中有多少个等于p的子串. 注释:$1\le strlen(p)\le 10^4\qquad1\le strlen(s)\le 10^ ...

  5. jmeter常见问题汇总

    Aggregate Report 是 JMeter 常用的一个 Listener,中文被翻译为"聚合报告".今天再次有同行问到这个报告中的各项数据表示什么意思,顺便在这里公布一下, ...

  6. 功能测试很low?不能升级到高级测试工程师?

    功能测试很low?不能升级到高级测试工程师? 功能测试很low?功能测试很简单?功能测试就是黑盒测试?功能测试没有技术含量?功能测试工资低?只会功能测试没有竞争力?功能测试这活初中生都可以干?功能测试 ...

  7. web语义化之SEO和ARIA

    在快速理解web语义化的时候,只知道web语义化有利于SEO和便于屏幕阅读器阅读,但并不知道它是如何有利于SEO和便于阅读器阅读的,带着这个疑问,进行了一番探索总结. SEO 什么是SEO? SEO( ...

  8. 【Alpha版本】冲刺阶段 - Day1 - 启航

    Alpha 阶段成员分工及任务量 成员 分工 任务量(小时) 袁逸灏 完成app用户车辆,子弹发射,背景移动,暂停界面,音乐界面,音乐查找,音乐播放 25 刘伟康 项目进度把控.分配任务.组织会议.整 ...

  9. Beta阶段敏捷冲刺报告-DAY2

    Beta阶段敏捷冲刺报告-DAY2 Scrum Meeting 敏捷开发日期 2017.11.3 会议时间 13:00 会议地点 微信群 参会人员 项目组全体成员 会议内容 打包问题修复, 爬虫优化, ...

  10. Java暑期作业

    一.假期观影笔记--<熔炉> 影片<熔炉>是根据发生在韩国光州聋哑学校里的真实事件而改编.影片讲述的是在一所聋哑儿童学校里,校长.教务以及老师披着慈善的华丽外衣对学校中的多名未 ...