(JavaSE基础)八、Java 的多线程和并发库

1. 多线程的创建方式

(1)继承 Thread 类:但 Thread 本质上也是实现了 Runnable 接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过 Thread 类的 start()实例方法。start()方法是一个 native 方法,它将启动一个新线程,并执行 run()方法。这种方式实现多线程很简单,通过自己的类直接 extend Thread,并复写 run()方法,就可以启动新线程并执行自己定义的 run()方法。例如:继承 Thread 类实现多线程,并在合适的地方启动线程

1.public class MyThread extends Thread {
2. public void run() {
3. System.out.println("MyThread.run()");
4. } 
5.}
6.MyThread myThread1 = new MyThread();
7.MyThread myThread2 = new MyThread();
8.myThread1.start();
9.myThread2.start();

(2)实现 Runnable 接口的方式实现多线程,并且实例化 Thread,传入自己的 Thread 实例,调用 run( )方法

1.public class MyThread implements Runnable {
2. public void run() {
3. System.out.println("MyThread.run()");
4. }
5.}
6.MyThread myThread = new MyThread();
7.Thread thread = new Thread(myThread);
8.thread.start();

(3)、使用 ExecutorService、Callable、Future 实现有返回结果的多线程:ExecutorService、Callable、Future这 个 对 象 实 际 上 都 是 属 于 Executor 框 架 中 的 功 能 类 。 想 要 详 细 了 解 Executor 框 架 的 可 以 访 问http://www.javaeye.com/topic/366591 ,这里面对该框架做了很详细的解释。返回结果的线程是在 JDK1.5 中引入的新特征,确实很实用,有了这种特征我就不需要再为了得到返回值而大费周折了,而且即便实现了也可能漏洞百出。可返回值的任务必须实现 Callable 接口,类似的,无返回值的任务必须 Runnable 接口。执行 Callable 任务后,可以获取一个 Future 的对象,在该对象上调用 get 就可以获取到 Callable 任务返回的 Object 了,再结合线程池接口ExecutorService 就可以实现传说中有返回结果的多线程了。下面提供了一个完整的有返回结果的多线程测试例子,在JDK1.5 下验证过没问题可以直接使用。代码如下:

1.import java.util.concurrent.*;
2.import java.util.Date;
3.import java.util.List;
4.import java.util.ArrayList;
5.
6./**
7.* 有返回值的线程
8.*/ 
9.@SuppressWarnings("unchecked")
10.public class Test {
11.public static void main(String[] args) throws ExecutionException,
12. InterruptedException {
13. System.out.println("----程序开始运行----");
14. Date date1 = new Date();
15.
16. int taskSize = 5;
17. // 创建一个线程池
18. ExecutorService pool = Executors.newFixedThreadPool(taskSize);
19. // 创建多个有返回值的任务
20. List list = new ArrayList();
21. for (int i = 0; i < taskSize; i++) {
22. Callable c = new MyCallable(i + " ");
23. // 执行任务并获取 Future 对象
24. Future f = pool.submit(c);
25. // System.out.println(">>>" + f.get().toString());
26. list.add(f);
27. }
28. // 关闭线程池
29. pool.shutdown();
30.
31. // 获取所有并发任务的运行结果
32. for (Future f : list) {
33. // 从 Future 对象上获取任务的返回值,并输出到控制台
34. System.out.println(">>>" + f.get().toString());
35. }
36.
37. Date date2 = new Date();
38. System.out.println("----程序结束运行----,程序运行时间【"
39. + (date2.getTime() - date1.getTime()) + "毫秒】");
40.}
41.}
42.
43.class MyCallable implements Callable {
44.private String taskNum;
45.
46.MyCallable(String taskNum) {
47. this.taskNum = taskNum;
48.}
49.
50.public Object call() throws Exception {
51. System.out.println(">>>" + taskNum + "任务启动"); 
52. Date dateTmp1 = new Date();
53. Thread.sleep(1000);
54. Date dateTmp2 = new Date();
55. long time = dateTmp2.getTime() - dateTmp1.getTime();
56. System.out.println(">>>" + taskNum + "任务终止");
57. return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";
58.}
59.} 
 
 

2. 在 java 中 wait 和 sleep 方法的不同?

最大的不同是在等待时 wait 会释放锁,而 sleep 一直持有锁。wait 通常被用于线程间交互,sleep 通常被用于暂停执行。

3. synchronized 和 volatile 关键字的作用

一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是
立即可见的。
2)禁止进行指令重排序。

volatile 本质是在告诉 jvm 当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;
synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。

1.volatile 仅能使用在变量级别;
synchronized 则可以使用在变量、方法、和类级别的
2.volatile 仅能实现变量的修改可见性,并不能保证原子性;
synchronized 则可以保证变量的修改可见性和原子性
3.volatile 不会造成线程的阻塞;
synchronized 可能会造成线程的阻塞。
4.volatile 标记的变量不会被编译器优化;
synchronized 标记的变量可以被编译器优化

4. 分析线程并发访问代码解释原因

1. public class Counter {
2. private volatile int count = 0;
3. public void inc(){
4. try {
5. Thread.sleep(3);
6. } catch (InterruptedException e) {
7. e.printStackTrace();
8. }
9. count++;
10. }
11. @Override
12. public String toString() {
13. return "[count=" + count + "]";
14. }
15. }
16. //---------------------------------华丽的分割线-----------------------------
17. public class VolatileTest {
18. public static void main(String[] args) {
19. final Counter counter = new Counter();
20. for(int i=0;i<1000;i++){
21. new Thread(new Runnable() {
22. @Override
23. public void run() {
24. counter.inc();
25. }
26. }).start();
27. }
28. System.out.println(counter);
29. }
30. }

上面的代码执行完后输出的结果确定为 1000 吗?
答案是不一定,或者不等于 1000。这是为什么吗?
在 java 的内存模型中每一个线程运行时都有一个线程栈,线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的具体值 load 到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。也就是说上面主函数中开启了 1000 个子线程,每个线程都有一个变量副本,每个线程修改变量只是临时修改了自己的副本,当线程结束时再将修改的值写入在主内存中,这样就出现了线程安全问题。因此结果就不可能等于 1000了,一般都会小于 1000。

上面的解释用一张图表示如下:


image.png

5. 什么是线程池,如何使用?

线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用 new 线程而是直接去池中拿线程即可,节省了开辟子线程的时间,提高的代码执行效率。
在 JDK 的 java.util.concurrent.Executors 中提供了生成多种线程池的静态方法。

1. ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
2. ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(4);
3. ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(4);
4. ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();

然后调用他们的 execute 方法即可。

6. 常用的线程池有哪些?

  • newSingleThreadExecutor:创建一个单线程的线程池,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
  • newFixedThreadPool:创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
  • newCachedThreadPool:创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小。
  • newScheduledThreadPool:创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。

7. 请叙述一下您对线程池的理解?

(如果问到了这样的问题,可以展开的说一下线程池如何用、线程池的好处、线程池的启动策略)合理利用线程池能够带来三个好处。

  • 第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

8. 线程池的启动策略?

官方对线程池的执行过程描述如下:

26. /*
27. * Proceed in 3 steps:
28. *
29. * 1. If fewer than corePoolSize threads are running, try to
30. * start a new thread with the given command as its first
31. * task. The call to addWorker atomically checks runState and
32. * workerCount, and so prevents false alarms that would add
33. * threads when it shouldn't, by returning false.
34. *
35. * 2. If a task can be successfully queued, then we still need
36. * to double-check whether we should have added a thread
37. * (because existing ones died since last checking) or that
38. * the pool shut down since entry into this method. So we
39. * recheck state and if necessary roll back the enqueuing if
40. * stopped, or start a new thread if there are none.
41. *
42. * 3. If we cannot queue task, then we try to add a new
43. * thread. If it fails, we know we are shut down or saturated
44. * and so reject the task.
45. */
  1. 线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
  2. 当调用 execute() 方法添加一个任务时,线程池会做如下判断:
    a. 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
    b. 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。
    c. 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这
    个任务;
    d. 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告
    诉调用者“我不能再接受任务了”。
  3. 当一个线程完成任务时,它会从队列中取下一个任务来执行。
  4. 当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于
    corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。

9. 如何控制某个方法允许并发访问线程的个数?

1. package com.yange;
2.
3. import java.util.concurrent.Semaphore;
4. /**
5. *
6. * @author wzy 2015-11-30
7. *
8. */
9. public class SemaphoreTest {
10. /*
11. * permits the initial number of permits available. This value may be negative,
12. in which case releases must occur before any acquires will be granted.
13. fair true if this semaphore will guarantee first-in first-out granting of
14. permits under contention, else false
15. */
16. static Semaphore semaphore = new Semaphore(5,true);
17. public static void main(String[] args) {
18. for(int i=0;i<100;i++){
19. new Thread(new Runnable() {
20.
21. @Override
22. public void run() {
23. test();
24. }
25. }).start();
26. }
27.
28. }
29.
30. public static void test(){
31. try {
32. //申请一个请求
33. semaphore.acquire();
34. } catch (InterruptedException e1) {
35. e1.printStackTrace();
36. }
37. System.out.println(Thread.currentThread().getName()+"进来了");
38. try {
39. Thread.sleep(1000);
40. } catch (InterruptedException e) {
41. e.printStackTrace();
42. }
43. System.out.println(Thread.currentThread().getName()+"走了");
44. //释放一个请求
45. semaphore.release();
46. }
47. }

可以使用 Semaphore 控制,第 16 行的构造函数创建了一个 Semaphore 对象,并且初始化了 5 个信号。这样的效果是控件 test 方法最多只能有 5 个线程并发访问,对于 5 个线程时就排队等待,走一个来一下。第 33 行,请求一个信号(消费一个信号),如果信号被用完了则等待,第 45 行释放一个信号,释放的信号新的线程就可以使用了。

10. 三个线程 a、b、c 并发运行,b,c 需要 a 线程的数据怎么实现

根据问题的描述,我将问题用以下代码演示,ThreadA、ThreadB、ThreadC,ThreadA 用于初始化数据 num,只有当 num 初始化完成之后再让 ThreadB 和 ThreadC 获取到初始化后的变量 num。

分析过程如下:
考虑到多线程的不确定性,因此我们不能确保 ThreadA 就一定先于 ThreadB 和 ThreadC 前执行,就算 ThreadA先执行了,我们也无法保证 ThreadA 什么时候才能将变量 num 给初始化完成。因此我们必须让 ThreadB 和 ThreadC去等待 ThreadA 完成任何后发出的消息。

现在需要解决两个难题,一是让 ThreadB 和 ThreadC 等待 ThreadA 先执行完,二是 ThreadA 执行完之后给
ThreadB 和 ThreadC 发送消息。
解决上面的难题我能想到的两种方案,一是使用纯 Java API 的 Semaphore 类来控制线程的等待和释放,二是使用 Android 提供的 Handler 消息机制。

1. package com.example;
2. /**
3. * 三个线程 a、b、c 并发运行,b,c 需要 a 线程的数据怎么实现
4. *
5. */
6. public class ThreadCommunication {
7. private static int num;//定义一个变量作为数据
8.
9. public static void main(String[] args) {
10.
11. Thread threadA = new Thread(new Runnable() {
12.
13. @Override
14. public void run() {
15. try {
16. //模拟耗时操作之后初始化变量 num
17. Thread.sleep(1000);
18. num = 1;
19.
20. } catch (InterruptedException e) {
21. e.printStackTrace();
22. }
23. }
24. });
25. Thread threadB = new Thread(new Runnable() {
26.
27. @Override
28. public void run() {
29. System.out.println(Thread.currentThread().getName()+"获取到 num 的值为:"+num);
30. }
31. });
32. Thread threadC = new Thread(new Runnable() {
33.
34. @Override
35. public void run() {
36. System.out.println(Thread.currentThread().getName()+"获取到 num 的值为:"+num);
37. }
38. });
39. //同时开启 3 个线程
40. threadA.start();
41. threadB.start();
42. threadC.start();
43.
44. }
45. }
46.

解决方案一:

1. public class ThreadCommunication {
2. private static int num;
3. /**
4. * 定义一个信号量,该类内部维持了多个线程锁,可以阻塞多个线程,释放多个线程,
5. 线程的阻塞和释放是通过 permit 概念来实现的
6. * 线程通过 semaphore.acquire()方法获取 permit,如果当前 semaphore 有 permit 则分配给该线程,
7. 如果没有则阻塞该线程直到 semaphore
8. * 调用 release()方法释放 permit。
9. * 构造函数中参数:permit(允许) 个数,
10. */
11. private static Semaphore semaphore = new Semaphore(0);
12. public static void main(String[] args) {
13.
14. Thread threadA = new Thread(new Runnable() {
15.
16. @Override
17. public void run() {
18. try {
19. //模拟耗时操作之后初始化变量 num
20. Thread.sleep(1000);
21. num = 1;
22. //初始化完参数后释放两个 permit
23. semaphore.release(2);
24.
25. } catch (InterruptedException e) {
26. e.printStackTrace();
27. }
28. }
29. });
30. Thread threadB = new Thread(new Runnable() {
31.
32. @Override
33. public void run() {
34. try {
35. //获取 permit,如果 semaphore 没有可用的 permit 则等待,如果有则消耗一个
36. semaphore.acquire();
37. } catch (InterruptedException e) {
38. e.printStackTrace();
39. }
40. System.out.println(Thread.currentThread().getName()+"获取到 num 的值为:"+num);
41. }
42. });
43. Thread threadC = new Thread(new Runnable() {
44.
45. @Override
46. public void run() {
47. try {
48. //获取 permit,如果 semaphore 没有可用的 permit 则等待,如果有则消耗一个
49. semaphore.acquire();
50. } catch (InterruptedException e) {
51. e.printStackTrace();
52. }
53. System.out.println(Thread.currentThread().getName()+"获取到 num 的值为:"+num);
54. }
55. });
56. //同时开启 3 个线程
57. threadA.start();
58. threadB.start();
59. threadC.start();
60.
61. }
62. }

11. 同一个类中的 2 个方法都加了同步锁,多个线程能同时访问同一个类中的这两个方法吗?

这个问题需要考虑到Lock与synchronized 两种实现锁的不同情形。因为这种情况下使用Lock 和synchronized
会有截然不同的结果。Lock 可以让等待锁的线程响应中断,Lock 获取锁,之后需要释放锁。如下代码,多个线程不可访问同一个类中的 2 个加了 Lock 锁的方法。

63. package com;
64. import java.util.concurrent.locks.Lock;
65. import java.util.concurrent.locks.ReentrantLock;
66. public class qq {
67.
68. private int count = 0;
69. private Lock lock = new ReentrantLock();//设置 lock 锁
70. //方法 1
71. public Runnable run1 = new Runnable(){
72. public void run() {
73. lock.lock(); //加锁
74. while(count < 1000) {
75. try {
76. //打印是否执行该方法
77. System.out.println(Thread.currentThread().getName() + " run1: "+count++);
78. } catch (Exception e) {
79. e.printStackTrace();
80. }
81. }
82. }
83. lock.unlock();
84. }};
85. //方法 2
86. public Runnable run2 = new Runnable(){
87. public void run() {
88. lock.lock();
89. while(count < 1000) {
90. try {
91. System.out.println(Thread.currentThread().getName() +
92. " run2: "+count++);
93. } catch (Exception e) {
94. e.printStackTrace();
95. }
96. }
97. lock.unlock();
98. }};
99.
100.
101.
102. public static void main(String[] args) throws InterruptedException {
103. qq t = new qq(); //创建一个对象
104. new Thread(t.run1).start();//获取该对象的方法 1
105.
106. new Thread(t.run2).start();//获取该对象的方法 2
107. }
108. }
结果是:
Thread-0 run1: 0
Thread-0 run1: 1
Thread-0 run1: 2
Thread-0 run1: 3
Thread-0 run1: 4
Thread-0 run1: 5
Thread-0 run1: 6
........

而 synchronized 却不行,使用 synchronized 时,当我们访问同一个类对象的时候,是同一把锁,所以可以访问该对象的其他 synchronized 方法。代码如下:

1.package com;
2.import java.util.concurrent.locks.Lock;
3.import java.util.concurrent.locks.ReentrantLock;
4.public class qq {
5. private int count = 0;
6. private Lock lock = new ReentrantLock(); 
7. public Runnable run1 = new Runnable(){
8. public void run() {
9. synchronized(this) { //设置关键字 synchronized,以当前类为锁
10. while(count < 1000) {
11. try {
12. //打印是否执行该方法
13. System.out.println(Thread.currentThread().getName() + " run1: "+count++);
14. } catch (Exception e) {
15. e.printStackTrace();
16. }
17. }
18. }
19. }};
20. public Runnable run2 = new Runnable(){
21. public void run() {
22. synchronized(this) {
23. while(count < 1000) {
24. try {
25. System.out.println(Thread.currentThread().getName()
26. + " run2: "+count++);
27. } catch (Exception e) {
28. e.printStackTrace();
29. }
30. }
31. }
32. }};
33. public static void main(String[] args) throws InterruptedException {
34. qq t = new qq(); //创建一个对象
35. new Thread(t.run1).start(); //获取该对象的方法 1
36. new Thread(t.run2).start(); //获取该对象的方法 2
37. }
38.}
结果为:
Thread-1 run2: 0
Thread-1 run2: 1
Thread-1 run2: 2
Thread-0 run1: 0
Thread-0 run1: 4 Thread-0 run1: 5 Thread-0 run1: 6
 ......

12. 什么情况下导致线程死锁,遇到线程死锁该怎么解决?

11.1 死锁的定义:所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。
11.2 死锁产生的必要条件:
  • 互斥条件:线程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某 资源仅为一个线程
    所占有。此时若有其他线程请求该资源,则请求线程只能等待。
  • 不剥夺条件:线程所获得的资源在未使用完毕之前,不能被其他线程强行夺走,即只能由获得该资源的线程
    自己来释放(只能是主动释放)。
  • 请求和保持条件:线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有,
    此时请求进程被阻塞,但对自己已获得的资源保持不放。
  • 循环等待条件:存在一种线程资源的循环等待链,链中每一个线程已获得的资源同时被链中下一个线程所请
    求。即存在一个处于等待状态的线程集合{Pl, P2, ..., pn},其中 Pi 等待的资源被 P(i+1)占有(i=0, 1, ..., n-1),
    Pn 等待的资源被 P0 占有,如图 2-15 所示。


    image.png
11.3 产生死锁的一个例子
1.package itheima.com;
2./**
3.* 一个简单的死锁类
4.* 当 DeadLock 类的对象 flag==1 时(td1),先锁定 o1,睡眠 500 毫秒
5.* 而 td1 在睡眠的时候另一个 flag==0 的对象(td2)线程启动,先锁定 o2,睡眠 500 毫秒 
6.* td1 睡眠结束后需要锁定 o2 才能继续执行,而此时 o2 已被 td2 锁定;
7.* td2 睡眠结束后需要锁定 o1 才能继续执行,而此时 o1 已被 td1 锁定;
8.* td1、td2 相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁。
9.*/
10.public class DeadLock implements Runnable {
11. public int flag = 1;
12. //静态对象是类的所有对象共享的
13. private static Object o1 = new Object(), o2 = new Object();
14. public void run() {
15. System.out.println("flag=" + flag);
16. if (flag == 1) {
17. synchronized (o1) {
18. try {
19. Thread.sleep(500);
20. } catch (Exception e) {
21. e.printStackTrace();
22. }
23. synchronized (o2) {
24. System.out.println("1");
25. }
26. }
27. }
28. if (flag == 0) {
29. synchronized (o2) {
30. try {
31. Thread.sleep(500);
32. } catch (Exception e) {
33. e.printStackTrace();
34. }
35. synchronized (o1) {
36. System.out.println("0");
37. }
38. }
39. }
40. }
41. public static void main(String[] args) {
42. DeadLock td1 = new DeadLock();
43. DeadLock td2 = new DeadLock();
44. td1.flag = 1;
45. td2.flag = 0;
46. //td1,td2 都处于可执行状态,但 JVM 线程调度先执行哪个线程是不确定的。
47. //td2 的 run()可能在 td1 的 run()之前运行
48. new Thread(td1).start(); 
49. new Thread(td2).start();
50. }
51.}
11.4 如何避免死锁

在有些情况下死锁是可以避免的。两种用于避免死锁的技术:
1)加锁顺序(线程按照一定的顺序加锁)

1.package itheima.com;
2.public class DeadLock {
3. public int flag = 1;
4. //静态对象是类的所有对象共享的
5. private static Object o1 = new Object(), o2 = new Object();
6. public void money(int flag) {
7. this.flag=flag;
8. if( flag ==1){
9. synchronized (o1) {
10. try {
11. Thread.sleep(500);
12. } catch (Exception e) {
13. e.printStackTrace();
14. }
15. synchronized (o2) {
16. System.out.println("当前的线程是"+
17. Thread.currentThread().getName()+" "+"flag 的值"+"1");
18. }
19. }
20. }
21. if(flag ==0){
22. synchronized (o2) {
23. try {
24. Thread.sleep(500);
25. } catch (Exception e) {
26. e.printStackTrace();
27. }
28. synchronized (o1) {
29. System.out.println("当前的线程是"+
30. Thread.currentThread().getName()+" "+"flag 的值"+"0");
31. }
32. }
33. }
34. } 
35.
36. public static void main(String[] args) {
37. final DeadLock td1 = new DeadLock();
38. final DeadLock td2 = new DeadLock();
39. td1.flag = 1;
40. td2.flag = 0;
41. //td1,td2 都处于可执行状态,但 JVM 线程调度先执行哪个线程是不确定的。
42. //td2 的 run()可能在 td1 的 run()之前运行
43. final Thread t1=new Thread(new Runnable(){
44. public void run() {
45. td1.flag = 1;
46. td1.money(1);
47. }
48. });
49. t1.start();
50. Thread t2= new Thread(new Runnable(){
51. public void run() {
52. // TODO Auto-generated method stub
53. try {
54. //让 t2 等待 t1 执行完
55. t1.join();//核心代码,让 t1 执行完后 t2 才会执行
56. } catch (InterruptedException e) {
57. // TODO Auto-generated catch block
58. e.printStackTrace();
59. }
60. td2.flag = 0;
61. td1.money(0);
62. }
63. });
64. t2.start();
65. }
66.}

结果:

当前的线程是 Thread-0 flag 的值 1
当前的线程是 Thread-1 flag 的值 0

2)加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)

1. package itheima.com;
2.import java.util.concurrent.TimeUnit;
3.import java.util.concurrent.locks.Lock;
4.import java.util.concurrent.locks.ReentrantLock;
5.public class DeadLock { 
6. public int flag = 1;
7. //静态对象是类的所有对象共享的
8. private static Object o1 = new Object(), o2 = new Object();
9. public void money(int flag) throws InterruptedException {
10. this.flag=flag;
11. if( flag ==1){
12. synchronized (o1) {
13. Thread.sleep(500);
14. synchronized (o2) {
15. System.out.println("当前的线程是"+
16. Thread.currentThread().getName()+" "+"flag 的值"+"1");
17. }
18. }
19. }
20. if(flag ==0){
21. synchronized (o2) {
22. Thread.sleep(500);
23. synchronized (o1) {
24. System.out.println("当前的线程是"+
25. Thread.currentThread().getName()+" "+"flag 的值"+"0");
26. }
27. }
28. }
29. }
30.
31. public static void main(String[] args) {
32. final Lock lock = new ReentrantLock();
33. final DeadLock td1 = new DeadLock();
34. final DeadLock td2 = new DeadLock();
35. td1.flag = 1;
36. td2.flag = 0;
37. //td1,td2 都处于可执行状态,但 JVM 线程调度先执行哪个线程是不确定的。
38. //td2 的 run()可能在 td1 的 run()之前运行
39.
40. final Thread t1=new Thread(new Runnable(){
41. public void run() {
42. // TODO Auto-generated method stub
43. String tName = Thread.currentThread().getName();
44.
45. td1.flag = 1;
46. try {
47. //获取不到锁,就等 5 秒,如果 5 秒后还是获取不到就返回 false
48. if (lock.tryLock(5000, TimeUnit.MILLISECONDS)) { 
49. System.out.println(tName + "获取到锁!");
50. } else {
51. System.out.println(tName + "获取不到锁!");
52. return;
53. }
54. } catch (Exception e) {
55. e.printStackTrace();
56. }
57.
58. try {
59. td1.money(1);
60. } catch (Exception e) {
61. System.out.println(tName + "出错了!!!");
62. } finally {
63. System.out.println("当前的线程是"+Thread.currentThread().getName()+"释放锁!!
");
64. lock.unlock();
65. }
66. }
67. });
68. t1.start();
69. Thread t2= new Thread(new Runnable(){
70. public void run() {
71. String tName = Thread.currentThread().getName();
72. // TODO Auto-generated method stub
73. td1.flag = 1;
74. try {
75. //获取不到锁,就等 5 秒,如果 5 秒后还是获取不到就返回 false
76. if (lock.tryLock(5000, TimeUnit.MILLISECONDS)) {
77. System.out.println(tName + "获取到锁!");
78. } else {
79. System.out.println(tName + "获取不到锁!");
80. return;
81. }
82. } catch (Exception e) {
83. e.printStackTrace();
84. }
85. try {
86. td2.money(0);
87. } catch (Exception e) {
88. System.out.println(tName + "出错了!!!");
89. } finally {
90. System.out.println("当前的线程是"+Thread.currentThread().getName()+"释放锁!!
");
91. lock.unlock();
92. }
93. }
94. });
95. t2.start();
96. }
97.}

打印结果:

Thread-0 获取到锁!
当前的线程是 Thread-0 flag 的值 1
当前的线程是 Thread-0 释放锁!!
Thread-1 获取到锁!
当前的线程是 Thread-1 flag 的值 0
当前的线程是 Thread-1 释放锁!!

13. Java 中多线程间的通信怎么实现?

线程通信的方式:

  1. 共享变量
    线程间通信可以通过发送信号,发送信号的一个简单方式是在共享对象的变量里设置信号值。线程 A 在一个
    同步块里设置 boolean 型成员变量 hasDataToProcess 为 true,线程 B 也在同步块里读取 hasDataToProcess这个成员变量。这个简单的例子使用了一个持有信号的对象,并提供了 set 和 get 方法:
1.package itheima.com;
2.public class MySignal{
3. //共享的变量
4. private boolean hasDataToProcess=false;
5. //取值
6. public boolean getHasDataToProcess() {
7. return hasDataToProcess;
8. }
9. //存值
10. public void setHasDataToProcess(boolean hasDataToProcess) {
11. this.hasDataToProcess = hasDataToProcess;
12. }
13. public static void main(String[] args){
14. //同一个对象
15. final MySignal my=new MySignal();
16. //线程 1 设置 hasDataToProcess 值为 true
17. final Thread t1=new Thread(new Runnable(){
18. public void run() {
19. my.setHasDataToProcess(true);
20. }
21. });
22. t1.start();
23. //线程 2 取这个值 hasDataToProcess
24. Thread t2=new Thread(new Runnable(){
25. public void run() {
26. try {
27. //等待线程 1 完成然后取值
28. t1.join();
29. } catch (InterruptedException e) {
30. e.printStackTrace();
31. }
32. my.getHasDataToProcess();
33. System.out.println("t1 改变以后的值:" + my.isHasDataToProcess());
34. }
35. });
36. t2.start();
37.}
38.}

结果:

t1 改变以后的值:true

2.wait/notify 机制
以资源为例,生产者生产一个资源,通知消费者就消费掉一个资源,生产者继续生产资源,消费者消费资源,以此循环。代码如下:

1.package itheima.com;
2.//资源类
3. class Resource{
4. private String name;
5. private int count=1;
6. private boolean flag=false;
7. public synchronized void set(String name){
8. //生产资源
9. while(flag) {
10. try{
11. //线程等待。消费者消费资源
12. wait();
13. }catch(Exception e){}
14. }
15. this.name=name+"---"+count++;
16. System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
17. flag=true;
18. //唤醒等待中的消费者
19. this.notifyAll();
20. }
21. public synchronized void out(){
22. //消费资源
23. while(!flag) {
24. //线程等待,生产者生产资源
25. try{wait();}catch(Exception e){}
26. }
27. System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
28. flag=false;
29. //唤醒生产者,生产资源
30. this.notifyAll();
31. }
32.}
33. //生产者
34. class Producer implements Runnable{
35. private Resource res;
36. Producer(Resource res){
37. this.res=res;
38. }
39. //生产者生产资源
40. public void run(){
41. while(true){
42. res.set("商品");
43. }
44. }
45. }
46. //消费者消费资源
47. class Consumer implements Runnable{
48. private Resource res;
49. Consumer(Resource res){
50. this.res=res;
51. } 
52. public void run(){
53. while(true){
54. res.out();
55. }
56. }
57. }
58.public class ProducerConsumerDemo{
59. public static void main(String[] args){
60. Resource r=new Resource();
61. Producer pro=new Producer(r);
62. Consumer con=new Consumer(r);
63. Thread t1=new Thread(pro);
64. Thread t2=new Thread(con);
65. t1.start();
66. t2.start();
67. }
68.} 

14. 线程和进程的区别

  • 进程:具有一定独立功能的程序关于某个数据集合上的一次运行活动,是操作系统进行资源分配和调度的一个独立单位。
  • 线程:是进程的一个实体,是 cpu 调度和分派的基本单位,是比进程更小的可以独立运行的基本单位。

特点:线程的划分尺度小于进程,这使多线程程序拥有高并发性,进程在运行时各自内存单元相互独立,线程之间内存共享,这使多线程编程可以拥有更好的性能和用户体验
注意:多线程编程对于其它程序是不友好的,占据大量 cpu 资源。

15. 请说出同步线程及线程调度相关的方法?

wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理 InterruptedException 异常;
notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关;
notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
注意:java 5 通过 Lock 接口提供了显示的锁机制,Lock 接口中定义了加锁(lock()方法)和解锁(unLock()方法),增强了多线程编程的灵活性及对线程的协调

16. 启动一个线程是调用 run()方法还是 start()方法?

启动一个线程是调用 start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由 JVM 调度并执行,这并不意味着线程就会立即运行。
run()方法是线程启动后要进行回调(callback)的方法。

你可能感兴趣的:((JavaSE基础)八、Java 的多线程和并发库)