1.线程与进程
进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单元。含有三个特征:独立性、动态性和并发性。
并发性是指在同一时刻只有一条指令执行,但多个进程指令快速替换,给人一种多线程执行的感觉。并行性是指同一时刻,有多条指令在多个处理器上同时执行。
线程是进程的执行单元,线程不拥有系统资源,多个线程共享进程里的全部资源。线程的执行是抢占式的,同一个进程中的多个线程之间可以并发执行。
2.创建和启动多线程的三种方法
(1)Java使用Thread类代表线程,所有的线程对象必须是Thread类或其子类的实例。
步骤如下:
- 定义Thread类的子类并重写该类的run()方法(称为线程执行体)
- 创建Thread子类的实例,即创建了线程对象。
- 调用线程对象的start()方法来启动线程。
常用方法:
Thread.currentThread():currentThread()是Thread类的静态方法,返回当前正在执行的线程对象。
getName():是Thread类的实例方法,返回调用该方法的线程名字。
使用继承Thread类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量。
(2)实现Runnable接口创建线程类
步骤如下:
- 定义Runnable接口的实现类,并重写该接口的run()方法。
- 创建Runnable实现类的实例,并作为Thread的target来创建Thread对象,即线程对象。
- 调用线程对象的start()方法来启动多线程
(3)使用Callable和Future创建线程
Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法更强大:call()方法可以有返回值,也可以声明抛出异常。
创建并启动有返回值的线程步骤如下:
- 创建Callable接口的实现类,并实现call()方法,再创建Callable实现类的实例。
- 使用FutureTask类来包装Callable对象,FutureTask对象封装了该Callable对象的call()方法的返回值。
- 使用FutureTask对象作为Thread对象的target创建并启动新线程。
- 调用FutureTask对象的get()方法获得子线程执行结束后的返回值。
3.线程的生命周期
线程状态转换图如下:
可以调用isAlive()方法测试某个线程是否死亡。
4.控制线程
(1)join线程
让一个线程等待另一个线程的方法——join()方法,调用线程将被阻塞,直到join加入的线程执行结束为止。
(2)后台线程
如果前台线程全部死亡,则后台线程也自动死亡。调用Thread对象的setDaemon(true)方法可指定线程设置为后台线程。Thread类还提供了idDaemon()方法判断该线程是否是后台线程。
(3)线程睡眠:sleep
通过调用Thread类的静态sleep()方法来暂停线程并进入阻塞状态。
yield()静态方法也可以让当前线程暂停,而是转入就绪状态。(不建议使用)
(4)改变线程优先级
利用Thread类的setPriority(int newPriority)、getPriority()方法来设置和返回指定线程的优先级。参数范围是1-10之间,也可以使用三个静态常量:
MAX_PRIORITY:值为10
MIN_PRIORITY:值为1
NORM_PRIORITY:值为5
5.线程同步
Java多线程支持引入了同步监视器来解决不同线程同时修改某一对象的问题,使用同步监视器的通用方法就是同步代码块。如:
Synchronized(obj)
{
… //同步代码块
}
同步方法也就是使用synchronized关键字修饰的方法,同步方法的同步监视器是this,也就是调用该方法的对象。
(1)同步锁
显示的使用lock()和unlock()方法更加灵活,只是使用lock时显式使用lock对象作为同步锁,而同步方法系统隐式使用当前对象作为同步监视器。都符合“加锁->修改->释放锁”的模式。
(2)死锁
一旦出现死锁,整个程序既不会出现任何异常,也不会给出任何提示,只是所有线程都处于阻塞状态,无法继续。
6.线程通信
(1)使用Condition控制线程通信
使用Condition可以让那些得到Lock对象却无法继续执行的线程释放lock对象,Condition对象也可以唤醒其他处于等待的线程。Lock替代了同步方法或同步代码块,Condition替代了同步监视器。
Condition实例被绑定在Lock对象上,获得Lock实例的Condition实例,调用Lock对象的newCondition()方法即可。Condition提供了三个方法:
Await():使当前线程等待
Signal():唤醒在此lock对象上等待的单个线程。
signalAll():唤醒在此Lock对象上等待的所有线程。
(2)使用阻塞队列BlockingQueue控制线程通信
当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则该线程被阻塞;当消费者线程试图从BlockingQueue中取出元素时,如果该队列已空,则该线程被阻塞。
7.线程组和线程池
线程组处理异常的默认流程是:
- 如果该线程有父线程组,则调用父线程组的uncaughtException()方法处理异常。
- 如果该线程实例所属的线程类有默认的异常处理器(由setDefaultUncaughtExceptionhandler()方法设置的异常处理器),那么调用该异常处理器来处理该异常。
- 如果该异常对象是ThreadDeath对象,则不做任何处理;否则,将异常跟踪栈的信息打印到System.err错误输出流,并结束该线程。
使用线程池来执行线程任务的步骤如下:
- 调用Ececutors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池
- 创建Runnable实现类或Callable实现类的实例,作为线程执行任务。
- 调用ExecutorService对象的submit()方法来提交Runnable实例或Callable实例。
- 当不想提交任何任务时,调用ExecutorService对象的shutdown()方法来关闭线程池。
8.线程相关类
(1)ThreadLocal类
ThreadLocal类代表一个线程局部变量,为每一个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立的改变自己的副本,而不会和其他线程的副本冲突。
(2)包装线程不安全的集合
ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap等都是线程不安全的,Collection提供的类方法把这些集合包装成线程安全的集合。如:
//使用了synchronizedMap方法包装
HashMap m = Collections.synchronizedMap(new HashMap());