[关闭]
@XQF 2017-01-03T10:54:37.000000Z 字数 9848 阅读 1949

并发专题

java


一.多线程概述

1.线程的基本概念

a.程序

程序是一段静态代码,是人们解决问题的思维方式在计算机中的描述,是应用软件执行的蓝本,是一个静态的概念

b.进程

是程序的一个运行例程,是应用程序的一次动态执行过程。进程由若干个代码和数据块组成。每个进程还拥有其他资源,例如文件,动态内存地址,线程等。进程是计算机进行资源分配的独立单位。

c.线程

是进程中相对独立的一个执行单元,是操作系统调度的基本单位,,一个进程可以包含若干个线程。同一个进程中的各个线程对应于一组CPU指令,一组CPU寄存器以及一个堆栈。进程并不执行代码,只是代码存放的地址空间。进程地址空间中存放的代码由线程来执行。线程的执行由操作系统调度。

2.线程的运行机制

JVM的很多任务都依赖于线程调度,执行程序代码的任务是由线程来完成的。在Java中,每一个线程都有一个独立的程序计数器和方法调用栈。
程序计数器,也就是一个记录线程当前执行程序代码位置的寄存器,当线程在执行过程中,程序计数器指向的是下一条要执行的指令。
方法调用栈用来描述线程在执行一系列的方法调用过程。栈中的每一个元素称为一个栈帧。每一个栈帧对应一个方法调用,帧中保存了方法调用的参数,局部变量和程序执行过程中的临时数据。
当JVM进程被启动时,同一个JVM进程中有且只有一个进程,就是自己。在这个JVM环境中,所有程序的运行都是以线程来运行的。JVM会最先产生一个主线程,由它来运行指定程序的入口。在这个程序中,主线程从main方法开始中执行。main()执行结束,JVM也跟着退出。

二.线程的创建和启动

线程只有在创建之后才会存在,创建后的线程,要想获得处理执行,还要再经过启动。

1.线程的创建

a.继承Thread类

首先看看Thread类的构造方法
QQ图片20161129155435.png-11.1kB

常用的方法:

  1. void run() //线程运行时所执行的代码都在这个方法中,是Runnable接口声明的唯一方法
  2. void start() //使该线程开始执行,java虚拟机调用该线程的run方法
  3. static int activeCount() //返回当前线程的线程组中活动线程的数目
  4. static Thread currentThread() //返回当前正在执行线程的引用
  5. static int enumerate(Thread []t) //将当前线程组中的每一个活动线程复制到指定的数组中
  6. String getName() //返回该线程的名称
  7. int getPriority() //返回该线程的优先级
  8. Thread.State getState() //返回线程的状态
  9. Thread Group getThreadGroup() //返回线程所在的线程组
  10. final boolean isAlive() //测试线程是否处于活动状态
  11. void setDaemon(boolean on) //将该线程标记为守护线程(true)或者用户线程
  12. void setName(String name) //改变线程名称,使之与参数name相同
  13. void interrupt() //中断线程
  14. void join() //等待线程终止,有多个重载
  15. static void yield() //暂停当前正在执行的线程对象,并执行其他线程

实例:

  1. /**
  2. * Created by XQF on 2016/11/30.
  3. */
  4. class MyThread extends Thread {
  5. int count = 1, number;
  6. public MyThread(int number) {
  7. this.number = number;
  8. System.out.println("创建线程 " + number);
  9. }
  10. public void run() {
  11. while (true) {
  12. System.out.println("线程 " + number + ":计数 " + count);
  13. if (++count == 6) {
  14. return;
  15. }
  16. }
  17. }
  18. }
  19. public class TestThread {
  20. public static void main(String[] args) {
  21. for (int i = 0; i < 3; i++) {
  22. new MyThread(i + 1).start();
  23. }
  24. }
  25. }

实验结果:

  1. 创建线程 1
  2. 创建线程 2
  3. 创建线程 3
  4. 线程 1:计数 1
  5. 线程 1:计数 2
  6. 线程 1:计数 3
  7. 线程 1:计数 4
  8. 线程 1:计数 5
  9. 线程 2:计数 1
  10. 线程 3:计数 1
  11. 线程 2:计数 2
  12. 线程 2:计数 3
  13. 线程 2:计数 4
  14. 线程 2:计数 5
  15. 线程 3:计数 2
  16. 线程 3:计数 3
  17. 线程 3:计数 4
  18. 线程 3:计数 5

这个例子深刻的说明了多线程的并发问题,线程执行的顺序是杂乱无章的,执行顺序的不确定性。线程1执行一下后换B,或者C。额,主要是他们的优先级是差不多的,而且这个也没有设置同步执行。

b.实现Runnable接口

老实说这个接口编程思想还是有一定道理。这个应该叫做一个任务,先定义一个任务,然后再给任务绑定一个线程去执行任务。

  1. /**
  2. * Created by XQF on 2016/11/30.
  3. */
  4. class MyRunnable implements Runnable {
  5. private int count = 1, number;
  6. public MyRunnable(int number) {
  7. this.number = number;
  8. System.out.println("创建第" + number + "个任务!");
  9. }
  10. public void run() {
  11. while (true) {
  12. System.out.println("任务 " + number + ":计数 " + count);
  13. if (++count == 6) {
  14. return;
  15. }
  16. }
  17. }
  18. }
  19. public class TestRunnable {
  20. public static void main(String[] args) {
  21. for (int i = 0; i < 3; i++) {
  22. MyRunnable myRunnable = new MyRunnable(i + 1);
  23. Thread thread = new Thread(myRunnable);
  24. thread.start();
  25. }
  26. }
  27. }

结果:

  1. 创建第1个任务!
  2. 创建第2个任务!
  3. 创建第3个任务!
  4. 任务 1:计数 1
  5. 任务 2:计数 1
  6. 任务 1:计数 2
  7. 任务 1:计数 3
  8. 任务 3:计数 1
  9. 任务 2:计数 2
  10. 任务 3:计数 2
  11. 任务 1:计数 4
  12. 任务 3:计数 3
  13. 任务 2:计数 3
  14. 任务 3:计数 4
  15. 任务 1:计数 5
  16. 任务 3:计数 5
  17. 任务 2:计数 4
  18. 任务 2:计数 5

2.线程的启动

使用线程对象的start()方法来启动线程,有两方面的功能:

  1. 为线程分配必要的资源,使线程处于可运行状态
  2. 调用线程的run()方法来运行线程。

三.线程的状态和转换

java线程的生命周期分为七种状态:

1.创建状态

要是创建了一个线程而没有启动,那么线程就是处于创建状态

2.可运行状态

如果对一个处于创建状态的线程调用start()方法,则此线程就进入了可运行状态。可运行状态相当于是有可能运行也有可能就绪等待。因为假如有很多的可运行线程就需要操作系统的调度。

3.阻塞和等待状态

这两个状态是由运行状态转换过来的。处于这个状态就暂时放弃了获得处理机的权利,直到线程调试器重新激活它。
处于阻塞和等待状态的原因

  1. 当一个线程试图获取一个内部的对象锁,而该锁被其他线程拥有,则进入阻塞状态。当其他线程释放锁且系统允许本线程持有的时候,本线程变为可运行状态
  2. 当线程调用wait()方法来等待另一个线程的通知或者调用join()方法等待另一个线程执行结束的时候,就会进入等待状态。
  3. 如果线程调用sleep(),wait(),join()等方法的时候,传递一个超时参数,这些方法会使得线程进入计时等待状态。

4.死亡状态

线程在其他任何状态下调用stop()方法。
三种方式进入死亡:

  1. run()方法正常结束
  2. 线程抛出未捕获Exception或者error
  3. 直接使用stop()方法,不过此方法容易导致死锁,不推荐

四.线程控制

在生命周期内,状态经常发生变化,由于系统有多个处于活动状态的线程,我们往往通过控制线程的状态变化来协调多个线程并发执行。

1.线程睡眠

调用sleep方法,有两个重载:
sleep方法要抛出异常
QQ图片20161129155435.png-4.4kB

2.线程让步

yield()方法

sleep()和yield()的区别

  1. sleep()方法使当前线程进入计时等待状态,所以执行sleep方法的线程在指定的时间内肯定不会执行;yiele()方法使当前的线程重新回到可运行状态,所以执行yield()方法的线程有可能在进入到可执行状态后马上被执行
  2. sleep()使得优先级低的线程得到执行机会,当然也可以让同优先级和高优先级的线程有执行机会,但是yield()只能让同优先级线程有执行机会。
  3. sleep()方法声明抛出InterruptedException异常,而yield()方法不会抛出任何异常。

3.线程之间的协作

就是几个线程分工交替执行,当然执行调度就是使用前面的sleep(),yield()或者join()方法来实现。

4.后台线程

主线程终止时后台线程自动终止。当然可以在主线程终止之前手动终止

  1. public class DaemonTest {
  2. public static void main(String[] args) {
  3. Thread thread = new Thread(new Runnable() {
  4. public void run() {
  5. System.out.println("这是一个后台线程");
  6. }
  7. });
  8. thread.setDaemon(true);//设置为后台线程
  9. thread.start();
  10. }
  11. }

5.线程优先级

  1. void setPriority(int newPriority);//设置新的优先级
  2. int getPriority(); //获取线程的优先级

优先级通常是1-10这之间的整数,JVM不会改变一个线程的优先级,然而这个值是没有保证的,一些JVM可能识别不了10个不同的值。

线程默认优先级为5,Thread类有三个常量:

  1. MAX_PRIORITY,10
  2. MIN_PROORITY,5
  3. NORM_PRIORITY,1

设计多线程应用程序的时候,一定不要依赖优先级,因为调度优先级的操作是没有保障的,只能把优先级的作用作为一种提高程序效率的办法,但是不要依赖

5.线程的同步处理

a.多线程引发的问题

线程之间竞争CPU资源,使得线程无序访问这些资源,最终无法得到正确的结果。

b.同步代码块

Java虚拟机为每一个对象配备了一把锁和一个等候集,这个对象可以使实例对象,也可以是类对象。

通过new创建一个实例对象,从而获得对象的引用,通过java.lang.Class类的forName成员方法获得类对象的引用。
一个类的静态成员方法和静态成员属于类对象,而非静态的东西属于实例对象

同步块的核心代码:

  1. synchronized(synObject){
  2. //关键代码
  3. }

在定义类的时候对里面的关键代码进行控制。其中的关键代码必须获得对象synObject的锁才能执行。当一个线程欲进入该对象的关键代码时,JVM将检查该对象的锁是不是已经被其他线程获得,如果没有,则JVM将把该对象的锁交给当前请求锁的线程,该线程获得锁之后就可以进入关键代码区域。

大致就是这么个意思了,我有一个公共资源的对象,明显,这个对象被给予了一把锁。多个线程访问该对象的时候就要按照规矩来获得锁才能进入访问。

  1. /**
  2. * Created by XQF on 2016/11/30.
  3. */
  4. class User {
  5. private String code;
  6. private int cash;
  7. public User(String code, int cash) {
  8. this.code = code;
  9. this.cash = cash;
  10. }
  11. public String getCode() {
  12. return code;
  13. }
  14. public void setCode(String code) {
  15. this.code = code;
  16. }
  17. public void oper(int x) {
  18. synchronized (this) {//这里
  19. this.cash += x;
  20. System.out.println(Thread.currentThread().getName() + "运行结束,增加 " + x + " ,当前用户余额为: " + cash);
  21. }
  22. }
  23. }
  24. class HerThread extends Thread {
  25. private User u;
  26. private int y;
  27. HerThread(String name, User u, int y) {
  28. super(name);
  29. this.u = u;
  30. this.y = y;
  31. }
  32. public void run() {
  33. u.oper(y);
  34. }
  35. }
  36. public class SynchronizedBlock {
  37. public static void main(String[] args) {
  38. User u = new User("hh", 100);
  39. HerThread t1 = new HerThread("线程a", u, 20);
  40. HerThread t2 = new HerThread("线程b", u, -60);
  41. HerThread t3 = new HerThread("线程c", u, -80);
  42. HerThread t4 = new HerThread("线程d", u, -30);
  43. HerThread t5 = new HerThread("线程e", u, 32);
  44. HerThread t6 = new HerThread("线程f", u, 21);
  45. t1.start();
  46. t2.start();
  47. t3.start();
  48. t4.start();
  49. t5.start();
  50. t6.start();
  51. }
  52. }

这个实例也算是深刻理解同步块儿了,总是觉得这个例子很是巧妙。让线程与用户独立,设计方法真是不错。

c.同步方法

  1. /**
  2. * Created by XQF on 2016/11/30.
  3. */
  4. class UUUUUuser {
  5. private String code;
  6. private int cash;
  7. public UUUUUuser(String code, int cash) {
  8. this.code = code;
  9. this.cash = cash;
  10. }
  11. public String getCode() {
  12. return code;
  13. }
  14. public void setCode(String code) {
  15. this.code = code;
  16. }
  17. public synchronized void oper(int x) {//这里
  18. this.cash += x;
  19. System.out.println(Thread.currentThread().getName() + "运行结束,增加 " + x + " ,当前用户余额为: " + cash);
  20. }
  21. }
  22. class HisThread extends Thread {
  23. private UUUUUuser u;
  24. private int y;
  25. HisThread(String name, UUUUUuser u, int y) {
  26. super(name);
  27. this.u = u;
  28. this.y = y;
  29. }
  30. public void run() {
  31. u.oper(y);
  32. }
  33. }
  34. public class SynchronizedMethod {
  35. public static void main(String[] args) {
  36. UUUUUuser u = new UUUUUuser("hh", 100);
  37. HisThread t1 = new HisThread("线程a", u, 20);
  38. HisThread t2 = new HisThread("线程b", u, -60);
  39. HisThread t3 = new HisThread("线程c", u, -80);
  40. HisThread t4 = new HisThread("线程d", u, -30);
  41. HisThread t5 = new HisThread("线程e", u, 32);
  42. HisThread t6 = new HisThread("线程f", u, 21);
  43. t1.start();
  44. t2.start();
  45. t3.start();
  46. t4.start();
  47. t5.start();
  48. t6.start();
  49. }
  50. }

回到类引用上,我们也可以把类的静态方法设为synchronied,以控制其对类静态成员变量饿的访问

d.线程通信

java提供三个方法来支持线程通信,者三个方法都是Object类的final方法:

  1. final void wait() throws InterrruptedException
  2. final void notify()
  3. final void notifyAll();

这三个方法只能在synchronized关键字的作用范围内使用,并且是在同一个同步问题中搭配使用这三个方法才有实际意义。

notify()和notifyAll()都能唤醒线程,但notify()只能唤醒一个,究竟是哪一个也不确定,但是notifyAll()能唤醒这个对象上等待队列中的所有线程。为了安全起见,我们大多数的时候调用notifyAll(),除非你明确的知道要唤醒哪一个线程

  1. /**
  2. * Created by XQF on 2016/12/1.
  3. */
  4. //在共享资源里面进行同步设置
  5. class ShareData {
  6. private char c;
  7. private boolean writeAble = true;//通知变量,这里是通知是否可写
  8. //将setter设为同步方法,一次只能一个线程获得对象锁
  9. public synchronized void setShareData(char c) {
  10. //拿到对象锁,但是通知变量声明此时不可写,于是wait()释放锁,本线程进入等待序列,(不可生产)
  11. if (!writeAble) {
  12. try {
  13. wait();
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. //要是通知变量表示此时可以写(可生产)就生产
  19. this.c = c;
  20. //writeAble由初始的true变false,表示已经有产品,可以通知消费者消费了,有了产品了就不用再生产了
  21. writeAble = false;
  22. //通知刚进入等待序列要消费的那个线程
  23. notify();
  24. }
  25. public synchronized char getShareData() {
  26. //要是没有产品(这里表示可写),于是wait()释放锁,本线程进入等待序列,(没有产品,不可消费)
  27. if (writeAble) {
  28. try {
  29. wait();
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. }
  34. //要是通知变量表示此时可以写(可消费)
  35. //消费了,通知生产者可以继续生产了
  36. writeAble = true;
  37. //通知生产者线程生产了,没饭吃了
  38. notify();
  39. return c;
  40. }
  41. }
  42. class Producer extends Thread {
  43. private ShareData s;
  44. Producer(ShareData s) {
  45. this.s = s;
  46. }
  47. public void run() {
  48. for (char c = 'A'; c <= 'Z'; c++) {
  49. try {
  50. Thread.sleep((int) Math.random() * 400);
  51. } catch (InterruptedException e) {
  52. e.printStackTrace();
  53. }
  54. s.setShareData(c);
  55. System.out.println(c + " produced by Producer!");
  56. }
  57. }
  58. }
  59. class Consumer extends Thread {
  60. ShareData s;
  61. Consumer(ShareData s) {
  62. this.s = s;
  63. }
  64. public void run() {
  65. char c;
  66. do {
  67. try {
  68. Thread.sleep((int) Math.random() * 400);
  69. } catch (InterruptedException e) {
  70. e.printStackTrace();
  71. }
  72. c = s.getShareData();
  73. System.out.println(c + " consumed by Consumer!");
  74. } while (c == 'Z');
  75. }
  76. }
  77. public class ThreadCommunicationTest {
  78. public static void main(String[] args) {
  79. ShareData s = new ShareData();
  80. new Consumer(s).start();
  81. new Producer(s).start();
  82. }
  83. }

e.死锁

死锁就是多个线程同时被阻塞,他们中的一个或全部都在等待某个资源的释放。由于被无限期的阻塞,因此程序不能正常执行。

死锁时,一个线程等待另一个线程释放资源,而同时另一个线程又在等待第一个线程释放资源。

下面这个实例是绝对的经典:
程序中有两个共享对象,每一个线程都需要获得这两个对象的锁后才可以执行。于是我们让线程1拿到obj1的锁后睡一会儿让线程2拿到了obj2的锁。于是线程1等待线程2释放obj2的锁,而线程2也在等待线程1释放obj1的锁,。,。,形成了死锁。

  1. /**
  2. * Created by XQF on 2016/12/1.
  3. */
  4. class MyThread1 implements Runnable {
  5. public void run() {
  6. synchronized (DeadLockExample.obj1) {
  7. System.out.println("线程1进入obj1同步代码块");
  8. //进入之后让他休眠10毫秒,将运行权力交给其他线程,例如线程2,,。专门放水的
  9. try {
  10. Thread.sleep(10);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. synchronized (DeadLockExample.obj2) {
  15. System.out.println("线程1进入obj2同步代码块");
  16. }
  17. }
  18. }
  19. }
  20. class MyThread2 implements Runnable {
  21. public void run() {
  22. synchronized (DeadLockExample.obj2) {
  23. System.out.println("线程2进入obj2同步代码块");
  24. //进入之后让他休眠10毫秒,将运行权力交给其他线程,例如线程2,,。专门放水的
  25. try {
  26. Thread.sleep(10);
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. synchronized (DeadLockExample.obj1) {
  31. System.out.println("线程2进入obj1同步代码块");
  32. }
  33. }
  34. }
  35. }
  36. public class DeadLockExample {
  37. public static Object obj1 = new Object();
  38. public static Object obj2 = new Object();
  39. public static void main(String[] args) {
  40. Thread t1 = new Thread(new MyThread1());
  41. Thread t2 = new Thread(new MyThread2());
  42. t1.start();
  43. t2.start();
  44. }
  45. }

国际惯例:总结

知识点应该是都理解了,设计模式还是不是很懂。这里主要是将我们的共享资源与线程进行了绑定,在线程内部访问资源的模式。由于之前对共享资源内部的设定。就会按照我们的希望执行。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注