基础知识
线程的本质是机器执行的路径,是操作系统的最小执行单元,它完全是多任务操作系统之上的虚拟概念,从硬件角度来说每一个cup只有一条执行路径。成百上千个线程同时执行是操作系统调度的假象。
每一个计算机程序,或每个进程,至少有一个线程,这个线程称为主线程,Java的主线程称为main thread。它以某个class类的main方法为起点。任何计算机程序都是由单一线程开始运行的。进程独享一个内存空间,进程中的线程共享这个内存空间,每个线程有单独的栈来存储数据。
并发编程的术语
屏障(Barrier):屏障代表多个线程的集合点,需要所有的线程都到达该集合点后才能继续往下执行,因此先到达的线程会进入等待。
条件变量(Condition Variable):条件变量是和某个锁(Lock)关联的变量,通常用于同步环境中,实现等待-唤醒机制。线程可以在拥有锁的情况下,等待某个条件变量;或者在拥有锁的前提下,唤醒一个或者全部正在等待某个条件变量的线程。
条件变量也程事件变量(Event Variable)。
临界区域(Critical Section):代表一个同步化的方法和代码块,所有线程必须串行的经过临界区域。本质上就是一个隐藏的锁获取和释放区域。
锁(Lock):用于表示进入临界区域特定线程的访问权限。读写锁(Read/Write Lock)可以允许多个线程进行同时取得,只要它们同意都是进行“读”操作。
监控器(Monitor): 该术语在不同的系统中代表的含义不同,可能代指Lock,也可能代指等待-唤醒机制
互斥(Mutex):和Lock类似,它往往是跨进程的、基于操作系统级别的。
信号量(Semaphore):线程可以等待一个或多个信号的计数,另一个线程则可以释放一个或多个信号的计数,当线程活的足够的计数后,停止等待。
Java线程的创建和管理
创建线程
Java允许两种创建线程的风格
- 继承java.lang.Thread类,重写run方法
- 实现java.lang.Runable接口,并将起传给Thread的构造方法。
Thread对象本质上并不代表线程本身,而是一组与线程有关的方法和数据的封装。
Thread的构造函数有以下参数:
name : Thread的名称,默认Thread-N ,N是一个唯一的数字;
Runable target :新线程执行的指令列表,位于run()方法中
ThreadGroup group : 新线程加入的线程组,默认与调用新线程构造方法的那个线程的线程组一致
long stackSize:新线程执行方法时,存放临时变量的栈大小。
线程的生命周期方法
new Thread() : 创建线程,在Java中,线程也是对象,因此它的对象是调用构造器完成的。线程被构造后即存在,但这是还没有进行执行,在这是其它线程可以和该线程进行交互,例如:设置优先级、线程守护和名称。
start() : 启动线程,调用start()方法后,jvm中创建了一个新的线程,并且线程的isAlive()==true,等待系统调度进行执行。
stop(): 终结线程,不能调用stop()方法来终结线程,它有缺陷,已经被弃用。
sleep()/wait()/join()/suspend():线程等待, suspend()方法用来暂停线程,但是与stop()一样有缺陷,已经弃用。Thread.sleep()静态方法,可以让线程暂停一段时间,之后自动回复执行,sleep暂停时并不会释放锁。Object.wait()方法依赖于同步,不能单独进行使用,wait方法会释放对象持有的锁。
notify()/notifyAll()/resume(): 线程恢复,notify()和notifyAll()用于唤醒调用wait()方法阻塞的线程,唤醒线程后,会尝试获取monitor。resume()用于恢复暂停的线程,有缺陷。
清除:线程结束后,其Java对象扔可以访问,可以附带一些有价值的信息。如果Thread线程脱离作用域会被GC回收,回收可能带着系统资源的清理。
线程的正确停止方式
设定标记位
public class MyThread implements Runnable { private volatile boolean flag = true; @Override public void run() { while ( flag ) ; System.out.println( "Thread Interrupt" ); } public static final void main( String[] arg ) throws Exception { MyThread myThread = new MyThread(); Thread thread = new Thread( myThread ); thread.start(); System.out.println( "123" ); TimeUnit.SECONDS.sleep( 5 ); myThread.setFlag( false ); } public void setFlag( boolean flag ) { this.flag = flag; } public boolean isFlag() { return flag; } }利用中断
调用该方法,可以让任何处于阻塞(Blocking)方法调用中的线程获得退出的机会。该方法有两个效应:
- 线程的阻塞方法(可能)会抛出InterruptedException,程序代码可以捕获此异常并退出
- 设置线程内部“已中断”标记为true,提示线程已经被中断。可以使用isInterrupted()方法来检查该标记。即使线程没有被Block,这个标记也会被设置
Thread t = new Thread() { public void run() { while ( !isInterrupted() ) ; System.out.println( "interrupted" ); } }; t.start(); TimeUnit.SECONDS.sleep( 1 ); t.interrupt();
利用异常判断(针对出于阻塞状态的线程)
Thread t = new Thread() { public void run() { try { TimeUnit.SECONDS.sleep( 3600 ); } catch ( InterruptedException e ) { System.out.println( "interrupted" ); } } }; t.start(); TimeUnit.SECONDS.sleep( 1 ); t.interrupt();
关于interrupt()方法,还需要注意:
- 如果线程在调用Object.wait()、join()、sleep()方法时被其它线程中断,其中断标记位会被清除,并接收到InterruptedException
- 如果线程在InterruptibleChannel上执行I/O时被其它线程中断,则通道被关闭,线程的中断标记位被设置,并接受到ClosedByInterruptException。 ServerSocketChannel、SocketChannel、FileChannel、DatagramChannel等都是可中断通道
- 如果线程在Selector上执行select操作时被中断,则线程的中断标记位被设置,并立即返回