多线程(二)JavaSE基础面试

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

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

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

53. package com.example;
54. /**
55. * 三个线程 a、b、c 并发运行,b,c 需要 a 线程的数据怎么实现(上海 3 期学员提供)
56. *
57. */
58. public class ThreadCommunication {
59. private static int num;//定义一个变量作为数据
8.
60. public static void main(String[] args) {
10.
61. Thread threadA = new Thread(new Runnable() {
12.
62. @Override
63. public void run() {
64. try {
65. 16. //模拟耗时操作之后初始化变量 num
66. Thread.sleep(1000);
67. num = 1;
19.
68. } catch (InterruptedException e) {
69. e.printStackTrace();
70. }
71. }
72. });
73. Thread threadB = new Thread(new Runnable() {
26.
74. @Override
75. public void run() {
76. System.out.println(Thread.currentThread().getName()+"获取到 num 的值为:"+num);
77. }
78. });
79. Thread threadC = new Thread(new Runnable() {
33.
80. @Override
81. public void run() {
82. System.out.println(Thread.currentThread().getName()+"获取到 num 的值为:"+num);
83. }
84. });
85. //同时开启 3 个线程
86. threadA.start();
87. threadB.start();
88. threadC.start();
43.
89. }
90. }
46.
解决方案一:
91. public class ThreadCommunication {
92. private static int num;
93. /**
94. * 定义一个信号量,该类内部维持了多个线程锁,可以阻塞多个线程,释放多个线程,
95. 线程的阻塞和释放是通过 permit 概念来实现的
96. * 线程通过 semaphore.acquire()方法获取 permit,如果当前 semaphore 有 permit 则分配给该线程,
97. 如果没有则阻塞该线程直到 semaphore
98. * 调用 release()方法释放 permit。
99. * 构造函数中参数:permit(允许) 个数,
100. */
101.11. private static Semaphore semaphore = new Semaphore(0);
101. public static void main(String[] args) {
13.
102. Thread threadA = new Thread(new Runnable() {
15.
103. @Override
104. public void run() {
105. try {
106. //模拟耗时操作之后初始化变量 num
107. Thread.sleep(1000);
108. num = 1;
109. //初始化完参数后释放两个 permit
110. semaphore.release(2);
24.
111. } catch (InterruptedException e) {
112. e.printStackTrace();
113. }
114. }
115. });
116. Thread threadB = new Thread(new Runnable() {
31.
117. @Override
118. public void run() {
119. try {
120. //获取 permit,如果 semaphore 没有可用的 permit 则等待,如果有则消耗一个
121. semaphore.acquire();
122. } catch (InterruptedException e) {
123. e.printStackTrace();
124. }
125. System.out.println(Thread.currentThread().getName()+"获取到 num 的值为:"+num);
126. }
127. });
128. Thread threadC = new Thread(new Runnable() {
44.
129. @Override
130. public void run() {
131. try {
132. //获取 permit,如果 semaphore 没有可用的 permit 则等待,如果有则消耗一个
133. semaphore.acquire();
134. } catch (InterruptedException e) {
135. e.printStackTrace();
136. }
137. System.out.println(Thread.currentThread().getName()+"获取到 num 的值为:"+num); 
138.54. }
138. });
139. //同时开启 3 个线程
140. threadA.start();
141. threadB.start();
142. threadC.start();
60.
143. }
144. }
145. 同一个类中的 2 个方法都加了同步锁,多个线程能同时访问同一个类中的这两
个方法吗?(2017-2-24)
这个问题需要考虑到Lock与synchronized 两种实现锁的不同情形。因为这种情况下使用Lock 和synchronized 
会有截然不同的结果。Lock 可以让等待锁的线程响应中断,Lock 获取锁,之后需要释放锁。如下代码,多个线程不可
访问同一个类中的 2 个加了 Lock 锁的方法。
146. package com;
147. import java.util.concurrent.locks.Lock;
148. import java.util.concurrent.locks.ReentrantLock;
149. public class qq {
67.
150. private int count = 0;
151. private Lock lock = new ReentrantLock();//设置 lock 锁
152. //方法 1
153. public Runnable run1 = new Runnable(){
154. public void run() {
155. lock.lock(); //加锁
156. while(count < 1000) {
157. try { 
158. //打印是否执行该方法
159. System.out.println(Thread.currentThread().getName() + " run1: "+count++);
160. } catch (Exception e) {
161. e.printStackTrace();
162. }
163. }
164. }
165. lock.unlock();
166. }}; 
167.85. //方法 2
167. public Runnable run2 = new Runnable(){
168. public void run() {
169. lock.lock();
170. while(count < 1000) {
171. try {
172. System.out.println(Thread.currentThread().getName() + 
173. " run2: "+count++);
174. } catch (Exception e) {
175. e.printStackTrace();
176. }
177. }
178. lock.unlock();
179. }};
99.
100.
101.
180. public static void main(String[] args) throws InterruptedException {
181. qq t = new qq(); //创建一个对象
182. new Thread(t.run1).start();//获取该对象的方法 1
105.
183. new Thread(t.run2).start();//获取该对象的方法 2
184. }
185. }
结果是:
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
......

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值