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. 前端的UI设计与交互之字体篇

    跨平台的字体设定,力求在各个操作系统下都有最佳展示效果.字体是界面设计中最重要的基本构成之一,用户通过文本来消化内容和完成工作,优雅的字体将大大提升用户的阅读体验及工作效率.在满足不同终端始终保持良好 ...

  2. 前端的UI设计与交互之设计原则篇

    1.亲密性 a)纵向间距示例这三种规格分别为:8px(小号间距).16px(中号间距).24px(大号间距). b)在这三种规格不适用的情况下,可以通过加减『基础间距』的倍数,或者增加元素来拉开信息层 ...

  3. linux --> ubuntu和mac通过samba共享

    ubuntu和mac通过samba共享 如果想快速配置,直接跳到第五步. 一.安装smb 执行下列命令 sudo apt-get install samba sudo apt-get install ...

  4. Algorithm --> 投资组和求最大利润

    投资组和求最大利润 题目: 投资人出资一笔费用mount,投资给不同的公司(A,B,C....),求最大获取利润? 例如:投资400百万,给出两家公司A和B: 1.如果投资一百万给A公司,投资3百万给 ...

  5. KVM之二:配置网络

    1.安装KVM a.通过yum安装虚拟化的软件包 [root@kvm ~ ::]#yum install -y kvm virt-* libvirt bridge-utils qemu-img 说明: ...

  6. WEB 表格测试点

    Web页面的表格测试点: 1.表格列名 2.表格翻页.表格跳转到多少页.最后一页.首页 3.表格每页显示的数据, 数据的排序 4.表格无数据 5.表格支持的最大数据量 6.表格中数据内容超长时,显示是 ...

  7. [Java反射机制]用反射改进简单工厂模式设计

    如果做开发的工作,工厂设计模式大概都已经深入人心了,比较常见的例子就是在代码中实现数据库操作类,考虑到后期可能会有数据库类型变换或者迁移,一般都会对一个数据库的操作类抽象出来一个接口,然后用工厂去获取 ...

  8. 福州大学软工1715|W班-启航

    新的一学期即将开启,而在仅剩的几天的时间内,我将为接下来的软工实践助教事宜忙碌起来.要学习的东西很多,要关注的东西也很多. 虽然我现在还在茫然阶段,虽然我对<构建之法>还不太熟悉,但是,我 ...

  9. APP案例分析

    产品 蓝叠安卓模拟器 选择理由     看了一眼桌面,就这个比较有意思.现在很多人喜欢玩手游,经常喜欢开个小号搞事情.这时候身边又没有多余的手机,怎么办?安卓模拟器下一个.手机屏幕太小玩起来没意思怎么 ...

  10. POST请求的提交

    var http = require("http"); var querystring = require("querystring"); //创建服务器 va ...