第二章|第二章 Java并行程序基础

1. 进程和线程

  • 进程是系统进行资源分配的基本单位。早期,进程是程序的基本执行实体,当代计算机结构中,进程是线程的容器。
  • 【第二章|第二章 Java并行程序基础】线程是轻量级的进程,是程序执行的最小单位。使用多线程而不是多进程是因为线程的切换和调度的成本远远小于进程。

    第二章|第二章 Java并行程序基础
    文章图片
    线程生命周期
  1. NEW : 刚刚创建的线程,还没开始执行,等到start()方法调用,获得相关资源后进入RUNNABLE状态。
  2. RUNNABLE:线程执行状态。
  3. BLOCKED:线程执行时遇到sychronized同步块,进入BLOCKED状态。线程暂停执行,直到获取请求的锁,进入RUNNABLE。
  4. WAITING和TIMED_WAITING:等待状态,等待一些事件。如:wait()方法等待的线程在等待notify()方法,join()方法等待的线程在等待目标线程停止。等到期望事件后进入RUNNABLE状态。
  5. TERMINATED:线程执行完毕。
2. 初始线程:线程的基本操作
2.1 新建线程
  1. 继承Thread类,重写run方法(Java单继承,不建议使用)
  2. 实现Runnable接口,实现run方法,将实现类对象传入Thread构造函数。
class Thread1 implements Runnable{ @Override public void run() { System.out.println("i am a thread"); } }Thread thread2 = new Thread(new Thread1()); thread.start();

2.2 终止线程
  • Thread提供了一个stop()方法,但是不建议使用,这个方法会直接终止线程,释放这个线程所持有的所有锁。会破坏数据一致性。例如写线程正在写数据,直接stop,读线程获取锁,此时读出的数据是不对的。
  • 要想终止线程,可以设置一个flag标志,需要终止时,调用方法,改变flag的值。
2.3 中断线程
  • 中断线程不会使线程立即退出,而是给线程发送一个通知,告知目标线程,至于目标线程如何处理,完全由目标线程自行决定。
void interrupt():中断线程 boolean isInterrupted():判断是否被中断 static boolean interrupted():判断是否被中断,并且清除中断标志

Thread t1 = new Thread() { public void run() { while(true) { if(Thread.currentThread().isInterrupted()) { System.out.println("准备退出"); break; } try { Thread.sleep(2000); }catch (InterruptedException e) { Thread.currentThread().interrupt(); } Thread.yield(); } } }; t1.start(); Thread.sleep(2000); t1.interrupt();

  • 中断sleep()的线程会抛出InterruptedException异常,并且清除中断标志,如果不处理,下次循环不会检测到已中断,所以在异常处理中再次中断自己。
2.4 等待(wait)和通知(notify)
  • 两个方法属于Object类
  • 一个对象obj调用wait()方法后,当前线程就会停止执行,进入obj的等待队列,直到obj调用notify或者notifyAll方法。调用notify时,随机从obj的等待队列中选择一个线程唤醒。
  • 调用wait和notify之前,都需要获取此对象obj的锁。
  • wait()方法会释放所有的锁,sleep()方法不会释放。
第二章|第二章 Java并行程序基础
文章图片
等待队列 第二章|第二章 Java并行程序基础
文章图片
wait和notify工作流程 2.5 挂起(suspend)和继续执行(resume)
  • 已废弃
  • 被suspend的线程只有等到resume后才能继续执行。
  • suspend在暂停线程的同时,不会释放任何资源。
  • 被suspend的线程状态还是RUNNABLE。
2.6 等待线程结束(join)和谦让(yield)
  • join:当前线程阻塞,直到目标线程执行完毕。
  • yield:当前线程让出CPU,重新等待调度。
3. 分门别类的管理:线程组
  • 给线程分组
ThreadGroup tg = new ThreadGroup("A组"); Thread t1 = new Thread(tg,new Thread(),"线程1"); Thread t2 = new Thread(tg,new Thread(),"线程2"); System.out.println(tg.activeCount()); tg.list();

4 守护线程(Daemon)
  • 作为系统后台服务。
  • 其他非守护线程结束后,守护线程自动结束。
    -setDaemon()必须在start()之前设置。
Thread t1 = new Thread(); t1.setDaemon(true);

5. 线程优先级
  • 优先级高的线程在竞争资源时会更有优势,但只是概率问题。
  • 优先级与底层操作系统密切联系,各个平台表现不一。
//Java中用1-10表示,值越大优先级越高 public final static int MIN_PRIORITY = 1; public final static int NORM_PRIORITY = 5; public final static int MAX_PRIORITY = 10;

6. 线程安全与synchronized
  • volatile一定程度上改善了线程安全的问题,但是只能保证一个线程修改了数据后,其他线程能够看到这个改动。当两个线程同时修改这个数据时,依然会产生冲突。
  • sychronized可以实现线程的同步,通过对代码加锁,使得每一次只能有一个线程进入同步块。
  • 编码时要确保是同一个锁。
  • 三种加锁方式
  1. 指定加锁对象。
  2. 作用于实例方法:相当于对当前实例加锁。
  3. 作用于静态方法:相当于对当前类加锁。
7. 诡异的错误
  • 并发下普通的集合是不安全的。
  • 错误的加锁方式
//Integer是不可变的对象,一旦创建就不能修改,实际上每次i++都新建了Integer对象重新赋值,所以每次的锁不是同一个。 Integer i = 0; sychronized(i){ i++; }

    推荐阅读