Java 多线程(一) Thread API 基础
前言
Java
自开发之初就具有多线程多特性,其于JDK1.5
又增添了java.util.concurrent
内增添了非常多的多线程组件.于本章之中,我们优先总结下在Java
初期,我们经常使用的Java API
. 其中,虽然stop()
等方法已经废弃,我们仍然将其提出,并且提出其优/缺点.
Java中主要的API有如下几部分内容:
- 线程的创建 - Thread 类 与 Runnable接口
- 线程的启动 - start()方法与run()方法
- 线程的睡眠 - sleep()方法
- 线程的停止 - stop()与interrupted()
- 线程的暂停 - suspend()方法与resume()方法
- 线程的放弃 - yield()方法
- 线程的优先级与执行顺序 - setPriority()方法
- 守护线程 - setDaemeon()方法
- 其他API - Thread.currentThread() / isAlive() / getId() / getName()
本章中引用的示例都可以在如下的项目内找到
https://github.com/SeanYanxml/arsenal/tree/master/arsenal-java/arsenal-multithreading
如果你觉得本文或项目写的不错的话, 不妨给我一个Star
正文
线程的创建
线程的创建,我们主要有两种方式:
- 继承Thread类
实现Runnable方法
Thread
class StartThread extends Thread{
public void run(){
}
}
public void static void main(String []args){StartThread thread = new StartThread();
thread.start();
}
Runnable
class StartRunnabelThread implements Runnabel{
public void run(){
}
}
public void static void main(String []args){StartThread thread = new StartThread();
thread.start();
}
注意: 在Thread的构造函数内有Thread(Runnable)
我们有时可以这样创建,这样的结果就是其他线程调用某一个线程的run()
方法.
Thread threadA = new Thread();
Thread threadB = new Thread(threadA);
Thread threadC = new Thread(threadA);
@Override
public void run() {
if (target != null) {
target.run();
}
}
线程的启动
我们主要使用start()
方法与run()
. 其中run()
方法是在当前进程内进行运行线程; start()
方法是启动一个子线程进行运行.
# Thread
class StartThread extends Thread{
public void run(){
for(int i=0;i<10;i++){
System.out.print(i);
}
}
}
public void static void main(String []args){
// 写法1
StartThread thread = new StartThread();
thread.start();
System.out.println("END.");
// 写法2
StartThread thread = new StartThread();
thread.run();
System.out.println("END.");
}
方法1中输出为12345678910END
(顺序执行.)
方法2的输出为123END45678910
(END输出的先后是随机的.)
我们可以使用Thread.currentThread.getName()
的接口进行查看,可以发现方法1中为Main
,而方法二为Thread-0
.因为run()
方法为当前线程执行,而start()
方法为启动子线程进行执行.
线程的睡眠
线程的睡眠由sleep(mills)
方法进行启动.值得注意的是,sleep()
方法不会释放当前对象的锁.并且,在线程睡眠时对线程进行停止stop()
或interrupted()
方法,会抛出InterruptedException
.所以,经常需要throws
或者try-cath
代码块内.
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
线程的停止 - stop()与interrupted()
使用stop()
方法能够直接停止线程.
class SeanThreadDeathExceptionThread extends Thread{
public void run() {
try {
this.stop();
} catch (ThreadDeath e) {
System.out.println("进入Catch方法");
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread2 = new SeanThreadDeathExceptionThread();
thread2.start();
}
但是,当在线程sleep()
的时候进行停止,会抛出InterruptedException
异常.
此外,当线程stop()之后,是会释放线程的锁.但是,有时会直接停止线程的运行,逻辑立即停止,造成一些数据的错乱.
class SyncStopThread extends Thread{
private String username = "a";
private String password = "aa";
synchronized public void run(){
username = "b";
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
password = "b";
}
}
public static void main(String[] args) throws InterruptedException {
SyncStopThread thread = new SyncStopThread();
thread.run();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.stop();
System.out.println("username:"+thread.username+" password:"+password);
}
输出为username:b password: aa
,因为线程在运行到一半的时候停止了.
线程的停止 - interrupted()方法
关于停止的方法有interrpued()
和isInterrupted()
方法两种.
- 其中
interrupted()
方法为线程Thread的static
静态的方法,意指当前线程的是否停止;public static boolean interrupted()
isInterrupted()
的线程的Thread的dynamic
动态的方法,意指thread类的线程是否停止.public boolean isInterrupted()
其中具体的差距可以看下com.yanxml.multithreading.core.basic.stop.IsInterruptedThread
这个例子./**
- 1-7-2 判断线程是否
- */
class SeanIsInterruptedThread extends Thread{
public void run(){
super.run();
for(int i=0;i<100;i++){
System.out.println("i = "+i);
}
}
}
public class IsInterruptedThread {
// method test1
// interrupted() 方法测试当前线程是否停止 当前线程为main线程从未停止
public static void method1() throws InterruptedException{
Thread thread = new SeanIsInterruptedThread();
thread.start();
Thread.sleep(1000);
thread.interrupt();
System.out.println("是否停止1:"+thread.interrupted());
System.out.println("是否停止2:"+thread.interrupted());
}
// method test2
// interrupted() 方法测试当前线程是否停止 当前线程为main线程停止 第一次停止后 标示位重置 为false
public static void method2(){
Thread.currentThread().interrupt();
System.out.println("main 是否停止1:"+Thread.interrupted());
System.out.println("main 是否停止2:"+Thread.interrupted());
}
// method test3
public static void method3() throws InterruptedException{
SeanIsInterruptedThread thread3 = new SeanIsInterruptedThread();
thread3.start();
// Thread.sleep(1000);
thread3.interrupt();
System.out.println("thread isInterrupted 是否停止1:"+thread3.isInterrupted());
System.out.println("thread isInterrupted 是否停止2:"+thread3.isInterrupted());
}
public static void main(String[] args) throws InterruptedException {
// method1();
// method2();
method3();
}
}
// method3
// 线程未结束时
//thread isInterrupted 是否停止1:true
//thread isInterrupted 是否停止2:true// 线程结束时
//thread isInterrupted 是否停止1:false
//thread isInterrupted 是否停止2:false
常见的停止线程写法
# 写法1
class InterruptedThread extends Thread{
while(true){
if(this.isInterrupted()){
break;
// throw new InterruptedException
return;
}
// 业务逻辑
}
}
有时,我们还可以使用一个volatile
关键字修饰的变量进行辅助控制.
# 写法2
class InterruptedThread extends Thread{
public volatile boolean on = true;
public void run(){
while(on & !this.isInterrupted()){
// 业务逻辑
}
}
}
public class Main{
public static void main(String[] args) throws InterruptedException {
InterruptedThread thread = new InterruptedThread();
thread.start();
Thread.sleep(1000);
// 停止方法1
thread.interrupt();
// 停止方法2
// thread.on = false;
}
}
线程的暂停 - suspend()方法与resume()方法
我们一般暂停线程,主要是使用sleep()
方法.之前仍然有过时的suspend()和resume()方法
.其中suspend()
方法并未释放线程的锁.此外,当逻辑尚未运行结束后,直接运行有时会造成脏读问题.
class User{
private String username = "a";
private String password = "aa";
}
class SuspendThread extends Thread{
private User user;
public SuspendThread(User user){
this.user = user;
}
public void run(){
username = "b";
try {
this.suspend(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
password = "bb";
}
}
public static void main(String[] args) throws InterruptedException {
final User user = new User();
SuspendThread thread = new SuspendThread(user);
thread.run();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread printThread = new Thread(
new Runnable(){
public void run(){
System.out.println("username:"+user.username+" password:"+user.password);
}
}
);
}
此处的输出应当为username:b password:aa
,因为此处在suspend()
运行的时候进行输出,造成了一定的逻辑混乱.
线程的放弃 - yield()方法
线程的yield()
方法,意指当前线程放弃执行.剩余的几个线程重新争抢.有可能当前线程刚刚放弃,就又抢到了线程的执行.
/**
* 1-9 yield方法
* (放弃当前CPU资源.放弃时间不稳定,有可能刚刚放弃CPU资源,就又获得了CPU的资源.)
* */
class SeanYieldThread extends Thread{
public void run(){
long beginTime = System.currentTimeMillis();
int count = 0;
for(int i=0;i<10000;i++){
// Thread.yield();
count += 1;
}
long endTime = System.currentTimeMillis();
System.out.println("ALL Time: " + (endTime-beginTime));
}
}
public class YieldThread {
public static void main(String[] args) {
Thread thread = new SeanYieldThread();
thread.start();
}
}
// 非yield
//ALL Time: 0
// Thread.yield()
//ALL Time: 11
线程的优先级与执行顺序 - setPriority()方法
线程的优先级为1-10,默认值为5.常见的线程优先级为
- Thread.MAX_PRIORITY //10
- Thread.MIN_PRIORITY // 1
- Thread.NORM_PRIORITY //5
此外,线程的优先级具有如下的几个性质:
- (1). 线程的优先级和执行顺序不是一回事.
- (2). 优先级高的线程普遍比优先级低的线程先执行完毕.
- (3). 线程的优先级具有继承性.(A线程启动B线程 AB线程优先级一样)
守护线程 - setDaemeon()方法
有时,我们需要启动守护线程.我们可以通过thread.setDaemeon(true)
开启.默认为false
. Main
方法就是一个典型的守护线程,它会在所有线程结束后进行停止.
守护线程与普通线程的区别在于.普通线程停止全部后,JVM才会停止.如果这个线程是守护线程,JVM会忽视守护线程.
守护线程与普通线程
说到守护线程还有一个问题是守护进程.守护进程在Linux
系统内部.后台运行进程不等于守护进程.守护进程通常是带d
结尾的,我们通常使用的ssh远程登陆的sshd
就是守护进程.
什么是守护进程
守护进程的作用
后台进程不等于守护进程
解析Linux后台进程与守护进程的区别
其他API - Thread.currentThread() / isAlive() / getId() / getName()
Thread.currentThread()
获取当前运行的线程.Thread.currentThread().getName()
与this.currentThread().getName()
的区别在于,this.currentThread
始终指向的是创建者线程.this.getName()
与Thread.currentThread().getName()
有时可能不同,因为,有时创建者的run()
方法被其他线程调用了.package com.yanxml.multithreading.core.basic.current;
/**
- 显示currentThread()方法。
- 主要描述 Thread.currentThread.getName() 与 this.getName()
- thread.run() 和 thread.start()方法的区别。
*/
public class CurrentThreadDemo {
// 例A
public static void demoMethodA(){System.out.println(Thread.currentThread().getName());
}
// 例B
public static void demoMethodB(){Thread threadB = new SeanCurrentThread();
threadB.start();
// threadB.run();
}// 例C
public static void demoMethodC(){Thread threadC = new SeanCurrentThreadExt();
// threadC.start();
// threadC.run();Thread threadCExt = new Thread(threadC);
// threadCExt.start();
threadCExt.run();
}
public static void main(String[] args) {
// A Method.
// demoMethodA();
// B Method.
// demoMethodB();
// C Method.
demoMethodC();
}
}
class SeanCurrentThread extends Thread{
public SeanCurrentThread(){
System.out.println("构造方法打印: "+Thread.currentThread().getName());
}
public void run(){
System.out.println("运行方法打印: "+Thread.currentThread().getName());
}
}
class SeanCurrentThreadExt extends Thread{
public SeanCurrentThreadExt(){
// this.getName()这边的this指什么?
System.out.println("SeanCurrentThreadExt 构造 current:" + Thread.currentThread().getName());
System.out.println("SeanCurrentThreadExt this current:" + this.currentThread().getName());
System.out.println("SeanCurrentThreadExt 构造 this:" + this.getName());
}
public void run(){
System.out.println("SeanCurrentThreadExt run current:" + Thread.currentThread().getName());
System.out.println("SeanCurrentThreadExt this current:" + this.currentThread().getName());
System.out.println("SeanCurrentThreadExt run this:" + this.getName());
}
}
// 输出:
// case-1: main
// case - 2-1: start()
// 构造方法打印: main
// 运行方法打印: Thread-0// case - 2-2: run()
//构造方法打印: main
//运行方法打印: main// case - 3-1: threadC.start()
//SeanCurrentThreadExt 构造 current:main
//SeanCurrentThreadExt 构造 this:Thread-0
//SeanCurrentThreadExt run current:Thread-0
//SeanCurrentThreadExt run this:Thread-0//case - 3-2: threadC.run()
//SeanCurrentThreadExt 构造 current:main
//SeanCurrentThreadExt 构造 this:Thread-0
//SeanCurrentThreadExt run current:main
//SeanCurrentThreadExt run this:Thread-0//case - 3-3 threadCExt.start()
//SeanCurrentThreadExt 构造 current:main
//SeanCurrentThreadExt 构造 this:Thread-0
//SeanCurrentThreadExt run current:Thread-1
//SeanCurrentThreadExt run this:Thread-0//case - 3-4 threadCExt.run()
//SeanCurrentThreadExt 构造 current:main
//SeanCurrentThreadExt 构造 this:Thread-0
//SeanCurrentThreadExt run current:main
//SeanCurrentThreadExt run this:Thread-0// 结论1 : this.getName() 在创建时已经被指定了。
// 结论2 : start()为另启一个线程进行执行;run()为当前main()方法所在线程进行执行。isAlive()
判断线程是否存活- getId()
获取线程ID,Main主线程为1 - getName()
获取线程名称. 另外,可以通过setName()
对线程名称进行赋值.
后记
发现在使用Thread API
的过程中,我们更加应该重视下Thread的多个线程之间的状态转换. 具体的状态转换模式如下所示.(根据大学所学的经验,我们可以知道.注意包括: 三态模型 / 五态模型)
Thread详解
Reference
[1]. Java 多线程编程核心技术
[2]. Java并发编程的艺术
[3]. JavaTM Platform Standard Ed. 6
[4]. 当用Thread的子类构造创建多线程时,并同时传入Runnable覆盖run方法时,此时执行的run方法是
[5] 线程的几种状态转换
还没有评论,来说两句吧...