java多线程基础API
本次内容主要讲认识Java中的多线程、线程的启动与中止、yield()和join、线程优先级和守护线程。
1、Java程序天生就是多线程的
一个Java程序从main()方法开始执行,然后按照既定的代码逻辑执行,看似没有其他线程参与,但实际上Java程序天生就是多线程程序,因为执行main()方法的是一个名称为main的线程。
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean; public class OnlyMain {
public static void main(String[] args) {
//Java 虚拟机线程系统的管理接口
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
// 不需要获取同步的monitor和synchronizer信息,仅仅获取线程和线程堆栈信息
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
// 遍历线程信息,仅打印线程ID和线程名称信息
for (ThreadInfo threadInfo : threadInfos) {
System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
}
}
}
执行main方法,可以看到输出了6个线程信息,如下图所示:
main线程:用户程序入口。
Reference Handler:清除Reference的线程。
Finalizer:调用对象finalize方法的线程。
Signal Dispatcher:分发处理发送给JVM信号的线程。
Attach Listener:内存dump,线程dump,类信息统计,获取系统属性等。
Monitor Ctrl-Break:监控Ctrl-Break中断信号的线程。
2、线程的启动与中止
2.1 启动线程
启动一个新线程有2种方式,这样说的原因是参考Thread类的说明:There are two ways to create a new thread of execution.
先看第一种:One is to declare a class to be a subclass of <code>Thread</code>。意思就是继承自Thread类,重写run()方法,然后new出一个对象,调用start()方法。
class PrimeThread extends Thread {
long minPrime; PrimeThread(long minPrime) {
this.minPrime = minPrime;
} public void run() { } public static void main(String[] args) {
PrimeThread p = new PrimeThread(143);
p.start();
}
}
再看第二种:The other way to create a thread is to declare a class that implements the <code>Runnable</code> interface。也就是说实现Runnable接口,然后实现run()方法,再把此类的实例作为一个参数初始化一个Thread实例,执行start()方法。
class PrimeRun implements Runnable {
long minPrime; PrimeRun(long minPrime) {
this.minPrime = minPrime;
} public void run() { } public static void main(String[] args) {
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
}
}
2.2 Thread和Runnable的区别
Thread才是Java里对线程的唯一抽象,Runnable只是对任务(业务逻辑)的抽象。Thread可以接受任意一个Runnable的实例并执行。
2.3 线程的终止
(1)线程自然终止
要么是run执行完成了,要么是抛出了一个未处理的异常导致线程提前结束。
(2)stop
暂停、恢复和停止操作对应在线程Thread的API就是suspend()、resume()和stop()。但是这些API是过期的,也就是不建议使用的。不建议使用的原因主要有:以suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。正因为suspend()、resume()和stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法。
(3)中断
安全的终止则是其他线程通过调用某个线程A的interrupt()方法对其进行中断操作,中断好比其他线程对该线程打了个招呼,“A,你要中断了”,不代表线程A会立即停止自己的工作,同样的A线程完全可以不理会这种中断请求。因为java里的线程是协作式的,不是抢占式的。线程通过检查自身的中断标志位是否被置为true来进行响应。线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()来进行判断当前线程是否被中断,不过Thread.interrupted()会同时将中断标识位改写为false。
如果一个线程处于了阻塞状态(如线程调用了sleep、join、wait等),则在线程在检查中断标志时如果发现中断标志为true,则会在这些阻塞方法调用处抛出InterruptedException异常,并且在抛出异常后会立即将线程的中断标示位清除,即重新设置为false。
不建议自定义一个取消标志位来中止线程的运行。因为run方法里有阻塞调用时会无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取消标志。这种情况下,使用中断会更好,主要有以下2个原因:
① 一般的阻塞方法,如sleep等本身就支持中断的检查。
② 检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可以避免声明取消标志位,减少资源的消耗。
注意:处于死锁状态的线程无法被中断。
2.4 run()和start()
Thread类是Java里对线程概念的抽象,可以这样理解:我们通过new Thread()其实只是new出一个Thread的实例,还没有和操作系统中真正的线程挂起钩来。只有执行了start()方法后,才实现了真正意义上的启动线程。start()方法让一个线程进入就绪队列等待分配cpu,分到cpu后才调用实现的run()方法,start()方法不能重复调用,如果重复调用会抛出异常,通过源码可以发现,start()方法先判断当前线程的状态,如果线程状态不为0,就会抛出异常。而run方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方法并没有任何区别,可以重复执行,也可以被单独调用。
start()方法源码如下:
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this); boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
3、其他基础API
3.1 yield
yield()方法:使当前线程让出CPU占有权,但让出的时间是不可设定的。也不会释放锁资源。注意:因为又不是每个线程都需要这个锁的,而且执行yield( )的线程不一定就会持有锁。
所有执行yield()的线程有可能在进入到就绪状态后会被操作系统再次选中马上又被执行。
yield()方法在ConcurrentHashMap的initTable()方法中有用到,源码如下图所示:
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
sizeCtl小于0表示有其他线程在进行table的初始化,对于table的初始化工作,同一时间只能有一个线程执行,所以其他线程把CPU执行权让出,,提高CPU利用率。
3.2 join
把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的join()方法,直到线程A执行完毕后,才会继续执行线程B。
下面用一个隔壁老王舔到最后一无所有的故事来说明join的使用方法。有一天隔壁老王在食堂排队打饭,看到女神也来打饭,于是叫女神排在自己的前面,但是他万万没有想到的是,女神让她的男朋友排在女神前面,隔壁老王傻眼了。
public class JoinDemo {
//隔壁老王的女神
static class Goddess implements Runnable {
//女神的男朋友
private Thread gf; public Goddess(Thread gf) {
this.gf = gf;
} public void run() {
System.out.println("女神开始排队打饭.....");
try {
System.out.println("女神让男朋友插队.....");
gf.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(2000);//休眠2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 女神打饭完成.");
}
} //隔壁老王的女神的男朋友
static class GoddessBoyfriend implements Runnable {
public void run() {
System.out.println("女神的男朋友开始排队打饭.....");
try {
Thread.sleep(2000);//休眠2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 女神的男朋友打饭完成.");
}
} public static void main(String[] args) throws Exception {
//女神的男朋友
GoddessBoyfriend goddessBoyfriend = new GoddessBoyfriend();
Thread gbf = new Thread(goddessBoyfriend); //女神
Thread goddess = new Thread(new Goddess(gbf)); goddess.start();
gbf.start(); System.out.println("隔壁老王开始排队打饭.....");
System.out.println("隔壁老王请女神插队.....");
goddess.join();
try {
Thread.sleep(2000);//主线程休眠2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 隔壁老王打饭完成.");
}
}
4、线程优先级
在Java线程中,通过一个整型成员变量priority来控制优先级,优先级的范围从1~10,在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。
设置线程优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。在不同的JVM以及操作系统上,线程规划会存在差异,有些操作系统甚至会忽略对线程优先级的设定。所以线程的优先级在Java里一般不会特别设置,特别是不能把程序的正确运行寄托在线程的优先级上。
5、守护线程
Daemon(守护)线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这意味着,当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出。可以通过调用Thread.setDaemon(true)将线程设置为Daemon线程。我们一般用不上,比如垃圾回收线程就是Daemon线程。Daemon线程被用作完成支持性工作,但是在Java虚拟机退出时Daemon线程中的finally块并不一定会执行。在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。
下面是一个守护线程的例子:
public class DaemonThread {
private static class UseThread extends Thread {
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName()
+ " I am extends Thread.");
}
}
} public static void main(String[] args) throws InterruptedException {
UseThread useThread = new UseThread();
useThread.setDaemon(true);
useThread.start();
Thread.sleep(500);
}
}
在主线程运行500毫秒后,主线程自然终止,因为useThread是守护线程,所以Java虚拟机退出。
后面会继续介绍Java并发编程有关知识,如有不足之处请指出,笔者及时修正,谢谢。
java多线程基础API的更多相关文章
- Java 多线程基础(六)线程等待与唤醒
Java 多线程基础(六)线程等待与唤醒 遇到这样一个场景,当某线程里面的逻辑需要等待异步处理结果返回后才能继续执行.或者说想要把一个异步的操作封装成一个同步的过程.这里就用到了线程等待唤醒机制. 一 ...
- [转]Java多线程干货系列—(一)Java多线程基础
Java多线程干货系列—(一)Java多线程基础 字数7618 阅读1875 评论21 喜欢86 前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们 ...
- Java多线程基础:进程和线程之由来
转载: Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够 ...
- Java 多线程——基础知识
java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...
- Java多线程--基础概念
Java多线程--基础概念 必须知道的几个概念 同步和异步 同步方法一旦开始,调用者必须等到方法调用返回后,才能执行后续行为:而异步方法调用,一旦开始,方法调用就立即返回,调用者不用等待就可以继续执行 ...
- Java多线程基础知识总结
2016-07-18 15:40:51 Java 多线程基础 1. 线程和进程 1.1 进程的概念 进程是表示资源分配的基本单位,又是调度运行的基本单位.例如,用户运行自己的程序,系统就创建一个进程, ...
- Java基础16:Java多线程基础最全总结
Java基础16:Java多线程基础最全总结 Java中的线程 Java之父对线程的定义是: 线程是一个独立执行的调用序列,同一个进程的线程在同一时刻共享一些系统资源(比如文件句柄等)也能访问同一个进 ...
- 1、Java多线程基础:进程和线程之由来
Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够融会贯通 ...
- Java 多线程基础(一)基本概念
Java 多线程基础(一)基本概念 一.并发与并行 1.并发:指两个或多个事件在同一个时间段内发生. 2.并行:指两个或多个事件在同一时刻发生(同时发生). 在操作系统中,安装了多个程序,并发指的是在 ...
随机推荐
- 【转】mac os x配置adb命令的方法,苹果电脑设置adb命令的方法
http://www.myexception.cn/operating-system/1636963.html 步骤如下: 1. 启动终端Terminal (如果当前用户文件夹下已有.bash_pro ...
- Jenkins+jmeter设置定时执行任务
1.准备好你的jmeter脚本 2.测试命令行下脚本执行 1)cd进入脚本目录 2)dir命令查看该目录下的脚本文件 3)jmeter -n -t test.jmx -l result.jtl 命令 ...
- cs231n spring 2017 lecture1 Introduction to Convolutional Neural Networks for Visual Recognition
1. 生物学家做实验发现脑皮层对简单的结构比如角.边有反应,而通过复杂的神经元传递,这些简单的结构最终帮助生物体有了更复杂的视觉系统.1970年David Marr提出的视觉处理流程遵循这样的原则,拿 ...
- 2018湖南省赛B题“2018”
题面懒得敲了,反正看这篇博客的肯定知道题面. 比赛时想按约数的一些性质分情况讨论出公式然后在合并,结果单考虑矩阵里出现2018和1009(与2互质,1009出现次数等于2)出现的情况就写了一长串公式, ...
- 2018年宜賓美酒文化節浮空投影舞美特效 / 2018 Yibing Wine Festival Visual Effect Projection
客户 Client:五粮液集团 硬件 Hardware:PC,巴可投影机30,000流明*2 Barco projector 30,000 lumen*2 软件 Software:Resolume, ...
- python登陆接口编写
#coding:utf-8 import getpass,sys i=0 j=0 while i<3: username=raw_input('username:') #输入用户名 life_1 ...
- 前端学习之路CSS基础学习二
CSS属性相关 样式操作: (1)width:为元素设置宽度 (2)height:为元素设置高度 ps:块儿级标签才能设置长宽行内标签设置长宽没有任何影响 p{ width: 30px; height ...
- Pandas提取单元格的值
如提取第1行,第2列的值: df.iloc[[0],[1]] 则会返回一个df,即有字段名和行号. 如果用values属性取值: df.iloc[[0],[1]].values 返回的值会是列表,而且 ...
- linux find命令格式及find命令详解
本文详细介绍了linux find命令格式及find命令案例,希望对您的学习有所帮助.1.find命令的一般形式为:find pathname -options [-print -exec -ok . ...
- ES6中 const 关键字
const声明一个只读的常量.一旦声明,常量的值就不能改变. 定义后可以使用但不能修改 但是,const 定义的对象可能与我们想象的不太一样 定义了对象b ,我们可以在b上添加修改属性,再看一个列子 ...