程序员等级划分

一 缘由

最近因为找工作的原因,开始规划对自己的下一步发展和职业规划进行了深入的思考.总结自己的职业发展轨迹,想对自己是一步步成长的有个了解,所以就开始思考对目前很常见的对技术人员的初级,中级和高级的划分标准的问题.根据互联网上一些技术大神和职场专家的观点,再结合自己的工作经验.我总结了一些自己对这个问题的看法. 总的来说的话, 我认为可以使用下面这些判断标准:

1)初级技术人员: 功能实现.

2)中级技术人员: 模块级别的设计优化,以及性能优化.

3)高级技术人员: 系统级别的问题的排查, 性能调优, 一定的技术选型能力, 知识与技术的广度和深度, 以及一定的团队管理经验和能力.

4)架构师: 系统架构选型,搭建和优化,以及系统架构的重构.

当然,这样分类的边界并不是特别清楚或固定,但是大体上可以根据这几个标准来判断.

二 初级

对于初级程序员,他们的能力相对弱,决定了他们的关注点是: 业务功能的实现.他们所依赖的技术基础的级别往往是语言的语法规则等.他们平时的迫切任务也主要是拼命地完成上级分配给自己的任务.由于技术基础薄弱,他们通常为了完成任务而疲于奔命.自己手上的事情都来不及完成,所以导致他们没有时间停下了思考或者没有意识去思考.

下面这些问题是大多数初级程序员经常遇到的困惑:

1) apche与tomcat不都是网站服务器吗?为什么公司会同时使用这两个东西啊?(想当年,我刚毕业的时候也是搞不懂这两个东西的区别~~, 往事不堪回首啊, 哈哈……)

2)jsp中直接写java代码不是更加简单直观吗?为什么好像别人都在使用jstl和模板引擎(freemaker,velocity)这些东西啊?

3)不重视使用配置文件,甚至很多参数直接在代码里面写死了.

4)很多技术在宣传的时候往往又吹嘘自己的功能强大,丰富.实际情况是,除了核心功能会经常被使用之外,其实其他的功能都比较鸡肋.或者支持一些插件或模块性的扩展,也敢宣称自己无所不能,其实这类工作对企业和团队要求的投入还是比较大的.在学习技术的时候,这种情况也会让很多初级程序员很困惑.

总结来说的话,初级程序员对技术的认识仅仅停留在功能层面,他们没有足够的视野高度和广度,所以往往看不到这样的现实: 企业是在拼命地追求效率!

在此,有点感触也有些许悲哀: 一个人往往认为自己所看到的就是全部,却没有意识到自己的眼界是存在局限性.

在这个世界,不知道有多少人就活在自己的视野范围内啊! 他们忙碌,自豪甚至小有成就. 但是他们也就那样了,仅此而已.

三 中级

初级人员,随着项目经历的增加,看到了企业和软件系统是怎样的一种存在.尤其是经历过或者意识到重构的初级程序员就进入了中级程序员的行列.我至今无法忘记当我第一次听到了”重构”这个词的时候的震惊程度.入行这几年给我留下类似的这种巨大观念冲击的词语不多,大概有这几个:重现, 重构, 设计模式, 使用场景, 最佳实践, 系统架构等. 重现是软件开发过程中非常基础和重要的策略和原则;重构则是软件产品随着需求的变动和技术的发展经常会面临的问题;设计模式和架构则分别代表模块级别和系统级别的设计的同义词.

理解重构的含义之后,我瞬间明白了我多年的困惑,也精准的描述了我多年的状态.我为什么总是把已开发的软件该来该去?当初写第一行代码的时候怎么就没有想好这些呢?这些都是问题.虽然并不是每个程序员都能明确的意识到这种问题的存在,但是每个人都在被这个问题影响.刚刚认识到重构的时候,一半是豁然开朗,因为知道自己面临什么问题; 另一半则是焦急和渴望,因为迫切的想要找到解决办法.我有一段时间甚至甚至走到了另一个极端——过度设计. 从没有设计意思走到了追求完美的程序,神化设计的方向.

这种状态其实并没有持续多久,因为问题清楚了,答案其实也清楚了. 因为设计模式就天然的和重构联系在一起,这两者的关系是如此的紧密,以至于任何一者出现的地方,你总能看见另一个家伙的影子.设计模式

从这段描述,就能说明中级程序员的典型特点,就是具有一定的设计意识和思想.催生这种思想产生的动力就是:重构.

当然随着技术功底的加强,对于实现功能的任务已经越来越没有挑战性了.中级程序员会逐渐把剩余的生产力投入到软件性能这个层面.随着技术积累和项目积累,对常用技术的认识也就更全面和立体了,而不是像初级程序员那样只看到功能.

四 高级

老实说,我对自己当前的阶段的定位是: 我目前还处在高级程序员的入门口.刚刚跨入这个级别不久.在很多评判的标准上都还达不到或者等待检验.因为还没有完全经历过,所以就不太好谈这个问题.希望自己能够尽快到达这个级别的各个标准. 下面的话只能就自己的理解(而不是切身经历)来谈一谈.

作为高级开发人员的话,工作内容的复杂程度就不再是模块级别的了,而应该是系统级别的问题.随着技术和社会的发展,尤其是互联网的发展,目前的单机软件的地位越来越低.软件行业,可能其他技术行业也是这样,通常是那些最热门的领域,所面对的技术问题最多,因为业务压力和同行竞争对技术的驱动力是非常巨大的,一句露骨的话就是:钱的力量. 就像IT行业,目前最火热的无疑是互联网行业,所以互联网行业所面对的问题就非常的多,高安全,高性能,高可靠等,虽然与其他的领域相比,像人工智能这些领域,互联网的核心技术不一定最复杂,但是其发展速度和面临的挑战确实的非常大的.分布式技术就是应对互联网的这些挑战的最主要的解决方案.分布式技术这几年的发展离不开互联网行业的蓬勃发展.可以说互联网和大数据对分布式的发展起到了很大的推动作用.由于分布式技术的发展和成熟,也导致分布式技术和一些思想被其他IT领域采用,这又对技术人员提出了新的挑战.这样技术和业务就形成了良性的循环.

鉴于当前的行业的技术形态的特点,很多软件能力,就是以系统形式存在.所以,技术人员所面对的很多复杂的问题,都是系统级的问题.这就是为什么说高级程序员的处理的问题的级别是系统级别的.

除了工作或问题的复杂度,高级技术人员还应该在系统级别的性能调优,技术选型能力,知识的广度与深度以及团队管理等方面有扎实的功力. 这也是我目前需要努力的方向!

五 架构师

这个留待以后补充吧.

synchronized

一 简介

在java1.5之前, java程序员在多线程开发的时候,能够用来进行并发控制的手段并不多: synchronized关键字. 虽然自1.5之后java提供了更多的并发控制方式,比如可重入锁,自旋锁等,但是synchronized关键字因为它的简单(粗暴),使用的频率还是非常高.

二 使用方式

synchronized关键字主要有两种使用方式:

1)修饰方法.这样可以使其修饰的方法称为一个线程安全的方法.

2)修饰对象.synchronized修饰对象时,可以构造一个语句块,在这个语句块里的所有语句都是线程安全的.

对于第二种情况,即当synchronized关键字修饰对象的时候,有以下两点要注意:

1)synchronized不能修饰java的8中基本数据类型:boolean,byte,char,short,int,long,float,double.否则会导致编译错误. 需要说明的是, 这8类基本数据类型的封装类是可以被synchronized关键字修饰的.

2)synchronized也不能修饰引用为null的对象.否则在运行的时候会抛出NullPointException.

当synchronized修饰基本数据类型时,发生编译错误:

当synchronized修饰引用为null的对象时,程序运行过程中会从synchronized关键字所在位置抛出NullPointException:

三 底层机制

虽然说对普通的开发者来说,只需要从功能角度和层面掌握synchronized的主要功能和基本用法,但是如果要对java理解的更加深刻的话,就得理解synchronized背后的机制了.

我自己在很长一段时间都认为,synchronized关键字在修饰方法的时候和方法内部的synchronized的同步块是没有什么差别的,我总是这样理解:

认为

class SynDemo {
	public synchronized void fun(){
		System.out.println("hello");
	}
}

class SynDemo {
	public void fun() {
		synchronized (this) {
			System.out.println("hello");
		}
	}
}

本质上是一样的.

class SynDemo {
	public synchronized static void fun() {
		System.out.println("hello");
	}
}

class SynDemo {
	public void fun() {
		synchronized (SynDemo.class) {
			System.out.println("hello");
		}
	}
}

本质上也是一样的.

虽然在功能上可以这样理解,但本质上不是.如果深入到java虚拟机指令的层次,很容易就能发现这一点.

synchronized在修饰方法的时候其实对应虚拟机指令的的方法描述符: ACC_SYNCHRONIZED, 而sysnchronized同步块对应的虚拟机指令是monitorenter和moniterexit,他们分别表示进入和退出同步区域(临界区)

四 虚拟机指令: ACC_SYNCHRONIZED

待补充

五 虚拟机指令: monitorenter和moniterexit

待补充

如何选择

一 选择

选择的内核其实是分析

二 如何选择

如何做选择,其实是如何分析.分析是方法,就得结合具体的问题,在实际场景中运用了,没有通用的法则.但是,认识到选择与分析的联系至少具有这样的意义: 它使我们认识到,当我们面临困难的选择的时候,我们不应该迷茫和不知所措,也不该由自己一时的心情或情绪左右自己的理智,以免做出冲动的,甚至让自己后悔终生的选择,我们唯一正确的做法就是分析.

虽然分析没有万能的法则,但依然有规律可循,下面几个问题,我们在做选择的时候,几乎都需要考虑.

1)我的能力

喝过了太多鸡汤的人都知道,当今社会都是弘扬努力奋斗,人定胜天的精神.虽然说积极乐观总是好的,但是现在的各种消息面传达出来的信息,客观的讲,确实太夸张了.有的把人的能力夸张到领人吃惊的地步,仿佛一个人的能力就是无穷的一样,这种现象在各种励志故事和成功人士的报道中尤其常见.长期侵染在这种环境和氛围中的我们是非常危险的,因为我们经常意识不到一个真理: 人的能力是有限的.一个简单的数学公式就能反映这一点: 工作量=时间x效率. 如果说人的效率不太好考究的话(但是肯定是有限的), 那么人的寿命是有限的,这在当前就是铁的事实!

意识到自己的能力有限,至少在做选择的时候会变得理性.

你有多少时间? 很简单, 用天数乘以24就是你最多的可用时间,但正常还得每天减8个小时. 根据这个算法就能很容易算出自己的可支配时间. 效率虽然不如时间的算法简单,但其实也很好估算:根据自己的历史数据.对自己以前的做事效率进行简单的总结就能了解自己大概的效率了.比如:

1)平时看完一本书大概需要几个小时?

2)完成一件工作大概花了几天?

如果再对选项中的各种任务,结合自己的历史做一个评估那就会更加准确了.经验和历史的意义就在于此,可以给未来以参考.

2)我的期望

期望其实也是左右选择的很重要的因素.我认为在做选择的时候,应该克制感性,保持理性,显然期望并不是客观和理性的因素,所以总的来说我是赞同认清能力,克制期望.但在有些情况下,期望又可以看成是理性的因素,因为一个人的期望会影响一个人的能力.

可重入锁

一 说明

java自1.5之后提供了一种新的同步互斥机制——可重入锁.相关的类位于java.util.concurrent.locks包下面.

二 类图

其中最重要的就是Lock和Condition这两个接口,这两个接口描述了一把锁应该具有的功能.Condition的作用一开始可能让人迷惑,不理解java语言设计人员设计这个接口的目的是什么.其实如果把Lock的定位是为了取代或补充synchronized关键,那么Condition接口对标的就是Object类的wait(),notify(),notifyAll()这三个方法.这样理解的话,Condition的作用和用法就非常明确和具体了.

当我第一次接触ReadWriteLock这个接口的时候,往往会被它的名字迷惑,因为这名字让我相信这个接口的肯定是对Lock这个接口的扩展,然后会包含两类方法: 以读的方式加锁的方法和以写的方式加锁的方法,但其实不是.这其实是如何设计读写锁的问题了.我的第一观念的来源可能是受到读写文件相关的接口设计的影响.很多语言的读写文件的接口的设计往往是先打开文件,再读写文件.而在打开文件的方法参数中往往会带一个打开方式的参数(读,写或读写),即在动作里面描述(在这个过程我们只看到访问者和资源).但是java在设计读写锁的时候使用了另一种方式.即把读写锁封装成一个锁,但是两种锁的存在均对外暴露,而两种锁的性质由锁本身自己实现.这样的好处是把锁的状态集中在一起,便于锁的实现,读锁和写锁之间的协作会很容易比如说,读锁和写锁之间可以轻松的了解对方的加锁状态以及加锁的线程数,这两个信息对当前是否可以上锁很有必要.如果分开的话,两个锁之间的协调就是一个很大的问题.

如果像打开文件那样,把锁的方式用参数传递,比如可以这样设计读写锁:

public interface ReadWriteLock extends Lock {
    public void lockRead();
    public void unlockRead();

    …其他方法…

}

这样的话就需要对Lock接口中的每个方法都提供一个读方式的接口方法,这样就显得很臃肿.所以java设计者直接把两种锁暴露出来,让程序员自己先去获取具体的锁,然后在对锁进行操作.从设计角度来看,ReadWriteLock并不是锁,而是两个锁的管理者.

三 可重入的内涵

四 Lock的使用方式

对于一把锁,很容易想到的两个基本功能就是加锁和解锁.lock()和unlock()就是对这两个基本功能的描述.如果再考虑等待锁的时候线程是否可以被中断,就可以再引入lockInterruptibly().如果再要请求锁的时候不阻塞,而是通过返回值来判断锁是否可用,所以又引入了tryLock()方法.

lock(): 请求锁,如果不可用则阻塞当前线程,直到锁可用为止.

lockInterruptibly(): 等待锁的时候该可以被其他线程中断,抛出InterruptedException.

boolean tryLock(): 非阻塞方式请求锁.

boolean tryLock(time, timeUnit): 非阻塞方式请求锁, 但是会等待一段时间,如果时间结束后,锁还不可用的话,就返回.

unlock(): 解锁.

newCondition(): 获得锁上的条件对象.

如何构造锁?

java设计者已经为我们提供了一个Lock的实现类,ReentrantLock, 创建一个ReentrantLock对象即可.

Lock的使用示例:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Data {
	private int flag;
	private Lock lock = new ReentrantLock();
	
	public int getFlag() {
		return flag;
	}

	public void setFlag(int flag) {
		try{
			lock.lock(); //加锁
			System.out.println(Thread.currentThread().getName() + " in.");
			Thread.sleep(3000);
			this.flag = flag;
			System.out.println(Thread.currentThread().getName() + " out.");
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock(); //释放锁, 一定要放在finally块中
		}
	}
}
class Runner implements Runnable{
	private Data data;
	private int value;
	public Runner(Data data, int value) {
		this.data = data;
		this.value = value;
	}
	
	@Override
	public void run() {
		data.setFlag(value);
	}
}
public class LockDemo {
	public static void main(String[] args) {
		Data data = new Data();
		Thread t1 = new Thread(new Runner(data, 1), "T1");
		Thread t2 = new Thread(new Runner(data, 2), "T2");
		t1.start();
		t2.start();
	}
}

输出结果:

有一点要注意: 释放锁的操作一定要放在finally块中. 和synchronized同步方法相比,锁虽然更加灵活,但是也带来了风险,用得不好,很可能就会导致死锁发生.

五 ReadWriteLock的使用

与Lock相比ReadWriteLock的read锁的lock()方法可以被多个线程调用而不阻塞(读共享),而write锁的lock()方法则与write锁的lock()和read锁的lock()是互斥的,即有各个操作已经发生,则会导致其他线程阻塞(写互斥)

使用读写锁的基本流程如下:

1)创建读写锁对象,java已经提供了ReentrantReadWriteLock类,可以创建一个这种类的对象.

2)拿到具体的锁对象,如获取读锁: Lock readLock = readwriteLock.readLock(); 获取写锁: Lock writeLock = readwriteLock.writeLock();

3)加锁,readLock.lock()或writeLock.lock()

4)释放锁.


与普通的锁在使用上的区别是: 读写锁在上锁之前要先拿到对应类型的锁.


ReadWriteLock使用示例:

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class Disk {
	private ReadWriteLock lock = new ReentrantReadWriteLock();
	private String data = "";
	
	public String read() {
		try {
			System.out.println(Thread.currentThread().getName() + "准备读...");
			lock.readLock().lock();
			System.out.println(Thread.currentThread().getName() + "开始读...");
			Thread.sleep(30000);
			System.out.println(Thread.currentThread().getName() + "完成读...");
			return data;
		} catch(Exception e) {
			e.printStackTrace();
			return null;
		} finally {
			lock.readLock().unlock();
		}
	}
	
	public void write(String data) {
		try {
			System.out.println(Thread.currentThread().getName() + "准备写...");
			lock.writeLock().lock();
			System.out.println(Thread.currentThread().getName() + "开始写...");
			Thread.sleep(50000);
			this.data = data;
			System.out.println(Thread.currentThread().getName() + "写完成...");
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			lock.writeLock().unlock();
		}
	}
}

class Reader implements Runnable {
	private Disk disk;
	public Reader(Disk disk) {
		this.disk = disk;
	}
	@Override
	public void run() {
		String data = disk.read();
		System.out.println("从磁盘读到数据: " + data);
	}
}

class Writer implements Runnable {
	private Disk disk;
	public Writer(Disk disk) {
		this.disk = disk;
	}
	@Override
	public void run() {
		String data = System.currentTimeMillis() + "";
		disk.write(data);
		System.out.println("成功向磁盘写入数据: " + data);
	}
}

public class ReadWriteLockDemo {
	public static void main(String[] args) {
		Disk disk = new Disk();
		Thread r1 = new Thread(new Reader(disk), "R1");
		Thread r2 = new Thread(new Reader(disk), "R2");
		Thread w1 = new Thread(new Writer(disk), "W1");
		r1.start();
		w1.start();
		r2.start();
	}
}

运行结果:

六 Condition的使用

以后再补充.

Semaphore类

一 说明

java.util.concurrent.Semaphore.

这是java提供的对信号量的支持类.

信号量是用来对某一共享资源所能访问的最大个数进行控制.

二 主要方法

acquire(): 申请一个许可证书,如果没有可用的许可证书的话,就一直等着,在等待过程中可以被中断.

acquire(int permits): 申请permits个许可证书.

acquireUninterruptibly(): 与acquire()方的区别是,线程等待的时候无法被中断.

acquireUninterruptibly(int permits): 申请permits个许可证书.

release(): 释放一个证书.

release(int permits): 释放permits个证书.

boolean tryAcquire(): 尝试获取一个证书.这个方法不会阻塞,而是马上返回,通过返回值可以判断是否获取成功,即这个方法可以看成是acquire()方法的异步版本.

boolean tryAcquire(long timeout, TimeUnit unit): 与tryAcquire(): 尝试获取一个证书,如果没有成功,它会尝试一段时间.

boolean tryAcquire(int permits)

boolean tryAcquire(int permits, long timeout, TimeUnit unit)

isFair(): 唤醒等待线程的策略是否公平的(FIFO).

boolean hasQueuedThreads(): 是否有线程在等待.

int getQueueLength(): 等待线程的数量.

三 重要属性

1) permits: 许可证数量. 包括许可证总数量, 可用的许可证数量.

2) queue: 等待线程队列, 用于存放等待线程.

3) fair: 等待线程的调度策略(是否公平), 是否是FIFO.

四 使用场景

留待以后补充.

java线程池

一 接口与类

先看看线程池相关类之间的关系:

其中,

Executor: 是执行引擎的接口,可以具体化地类比成一台机器,它只有一个方法: void execute(Runnable command). 显然这符合机器的最主要的功能特点, 即: 只需要给它发送指令(任务).

ExecutorService: 是执行引擎管理者(服务)的接口,同理可以具体类比为一个工厂. 这个接口对工厂的主要功能进行了提取和抽象, 提供了机器的启停,状态监控等功能的描述.

ScheduledExecutorService: 这是一种可以可调度的机器管理者(工厂),发送到它上面的指令(任务)可以调度执行,不过它支持的调度模式仅支持周期性执行任务的策略.

ThreadPoolExecutor: 一个工厂为了加快生产过程,内部肯定置办了多台机器,程序中对应的就是线程池.所有的ExecutorService实现类的功能也是直接依赖这个类,这个类的功能特点直接影响java线程池相关的功能,理解这个类的工作模式是理解java线程池的关键.

Excecutors: 普通程序员在使用线程池时,可能更喜欢根据自己这样的模式: 只需要描述自己需要的线程池的特点,就能直接获得线程池.Executors类就是就是出于这样的考虑. 或者说java设计者把常用的线程池的创建工作已经做了,普通程序员只需要选择自己需要的线程池(当然你要清楚自己的功能需求,以及可供选择的几种线程池的特性). 当然如果确实有需要,程序要也可以自己手动创建线程池,java也提供了可供选的基础类,就是前面提到的ThreadPoolExecutor类.

编码开发的时候,最常见的方式都是面向ExecutorService这个接口编程的,所以掌握ExecutorService接口是非常重要的. ExecutorService接口主要的方法如下:

void execute(Runnable command): 这是继承自Executor接口的方法,用于提交任务,如果线程池已经关闭,则会抛出RejectedExecutionException异常.

<T> Future<T> submit(Callable<T> task): 提交实现了的Callable接口的任务.同样,如果线程池已经关闭,则会抛出RejectedExecutionException异常.

void shutdown(): 关闭线程池,但是会等待线程池中所有任务执行完毕,而且不能再向线程中提交新的任务.

List<Runnable> shutdownNow(): 马上关闭线程池.不等所有任务完成,而是强制停止正在运行的任务,并返回等待执行(还没有运行)的任务.

isShutdown(): 线程池是否已经关闭.也即线程池是否已经调用了shutdown()或shutdownNow()等方法.

isTerminated(): 线程池是否已经终止,可以把这时的线程池状态理解为:已经调用了shutdown()或shutdownNow()等方法,而且所有线程都已经运行完毕[对此,还不是很确定,还需要后续检验].

二 各种线程池

前面java设计者已经为们提供了很多现有的线程池,这些线程池已经能满足常规的开发工作,前面也提到我们需要做的就是理解各个线程池的特性,然后根据自己的需求选择适合的线程池.现有的线程池的创建工作封装在Executors类的静态方法中,重要的有下面个(类):

1)newSingleTheadExecutor():  这是最简单的线程池,简单到java设计人员甚至不认为它是一个池,因为它内部只有一个线程,用机器和工厂的类比来说,这更像是一台机器,而不是工厂(包含多台机器).

2)newFixedThreadPool(int count): 固定线程数量的线程池.

3)newCachedThreadPool(): 一种弹性线程池,它基于这样的策略来管理池中的线程: 线程不够的就新建,一段时间内没有使用就回收.它试图在这两个问题之间做好平衡: 创建线程耗资源, 维护线程同样也耗资源.

4)newScheduledThreadPool(int count): 它是为这样的需求提供的: 周期性的执行某个任务.

前面说明了各种线程池的特性,但是该怎么选择呢?

single线程池和schedule线程的使用场景比较明显也比较少用到.经常用到的是fixed线程池和cached线程池.

single线程池

single线程适池用于并发量不大,不需要并发甚至不能并发的场景,而且还有一个重点: single线程可以确保提交给它的任务顺序执行,这在很多需求下很有用.

schedule线程池

这种线程池的使用场景比较明显

fixed线程池和cached线程池

这两个线程池其实都是在线程创建和线程维护之间取舍.根本上看,可以从这三个方面考虑:

1)任务量

2)提交任务的频度

3)单个任务执行的耗时长短

一般来说:

任务量大, 提交任务频度小: 使用cached线程池

任务量小, 单个任务执行的耗时长: 使用fixed线程池

其他场景的化就得具体分析了.

三 ThreadPoolExecutor分析

其实前面提到的各种线程池都是基于ThreadPoolExecutor类.它们的很多特性都依赖ExecutorThreadPool.其中最主要的两个特性就是线程数(fixed线程池)和线程回收(cached线程池).

从ThreadPoolExecutor的构造函数就能很容易理解这一点,ThreadPoolExecutor的构造函数如下:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue)

其中参数的含义:

corePoolSize: 初始化的线程池中的线程数

maximumPoolSize: 线程池中最大线程数

keepAliveTime: 线程的生命长短,用于空闲线程回收,0表示线程池中的空闲线程永远维护,不回收

unit: 时间单位

workQueue: 任务队列

显然,maximumPoolSize和keepAliveTime两个参数就能支撑fixed线程池中的线程数和cached线程池的线程回收特性.

四 调度线程池

ScheduledExecutorService用的不多,留待以后补充.

线程安全

一 线程安全的定义

线程安全的书面定义常常是: 多个线程访问同一段代码,不会产生不确定的结果.

这种定义是从结果的角度来判断.这种定义方式比较数学化(集合味道),具有普适性,但是很抽象.因为程序员更喜欢一种直观,场景化,准则性和指导性的东西.

比如程序员更喜欢下面这些:

1)如果是单线程环境,一定是线程安全的

2)如果线程之间没有共享资源,一定是线程安全的

3)如果资源是只读的,就一定是线程安全的.

4)如果对资源加锁,就一定是线程安全的.

……

有了这些场景化的具体描述,程序员就可以根据这些条件,去判断自己的代码是否线程安全.

二 具体化

其实线程安全归根结底,线程安全涉及两个方面:多个线程和共享资源.这既是引起线程安全问题的两个必要条件,也是线程安全问题反映到的两个具体对象.

保证线程安全可以从这两个方面着手,而检查多线程问题同样也可要从这两个对象来入手,加上访问类型,来看线程安全问题:

1)对共享资源的访问方式包括两种基本操作: 读和写.

2)共享资源本身: 如果多个线程同时访问共享资源,会不会对资源本身的结构造成破坏,这其实是要确保资源的完整性[更新动作不是原子性的].

3)各个线程本身: 如果多个线程同时访问共享资源,每个线程的计算结果是否正确[和计算结果的业务含义有关].

Thread类

一 重要方法

start()

join()

interrupt()

isInterrupted()

static interrupted()

static yeild()

static sleep()

static currentThread()

[已经废弃的方法]:

stop():强制停止线程,不安全

suspend():暂停线程,可能导致死锁

resume():恢复线程,可能导致死锁

二 重要属性

1)id: 唯一且终生不变. 线程终止之后可以再被使用

2)name: 线程名称

3)state: 线程状态

4)daemon: 标识线程是否为守护线程

5)priority: 优先级

6)threadGroup: 线程组

7)interruption: 中断标识

三 线程状态

NEW: 新建状态,即创建Thread对象之后,调用start()方法之前线程所处的状态

BLOCKED: 阻塞状态,当线程试图进入synchronized区域,等待同步锁时的状态

TIMED_WAITING: 时间等待状态,线程调用sleep()方法之后进入该状态

WAITING: 等待状态,线程调用了Object.wait()方法之后进入该状态

TERMINATED: 终止状态,线程的run()方法运行结束之后的状态

RUNNABLE: 可运行状态,或者正在运行的状态

线程运行状态:

import java.util.concurrent.CountDownLatch;
public class ThreadStateDemo {
	public static CountDownLatch latch = new CountDownLatch(1);
	public static void main(String[] args) throws Exception {
		Thread t1 = new Thread(new Runnable() {
			private String name = "T1";
			@Override
			public void run() {
				try {
					System.out.println(name + " before sleep()");
					Thread.sleep(30000);
					System.out.println(name + " after sleep()");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});
		Thread t2 = new Thread(new Runnable(){
			private String name = "T2";
			@Override
			public void run() {
				System.out.println(name + " before syncMethod()");
				syncMethod();
				System.out.println(name + " after syncMethod()");
			}
		});
		Thread t3 = new Thread(new Runnable(){
			private String name = "T3";
			@Override
			public void run() {
				System.out.println(name + " before syncMethod()");
				syncMethod();
				System.out.println(name + " after syncMethod()");
			}
		});
		Thread.State state1 = t1.getState();
		System.out.println("线程状态: " + state1); //NEW
		t1.start();
		t2.start();
		Thread.sleep(2000);
		t3.start();
		Thread.sleep(2000);
		Thread.State state2 = t1.getState();
		System.out.println("线程状态: " + state2); //TIMED_WAITING
		Thread.State state3 = t2.getState();
		System.out.println("线程状态: " + state3); //WAITING
		Thread.State state4 = t3.getState(); //BROKEN
		System.out.println("线程状态: " +state4);
		ThreadStateDemo.latch.countDown(); //唤醒Run
		Thread.sleep(5000);
		Thread.State state5 = t2.getState();
		System.out.println("线程状态: " + state5); //TERMINATED
		Thread.State state6 = Thread.currentThread().getState();
		System.out.println("线程状态: " + state6); //RUNNABLE
	}
	public synchronized static void syncMethod() {
		try {
			ThreadStateDemo.latch.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

四 关于线程中断

interrupt(): 设置线程的中断状态,必须在线程运行(start方法)之后调用才能生效

isInterrrupted(): 获取线程的中断状态

Thread.interrupted(): 清除当前线程的中断状态

说明:

1) 如果某个线程处于TIMED_WAITING或WAITING状态,即已经调用过了sleep()方法或wait()方法,如果再修改线程的中断状态,即调用该线程对象的interrupt()方法,则会从sleep()或wait()方法的位置抛出InterrupedException

2) 如果某个线程的中断状态为true,即已经调用了线程对象的interrupt()方法,当该线程试图进入TIMED_WAITING或WAITING状态,即如果再调用sleep()方法或wait()方法,则会从sleep()或wait()方法的位置抛出InterrupedException

3)为了避免调用sleep()或wait()方法时抛出异常,可以先用isInterrupted()方法检查线程的中断状态,在调用Thread.interruped()方法清除中断状态.

线程中断:

public class ThreadDemo {
	static Object lock = new Object();
	public static void main(String[] args) {
		Thread t = new Thread(new Runnable() {
			@Override
			public void run() {
				//耗时
				for(int i=0; i&lt;Integer.MAX_VALUE; i++) {
					for(int j=0; j&lt;100; j++) {
						long x = 1;
						long y = 2;
						long z = x * y;
					}
				}
				try {
					System.out.println("线程的中断状态: " + Thread.currentThread().isInterrupted());
					//判断中断状态
					if (Thread.currentThread().isInterrupted()) {
						Thread.interrupted();  //清除中断状态
					}
					//调用sleep()或wait()方法
					synchronized (lock) {
						lock.wait();
					}
					Thread.sleep(1000);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
		try {
			t.start();
			t.interrupt();
			System.out.println("isInterrupted: " + t.isInterrupted());
			Thread.sleep(3000);
			System.out.println("isInterrupted: " + t.isInterrupted());
			synchronized(lock) {
				lock.notify();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

五 未捕获异常处理器

setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh): 为线程设置未捕获异常的处理器

static setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh): 为所有线程设置未捕获异常的处理器

接口Thread.UncaughtExceptionHandler仅包含一个方法: public void uncaughtException(Thread t, Throwable e)

代码示例:

import java.lang.Thread.UncaughtExceptionHandler;
class UncaughtExceptionHandlerImpl implements UncaughtExceptionHandler {
	private String name;
	public UncaughtExceptionHandlerImpl(String name) {
		this.name = name;
	}
	@Override
	public void uncaughtException(Thread t, Throwable e) {
		System.out.println(name + " - 捕获到未捕获异常");
		System.out.println(name + " - 抛出异常的线程: " + t);
		System.out.println(name + " - 抛出的异常对象: " + e);
	}
}
public class ThreadDemo {
	static Object lock = new Object();
	public static void main(String[] args) {
		Thread t = new Thread(new Runnable() {
			@Override
			public void run() {
				Object o = null;
				o.toString();//拋出异常
			}
		});
		UncaughtExceptionHandler eHandler = new UncaughtExceptionHandlerImpl("异常处理器");
		UncaughtExceptionHandler defaultHandler = new UncaughtExceptionHandlerImpl("默认的异常处理器");
		t.setUncaughtExceptionHandler(eHandler);
		Thread.setDefaultUncaughtExceptionHandler(defaultHandler);
		t.start();
		//main线程中抛出异常
		Object o = null;
		o.toString();
		System.out.println("main方法将要结束退出");
	}
}

运行结果:

作用说明: 未捕获异常处理器可以在线程异常退出时释放资源,日志记录,防止异常直接展示给客户等