Java并发编程基础(一)

多线程得状态

NEW

建立一个线程对象,此时得线程尚未真正得存在,只是建立了一个通常得对象,在这个对象调用了start()以后将进入了就绪得状态RUNNABLE.java

RUNNABLE

建立得线程对象只有调用start()以后才正式得建立了一个线程,这个状态就是就绪状态,此状态得线程能够得到CPU的调度(也就是线程得到了执行的资格),RUNNABLE状态得线程只能意外得终止或者进入RUNNING状态,不会进入BLOCK或者TERMINATED状态,由于这些状态的须要调用wait和sleep,或者其余的block的IO操做,须要得到CPU的执行权才能够。算法

RUNNING

一旦CPU经过轮询的或者其余的方式从任务可执行的队列中选中了线程,那么此时它才是真正的执行本身的逻辑代码,RUNNING状态的其实也是RUNNABLED状态,可是反过来不成立。RUNNING状态能够在一下状态切换。数组

1.TERMINATED: 使用stop(),可是JDK不推荐使用。缓存

2.BLOCKED状态,好比sleep,或者wait()进入waitSet中,或者进行阻塞IO操做(好比网路数据的读写),或者获取某个锁资源,从而进入了该锁的阻塞队列中进入了BLOCKED状态.网络

3.RUNNABLE:CPU的调度器的轮询,使得哎线程放弃执行,进入RUNNABLE状态,或者该线程主动的调用了yield方法,放弃CPU的执行权.数据结构

BLOCKED

由上可知进入BLOCKED状态的缘由。线程进入BLOCKED状态能够在如下几种状态切换。多线程

1.TERMINATED:调用stop()不推荐或者JVM Crash.app

2.RUNNABLE:线程阻塞的操做结束,进入了RUNNABLE状态;线程完成指定的休眠,进入RUNNABLE;Wait中的线程渠道了某个锁资源,进入RUNNABLE;如今在阻塞的过程被打断,调用了interrupt()。xss

TERMINATED

TERMINATED是一个线程的最终状态,线程进入TERMINATED状态,意味着该线程的整个生命周期都结束了,下列的状况将会是线程进入TERMIATED状态.ide

1.线程运行正常结束,结束生命周期

2.线程运行出错的意外结束。

3.JVM Crash,致使全部的线程都结束。

线程的start()源码解析。

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 */
			}
		}
	}

由上面的代码能够知道,实际的调用启动的方式是start0(),该方法是一个本地方法private native void start0();除此以外咱们还能够知道,刚建立未启动的线程的threadStatus=0,不能屡次启动Thread,不然会提示java.lang.IllegalThreadStateException,启动的线程会被加入到一个线程组中,进入terminated状态的线程不能在其回到runnable和runing状态.

模板方法在多线程的使用

[@Override](https://my.oschina.net/u/1162528)
	public void run() {
		if (target != null) {
			target.run();
		}
	}

由上面这段程序能够知道,若是在构建Thread对象的时候传入了Runnable,那么run方法就是调用了Runnable的run,不然咱们就须要重写Thread的run(). 例子:

public class TemplateDemo {
//至关于start(),负责编写算法结构
public final void modify(String msg){
	System.out.println("-----------------------");
	showInfo(msg);
	System.out.println("-----------------------");
}
//详单与run
public void showInfo(String msg){

}
	public static void main(String[] args) {
		TemplateDemo demo = new TemplateDemo(){
			[@Override](https://my.oschina.net/u/1162528)
			public void showInfo(String msg) {
				System.out.println("content:"+msg);
			}
		};
		demo.modify("hello world");
	}
}

执行结果: https://oscimg.oschina.net/oscnet/dd6ee5272a33f2181bdb54c0c1fb89ef7b2.jpg

线程的构造函数

线程的命名

在多线程的环境,为线程起一个特殊的名字是颇有必要的,能有助于咱们对问题发容排查和线程的追踪。

线程的默认命名

public Thread() {
		init(null, null, "Thread-" + nextThreadNum(), 0);
	}

	private static int threadInitNumber;
	private static synchronized int nextThreadNum() {
		return threadInitNumber++;
	}

由上面的源码能够知道Thread的命名规则是Thread-i.

修改线程的名字

在线程建立以后,启动以前,咱们有机会去修改线程的名字,可是若是线程已经启动了,就没法修改线程的名字,由以下的代码可得知:

public final synchronized void setName(String name) {
		checkAccess();
		if (name == null) {
			throw new NullPointerException("name cannot be null");
		}

		this.name = name;
		//非NEW状态的线程是没法启动的
		if (threadStatus != 0) {
			setNativeName(name);
		}
	}

线程的父子关系

由下面的代码片断能够知道,任何线程的建立都是创建在其余线程的基础之上。

private void init(ThreadGroup g, Runnable target, String name,
					  long stackSize, AccessControlContext acc,
					  boolean inheritThreadLocals) {
		if (name == null) {
			throw new NullPointerException("name cannot be null");
		}

		this.name = name;
		//获取当前的线程
		Thread parent = currentThread();
		SecurityManager security = System.getSecurityManager();

Thread与ThreadGroup

在Thread的构造函数中,能够显示的指定线程的Group,也就是ThreadGroup

SecurityManager security = System.getSecurityManager();
		if (g == null) {
			/* Determine if it's an applet or not */

			/* If there is a security manager, ask the security manager
			   what to do. */
			if (security != null) {
				g = security.getThreadGroup();
			}

			/* If the security doesn't have a strong opinion of the matter
			   use the parent thread group. */
			if (g == null) {
				//获取父线程的线程组
				g = parent.getThreadGroup();
			}
		}

由上能够知道建立的当前线程会被加入当前父线程的线程组。

例子:

public class ThreadGroupDemo {

		public static void main(String[] args) {
			Thread t1 = new Thread(new Runnable() {
				[@Override](https://my.oschina.net/u/1162528)
				public void run() {

				}
			});
			t1.start();
			ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
			System.out.println("mainGroup:"+threadGroup.getName());
			Thread t2 = new Thread(new ThreadGroup("demo"), new Runnable() {
				[@Override](https://my.oschina.net/u/1162528)
				public void run() {

				}
			});
			System.out.println("t1 threadGroup:"+t1.getThreadGroup().getName());
			System.out.println("t2 threadGroup:"+t2.getThreadGroup().getName());
		}
	}

执行结果:

Thread与Runnable

Thread负责线程本省的职责和控制,而Runnable则负责逻辑执行单元的部分,这里就再也不赘述。

Thread与JVM虚拟机栈.

在Thread的构造函数中有一个参数stackSize,经过官方的文档可知,通常状况下,建立线程的时候不会手动的执行栈内存的地址空间字节数组,统一经过xss参数进行设置便可,经过上面的刮北风王菲的文档的描述,咱们不难发现stacksize越大则表明着正在线程方法调用递归的深度就越深,stacksize越小则表明着建立的线程数量越多,固然了,这个参数对平台的依赖仍是比较高的。经过对虚拟机的屡次的参数调整,以下图所示:

获得入以下的表格:

JVM内存结构

1.程序计数器

不管任何语言,其实都是须要哦有操做系统经过控制总线向CPU发送机器指令,程序计数器在JVM中所起的做用就是用于存放当前线程接下来将要执行的字节码指令,分支,循环,跳转,异常处理等信息.在任什么时候候,一个处理器只执行其中一个线程中的指令,为了可以在CPU时间片轮状切换上下文以后顺利回到正确的执行位置,每条线程都须要具有一个独立的程序计数器,各个线程之间互相不影响,所以JVM将此块内u才能区域涉及成线程私有的。

2.Java虚拟机栈 Java虚拟机栈也是线程私有的,它的生命周期与线程相同,是在JVM运行时所建立的,在线程中,方法执行的时候都会建立一个名为栈帧(stack frame)的数据结构,主要用于存放局部变量表,操做栈,动态连接,方法出口等信息.每一个线程在建立的时候,JVM都回为其建立对应的虚拟机栈,虚拟机栈的大小可能够经过-xss来配置,方法的调用时栈帧压入和弹出的过程,等同的虚拟机栈若是局部变量表等占用内存越小则可呗压入的栈帧就回越多,反之则可呗压入的栈帧就回越少,通常栈帧将内存的大小称为宽度,而张震的数量则称为虚拟机栈的深度。

3.本地方法栈

Java提供了调用本地方法的接口(Java Native Interface),也就是C/C++程序,在线程的执行过程,常常回碰到调用JNI方法的状况,好比网络通讯,文件操做的底层,甚至是String的intern等都是JNI方法,JVM为本地方法所划分的内存区域即是本地方法栈,这块内存区域其自由度很是高,彻底靠不一样的JVM厂商来实现,Java虚拟机规范并为给出强制的规定,一样它也是线程私有的内存区域。

4.堆内存

堆内存是JVM中最大的一块内存区域,被全部的线程所共享,Java在运行期间建立的全部对象几乎都存在该内存区域,该内存区域也是垃圾回收器重点照顾的区域,所以有些时候堆内存被称为GC堆。堆内存还被细分为新生代,老年代,更细分为Eden区,From Survivor区和To Survivor区。

5.方法区 方法区也是被多线程共享的内存区域,它主要用于存储已经被虚拟加载的类信息,常量,经他变量,即便编译器(JIT)编译后的代码等数据,虽然在Java虚拟机规范中,将堆内存划分为堆内存的一个逻辑分区,可是被称为非堆,甚至有时被称为"持久代",在HotSpot JVM中,方法区仍是回被细化为持久代和代码缓存区,代码缓存区主要存储编译后的本地代码.

守护线程

守护线程是一类比较特殊的线程,通常用于处理一些后要的工做,像JDK的垃圾回收线程。在正常的状况下,若JVM中没有一个非守护线程,则JVM的进程会退出。

例子:

public class DeamonThreadDemo {
	public static void main(String[] args) throws InterruptedException {
		Thread thread = new Thread(()->{
			while (true){
				try {
				Thread.sleep(1);
				}catch (Exception e){
					e.printStackTrace();
				}
			}
		});
		//设置守护线程
//        thread.setDaemon(true);
		thread.start();
		Thread.sleep(2_000L);
		System.out.println("Main thread finished lifecycle.");
	}
}

上面的代码若是没有放开注释的那行,那么运行就不会结束,JVM不会退出,若是放开了注释的那行,那么JVM会正常的退出.由此能够知道,守护线程具有自动结束生命周期的特性,而非守护线程不具有这个特色。能够做为后台服务做用,可是在父线程退出以后也可以正常的结束,那么守护线程就是个很好的选择。

线程API

线程Sleep

Sleep是使线程进入休眠状态,休眠有一个很是重要的特征,拿就是其不会放弃监视器monitor锁的全部权。 sleep有两种重载的方法:

public static native void sleep(long millis) throws InterruptedException;//须要填充时间毫秒
public static void sleep(long millis, int nanos)//须要毫秒和纳秒

例子:

public class ThreadSleepDemo {


	public static void main(String[] args) {
	new Thread(()->{
		long startTime = System.currentTimeMillis();
		sleep(3_000L);
		long endTime = System.currentTimeMillis();
		System.out.println(String.format("total spend %d ms",(endTime-startTime)));
	}).start();
		long startTime = System.currentTimeMillis();
		sleep(3_000L);
		long endTime = System.currentTimeMillis();
		System.out.println(String.format("Main thread total spend %d ms",(endTime-startTime)));
	}
	private static void sleep(long ms){
		try {
			Thread.sleep(ms);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

执行结果:

使用TimeUnit替代了Thread.sleep

再JDK1.5以后,JDK引入一个枚举类TimeUnit,其对sleep方法提供了很好的封装,使用它能够省去时间但未的换算步骤,例如须要休眠5小时30分25秒100毫秒:

TimeUnit.HOURS.sleep(5);
		TimeUnit.MINUTES.sleep(30);
		TimeUnit.SECONDS.sleep(25);
		TimeUnit.MILLISECONDS.sleep(100);

上面的代码比单位换算要直观的多了。

线程yield

yield方法属于一种启发式的方法,其会提醒调度器我愿意放弃当前的CPU资源,若是CPU资源不紧张,则会忽略这种提醒。调用yield方法会使当前的线程从Running状态切花不能到Runnbale状态,通常不经常使用。

public class ThreadYieldDemo {

	public static void main(String[] args) {
		IntStream.range(0,2).mapToObj(ThreadYieldDemo::create).forEach(Thread::start);
	}
	private static Thread create(int index){
		return new Thread(()->{
		   if (index==0)
			   Thread.yield();//放弃CPU资源
			System.out.println(index);
		});
	}
}

执行的结果多是1 0或者是0 1出现,不肯定,由于这个yield方法只是一个提示,CPU执不执行是不肯定的。

yield和sleep

再JDK5之前的版本种,yield方法事实上是i调研了sleep(0),可是他们之间存在着本质的区别。

  • sleep会致使当前线程暂停知道的时间,没有CPU时间片的消耗。
  • yield只是对CPU调度器的一个提示,若是CPU调度器没有忽略这个提示,它会致使线程上下文的切换。
  • sleep会使用线程短暂的block,会再给定的时间内释放CPU资源。
  • yield会使Running状态的Thread进入Runnable状态(若是CPU调度器没有忽略这个提示语的话)
  • sleep几乎百分之百的完成了给定时间的休眠,而yield的提示并不能一旦担保。
  • 一个线程sleep另一个线程interrupt会捕获到终端八年信号,而yield不会。

设置线程优先级

  • public final void setPriority(int newPrioprity)为线程指定优先级。
  • public final int getPriority()获取线程的优先级。

在线程上设置优先级是一个提示的做用,并非,设置了就必定获得:

  1. 对于root用户,它会hint操做系统你想要设置的优先级,不然会忽略。
  2. 若是CPU比较忙,设置优先级可能会得到更多的CPU时间片,可是闲时有限即得搞底几乎不会有任何做用。

setPriority(int priority)的源码以下:

public final void setPriority(int newPriority) {
		ThreadGroup g;
		checkAccess();
		if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
			throw new IllegalArgumentException();
		}
		if((g = getThreadGroup()) != null) {
			if (newPriority > g.getMaxPriority()) {
				newPriority = g.getMaxPriority();
			}
			setPriority0(priority = newPriority);
		}
	}

从源码能够知道线程的最小优先级时1,最大的优先级时10,因此设置的时候只能在1-10之间(固然咱们也能够设置优先级的大小范围,可是只能在线程组内)。默认的线程优先级时5,子线程的优先级依赖父线程的优先级:

public class ThreadPriorityDemo {

	public static void main(String[] args) {
		Thread t1 = new Thread();
		System.out.println("t1 priority:"+t1.getPriority());
		Thread t2 = new Thread(()->{
			Thread t3 = new Thread();//子线程的优先级依赖父线程的优先级
			System.out.println("t3 priority:"+t3.getPriority());

		});
		t2.setPriority(6);
		t2.start();
		System.out.println("t2 priority:"+t2.getPriority());
	}
}

执行结果:

获取当前线程

public static Thread currentThread()用户返回当前执行线程得引用,这个方法虽然很简单,可是使用很是普遍。

public class CurrentThreadDemo {
	public static void main(String[] args) {
		new Thread(new Runnable() {
			[@Override](https://my.oschina.net/u/1162528)
			public void run() {
				System.out.println("1currentThread:"+Thread.currentThread().getName());
			}
		}).start();
		System.out.println("2currentThread:"+Thread.currentThread().getName());
	}
}

执行结果:

设置线程上下文类加载器

  • public ClassLoader getContextClassLoader()获取线程上下文得类加载器,简单来讲,就是这个线程是由那个类加载器加载得,若是在没有此u该线程的上下文加载器的状况下,则保持与父线程统一的类加载器。

  • public void setContextClassLoader(ClassLoader cl)设置改线程的类加载器,这个方法能够打破JAVA类加企的父委托机制,有时候该方法也被成为JAVA类加载器的后门。

线程interrupt

线程interrupt是一个很是重要的API,也是常用的反法国,与线程中断相关的API

  • public void interrupt()该方法会打断当前进入阻塞状态的线程(例如调用了sleep,wait,join,InterruptibleChannel的IO操做,Selector的wakeup方法,都会使线程进入阻塞状态),打断线程阻塞并不表明生命周期的结束,仅仅是打断了当前的阻塞状态。一旦线程被打断都会收到一个称为InterruptedException的异常,这个异常就像一个signal(信号)同样通知当前线程被打断了(注意,死亡状态的线程不能对其中断)

例子:

public class ThreadInterruptDemo {

	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(()->{
			try {
				TimeUnit.MINUTES.sleep(1);
			} catch (InterruptedException e) {
				System.out.println("receive interrupt msg");
			}
		});
		t1.start();
		TimeUnit.MILLISECONDS.sleep(2);
		t1.interrupt();//中断
	}
}

执行回结果:

  • public boolean isInterrupted() isInterrupted是Thread的一个成员方法,它主要判断当前线程是否被中断,该方法仅仅是堆interrupt表示的一个判断,并不会影响表示发生任何标识发生任何改变,这个与咱们即将学习到的interrupted是存在差异的。

例子:

public class ThreadInterruptedDemo {

	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(){
			@Override
			public void run() {
				//这里使用循环而不是使用sleep是由于sleep是可中断的(会收到中断信号,将中断的信号),会干扰到程序运行的结果
		 /*       while (true){
					//....
				}*/
				try {
					TimeUnit.MILLISECONDS.sleep(1);
				} catch (InterruptedException e) {
					System.out.printf("I am be interrupted ? %s\n",isInterrupted());
				}
			}
		};

		t1.start();
		TimeUnit.MILLISECONDS.sleep(2);
		System.out.printf("Thread is interrupted? %s\n",t1.isInterrupted());
		t1.interrupt();
		System.out.printf("Thread is interrupted? %s\n",t1.isInterrupted());
	}
}

若是打开注释的地方,去掉下面的线程休眠,就会出现 false true的结果,反之都是false,由于interrupt会将标识重置。

  • public static boolean interrupted() interrupted是一个静态方法,虽然其余也用于判断当前线程是否被中断,可是它和成员方法isInterrupted是有区别的,调用该方法会直接查出掉线程的interrupt标识,须要注意的是,若是当前线程被打断了,那么第一次调用的interrupted方法会返回true,而且当即擦除interrupt标识,第二次会包括之后的调用永远会返回false,除非在此期间线程又被一次的打断。

例子:

@Test
	public void testInterrupted() throws InterruptedException {
		Thread t1 = new Thread(){
			@Override
			public void run() {
				while (true)
				System.out.println(Thread.interrupted());
			}
		};
		t1.setDaemon(true);
		t1.start();
		TimeUnit.MILLISECONDS.sleep(2);
		t1.interrupt();
	}

执行结果:

线程join

Thread的join()与sleep同样是可中断的方法,也就是说,若是由其余的线程可执行对点给钱线程的interrupt操做,它也会捕抓到中断的信号,而且擦除线程的interrupt标识,Thread的API为咱们提供了以下的三个接口:

public final void join() throws InterruptedException

public final synchronized void join(long millis)

public final synchronized void join(long millis, int nanos)

join某个线程1,回事当前线程2进入等待,知道线程1运行结束生命周期,或者到达给定的时间,那么再此期间2线程是出于BLOCKED的,而不是1线程。

public class ThreadJoinDemo {

	public static void main(String[] args) throws InterruptedException {
		List<Thread> threadList = IntStream.range(1,3)
				.mapToObj(ThreadJoinDemo::create).collect(Collectors.toList());
		 threadList.forEach(Thread::start);
		for (Thread t:threadList) {
			//若是注释当前的代码,会使得三个线程交替出现,
			// 不然闲使得当前的两个线程交替出现,等这两个线程执行完成以后再执行主线程
			t.join();
		}
		for (int i=10;i>0;i--){
			System.out.println(Thread.currentThread().getName()+"#"+i);
			shortSleep();
		}
	}

	private static Thread create(int seq) {
		return new Thread(()->{
			for (int i=0;i<10;i++){
				System.out.println(Thread.currentThread().getName()+"#"+i);
				shortSleep();
			}
		});
	}
	private static void shortSleep(){
		try {
			TimeUnit.SECONDS.sleep(1);
		}catch (InterruptedException e){
		e.printStackTrace();
		}
	}
}

运行结果:

关闭一个线程

JDK里面的stop方法是能够关闭的,可是已经被JDK废弃掉的,JDK不推荐使用了,该关闭方法再关闭线程是可能不会是方法监视器锁。因此须要采用更加合理的线程关闭方法.

正常关闭

1.线程结束生命周期正常结束 线程运行结束,完成了本身的使命以后,就会正常的而退出,若是线程种的任何耗时比较短,或者时间可控,那么方法天然会正常的结束.

2.捕抓中断信号关闭线程

使用new Thread的方式建立线程,这种方式看似很简单,其实它的派生成本是比较高得,所以一个线程种每每会循环的执行某个任务,好比心跳检查,不断得接收网络得消息报文。系统决定退出得时候,能够借助中断线程得方式使其退出。 例子:

public class ThreadInterruptExitDemo {

	public static void main(String[] args) throws InterruptedException {
		Thread t = new Thread() {
			@Override
			public void run() {
				System.out.println("I will start work");
				while (!isInterrupted()) {
					System.out.println("I am working");
				}
				System.out.println("I will be exiting");
			}
		};
		t.start();
		TimeUnit.MILLISECONDS.sleep(10);
		System.out.println("I will be shutdown.");
		t.interrupt();
	}
}

执行结果: I will start work I am working ... I will be shutdown. I am working I am working I will be exiting

使用volatile开关控制

因为线程得interrupt标识极可能被擦除,或者逻辑单元种不会调用任何中断方法,因此volatile修饰得开发flag关闭线程也是一种很好得方式.

例子:

static class MyTask extends Thread{
		private volatile boolean closed=false;
		@Override
		public void run() {
			System.out.println("I will start work");
			while (!closed && !isInterrupted()) {
				System.out.println("I am working");
			}
			System.out.println("I will be exiting");
		}
		public void close(){
			this.closed=true;
			this.interrupt();
		}
	}
	public static void main(String[] args) throws InterruptedException {
		MyTask t = new MyTask();
		t.start();
		TimeUnit.MILLISECONDS.sleep(1);
		System.out.println("System will be shutdown.");
		t.close();
	}

执行结果: I will start work I am working I am working ... System will be shutdown. I am working I will be exiting

异常退出

再一个线程得执行单元种,是不容许抛出checked异常得,不论Thread得run方法仍是Runnable的run方法,若是线程的再运行过程当中式须要捕获checked异常而且判断是否还有运行下取的必要,那么此时能够将checked异常封装成unckecked异常(RuntimeException)抛出进而结束线程的生命周期.

进程假死

可使用jps和jstack等jdk自带的工具查看.

相关文章
相关标签/搜索