一、Java多线程
多任务与多线程
多任务(multitasking)
多任务,是指操作系统,在同一时刻运行多个程序的能力。
例如:在浏览网页的同时,可以打印文件。现代操作系统,多核CPU的计算机,并发数不受CPU数目的限制。操作系统将CPU的时间片资源,分配给每个进程,给人并行处理的感觉。
多线程
多线程,在较低层次扩展了多任务的该拿:同一程序执行多个任务。通常每个任务称为一个线程(thread),它是线程控制的简称。可以同时运行一个以上线程的程序称为多线程程序。
多线程与多进程的区别
本质区别在于,每个进程都拥有自己的一整套变量,而线程则共享数据。多线程程序,与进程相比更轻量,创建、销毁一个线程比启动新进程的开销小。同时,共享变量使得线程之间通信比进程之间的通信更高效、更容易。
如何创建Java线程
方法一:实现java.lang.Runnable接口
- 1、定义类。定义一个实现了 Runnable 接口的类,将任务代码转移到该类的run方法中。java.lang.Runnable接口非常简单,仅有一个方法:
package java.lang;
public interface Runnable{
public abstract void run();
}
//可以按照以下方法实现类
public class MyRunnable implements Runnable{
public void run(){
//task code
}
}
- 2、创建一个类对象
Runnable r = new MyRunnable();
- 3、由Runnable创建一个Thread对象:
Thread t = new Thread
- 4、启动线程:
t.start();
/**
*The Runner Class implements Runnable Interface
/
class Runner implements Runnable{
public void run(){
for(int index=0;index<100;index++){
LogUtil.log("run Runner,Cycle: " + index);
try{
if(Thread.currentThread().isInterrupted()){
LogUtil.log("exit Runner thread.... " );
break;
}
Thread.sleep(7);
}
catch(InterruptedException e){
//e.printStack();
LogUtil.log("exit Runner thread: " + index);
Thread.currentThread().interrupt();
//break;
}
}
}
}
class LogUtil{
private static String Version = "[Version: V2.1.1-20161214]";
public static void log(String msg){
System.out.println(msg);
}
public static void printVersion(){
System.out.println("---------------");
System.out.println(Version);
System.out.println("---------------");
}
}
public class App{
public static void main(String[] args) {
LogUtil.printVersion();
//创建Thread
Thread t = new Thread(new Runner());
t.start();
//sleep main thread
try{
Thread.sleep(18);
}
catch(InterruptedException ignored){
}
t.interrupt();
}
}
方法二:继 承java .lang.Thread的子类,重载Thread.run()方法
//1、定义Thread的子类,重载run()方法
public class PrimeThread extends Thread{
public PrimeThread(String name){
super(name);
}
public void run(){
int index = 0;
try{
while(index<100){
System.out.println(String.format("[ %s ]index = %s",this, index++));
Thread.sleep(1000);
}
}catch(Exception ex){
}
}
}
//2、创建子类对象
PrimeThread p = new PrimeThread("Comsumer Thread");
//3、调用start()方法(新建线程,执行run()方法)
p.start();
这种方法已经不再推荐。应该从运行机制上减少需要并行运行的任务数量。如果存在多个任务,需要为每个任务创建一个独立线程所付出的代价太大。后续使用线程池解决。
####API java.lang.Thread 1.0
#####void Thread(Runnable target)
构造一个新线程,用于调用给定target任务的run()方法
#####void start()
启动新线程,将引发调用run()方法。这个方法将立即返回,同时新线程将并行运行。
#####void run()
调用关联Runnable的run方法。
方法三:线程池
#线程状态以及属性
###中断线程
当对一个线程调用interrupt方法时,线程的***中断状态***将被置位。这个是每个线程都具备的boolean标志。每个线程都会不时地检查这个标志,以判断线程是否被中断。
####如何检测线程的中断状态是否置位?
如下,首先调用静态的Thread.currentThread方法获取当前线程,然后调用isInterrupted method方法。
while(!Thread.currentThread().isInterrupted()&& more work to do){
do more work
}
当线程处于阻塞状态(sleep或者wait)时,调用interrupt方法,阻塞调用会被InterruptedException异常中断。(存在不能被中断的阻塞I/O调用,应该考虑选择可中断的阻塞I/O调用)。
没有任何语言方面的需求要求一个被中断的线程必须终止。被中断的线程可以决定如何响应中断请求。它可以被终止,也可以在处理完InterruptedException后继续执行,而不理会中断。但是通常线程会简单地将中断作为一个终止的请求,这种线程的run方法如下:
public void run(){
try{
....
while(Thread.currentThread().isInterrupted()&&more work to do){
do more work
}
}catch(InterruptedException e){
// thread was interrupted during sleep or wait
}finally{
cleanup, if needed
}
// exit the run method terminates the thread
}
如果在每次迭代之后都调用sleep方法(或者其他可中断方法),则无需调用isInterrupted检测。如下:
public void run(){
try{
....
while(Thread.currentThread().isInterrupted()&&more work to do){
do more work
Thread.sleep(delay);
}
}catch(InterruptedException e){
// thread was interrupted during sleep or wait
}finally{
cleanup, if needed
}
// exit the run method terminates the thread
}
API java.lang.Thread 1.0
static Thread currentThread()
返回代表当前执行线程的Thread对象。
void interrupt()
向线程发送中断请求。线程的中断状态将被设置称为true。如果目前该线程被一个sleep调用阻塞。那么,InterruptedException异常被抛出。
bool isInterrupted()
测试线程是否被终止。这个调用buhl该把线程的中断状态。
static boolean interrupted()
测试当前线程(执行此interrypted()命令的线程)是否被中断。注意,这是一个静态方法。同时,这个调用会将当前线程的中断状态重置为false。
线程状态
|序号|状态|描述|
|—|
|1|New(新创建)|当使用new操作符创建一个新线程时,如new Thread®,线程还没有开始运行
|2|Runnable(可运行)|一旦调用start方法,线程处于runnable状态。一个可运行的线程,可能正在运行可能没有运行,取决于操作系统给线程提供运行的时间。(Java规范没有将正在运行,作为一个单独状态)
|3|Blocked(阻塞状态)|当一个线程试图获取一个内部的对象锁(而不是java.util.concurrent库中的锁),而该锁被其他线程持有,则该线程进入阻塞状态。当其他线程释放该锁,同时线程调度器允许本线程持有它时,该线程进入非阻塞状态。
|4|Waiting(等待)|当线程等待另一个线程通知调度器一个条件时,自己进入等待状态。在调用Object.wait或者Thread.join方法,或者是java.util.concurrent库钟的Lock或者Condition的等待方法时,就会进入等待状态。
|5|Timed waiting(计时等待)|调用了包含超时参数的方法,会导致线程进入Time waiting状态,一直保持到超时期满或者接收到适当的通知。包括:Thread.sleep、 Object.wait、Thread.join、Lock.tryLock、 Condition.wait的计时版本。
|6|Terminated(被终止)|情形一:因为run方法退出而正常死亡。情形二:因为一个没有捕捉的异常终止了run方法而意外死亡的。
API java.lang.Thread 1.0
void join()
等待终止指定的线程。
void join(long millis)
等待指定的线程死亡或者经过指定的毫秒数
Thread.State getState()
得到线程的状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED之一。
线程属性
线程优先级
在Java语言中,每个线程都有一个优先级。默认情况下,一个线程继承它的父线程的优先级。可以通过setPriority方法提供或者降低任何一个线程的优先级。可以将优先级设置为在MIN_PRIORITY(在Thread类中定义为1)与MAX_PRIORITY(定义为10)之间的任何值。NORM_PRIORITY被定义为5。
每当线程调度器有机会选择新线程时,它首先选择具有高优先级的线程。但是线程优先级是高度依赖于系统的。当虚拟机依赖于宿主机平台的线程实现机制时,Java线程的优先级被映射到宿主机平台的优先级上,优先级个数可能变多,可能变少。
多线程程序,千万不能将程序构建为功能的正确性依赖于线程的优先级。
####守护线程
守护线程是为其他线程提供服务的。当只剩下守护线程时,虚拟机就会退出了。
调用t.setDaemon(true);
可以将线程转行为守护线程。这个方法必须在线程启动之前调用。
####未捕获异常处理器
线程的run方法抛出任何不能被捕获的异常时,线程就会被终止。同时,在线程死亡之前,异常会被传递给一个用于未捕获异常的处理器。该处理器,是一个实现了Thread.UncaughtExceptionHandler接口的类。这个接口仅有一个方法:
void uncaughtException(Thread t,Throwable e);
可以使用Thread.setUncaughtExceptionHandler(UncaughtExceptionHandler eh)方法为任何线程安装一个处理器。也可以static的方法setDefaultUncaughtExceptionHandler为所有线程安装一个默认的处理器。
如果不安装处理器,则默认处理器是空。但是,如果不安装独立的线程处理器,此时的处理器就是线程的ThreadGroup对象。
线程同步与通信
线程通信主要通过共享访问字段或者字段引用的对象完成的,但是有可能出现两种错误:线程干扰(thread interference)和内存一致性错误(memory consistency)。用来防止这些错误的工具是同步(synchronization)。
Java的Memory Model
The Java Memory Model describes what behaviors are legal in multi-threaded code, and how threads may interact through memory.
synchronized 关键字
在Java多线程程序中,不同线程会同时请求访问(读或者写)相同的变量或者资源时(称为竞争资源),正确地使用 synchronized ,就可以避免多线程交互(英文:interleave)地访问竞争资源,实现内存一致性与线程安全。
Deeper
-
synchronized 使用特定的lock object作为参数(称为锁对象),紧跟着的是**{ 代码块 }**(又称临界区段)。
-
synchronized 可以修饰对象方法、类方法、{ 代码块 }。
//情况一:代码块
//例如:java.util.Vector 1365行
synchronized(lock object){
...
}
//情况二:对象方法
public synchronized void trimToSize(){
...
}
//等同于
public void trimToSize(){
synchronized(this){
...
}
}
//情况三:类方法
public synchronized static void trimToSize(){
...
}
//等同于
public static void trimToSize(){
synchronized(this.getClass()){
...
}
}
当多线程程序运行时,
(1)如果相互关联的多线程,在执行过程中遇到相同lock object修饰的synchronized时;
(2)他们都会请求lock/acquire/own锁对象,最终会有一个线程获得lock object,继续执行,其他线程都会挂起(处于Wait状态);
(3)此线程执行结束时,会释放lock object。其他挂起线程中,会有一个线程lock/acquire/own这个lock object,被唤醒,继续执行,如此循环(2)、(3)。
(4)任何一个处于**{ 代码块 }的写操作,对于其他关联线程(使用了相同lock object修饰的{ 代码块 }**)都是可见的。