Java并发编程(一)Thread详解

news/2024/7/1 2:43:47

一、概述

在开始学习Thread之前,我们先来了解一下 线程和进程之间的关系:

线程(Thread)是进程的一个实体,是CPU调度和分派的基本单位。 线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 线程和进程的关系是:线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。

由上描述,可以得知线程作为cpu的基本调度单位,只有把多线程用好,才能充分利用cpu的多核资源。

本文基于JDK 8(也可以叫JDK 1.8)。

二、线程使用

2.1 启动线程

创建线程有四种方式:

  • 实现Runnable接口
  • 继承Thread类
  • 使用JDK 8 的Lambda
  • 使用Callable和Future

2.1.1 Runnable创建方式

public class MyThread implements Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}
}
Thread thread = new Thread(new MyThread());
thread.start();

2.1.2 继承Thread创建方式

public class MyThread extends Thread{@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}
}
MyThread thread = new MyThread();
thread.start();

以上代码有更简单的写法,如下:

Thread thread = new Thread(){@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}
};
thread.start();

2.1.3 Lambda创建方式

new Thread(()-> System.out.println(Thread.currentThread().getName())).start();

2.1.4 使用Callable和Future

看源码可以知道Thread的父类是Runnable是JDK1.0提供的,而Callable和Runnable类似,是JDK1.5提供的,弥补了调用线程没有返回值的情况,可以看做是Runnable的一个补充,下面看看Callable的实现。

public class MyThread implements Callable<String> {@Overridepublic String call() throws Exception {System.out.println(Thread.currentThread().getName());return Thread.currentThread().getName();}
}
Callable<String> callable = new MyThread();
FutureTask<String> ft = new FutureTask<>(callable);
new Thread(ft,"threadName").start();
System.out.println(ft.get());

2.1.5 run()和start()的区别

真正启动线程的是start()方法而不是run(),run()和普通的成员方法一样,可以重复使用,但不能启动一个新线程。

2.2 Thread的常用方法

Thread类方法

方法说明
start()启动线程
setName(String name)设置线程名称
setPriority(int priority)设置线程优先级,默认5,取值1-10
join(long millisec)挂起线程xx毫秒,参数可以不传
interrupt()终止线程
isAlive()测试线程是否处于活动状态

Thread静态(static)方法

方法说明
yield()暂停当前正在执行的线程对象,并执行其他线程。
sleep(long millisec)/sleep(long millis, int nanos)挂起线程xx秒,参数不可省略
currentThread()返回对当前正在执行的线程对象的引用
holdsLock(Object x)当前线程是否拥有锁

2.3 sleep()和wait()的区别

sleep为线程的方法,而wait为Object的方法,他们的功能相似,最大本质的区别是:sleep不释放锁,wait释放锁。

用法上的不同:sleep(milliseconds)可以用时间指定来使他自动醒过来,如果时间不到你只能调用interreput()来终止线程;wait()可以用notify()/notifyAll()直接唤起。

重点: 测试wait和sleep释放锁的代码如下:

public class SynchronizedTest extends Thread {int number = 10;public synchronized void first(){System.out.println("this is first!");number = number+1;}public synchronized void secord() throws InterruptedException {System.out.println("this is secord!!");Thread.sleep(1000);
//        this.wait(1000);number = number*100;}@Overridepublic void run() {first();}
}
SynchronizedTest synchronizedTest = new SynchronizedTest();
synchronizedTest.start();
synchronizedTest.secord();
// 主线程稍等10毫秒
Thread.sleep(10);
System.out.println(synchronizedTest.number);

根据结果可以得知:

  • 执行sleep(1000)运行的结果是:1001
  • 执行wait(1000)运行的结果是:1100

总结: 使用 sleep(1000)不释放同步锁,执行的是10*100+1=1001,wait(1000)释放了锁,执行的顺序是(10+1)x100=1100,所以sleep不释放锁,wait释放锁。

三、线程状态

3.1 线程状态概览

线程状态:

  • NEW 尚未启动
  • RUNNABLE 正在执行中
  • BLOCKED 阻塞的(被同步锁或者IO锁阻塞)
  • WAITING 永久等待状态
  • TIMED_WAITING 等待指定的时间重新被唤醒的状态
  • TERMINATED 执行完成

线程的状态可以使用getState()查看,更多状态详情,查看Thread源码,如下图:

3.2 线程的状态代码实现

3.2.1 NEW 尚未启动状态

Thread thread = new Thread() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}
};
// 只声明不调用start()方法,得到的状态是NEW
System.out.println(thread.getState()); // NEW

3.2.2 RUNNABLE 运行状态

Thread thread = new Thread() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}
};
thread.start();
System.out.println(thread.getState()); // RUNNABLE

3.2.3 BLOCKED 阻塞状态

使用synchronized同步阻塞实现,代码如下:

public class MyCounter {int counter;public synchronized void increase()  {counter++;try {Thread.sleep(10*1000);} catch (InterruptedException e) {e.printStackTrace();}}
}
MyCounter myCounter = new MyCounter();
// 线程1调用同步线程,模拟阻塞
new Thread(()-> myCounter.increase()).start();
// 线程2继续调用同步阻塞方法
Thread thread = new Thread(()-> myCounter.increase());
thread.start();// 让主线程等10毫秒
Thread.currentThread().sleep(10);
// 打印线程2,为阻塞状态:BLOCKED
System.out.println(thread.getState());

3.2.4 WAITING 永久等待状态

public class MyThread extends Thread{@Overridepublic void run() {synchronized (MyThread.class){try {MyThread.class.wait();System.out.println(Thread.currentThread().getName());} catch (InterruptedException e) {e.printStackTrace();}}}
}
Thread thread = new Thread(new MyThread());
thread.start();
// 主线程挂起200毫秒,等thread执行完成
Thread.sleep(200);
// 输出WAITING,线程thread一直处于被挂起状态
System.out.println(thread.getState());

唤醒线程: 可使用 notify/notifyAll 方法,代码如下:

synchronized (MyThread.class) {MyThread.class.notify();
}

使线程WAITING的方法:

  • Object的wait() 不设置超时时间
  • Thread.join()不设置超时时间
  • LockSupport的park()

查看Thread源码可以知道Thread的join方法,底层使用的是Object的wait实现的,如下图:

注意: 查看Object的源码可知wait(),不传递参数,等同于wait(0),设置的“0”不是立即执行,而是无限的等待,不执行,如下图:

3.2.5 TIMED_WAITING 超时等待状态

TIMED_WAITING状态,只需要给wait设置上时间即可,代码如下:

public class MyThread extends Thread{@Overridepublic void run() {synchronized (MyThread.class){try {MyThread.class.wait(1000);System.out.println(Thread.currentThread().getName());} catch (InterruptedException e) {e.printStackTrace();}}}
}

调用代码还是一样的,如下:

Thread thread = new Thread(new MyThread());
thread.start();
// 主线程挂起200毫秒,等thread执行完成
Thread.sleep(200);
// 输出TIMED_WAITING
System.out.println(thread.getState());
synchronized (MyThread.class) {MyThread.class.notify();
}

3.2.6 TERMINATED 完成状态

Thread thread = new Thread(()-> System.out.println(Thread.currentThread().getName()));
thread.start();
// 让主线程等10毫秒
Thread.currentThread().sleep(10);
System.out.println(thread.getState());

四、死锁

根据前面的知识,我们知道使用sleep的时候是不释放锁的,所以利用这个特性我们可以很轻易的写出死锁的代码,具体的流程如图(图片来源于杨晓峰老师文章):

代码如下:

static  Object object1 = new Object();
static  Object object2 = new Object();public static void main(String[] args) {Thread thread = new Thread(){@Overridepublic void run() {synchronized (object1){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (object2){System.out.println(Thread.currentThread().getName());}}}};Thread thread2 = new Thread(){@Overridepublic void run() {synchronized (object2){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (object1){System.out.println(Thread.currentThread().getName());}}}};thread.start();thread2.start();

运行上面的代码,程序会处于无限等待之中。

五、总结

根据上面的内容,我们已经系统的学习Thread的使用了,然而学而不思则罔,最后留一个思考题:根据本文介绍的知识,怎么能避免死锁?(哈哈,卖个关子,根据文章的知识点组合可以得出答案)

源码下载:https://github.com/vipstone/j...


推荐部分

本人最近看了前Oracle首席工程师杨晓峰的课程,也是第四部分引用的流程图的主人,感觉很不错,推荐给你,一起来系统的学习Java核心吧。

参考文档

https://docs.oracle.com/javas...


http://lihuaxi.xjx100.cn/news/236942.html

相关文章

再见,Python!你好,Go语言

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自区块链技术社区&#xff0c;未经允许拒绝转载。 AI 前线导读&#xff1a;Go 语言诞生于谷歌&#xff0c;由计算机领域的三位宗师级大牛 Rob Pike、Ken Thompson 和 Robert Griesemer 写成。由于出身…

JAVA对图片的任意角度旋转,以及镜像操作

package relevantTest;/* * 该代码实现了对图像的水平镜像变换&#xff0c;垂直镜像变换&#xff0c;任意角度旋转&#xff0c;jtf的实时监控&#xff0c;以及对图像的缩放变换&#xff0c;以及按钮的若隐若现效果。 * 在对图像进行任意角度旋转时最好是在原始图片未进行任何操…

CPU阿甘

原创&#xff1a; 老刘 码农翻身 2016-04-15 上帝为你关闭了一扇门&#xff0c;就一定会为你打开一扇窗这句话来形容我最合适不过了。我是CPU, 他们都叫我阿甘&#xff0c; 因为我和《阿甘正传》里的阿甘一样&#xff0c; 有点傻里傻气的。上帝把我制造出来&#xff0c; 给我了…

Go语言的Channel文章,整个人都感觉不好了

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自区块链技术社区&#xff0c;未经允许拒绝转载。 Go的Channel是一个很强大的并发数据模型&#xff0c;在一个发送者和多个消费者情况下工作得最好&#xff0c;但是如果是多个发送者&#xff0c;那么…

建立CentOS 6.9 的Yum本地源

1、建立一个本地Yum的软件仓库1mkdir /media/cdrom2、把CentOS6.9光盘装载到/media/cdrom1mount /dev/cdrom /media/cdrom3、安装createrepo1 rpm -ivh /media/cdrom/Packages/createrepo-[按tab键] deltarpm-[按tab键] python-deltarpm-[按tab键] createrepo-0.9.9-26.…

iOS开发之AVKit框架使用

2019独角兽企业重金招聘Python工程师标准>>> iOS开发之AVKit框架使用 一、引言 在iOS开发框架中&#xff0c;AVKit是一个非常上层&#xff0c;偏应用的框架&#xff0c;它是基于AVFoundation的一层视图层封装。其中相关文件和类都十分简单&#xff0c;本篇博客主要整…

Go语言开发常见陷阱,你遇到过几个?

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自区块链技术社区&#xff0c;未经允许拒绝转载。 Go作为一种简便灵巧的语言&#xff0c;深受开发者的喜爱。但对于初学者来说&#xff0c;要想轻松驾驭它&#xff0c;还得做好细节学习工作。 初学者…

【数据结构】Java实现数据结构的前置知识,时间复杂度空间复杂度,泛型类的讲解

文章目录数据结构时间复杂度、空间复杂度包装类、装箱与拆箱泛型擦除机制数据结构 当我们在成为一名程序员的这条道路上努力的时候&#xff0c;我们一定经常听到这个词数据结构。那么究竟什么是数据结构呢&#xff1f;数据结构顾名思义&#xff0c;就是数据结构&#xff0c;数…