为了高效完成任务和利用CPU资源,操作系统设计为多任务操作系统,而多进程和多线程是实现多任务的方式。
进程是指运行于内存中的应用程序,线程是指进程中的一个执行流程。一个进程中可以启动多个线程,进程中的多个线程共享进程的内存,线程总是属于某个进程。进程是OS(Operating System)分配资源的最小单位。 线程是OS调度的最小单位。
多线程的核心在于多个代码块并发执行,本质特点在于个代码块之间的代码乱序执行。
Java中实现多线程有两种方法:继承Thread类、实现Runnable接口,实现Runnable接口有
如下优势:
1、可以避免由于Java的单继承特性而带来的局限;
2、增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的;
3、适合多个相同程序代码的线程区处理同一资源的情况。
继承Thread类
Thread thread = new Thread(){
public void run(){
System.out.println("Thread running");
}
};
thread.start();
实现Runnable接口
Runnable myRunnable = new Runnable(){
public void run(){
System.out.println("Runnable running");
}
}
Thread thread = new Thread(myRunnable);
thread.start();
线程的状态转换是线程控制的基础。线程状态可分为五大状态:分别是新建、就绪、运行、等待/阻塞、死亡。
1、新建状态:线程对象已经创建,没有调用start()方法。
2、就绪状态:调用start()方法,线程进入可运行状态,等待调度程序选择作为运行线程。线程运行之后从阻塞、等待或睡眠状态回来后,返回到就绪状态。
3、运行状态:线程调度程序从就绪线程池中选择一个线程作为当前线程时线程所处的状态,是线程进入运行状态的唯一方式。
4、等待/阻塞/睡眠状态:线程不会被分配CPU时间,无法执行;阻塞于I/O,或者阻塞于同步锁。三状态共同点是:线程仍然存活,但是当前没有条件运行,可能返回到可运行状态。
5、死亡状态:线程的run()方法完成时处于死亡状态。不推荐方法:调用 stop()会产生异常;调用 destroy() 方法强制终止,不会释放锁。死亡状态的线程调用start()方法,会抛java.lang.illegalThreadStateException异常。
Java内存模型规定所有的变量存在主存(共享内存)中,每个线程有自己的工作内存。每个线程不能访问其他线程的工作内存。
Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,当线程进入或者离开同步代码块时才将私有拷贝与共享内存中的原始值比较。
原子性
即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
可见性
指多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性
即程序执行的顺序按照代码的先后顺序执行。
Volatile关键字特性
Volatile修饰的成员变量被线程访问时,从共享内存中重读该成员变量的值。当成员变量发生变化时,线程将变化值回写到共享内存。
volatile是一种稍弱的同步机制,在访问volatile变量时不会执行加锁操作,也就不会执行线程阻塞,因此volatile变量是一种比synchronized关键字更轻量级的同步机制。
volatile关键字无法保证操作的原子性。
当且仅当满足以下所有条件时,才应该使用volatile变量:
1、对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。
2、该变量没有包含在具有其他变量的不变式中。
3、在访问变量时不需要加锁。
Volatile变量通常用作某个操作完成、发生中断或者状态的标志。典型用法:检查某个状态标记以判断是否退出循环。
Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,就不能保证有序性,虚拟机可以进行重排序。
具体介绍happens-before原则(先行发生原则):
程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
这8条原则摘自《深入理解Java虚拟机》。
当某个计算的正确性取决于多个线程的交替执行时序,就会发生竞态条件。
竞态条件的本质:基于一种可能失效的观察结果来做出判断或者执行某个计算流程,称为“先检查后执行”。
采用synchronized修饰符实现的同步机制叫做互斥锁机制,获得的锁叫做互斥锁。多个线程访问同一个synchronized 方法时,必须获得调用该方法的类实例的锁才能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。
加锁机制既可以确保可见性又可以确保原子性,而volatiel变量只能确保可见性。
线程可以阻塞于四种状态:
1、当线程执行Thread.sleep()时,它一直阻塞到指定的毫秒时间之后,或者阻塞被另一个线程打断;
2、当线程执行wait()语句时,它会一直阻塞到接到通知(notify())、被中断或经过了指定毫秒时间为止;
3、线程阻塞与不同I/O阻塞的方式有多种。常见方式是InputStream的read()方法,该方法一直阻塞到从流中读取一个字节的数据为止,它可以无限阻塞,因此不能指定超时时间;
4、线程也可以阻塞等待获取某个对象锁的访问权限(即等待获得synchronized语句必须的锁时阻塞)。
Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 。
用户线程即运行在前台的线程,而守护线程是运行在后台的线程。 守护线程作用是为其他前台线程的运行提供服务,垃圾回收线程就是守护线程。
另外有几点需要注意:
1、设置守护线程setDaemon(true)必须在调用线程的start()方法之前设置,否则会抛出IllegalThreadStateException异常。
2、在守护线程中产生的新线程也是守护线程。
3、 并非所有的应用都可以分配给守护线程来进行服务,比如读写操作或者计算逻辑。
线程的优先级代表该线程的重要程度,当有多个线程同时处于可执行状态并等待获得 CPU 时间时,线程调度系统根据各个线程的优先级来决定分配 CPU 时间,优先级高的线程更高几率获得 CPU时间。
调用 Thread 类的方法getPriority() 和setPriority()来存取线程的优先级,线程的优先级界于1(MIN_PRIORITY)和10(MAX_PRIORITY)之间,缺省是5(NORM_PRIORITY)。
参考资料:http://www.cnblogs.com/oubo/archive/2012/01/05/2394637.html