Java并发编程1-并发编程基础

1 为什么要使用多线程

使用多线程的原因主要有以下几点:

(1)更有效的运用多核心处理器

一个线程在一个时刻只能运行在一个处理器核心上,所以单线程程序在同一时刻只能使用一个处理器核心,而多个线程在同一时刻可以使用多个处理器核心,显然能更加有效的运用多核心处理器。

(2)更快的响应时间

一些较为复杂的代码可以使用多线程技术,即将数据一致性不强的操作派发给其他线程处理(也可以使用消息队列),如生成订单快照、发送邮件等。这样做的好处是响应用户请求的线程能够尽可能快地处理完成,缩短了响应时间,提升了用户体验。

(3)更好的编程模型

Java为多线程编程提供了良好、考究并且一致的编程模型,使开发人员能够更加专注于问题的解决,即为所遇到的问题建立合适的模型,而不是绞尽脑汁地考虑如何将其多线程化。一旦开发人员建立好了模型,稍做修改总是能够方便地映射到Java提供的多线程编程模型上。

2 线程优先级

(1)通过一个整型成员变量priority来控制优先级,优先级高的线程分配时间片的数量要多于优先级低的线程。

(2)可以通过setPriority(int)方法来修改优先级,优先级的范围从1~10,默认优先级是5。

(3)设置线程优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。

(4)线程优先级不能作为程序正确性的依赖,有以下两点原因:1 在数量基数不大的情况下优先级的区分并不明显,甚至出现优先级低的线程先于优先级高的执行的情况。2 有些操作系统会忽略Java对线程优先级的设定,例如:Mac OS X 10.10,Java版本为1.7.0_71,环境下和Ubuntu 14.04环境下,所有Java线程优先级均为5,对线程优先级的设置会被忽略。

3 线程的状态

Java线程在运行的生命周期中可能处于下表所示的6种不同的状态,在给定的一个时刻,线程只能处于其中的一个状态。

124810_Ezx6_3145136.png

Java线程状态变迁如下图示:

125254_nEqN_3145136.png

注意:(1)Java将操作系统中的运行和就绪两个状态合并称为运行状态。(2)阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态,但是阻塞在java.concurrent包中Lock接口的线程状态却是等待状态,因为java.concurrent包中Lock接口对于阻塞的实现均使用了LockSupport类中的相关方法。

4 Daemon线程

(1)Daemon线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。

(2)当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出。

(3)可以通过调用Thread.setDaemon(true)将线程设置为Daemon线程。

注意: Daemon属性需要在启动线程之前设置,不能在启动线程之后设置。

(4)Daemon线程被用作完成支持性工作,但是在Java虚拟机退出时Daemon线程中的finally块
并不一定会执行,所以不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑,示例如下:

public class Daemon {
	public static void main(String[] args) {
		Thread thread = new Thread(new DaemonRunner(), "DaemonRunner");
		thread.setDaemon(true);
		thread.start();
	}

	static class DaemonRunner implements Runnable {
		@Override
		public void run() {
			try {
				SleepUtils.second(10);
			} finally {
				System.out.println("DaemonThread finally run.");
			}
		}
	}
}

运行Daemon程序,可以看到在终端或者命令提示符上没有任何输出。main线程(非Daemon线程)在启动了线程DaemonRunner之后随着main方法执行完毕而终止,而此时Java虚拟机中已经没有非Daemon线程,虚拟机需要退出。Java虚拟机中的所有Daemon线程都需要立即终止,因此DaemonRunner立即终止,但是DaemonRunner中的finally块并没有执行。

5 构造线程

一个新构造的线程对象是由其parent线程来进行空间分配的,而child线程继承了parent是否为Daemon、优先级和加载资源的contextClassLoader以及可继承的ThreadLocal,同时还会分配一个唯一的ID来标识这个child线程。至此,一个能够运行的线程对象就初始化好了,在堆内存中等待着运行。java.lang.Thread中对线程进行初始化的部分的代码如下:

private void init(ThreadGroup g, Runnable target, String name,
			long stackSize, AccessControlContext acc) {
		if (name == null) {
			throw new NullPointerException("name cannot be null");
		}
		// 当前线程就是该线程的父线程
		Thread parent = currentThread();
		this.group = g;
		// 将daemon、priority属性设置为父线程的对应属性
		this.daemon = parent.isDaemon();
		this.priority = parent.getPriority();
		this.name = name.toCharArray();
		this.target = target;
		setPriority(priority);
		// 将父线程的InheritableThreadLocal复制过来
		if (parent.inheritableThreadLocals != null)
			this.inheritableThreadLocals = ThreadLocal
					.createInheritedMap(parent.inheritableThreadLocals);
		// 分配一个线程ID
		tid = nextThreadID();
}

6 启动线程

线程对象在初始化完成之后,调用start()方法就可以启动这个线程。线程start()方法的含义是:当前线程(即parent线程)同步告知Java虚拟机,只要线程规划器空闲,应立即启动调用start()方法的线程。

注意:启动一个线程前,最好为这个线程设置线程名称,因为这样在使用jstack分析程
序或者进行问题排查时,就会给开发人员提供一些提示,自定义的线程最好能够起个名字。

转载于:https://my.oschina.net/u/3145136/blog/841981

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值