Java 多线程(一) Thread API 基础

版权声明:欢迎转载,转载请说明出处. 大数据Github项目地址https://github.com/SeanYanxml/bigdata。 https://blog.csdn.net/u010416101/article/details/88631340

前言

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方法是

猜你喜欢

转载自blog.csdn.net/u010416101/article/details/88631340