java 线程基础篇,看这一篇就够了。
前言:
Java三大基础框架:集合,线程,io基本是开发必用,面试必问的核心内容,今天我们讲讲线程。
想要把线程理解透彻,这需要具备很多方面的知识和经验,本篇主要是关于线程基础包括线程状态和常用方法。
本篇主要从线程常用方法来理解线程各个状态及状态的切换,之后再通过状态于状态之间的切换来加深对线程常用方法的应用于印象。
正题:
java中定义了线程的几种状态,在java.lang.Thread.State中,分别为以下6个:
NEW(初始化),RUNNABLE(就绪),BLOCKED(阻塞),WAITING(等待),TIMED_WAITING(超时等待),TERMINATED(终止)
1. 创建:通过继承Thread类,或者实现Runnable接口来创建一个线程。
方式1:继承Java.lang.Thread类,并覆盖run() 方法
优势:编写简单
劣势:单继承的限制----无法继承其它父类,同时不能实现资源共享
方式2:实现Java.lang.Runnable接口,并实现run()方法
优势:可继承其它类,多线程可共享同一个Thread对象
劣势:编程方式稍微复杂,如需访问当前线程,需调用Thread.currentThread()方法
public class Daqiu extends Thread{
@Override
public void run() {
System.out.println("我打完球了");
}
}
public class chifan implements Runnable{
@Override
public void run() {
System.out.println("我吃完了");
}
}
2.就绪:当线程调用start()方法,会进入准备就绪状态。
start方法是Thread 类的方法,在这个方法中会调用native方法(start0())来让线程就绪,等待CPU为该线程分配资源(时间片)。
3.运行:当线程获得cpu资源(时间片)会执行run()达到正真运行的效果,并可以调用yield()方法试探性的让出cpu执行器权。
1) 上面说了,start()方法最终调用了一个native的方法,并非java实现的,所以这里的run()方法是如何被调用的我们就不研究了。
2) yield()方法是对调度器的一个暗示表示愿意让出CPU执行器的当前使用权,但是调度器可以自由忽略这个提示。此方法使用极少,不过多研究。
3) 要注意,我们手动调用线程的run()方法也会执行run()方法的内容,但是这样就没有达到多线程的效果。这也是run()方法和start()方法的一个重要区别。通过下面的示例可以看出输出的线程名不一样。
public class TestThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args){
TestThread test = new TestThread();
test.start();
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.run();
}
结果:
Thread-0
main
Process finished with exit code 0
4.超时等待:又称为计时等待,让线程等待一定时间。
1) sleep(long): 让当前线程睡眠一定时间后再继续执行,此时只释放资源,不释放锁。这是一个静态的native方法,不过多研究。
2) join(): 该方法有三个重载join(),join(long millis),join(long millis,int nanoseconds),主要看第二个,就是等待一个线程指定毫秒数后再执行。无参数的join方法其实就是调用了join(0),即永远等待下去。不过通过源码我们可以看到,在while循环中有一个条件判断,即isAlive()方法,意思是如果当前线程还活着,就会一直等待下去。
public static native void sleep(long millis) throws InterruptedException;
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
对于join()方法的理解来看一个示例:
public class Chifan extends Thread{
private Daqiu daqiu;
public Chifan(String name,Daqiu daqiu){
super(name);
this.daqiu = daqiu;
}
@Override
public void run() {
try {
daqiu.join();
System.out.println(getName()+"我开始吃饭。。。");
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"我吃完了");
}
}
public class Daqiu extends Thread {
private int playTime;
public Daqiu(String name,int playTime) {
super(name);
this.playTime = playTime;
}
@Override
public void run() {
System.out.println(getName()+"我开始打球了");
try {
sleep(playTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"我打完球了");
}
}
public static void main(String[] args){
Daqiu daqiu = new Daqiu("打球线程:",1000);
Chifan chifan = new Chifan("吃饭线程:",daqiu);
System.out.println("打完球才能吃饭");
daqiu.start();
chifan.start();
}
打完球才能吃饭
打球线程:我开始打球了
打球线程:我打完球了
吃饭线程:我开始吃饭。。。
吃饭线程:我吃完了 Process finished with exit code 0
可以看到:daqiu.join();一定是等打完球了才会执行后面的吃饭。
5.等待:wait()方法只能在synchronized中调用,因为前提是已经拥有某对象锁,但是选择暂时交出去,此时线程将进入等待队列。
Object类中有三个不同参数的wait()方法,如果传入时间参数,也可以理解为计时等待,但于sleep()不同的是wait()方法会释放拥有的锁,当被其他持有该锁的线程调用notify()或notifyAll()唤醒时将进入同步队列去竞争锁。wait()实际也是调用了wait(long)方法,参数为0,表示一直等待下去。
6.阻塞:当synchronized(Obj)去竞争一个对象锁时,如果对象锁被其他线程占用,那么线程将进入等待队列(阻塞)
在java中的Object类型中,都是带有一个内存锁的,在有线程获取该内存锁后,其它线程无法访问该内存,从而实现JAVA中简单的同步、互斥操作。当出现阻塞锁定需要等其他持有该对象锁的线程调用wait(),notify()或notifyAll()释放对象锁后才有机会获取锁进入运行状态,所以wait(),notify()或notifyAll()只能在synchronized获取到对象锁内使用,这于第五点是相呼应的。(注意:调用notify()方法后,当前线程并不会马上释放该对象锁,要等到执行notify()方法的线程执行完才会释放对象锁)
7.终止:当线程run()方法正常执行完毕,或者出现未捕获的异常,线程就已经完成了他的使命进入终止状态了。
public class TestThread extends Thread {
private Student student;
public TestThread(String name,Student student){
super(name);
this.student = student;
}
@Override
public void run() {
synchronized (student){
try {
System.out.println("我是线程:"+Thread.currentThread().getName());
student.wait();
System.out.println("我是线程:"+Thread.currentThread().getName()+",我拿到了对象锁");
System.out.println("我是线程:"+Thread.currentThread().getName()+",我执行完了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class NotifyThread extends Thread {
private Student student;
public NotifyThread(String name,Student student){
super(name);
this.student = student;
}
@Override
public void run() {
synchronized (student){
System.out.println("我是唤醒线程"+Thread.currentThread().getName());
student.notifyAll();
System.out.println("我是唤醒线程"+Thread.currentThread().getName()+",我已经执行唤醒。");
}
}
}
public static void main(String[] args) throws InterruptedException {
Student student = new Student("小明","12");
TestThread testA = new TestThread("A",student);
TestThread testB = new TestThread("B",student);
NotifyThread testC = new NotifyThread("C",student);
testA.start();
testB.start();
sleep(10);
testC.start();
}
我是线程:A
我是线程:B
我是唤醒线程C
我是唤醒线程C,我已经执行唤醒。
我是线程:B,我拿到了对象锁
我是线程:B,我执行完了
我是线程:A,我拿到了对象锁
我是线程:A,我执行完了 Process finished with exit code 0
延申一个线程面试中的经典问题:创建3个线程,顺序答应‘A’,‘B’,‘C’各10次
其实思路也是利用wait()和notify()来控制线程的执行顺序,具体实现这里就不说了。
总结:
java中线程还有非常多的延申,包括线程底层实现,多线程同步,并发,安全,唤醒机制,以及线程池,要学习于沉淀的知识非常多。希望本文能让自己对线程的基础有个清晰的理解。
java 线程基础篇,看这一篇就够了。的更多相关文章
- Java入门基础知识点总结(详细篇)
Java入门基础知识点总结(详细篇)~~~~~目录 1.1 图解 1.1.1 Java基础知识点 1.1.2 Java基础语法的相关内容 1.2 关键字 1.3 标识符 1.3.1 标识符概念 1.3 ...
- Java 线程基础
Java 线程基础
- Java 线程基础,从这篇开始
线程作为操作系统中最少调度单位,在当前系统的运行环境中,一般都拥有多核处理器,为了更好的充分利用 CPU,掌握其正确使用方式,能更高效的使程序运行.同时,在 Java 面试中,也是极其重要的一个模块. ...
- Java线程池的了解使用—筑基篇
前言 Java中的线程池是一个很重要的概念,它的应用场景十分广泛,可以被广泛的用于高并发的处理场景.J.U.C提供的线程池:ThreadPoolExecutor类,可以帮助我们管理线程并方便地并行执行 ...
- java线程基础巩固---线程生命周期以及start方法源码剖析
上篇中介绍了如何启动一个线程,通过调用start()方法才能创建并使用新线程,并且这个start()是非阻塞的,调用之后立马就返回的,实际上它是线程生命周期环节中的一种,所以这里阐述一下线程的一个完整 ...
- java线程基础知识----线程基础知识
不知道从什么时候开始,学习知识变成了一个短期记忆的过程,总是容易忘记自己当初学懂的知识(fuck!),不知道是自己没有经常使用还是当初理解的不够深入.今天准备再对java的线程进行一下系统的学习,希望 ...
- Java 线程基础知识
前言 什么是线程?线程,有时被称为轻量进程(Lightweight Process,LWP),是程序执行流的最小单元.一个标准的线程由线程 ID,当前指令指针 (PC),寄存器集合和堆栈组成.另外,线 ...
- Java线程基础实例
概述 Java线程是一个在实战开发中经常使用的基础功能,而在Java中线程相关的类在java.lang和java.util.concurrent里 Thread package thread.base ...
- Java线程基础知识(状态、共享与协作)
1.基础概念 CPU核心数和线程数的关系 核心数:线程数=1:1 ;使用了超线程技术后---> 1:2 CPU时间片轮转机制 又称RR调度,会导致上下文切换 什么是进程和线程 进程:程序运行资源 ...
随机推荐
- QT常用控件(三)——自定义控件封装
引言 Qt已经提供了很多的基础控件供开发使用,而Qt原生的控件有时候并不能满足我们的需求,特别是在工业的运用上,比如我们需要一个日期时间的选择器,Qt虽然已经提供了原生的QDateTime控件,但这个 ...
- 预训练语言模型的前世今生 - 从Word Embedding到BERT
预训练语言模型的前世今生 - 从Word Embedding到BERT 本篇文章共 24619 个词,一个字一个字手码的不容易,转载请标明出处:预训练语言模型的前世今生 - 从Word Embeddi ...
- 【水】Dev-c++黑暗模式教程
前言 大家有没有觉得盯着Dev-c++那个白花花的背景盯久了之后会觉得眼睛不舒服-- 本人今天就来给大家带来一个黑暗模式的Dev-c++,可以让眼睛没那么难受(本人亲测有效) 效果如下图(猛男警告): ...
- requests 上件中文文件名报错解决方案
这几天在用wxpy写机器人,在调用里面的上传文件接口的时候,一直报错.经过排查后,发现是不支持中文文件名:在群里问了下作者后才知道是requests惹的祸,默认requests用的是unicode来处 ...
- 【剑指offer】65. 不用加减乘除做加法
剑指 Offer 65. 不用加减乘除做加法 知识点:数学:位运算 题目描述 写一个函数,求两个整数之和,要求在函数体内不得使用 "+"."-"."* ...
- Maven 手动安装JAR包到本地maven仓库后,但在项目中依旧报错找不到JAR包解决方法
本博客包含的内容: ①手动安装jar包到本地仓库: ②解决Missing artifact org.source.fastdfs:fastdfs:jar问题 .personSunflowerP { b ...
- STM32_从SystemInit、__main到main()
STM32 的 SystemInit() 和 __main Author by [YuCloud](https://www.cnblogs.com/yucloud/) 上篇文章 STM32启动代码分析 ...
- RabbitMQ的生产者消息确认(Publisher Confirms and Returns)和消费者ACK
https://www.cnblogs.com/wangzhongqiu/p/7815529.html https://blog.csdn.net/u012129558/article/details ...
- Golang语言系列-02-常用数据类型
Go语言常用数据类型 Go 语言中有丰富的数据类型,除了基本的整型.浮点型.布尔型.字符串.byte/rune 之外, 还有数组.切片.函数.map.通道(channel).结构体等. Go语言的基本 ...
- 【Python机器学习实战】决策树和集成学习(一)
摘要:本部分对决策树几种算法的原理及算法过程进行简要介绍,然后编写程序实现决策树算法,再根据Python自带机器学习包实现决策树算法,最后从决策树引申至集成学习相关内容. 1.决策树 决策树作为一种常 ...