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<Future> list = new ArrayList<Future>();
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<Object> {
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。
上面的解释用一张图表示如下:
(图片来自网络,非本人所绘)
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. 常用的线程池有哪些?(2017-11-23-wzz)
newSingleThreadExecutor:创建一个单线程的线程池,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
newFixedThreadPool:创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
newCachedThreadPool:创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
newScheduledThreadPool:创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。
newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。
7. 请叙述一下您对线程池的理解?(2015-11-25)
(如果问到了这样的问题,可以展开的说一下线程池如何用、线程池的好处、线程池的启动策略) 合理利用线程池能够带来三个好处。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定
性,使用线程池可以进行统一的分配,调优和监控。
8. 线程池的启动策略?(2015-11-25)
官方对线程池的执行过程描述如下:
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线程的数据怎么实现(上海3期学员提
供)
根据问题的描述,我将问题用以下代码演示,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线程的数据怎么实现(上海3期学员提供)
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个方法都加了同步锁,多个线程能同时访问同一个类中的这两
个方法吗?(2017-2-24)
这个问题需要考虑到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所示。
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中多线程间的通信怎么实现?(2017-2-24)
线程通信的方式:
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. 请说出同步线程及线程调度相关的方法?(2017-11-23-wzz)
wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,
而是由JVM确定唤醒哪个线程,而且与优先级无关;
notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁
的线程才能进入就绪状态;
注意:java 5 通过Lock接口提供了显示的锁机制,Lock接口中定义了加锁(lock()方法)和解锁(unLock()
方法),增强了多线程编程的灵活性及对线程的协调
16. 启动一个线程是调用run()方法还是start()方法?(2017-11-23-wzz)
启动一个线程是调用 start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由 JVM 调度并
执行,这并不意味着线程就会立即运行。
run()方法是线程启动后要进行回调(callback)的方法。