[疯狂Java讲义精粹] 第十一章|多线程

来源:互联网 发布:淘宝网65平开窗传动器 编辑:程序博客网 时间:2024/06/11 19:58

1. 线程概述

0. 一个任务通常就是一个程序, 每个运行中的程序就是一个进程. 当一个程序运行时, 内部可能包含了多个顺序执行流, 每个顺序执行流就是一个线程. 


1. 进程是系统进行资源分配和调度的一个独立单位. 三个特征: 

  1. 独立性: 进程可以拥有自己独立的资源, 每个进程都有自己独立的地址空间. 没有经过进程本身允许的情况下, 一个用户进程不可以直接访问其他进程的地址空间. 
  2. 动态性: 进程与程序的区别在于, 程序只是一个静态的指令集合, 进程是在系统中活动的指令集合. 在进程中加入了时间的概念. 进程有生命周期和各种不同的状态. 
  3. 并发性:多个进程可以在单个处理器上并发执行, 多个进程之间不互相影响. 

2. *并发性(concurrency) 和 并行性(parallel): 并行指在同一时刻有多条命令在多个处理器上同时执行; 并发指同一时刻只有一条命令执行, 但多个进程指令呗快速轮换执行, 使得在宏观上具有多个进程同时执行的效果. 

3. 多线程扩展了多进程的概念, 使得同一个进程可以同时并发处理多个任务. 线程(Thread)也被称作轻量级进程(Lightweight Process), 线程是进程的执行单元. 

4. 线程可以拥有自己的堆栈, 自己的程序计数器和局部变量, 但不拥有系统自语那, 它和父进程的其他线程共享该进程所拥有的全部资源. 

5. 线程的执行是抢占式的, 即当前运行的线程在任何时候都可能被挂起, 以便另一线程运行. 

6. main()方法对应的是Java程序默认的主线程. 

2. 线程的创建和使用

0. java.lang.Thread: 线程对象是Thread类或其子类的实例. 
2.1 继承Thread类创建线程类
0. 步骤: 
  1. 定义Thread类的子类, 重写该类的run()方法, 该run()方法代表了线程要完成的任务, 因此run()方法称为线程执行体. 
  2. 创建Thread子类的实例, 即创建线程对象. 
  3. 调用线程对象的start()方法启动线程. 

1. 例子(FirstThread.java). 
public class FirstThread extends Thread {private int i; public void run(){for (; i < 100; i++){System.out.println(getName() + " " + i);}}public static void main(String[] args){for (int i = 0; i < 100; i++){System.out.println(Thread.currentThread().getName()+ " " + i);if (i == 20){FirstThread a = new FirstThread();a.start();new FirstThread().start();}}}}
# Thread.currentThread(): Thread类的静态方法, 返回当前正在执行的线程对象. 
# getName(): Thread类的实例方法, 返回调用该方法的线程名. 
# Thread-0 和 Thread-1两个线程输出的i变量不连续-- i变量是FirstThread的实例属性, 而不是局部变量, 但因为程序每次创建线程对象时都需要创建一个FirstThread对象, 所以Thread-0和Thread-1不能共享该实例属性.

2. 可以用setName(String name) 和 getName() 方法设定和获取指定线程的名字. 默认主线程名"main", 用户启动的线程名字一次"Thread-0", "Thread-1"....

3. 继承Thread类创建的线程类, 多个线程之间不能共享线程类的实例变量. 
2.2 实现Runnable接口创建线程类
0. 步骤:
  1. 定义Runnable接口的实现类(重写run()方法). 
  2. 创建Runnable的对象, 并以此对象作为Thread的target来创建Thread对象. 创建时可以指定线程名. 

1. 例子(SecondThread). 
public class  SecondThread implements Runnable{private int i;public void run(){for (; i < 100; i++){System.out.println(Thread.currentThread().getName() + " " + i);}}public static void main(String[] args){for (int i = 0; i < 100; i++){System.out.println(Thread.currentThread().getName() + " " + i);if (i == 20){SecondThread st = new SecondThread();new Thread(st, "THE-ONE").start();new Thread(st, "THE-2ONE").start();}}}}
# 两个子线程的i变量是连续的, 也就是采用Runnable接口的方式创建的多个线程可以共享线程的实例属性. 这是应为这种方式下, 程序所创建的Runnable接口的对象只是线程的target, 而多个线程可以共享一个target, 所以多个线程可以共享同一个线程类(实际上应该是线程的target类)的实例属性. (? 相当于对同一个任务(target) 用了多线程来实现.)
2.3 使用Callable和Future创建线程
0. java.util.concurrent.Callable接口提供了一个call()方法可以作为线程执行体. call()方法与java.lang.Runnbale的run()方法类似但更强大. 

1. 因为Callabe不是Runnable的子接口, 所以Callable对象不能作为Thread的target. 但Java提供了一个java.util.concurrent.Future接口代表call()方法的返回值, 并为Future提供了FutureTask实现类, FutureTask即实现了Future又实现了Runnable, 可以作为Thread类的target. 

2. Future接口有如下方法控制他关联的Callable任务: 
  1. boolean cancel(boolean mayInterruptIfRunning): 试图取消该Future里关联的Callable任务. 
  2. V get(): 返回Callable任务里call()方法的返回值. 此方法将导致程序阻塞, 必须等到子线程结束后才会得到返回值. 
  3. V get(long timeout, TimeUnit unit): 最多阻塞timeout和unit指定的时间, 如果指定时间后Callable任务还没有返回值, 将会抛出TimeoutException. 
  4. boolean isCancelled(): 如果在Callable任务正常完成之前被取消, 则返回true. 
  5. boolean isDone()

3. Callable接口有泛型限制, Callable接口里的泛型形参类型与call()方法返回值类型相同. 

4. 步骤:
  1. 创建Callable接口的实现类, 实现call()方法.
  2. 创建Callable的实例, 使用FutureTask类来包装Callable对象. 
  3. 使用FutureTask对象作为Thread对象的target创建并启动新线程. 
  4. 调用FutureTask对象的get()方法获取子线程执行结束后的返回值. 

5. 例子. 
import java.util.concurrent.Callable;import java.util.concurrent.FutureTask;public class ThirdThread implements Callable<Integer>{public Integer call(){int i = 0;for (; i < 100; i++){System.out.println(Thread.currentThread().getName() + " " + i);}return i;}public static void main(String[] args){ThirdThread rt = new ThirdThread();FutureTask<Integer> task = new FutureTask<Integer>(rt);for (int i = 0; i < 100; i++){System.out.println(Thread.currentThread().getName() + "的循环变量i的值: " + i);if (i == 20){new Thread(task, "有返回值的线程").start();}}try{System.out.println("子线程的返回值: " + task.get());}catch (Exception ex){ex.printStackTrace();}}}
2.4 创建线程的三种方式对比
  1. 使用Runnable或Callable创建的多个线程可以共享同一个target对象. 还可以继承其他父类. 
  2. 继承Thread类创建多线程因为线程已经继承了Thread类, 所以不能再继承其他父类. 

3. 线程的生命周期

线程声明周期包括: 新建(New)→就绪(Runnable)→运行(Running)→阻塞(Blocked)→死亡(Dead)五种状态. 线程状态可能多次在Running和Blocked之间切换. 
3.1 新建和就绪状态
0. 启动线程应该调用start()方法, 如果调用run()方法则会使该线程一直处于Running状态, 不能和其他线程并发执行. 

1. 线程对象调用start()之后, 该线程处于Runnable状态, 但是并没有运行, 什么时候运行取决于JVM里线程调度器的调度. 

2. 如果希望调用子线程的start()方法后线程立即执行, 可以用Thread.sleep(1)来让当前运行的线程(主线程)睡眠1毫秒. (因为这1毫秒内CPU不会空闲, 它去执行另一个Runnable状态的线程.) 
3.2 运行和阻塞状态
0. 现代桌面和服务器OS都采用抢占式调度策略, 但有的小型设备如手机可能采用"协作式调度策略"--只有当一个线程调用了它的sleep()或yield()方法后才会放弃所占用的资源(即该线程必须主动放弃). 

1. 进入阻塞状态的条件和解除方法:
  1. sleep()
  2. 线程调用了阻塞式的IO方法, 在该方法返回之前, 该线程被阻塞. 
  3. 线程试图获得一个"同步监视器", 但该同步监视器被其他线程所持有. (成功获得同步监视器后解除阻塞.)
  4. 线程在等待某个通知(notify). 
  5. 程序调用suspend()方法将该线程挂起. (调用resume()方法恢复.)

2. 被阻塞的线程的阻塞接触后, 进入就绪状态(Runnable)不是直接进入运行状态(Running). 

3. 通常运行状态和就绪状态不受程序控制, 但yield()方法可以让运行状态的线程进入就绪状态. 
3.3 线程死亡
0. 线程结束方式有三种, 结束后就是Dead状态. 
  1. run()或call()方法执行完. 
  2. 线程抛出一个未捕获的Exception或Error. 
  3. 直接调用该线程的stop()方法.(容易导致死锁, 不推荐.)

1. * 主线程结束时, 其他线程不受任何影响. 子线程一旦启动起来就有和主线程一样的地位. 

2. 线程对象的isAlive()方法返回它的状态: true代表处于就绪、运行或阻塞状态, false代表新建或死亡. 

3. 只能对新建状态的线程用start(), 但对新建状态的线程调用两次start()或对一个死亡的线程调用start()会报IllegalThreadStateException. 

4. 控制线程

4.1 join线程
0. 在程序执行流中调用其他线程的join()方法时, 调用线程(主调)被阻塞, 直到被join()方法加入的join线程执行完毕. 

1. 例子.
public class JoinThread extends Thread {public JoinThread(String name){super(name);}public void run(){for (int i = 0; i < 100; i++){System.out.println(getName() + " " + i);}}public static void main(String[] args) throws Exception{new JoinThread("new thread").start();for (int i = 0; i < 100; i++){if (i == 20){JoinThread jt = new JoinThread("the joined thread");jt.start();jt.join();}System.out.println(Thread.currentThread().getName() + " " + i);}}}

2. join()方法3种重载形式: 
  1. join()
  2. join(long millis): 等待被join的线程的时间最长为millis毫秒. (超时则join进的线程和调用join线程的线程并发.) 
  3. join(long millis, int nanos): 时间最长为(millis毫秒+nanos毫微秒(纳秒)). * 毫秒, ms, 千分之一秒; 纳秒, ns, 十亿分之一秒.  
4.2 后台线程(Deamon Thread)

0. "后台线程"是在后台运行的, 为其他线程提供服务的线程(又叫"守护线程", "精灵线程".) JVM的垃圾回收线程就是后台线程.  (Deamon, 是后台进程, 守护进程.)

1. 如果所有前台线程都死亡, 则后台线程自动死亡. 当整个虚拟机中只剩下后台线程时, 程序就没有继续运行的必要了, 所以虚拟机也就退出了. 

2. Thread对象的setDaemon(true)方法将指定线程设置成后台线程. 

3. 例子. 
public class DaemonThread extends Thread {public void run(){for (int i = 0; i < 1000; i++){System.out.println(getName() + " " + i);}}public static void main(String[] args){DaemonThread t = new DaemonThread();t.setDaemon(true);t.start();for (int i = 0; i < 10; i++){System.out.println(Thread.currentThread().getName() + " " + i);}}}

4. 前台线程创建的线程默认是前台线程, 后台线程创建的是后台线程. Thread的isDaemon()方法判断是否后台. 

5. * 前台线程死亡后, JVM通知后台线程死亡, 这还要个时间. 
4.3 线程睡眠: sleep
0. sleep()是Thread的静态方法, 让线程暂停一段时间, 进入阻塞(blocked状态). 两种重载形式:
  1. static void sleep(long millis)
  2. static void sleep(long millis, int nanos): millis毫秒 + nanos毫微秒. 
1. sleep() 方法声明抛出了InterruptedException异常, 所以调用sleep()时要么捕捉, 要么显式声明抛出该异常. 

2. 例子. 
import java.util.Date;public class SleepTest{public static void main(String[] args)throws Exception{for (int i = 0; i < 10; i++){System.out.println("当前时间" + new Date());Thread.sleep(1000);}}}
4.4 线程让步: yield 
0. yield也是Thread的静态方法. 

1. yield将线程转为就绪状态. 只是让线程暂停一下, 如果程序中不存在优先级比该线程高或相同的线程, 则此让步的线程继续运行; 如果有, 另算 (给优先级更高的线程让步). 

2. sleep() 方法声明抛出InterruptedException异常, yield() 方法没抛出异常. 

3. 例子. 
public class YieldTest extends Thread{public YieldTest(String name){super(name);}public void run(){for (int i = 0; i < 50; i++){System.out.println(getName() + " " + i);if (i == 20){Thread.yield();}}}public static void main(String[] args) throws Exception{YieldTest yt1 = new YieldTest("HIGH");//yt1.setPriority(Thread.MAX_PRIORITY);yt1.start();YieldTest yt2 = new YieldTest("LOW");//yt2.setPriority(Thread.MIN_PRIORITY);yt2.start();}}
4.5 改变线程优先级
0. 优先级高的线程更多执行机会. 

1. 线程的默认优先级与创建它的父线程优先级相同. main默认普通优先级. 

2. Thread类提供setPriority(int newPriority)和getPriority()方法. 其中newPriority参数是1到10的整数, 也可以使用Thread类的3个静态常量: 
  • MAX_PRIORITY: 值为10. 
  • MIN_PRIORITY: 1. 
  • NORM_PRIORITY: 5.

3. 不同系统提供的优先级不同(win2000只提供了7种优先级), 不一定能和Java的10个优先级对应, 所以为了更好的可移植性, 用静态常量. 

4. 例子. 
public class PriorityTest extends Thread{public PriorityTest(String name){super(name);}public void run(){for (int i = 0; i < 50; i++){System.out.println(getName() + "'s priority is " + getPriority() + ", i's value is " + i);}}public static void main(String[] args){Thread.currentThread().setPriority(6);for (int i = 0; i < 30; i++){if (i == 10){PriorityTest low = new PriorityTest("LOW");low.start();System.out.println("the INIT priority is: " + low.getPriority());low.setPriority(Thread.MIN_PRIORITY);}if (i == 20){PriorityTest high = new PriorityTest("HIGH");high.start();System.out.println("the INIT priority is: " + high.getPriority());high.setPriority(Thread.MAX_PRIORITY);}}}}

5. 线程同步

5.1 线程安全问题
如"银行取钱问题"(几个线程并发操作同一对象). 
5.2 同步代码块
0. 用同步监视器解决上述问题. 
synchronized(obj){同步代码块}
# obj作为同步监视器. 
# 线程执行同步代码块之前, 先对obj进行锁定(如果已被锁定, 则同步代码块不能运行)在执行, 执行结束后释放锁定. 
# 可以保证同一对象同一时刻最多由一个线程处理. 

1. 用法.
public void run(){synchronized(obj){//同步代码块}}
5.3 同步方法
0. 与5.2类似, 用synchronized关键字修饰某个方法, 则此方法叫同步方法. 

1. 同步方法的同步监视器无须显示声明. 同步方法的同步监视器是this(即这个对象本身), 

2. 不可变类的对象状态是不可变的, 所以它的对象总是线程安全的. 

3. 例子. 
public synchronized void draw(double drawAmount){if (balance >= drawAmount){System.out.println(Thread.currentThread().getName() + "取钱成功!吐出马奶: " + drawAmount);balacne -= drawAmount;System.out.println("\t余额: " + balance);}else{System.out.println(Thread.currentThread().getName() + "取钱失败,钱不够.");}}

4. aynchronized关键字只能修饰方法和代码块, 不能修饰构造器和Field. 
5.4 释放同步监视器的锁定
0. 程序不能显式释放对同步监视器的锁定. 

1. 释放锁定的情况:
  1. 同步方法/同步代码块执行结束时. 
  2. 同步方法/同步代码块中遇到break or return时. 
  3. 同步方法/同步代码块出现未处理的Error or Exception 导致异常终止时. 
  4. *当前线程执行同步方法/同步代码块时, 程序执行了同步监视器的wait()方法, 则当前线程暂停, 并释放同步监视器. 
2. 这样不会释放:
  1. *线程执行同步方法/同步代码块时, 程序调用Thread.sleep()或Thread.yield()方法来暂停当前线程时, 不释放同步监视器的锁定. 
  2. 线程执行同步代码块时, 其他线程调用了该线程的suspend()方法将该线程挂起, 不释放. 
5.5 同步锁(Lock)
0. 还可以显式定义同步锁对象实现同步, 同步锁使用Lock对象充当. 

1. 某些锁能对共享资源并发访问, 如ReadWriteLock读写锁. java.util.concurrent.locks.Lock和 java.util.concurrent.locks.ReadWriteLock是两个根接口, 有ReentrantLock(可重入锁)和ReentranReadWriteLock实现类. 

2. ReentrantLock是可重入锁, 即一个线程可以对一个已被加锁的ReentrantLock锁再次加锁, ReentrantLock对象会追踪lock()方法的嵌套调用. 

3.?一段被锁保护的代码可以调用另一个被相同锁保护的方法. 

4. 用法. 
class X{private final ReentrantLock lock = new ReentrantLock();...public void m(){lock.lock();try{...}finally{lock.unlock();}}}
5.6 死锁
0. 两个线程相互等待对方释放同步监视器对象时发生死锁. 

1. "死锁"和"程序阻塞"看上去一样, 其实不一样. 

2. 例子. 
class A{public synchronized void foo(B b){System.out.println("当前线程名: " + Thread.currentThread().getName() + "进入了实例A的foo方法");try{Thread.sleep(200);}catch (InterruptedException ex){ex.printStackTrace();}System.out.println("当前线程名: " + Thread.currentThread().getName() + "企图调用B实例的last方法");b.last();}public synchronized void last(){System.out.println("进入了A类的last方法内部");}}class B{public synchronized void bar(A a){System.out.println("当前线程名: " + Thread.currentThread().getName() + "进入了实例B的bar方法");try{Thread.sleep(200);}catch (InterruptedException ex){ex.printStackTrace();}System.out.println("当前线程名: " + Thread.currentThread().getName() + "企图调用A实例的last方法");a.last();}public synchronized void last(){System.out.println("进入了B类的last方法内部");}}public class DeadLock implements Runnable{A a = new A();B b = new B();public void init(){Thread.currentThread().setName("主线程");a.foo(b);System.out.println("进入主线程之后");}public void run(){Thread.currentThread().setName("副线程");b.bar(a);System.out.println("进入副线程之后");}public static void main(String[] args){DeadLock dl = new DeadLock();new Thread(dl).start();dl.init();}}
运行后输出
为毛先输出"主线程..."啊? 

6. 线程通信

程序不能控制线程的轮换执行, 但可以通过一些机制保证线程协调运行. 
6.1 传统的线程通信 
0. Object类提供了wait(), notify()和notifyAll()三个方法实现线程通信, 它们不属于Thread类. 
  1. wait(): 导致当前线程等待, 直到其他线程调用该同步监视器的notify()或notifyAll()方法来唤醒该线程(也可以指定等待时间). * 调用wait()方法会释放当前线程对同步监视器的锁定. 
  2. notify(): 唤醒在此同步监视器上等待的单个线程. 如果多个线程都是在此同步监视器上等待, 则随机唤醒一个. 只有当前线程放弃对该监视器的锁定后(用wait()方法), 才可以执行被唤醒的线程. 
  3. notifyAll(): 唤醒在此同步监视器上等待的所有个线程. 只有当前线程放弃对该监视器的锁定后(用wait()方法), 才可以执行被唤醒的线程. 

1. wait(), notify()和notifyAll()方法由同步监视器对象来调用:
  1. 对synchronized修饰的同步方法, 因为默认的实例(this)是同步监视器本身, 所以可以直接使用这三个方法. 
  2. 对synchronized修饰的同步代码块

2. wait()方法抛出InterreptedException异常, 像sleep()一样. 
6.2 使用Condition控制线程通信 
0. 如果不使用synchronized关键字保证同步, 则不能使用wait(), notify()和notifyAll()方法进行通信. 使用Lock对象保证同步时, 用Condition类保持协调. 

1. Condition实例被绑定在Lock实例上, 要获得它用Lock对象的newCondition()方法即可. 

2. Condition的三个方法: 
  1. await(): 类似隐式同步监视器上的wait()方法, 导致当前线程等待, 直到其他线程调用该Condition的signal()或signalAll()方法来唤醒该线.
  2. signal(): 唤醒在此Lock对象上等待的单个线程. 如果多个线程都是在此Lock对象上等待, 则随机唤醒一个. 只有当前线程放弃对该Lock对象的锁定后(用await()方法), 才可以执行被唤醒的线程. 
  3. signalAll(): 唤醒在此Lock对象上等待的所有线程. 只有当前线程放弃对该Lock对象的锁定后, 才可以执行被唤醒的线程. 

3. 例子.
import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;import java.util.concurrent.locks.Condition;public class Account{private final Lock lock = new ReentrantLock();private final Condition cond = lock.newCondition();private String accountNo;private double balance;private boolean flag = false;public Account(){}public Account(String accountNo, double balance){this.accountNo = accountNo;this.balance = balance;}public void setAccountNo(String accountNo){this.accountNo = accountNo;}public String getAccountNo(){return accountNo;}public double getBalance(){return balance;}public void draw(double drawAmount){lock.lock();try{if (!flag){cond.await();}else{System.out.println(Thread.currentThread().getName() + "取钱: " + drawAmount);balance -= drawAmount;System.out.println("\t账户余额为: " + balance);flag = false;cond.signalAll();}}catch(InterruptedException ex){ex.printStackTrace();}finally{lock.unlock();}}public void deposit(double depositAmount){lock.lock();try{if (flag){cond.await();}else{System.out.println(Thread.currentThread().getName() + "存款: " + depositAmount);balance += depositAmount;System.out.println("账户余额为: " + balance);flag = true;cond.signalAll();}}catch (InterruptedException ex){ex.printStackTrace();}finally{lock.unlock();}}// 此处省略了hashCode()和equals()方法. }
6.3 使用阻塞队列(BlockingQueue)控制线程通信 
0. BlockingQueue是Queue的子接口, 但通常不作为容器, 而作为线程同步的工具. 

1. BlockingQueue队列特征: 当生产者线程试图向BlockingQueue中放入元素时, 如果队列已满, 则线程阻塞; 消费者线程试图从队列中取出元素时, 若已空, 阻塞. 

2. BlockingQueue的方法:
  1. put(E e): 把e元素放入BlockingQueue, 如果队列已满, 阻塞. 
  2. take(): 从BlockingQueue的头部取出元素, 若已空, 阻塞. 
  3. 队尾插入元素: add(E e), offer(E e), put(E e)方法. 如果已满, 分别: 抛出异常, 返回false, 阻塞队列. 
  4. 队头删除并返回元素: remove(), poll(), take()方法. 如果已空, 分别: 抛出异常, 返回false, 阻塞队列. 
  5. 队头取出但不删除元素: element(), peek()方法. 如果已空, 分别: 抛出异常, 返回false. 

3. 例子. 
import java.util.concurrent.BlockingQueue;import java.util.concurrent.ArrayBlockingQueue;class Producer extends Thread{private BlockingQueue<String> bq; public Producer(BlockingQueue<String> bq){this.bq = bq; }public void run(){String[] strArr = new String[]{"Java", "Struts", "Spring"};for (int i = 0; i < 9999999; i++){System.out.println(getName() + "生产者准备生产集合元素!");  try{Thread.sleep(200);bq.put(strArr[i % 3]);}catch(Exception ex){ex.printStackTrace();}System.out.println(getName() + "生产完成: " + bq);}}}class Consumer extends Thread{private BlockingQueue<String> bq;public Consumer(BlockingQueue<String> bq){this.bq = bq;}public void run(){while(true){System.out.println(getName() + "消费者准备消费集合元素!");try{Thread.sleep(200);bq.take();}catch(Exception ex){ex.printStackTrace();}System.out.println(getName() + "消费完成: " + bq);}}}public class BlockingQueueTest2{public static void main(String[] args){BlockingQueue<String> bq = new ArrayBlockingQueue<>(1);new Producer(bq).start();new Producer(bq).start();new Producer(bq).start();new Consumer(bq).start();}}
# 运行结果有问题. 

7. 线程组和未处理的异常 

7.1 线程组
0. 用ThreadGroup表示线程组, 对线程进行分类管理. 所有线程都属于指定组(不显示指定则为默认组). 默认子线程和父线程处于同一个线程组(线程a属A组, a创建b且不指定线程组, 则b属A组). 

1. 线程组一旦指定, 不能改.

2. Thread提供了构造器设置新建线程属于哪个线程组:
  1. Thread(ThreadGroup group, Runnable target): 以target的run()方法作为线程执行体创建新线程, 属于group线程组. 
  2. Thread(ThreadGroup group, Runnable target, String name):  以target的run()方法作为线程执行体创建名为name的新线程, 属于group线程组. 
  3. Thread(ThreadGroup group, String name): 创建新线程, 名为name, 属于group线程组. 

3. 所属线程组不能变更, 所以Thread有getThreadGroup()方法没有setThreadGroup(). 

4. ThreadGroup的构造器:
  1. ThreadGroup(String name): 
  2. ThreadGroup(ThreadGroup parent, String name): 以指定的父线程组创建名为name的线程组. 

5. ThreadGroup类的常用方法:
  1. int activeCount(): 返回此组中活动线程的数目. 
  2. interrupt(): 中断此组所有线程. 
  3. isDaemon(): 判断改组是否为后台线程组. 
  4. setDaemon(boolean daemon): 设为后台线程组(特征: 后台线程组的最后一个线程执行结束或被销毁后, 后台线程组自动销毁). 
  5. setMaxPriority(int pri): 设置线程组最高优先级. 
  6. 例子. 
    class MyThread extends Thread {public MyThread(String name){super(name);}public MyThread(ThreadGroup group, String name){super(group, name);}public void run(){for (int i = 0; i < 20; i++){ System.out.println(getName() + "线程的i变量" + i);}}}public class ThreadGroupTest{public static void main(String[] args){ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();System.out.println("主线程组的名字是: " + mainGroup.getName());System.out.println("主线程是否是后台线程: " + mainGroup.isDaemon());new MyThread("主线程组的线程").start();ThreadGroup tg = new ThreadGroup("新线程组");tg.setDaemon(true);System.out.println("tg是否是后台线程组: " + tg.isDaemon());MyThread tt = new MyThread(tg, "tg组的线程甲");tt.start();new MyThread(tg, "tg组的线程乙").start();}}
7.2 异常处理
0. 用Thread.UncaughtExceptionHandler的uncaughtException()方法对线程组内未处理的异常进行处理. 

1. Thread.UncaughtExceptionHandler是Thread类的静态内部接口, 它只有一个方法: void uncaughtExceptionHandler(Thread t, Throwable e) , t代表出现异常的线程, e代表该线程抛出的异常. 

2. 如果线程执行过程中抛出了一个未处理的异常, JVM在结束该线程之前会自动查找是否有对应的Thread.UncaughtExceptionHandler对象, 如果有, 则调用该对象的uncaughtException(Thread t, Throwable e)方法来处理该异常. 

3. Thread类提供了两种设置异常处理器的方法:
  1. static setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh): 为该线程类的所有线程实例设置默认的异常处理器. 
  2. setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh): 为指定的线程实例设置异常处理器.  


4. ThreadGroup类实现了Thread.UncaughtExceptionHandler接口, 所以每个线程所属的线程组都会作为默认的异常处理器. 一个线程抛出异常时, JVM先查找该异常对应的异常处理器, 如果没有, 则JVM调用该线程所属的线程组对象的uncaughtException()方法来处理该异常. 线程组处理异常的默认流程: 

  1. 如果该线程组有父线程组, 调用父线程组的uncaughtException()方法处理. 
  2. 如果该线程实例所属的线程类有默认的异常处理器, 调用该异常处理器处理异常. 
  3. 如果该异常对象是ThreadDeath的对象, 则不作任何处理; 否则, 将异常跟踪栈的信息打印到System.err错误输出流, 并结束该线程. 

5. 例子. 
class MyExHandler implements Thread.UncaughtExceptionHandler{public void uncaughtException(Thread t, Throwable e){System.out.println(t + " -X- " + e);}}public class ExHandler {public static void main(String[] args){Thread.currentThread().setUncaughtExceptionHandler(new MyExHandler());int a = 5 / 0;System.out.println(" RIGHT ");}}

8. 线程池

0. 使用线程池可以提供性能, 尤其是要创建大量生存期短暂的线程时. 

1. 线程池在系统启动时就创建大量空闲线程, 程序将一个Runnable对象或Callable对象传给线程池, 线程池就会启动一个线程来执行它们的run()或call()方法, 执行结束线程不死, 而是返回线程池中称为空闲状态等待执行下一个Runnable对象的run()或call()方法. 

2. 线程池的最大县城参数可以控制系统中并发线程数不超过此数. 
8.1 Java 5 实现的线程池
0. Executors工厂类包括几个静态工厂方法创建线程池: 
  1. newCachedThreadPool(): 创建一个有缓存功能的线程池, 系统根据需要创建线程, 这些线程将会被缓存在线程池中. 
  2. newFixedThreadPool(int nThreads): 创建一个可重用的, 具有固定线程数的线程池. 
  3. newSingleThreadExecutor(): 创建一个只有一个线程的线程池, 相当于使用newFixedThreadPool()方法时传入1. 
  4. newScheduledThreadPool(int corePoolSize): 创建具有指定线程数的线程池, 它可以在指定延迟后执行线程任务. corePoolSize指池中所保存的线程数(即使线程是空闲的也被保存在线程池内). 
  5. newSingleThreadScheduledExecutor(): 创建只有一个线程的线程池, 它可以在指定延迟后执行线程任务. 
# 前三个方法返回ExecutorService对象; 后两个返回ScheduledExecutorService线程池(它是ExecutorService的子类), 它可以在指定延迟后或周期性地执行线程任务. 

1. ExecutorService的三个方法: 
  1. Future<?> submit(Runnable task): 将一个Runnable对象提交给指定线程池, 线程池将在有空闲线程时执行Runnable对象代表的任务. Future对象代表Runnable任务的返回值(run()方法没有返回值, 所以Future此时返回null), 还可以调用Future的isDone(), isCancelled()方法获得Runnable对象的执行状态. 
  2. <T> Future<T> submit(Runnable task, T result): result显示指定线程执行结束后的返回值, 所以Future对象将在run()方法执行结束后返回result. 
  3. <T> Future<T> submit(Callable<T> task): Future代表Callable对象里的call()方法的返回值. 

2. ScheduledExecutorService的三个方法: 
  1. ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit): 指定callable任务将在delay延迟后执行. 
  2. ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit): 指定command任务将在delay延迟后执行. 
  3. ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit): 在initialDelay, initialDelay+period, initialDelay+2*period....处重复执行. 
  4. ScheduledFuture<?> scheduleWithFixeDelay(Runnable command, long initialDelay, long delay, TimeUnit unit): 在initialDelay处开始重复执行, 且每一次执行中止和下一次执行开始之间间隔delay. 

3. 用完一个线程池后, 应该将其关闭. 
  1. shutdown(): 线程池不再接受新任务, 但已提交任务会执行完成(所有任务执行完后, 池中所有线程死亡). 
  2. shutdownNow(): 该方法试图停止所有正在执行的活动任务, 暂停处理正在等待的任务, 并返回等待执行的任务列表. 

4. 例子. 
import java.util.concurrent.Executors;import java.util.concurrent.ExecutorService;class MyThread implements Runnable{public void run(){for (int i = 0; i < 100; i++){System.out.println(Thread.currentThread().getName() + "的i值为: " + i);//System.out.println(getName() + "的i值为: " + i);}}}public class ThreadPoolTest{public static void main(String[] args) throws Exception{ExecutorService pool = Executors.newFixedThreadPool(6);pool.submit(new MyThread());pool.submit(new MyThread());pool.shutdown();}}
8.2 Java 7 新增的ForkJoinPool
0. 引入ForkJoinPool是为了充分利用多cpu优势. ForkJoinPool把一个任务分成数个小任务给各cpu并行计算, 计算结果再合成一个任务. 

1. ForkJoinPool的两个构造器: 
  1. ForkJoinPool(int parallelism): 创建一个包含parallelism个并行线程的ForkJoinPool. 
  2. ForkJoinPool(): 以Runtime.availableProcessors()方法的返回值作为parallelism参数来创建ForkJoinPool. 

9. 线程相关类

9.1 ThreadLocal类
0. java.lang.ThreadLocal<T>. 

1. ThreadLocal(即thread local variable, 线程局部变量), 通过把数据放在ThreadLocal中, 每个使用该变量的线程都将得到一个ThreadLocal的副本, 从而避免并发访问的线程安全问题. 

2. ThreadLocal的单个public方法: 
  1. T get(): 返回此线程局部变量中当前线程副本中的值. 
  2. void remove(): 删除此线程局部变量中当前线程的值. 
  3. void set(T value): 设置此线程局部变量中当前线程副本中的值. 

3. 例子. 
class Account{private ThreadLocal<String> name = new ThreadLocal<>();public Account(String str){this.name.set(str);System.out.println("---" + this.name.get());}public String getName(){return name.get();}public void setName(String str){this.name.set(str);}}class MyTest extends Thread {private Account account;public MyTest(Account account, String name){super(name);this.account = account;}public void run(){for (int i = 0; i < 10; i++){if ( i == 6){account.setName(getName());}System.out.println(account.getName() + "'s i is: " + i);}}}public class ThreadLocalTest{public static void main(String[] args){Account at = new Account("initName");new MyTest(at, "Thread-A").start();new MyTest(at, "Thread-B").start();}}
9.2 包装线程不安全的集合
0. 像ArrayList, LinkedList, HashSet, TreeSet, HashMap, TreeMap等集合都是线程不安全的. 可以使用Collections提供的静态方法把这些集合包装成线程安全的集合:
  1. <T> Collection<T> synchronizedCollection(Collection<T> c): 返回指定collection对应的线程安全的collection. 
  2. static <T> List<T> synchronizedList(List<T> list): 返回指定List对象对应的线程安全List对象. 
  3. static <K, V> Map<K, V> synchronizedMap(Map<K, V> m): 返回指定Map对象对应的线程安全Map对象. 
  4. static <T> Set<T> synchronizedSet(Set<T> s): 返回指定Set对象对应的线程安全Set对象. 
  5. static <K, V> SortedMap<K, V> synchronizedSortedMap(SortedMap<K, V> m): 返回指定SortedMap对象对应的线程安全SortedMap对象. 
  6. static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s): 返回指定SortedSet对象对应的线程安全SortedSet对象. 

1. 如果需要把某个集合包装成线程安全的集合, 则应该在创建之后立即包装. 
9.3 线程安全的集合
0. java.util.concurrent包提供了大量支持高效并发访问的集合接口和实现类. 

1. 以Concurrent开头的集合类: ConcurrentHashMap, ConcurrentSkipListMap, ConcurrentSkipListSet等. 
  1. 支持多个线程并发写入访问(安全), 但读取操作不必锁定. 
  2. 采用复杂算法保证了永远不会锁住整个集合. 

2. CopyOnWrite开头的集合类: CopyOnWriteArrayList, CopyOnWriteArraySet等. 
  1. CopyOnWriteArraySet的底层封装了CopyOnWriteArrayList, 所以实现机制类似. 
  2. 线程对CopyOnWriteArrayList集合执行读取操作时, 线程直接读取集合本身, 无需加锁与阻塞. 执行写操作时, 该集合在底层复制一份新的数组, 对新数组进行写入操作. 
  3. 对CopyOnWriteArrayList写入时频繁复制数组, 性能差, 但读取时性能好, 所以CopyOnWriteArrayList适合用在读取操作远远大于写入操作的场景中, 如缓存. 
原创粉丝点击