android知识回顾-----多线程
2016-11-02 18:22
423 查看
多线程详细基础教程:
http://www.mamicode.com/info-detail-517008.html
http://www.importnew.com/12773.html
1)现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?
这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉。这个多线程问题比较简单,可以用join方法实现。
sleep和yield有点相似都是让步,但是让法不一样
sleep表示,当前线程停 一定时间后,再往下执行。把机会让给别的线程。他不管优先级,反正哥就是让别的先走
yield表示给同优先级或更高优先级的线程一个运行的机会。至于有没有生效,不太确定。
join:表示等其它线程,它有一个时间参数,也可以没有时间参数。当没有的时候,是表示,等这个线程执行完才往下执行。
当有时间参数的时候,有两种情况:
先设:主线程为mainThread,分线程为aThread. 三个线程类似
mainThread里执行aThread.join(10000) ;表示mainThread等aThread执行10秒再说。但如果10秒里,aThread最多只要执行5秒就完事,那么,mainThread只要等5秒就会不再等了。因为aThread已经完事了。如果10秒里aThread要10秒以上的时间才能执行完。那会怎样呢?mainThread会不管你执行完没完,等你10秒就不等了。哥先走一步。
package join;
public class JoinTest
{
public static void main(String[] args) {
Thread t = new Thread(new MyRunable());
t.start();
try {
System.out.println("join前");
t.join(100000);
System.out.println("join后");
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyRunable implements Runnable {
@Override
public void run() {
try {
System.out.println("开始睡");
Thread.sleep(2000);
System.out.println("睡完");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
以上代码是等了10秒,但是分线程2秒就完事,所以,2秒后主线程就开始往下走了。大家可以修改时间感受一下,记住如果join()里没有时间参数,表示,无限等,等它完事才往下执行,比较痴情哦。杨过才16年呢
2)在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?
lock接口在多线程和并发编程中最大的优势是它们为读和写分别提供了锁,它能满足你写像ConcurrentHashMap这样的高性能数据结构和有条件的阻塞。Java线程面试的问题越来越会根据面试者的回答来提问。我强烈建议在你去参加多线程的面试之前认真读一下Locks,因为当前其大量用于构建电子交易终统的客户端缓存和交易连接空间。
http://blog.csdn.net/bestone0213/article/details/48974257 举例说明,但还是不清晰。。。。
http://blog.csdn.net/ghsau/article/details/7461369/ lock(写写互斥 读写互斥 读读不互斥) 和synchronized (写写互斥 读写互斥 读读hu'ch)
3)在java中wait和sleep方法的不同?
通常会在电话面试中经常被问到的Java线程面试问题。最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
获取对象锁进入运行状态。
什么意思呢?
举个列子说明:
运行效果:
如果注释掉代码:
运行效果:
且程序一直处于挂起状态。
4)用Java实现阻塞队列。
这是一个相对艰难的多线程面试问题,它能达到很多的目的。第一,它可以检测侯选者是否能实际的用Java线程写程序;第二,可以检测侯选者对并发场景的理解,并且你可以根据这个问很多问题。如果他用wait()和notify()方法来实现阻塞队列,你可以要求他用最新的Java 5中的并发类来再写一次。
https://my.oschina.net/bfleeee/blog/275227 两种方式实现阻塞队列 分别是synchronized 的wait notify 和lock 的condition
5)用Java写代码来解决生产者——消费者问题。
与上面的问题很类似,但这个问题更经典,有些时候面试都会问下面的问题。在Java中怎么解决生产者——消费者问题,当然有很多解决方法,我已经分享了一种用阻塞队列实现的方法。有些时候他们甚至会问怎么实现哲学家进餐问题。
http://blog.csdn.net/zll793027848/article/details/8680785
哲学家进餐问题>>>??????? 还不懂
6)用Java编程一个会导致死锁的程序,你将怎么解决?
这是我最喜欢的Java线程面试问题,因为即使死锁问题在写多线程并发程序时非常普遍,但是很多侯选者并不能写deadlock free code(无死锁代码?),他们很挣扎。只要告诉他们,你有N个资源和N个线程,并且你需要所有的资源来完成一个操作。为了简单这里的n可以替换为2,越大的数据会使问题看起来更复杂。通过避免Java中的死锁来得到关于死锁的更多信息。
7) 什么是原子操作,Java中的原子操作是什么?
非常简单的java线程面试问题,接下来的问题是你需要同步一个原子操作。
http://88250.b3log.org/java-atomic-conncurrent
8) Java中的volatile关键是什么作用?怎样使用它?在Java中它跟synchronized方法有什么不同?
自从Java 5和Java内存模型改变以后,基于volatile关键字的线程问题越来越流行。应该准备好回答关于volatile变量怎样在并发环境中确保可见性、顺序性和一致性。
??????????http://sakyone.iteye.com/blog/668091
9) 什么是竞争条件?你怎样发现和解决竞争?
这是一道出现在多线程面试的高级阶段的问题。大多数的面试官会问最近你遇到的竞争条件,以及你是怎么解决的。有些时间他们会写简单的代码,然后让你检测出代码的竞争条件。可以参考我之前发布的关于Java竞争条件的文章。在我看来这是最好的java线程面试问题之一,它可以确切的检测候选者解决竞争条件的经验,or writing code which is free of data race or any other race condition。关于这方面最好的书是《Concurrency practices in Ja
1.竞争条件:
在Java多线程中,当两个或以上的线程对同一个数据进行操作的时候,可能会产生“竞争条件”的现象。这种现象产生的根本原因是因为多个线程在对同一个数据进行操作,此时对该数据的操作是非“原子化”的,可能前一个线程对数据的操作还没有结束,后一个线程又开始对同样的数据开始进行操作,这就可能会造成数据结果的变化未知。
竞争条件参考以下的例子:
[java] view
plain copy
public class TestThread {
public static void main(String[] args) {
// new 出一个新的对象 t
MyThread t = new MyThread();
/**
* 两个线程是在对同一个对象进行操作
*/
Thread ta = new Thread(t, "Thread-A");
Thread tb = new Thread(t, "Thread-B");
ta.start();
tb.start();
}
}
class MyThread implements Runnable {
// 变量 a 被两个线程共同操作,可能会造成线程竞争
int a = 10;
@Override
public void run() {
for (int i = 0; i < 5; i++) {
a -= 1;
try {
Thread.sleep(1);
} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " → a = " + a);
}
}
}
最终运行结果如下:
Thread-A → a = 8
Thread-B → a = 7
Thread-A → a = 6
Thread-B → a = 5
Thread-A → a = 4
Thread-B → a = 3
Thread-A → a = 2
Thread-B → a = 1
Thread-A → a = 0
Thread-B → a = 0
从上面的结果中我们可以看到,在线程A对数据进行了操作之后,他还没有来得及数据进行下一次的操作,此时线程B也对数据进行了操作,导致数据a一次性被减了两次,以至于a为9的时候的值根本没有打印出来,a为0的时候却被打印了两次。
那么,我们要如何才能避免结果这种情况的出现呢?
2.线程锁
如果在一个线程对数据进行操作的时候,禁止另外一个线程操作此数据,那么,就能很好的解决以上的问题了。这种操作叫做给线程加锁。
[java] view
plain copy
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestThread {
public static void main(String[] args) {
// new 出一个新的对象 t
MyThread t = new MyThread();
/**
* 两个线程是在对同一个对象进行操作
*/
Thread ta = new Thread(t, "Thread-A");
Thread tb = new Thread(t, "Thread-B");
ta.start();
tb.start();
}
}
class MyThread implements Runnable {
// 声明锁
private Lock lock = new ReentrantLock();
// 变量 a 被两个线程共同操作,可能会造成线程竞争
int a = 10;
@Override
public void run() {
// 加锁
lock.lock();
for (int i = 0; i < 5; i++) {
a -= 1;
try {
Thread.sleep(1);
} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " → a = " + a);
}
// 解锁
lock.unlock();
}
}
上面的代码给出了给线程枷锁的方式,可以看到,在线程对数据进行操作之前先给此操作加一把锁,那么在此线程对数据进行操作的时候,其他的线程无法对此数据进行操作,只能“阻塞”在一边等待当前线程对数据操作结束后再对数据进行下一次的操作,当前线程在数据的操作完成之后会解开当前的锁以便下一个线程操作此数据。
加锁之后的运行结果如下所示,运行结果符合了我们一开始的要求了。
Thread-A → a = 9
Thread-A → a = 8
Thread-A → a = 7
Thread-A → a = 6
Thread-A → a = 5
Thread-B → a = 4
Thread-B → a = 3
Thread-B → a = 2
Thread-B → a = 1
Thread-B → a = 0
3.线程同步
从JDK1.0开始,Java中的每一个对象都拥有一个内部锁,如果一个方法用关键字"synchronized"声明,那么对象的锁将保护整个方法。synchronized关键字使得我们不需要再去创建一个锁对象,而只需要在声明一个方法时加上此关键字,那么方法在被一个线程操作时就会自动的被上锁,这种操作的结果和目的与手动创建Lock对象来对数据进行加锁的结果和目的相类似。
用synchronized关键字加锁来对方法进行加锁:
[java] view
plain copy
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestThread {
public static void main(String[] args) {
MyThread t = new MyThread();
Thread ta = new Thread(t, "Thread-A");
Thread tb = new Thread(t, "Thread-B");
ta.start();
tb.start();
}
}
class MyThread implements Runnable {
int a = 10;
// synchronized 关键字对方法进行加锁
@Override
public synchronized void run() {
for (int i = 0; i < 5; i++) {
a -= 1;
try {
Thread.sleep(1);
} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " → a = " + a);
}
}
}
其结果和第二节中的结果一致。
总结:
Java中的多线程,当多个线程对一个数据进行操作时,可能会产生“竞争条件”的现象,这时候需要对线程的操作进行加锁,来解决多线程操作一个数据时可能产生问题。加锁方式有两种,一个是申明Lock对象来对语句快进行加锁,另一种是通过synchronized 关键字来对方法进行加锁。以上两种方法都可以有效解决Java多线程中存在的竞争条件的问题。
10) 你将如何使用thread dump?你将如何分析Thread dump?
在UNIX中你可以使用kill -3,然后thread dump将会打印日志,在windows中你可以使用”CTRL+Break”。非常简单和专业的线程面试问题,但是如果他问你怎样分析它,就会很棘手。
11) 为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?
这是另一个非常经典的java多线程面试问题。这也是我刚开始写线程程序时候的困惑。现在这个问题通常在电话面试或者是在初中级Java面试的第一轮被问到。这个问题的回答应该是这样的,当你调用start()方法时你将创建新的线程,并且执行在run()方法里的代码。但是如果你直接调用run()方法,它不会创建新的线程也不会执行调用线程的代码。阅读我之前写的《start与run方法的区别》这篇文章来获得更多信息。
第一点:
start与run方法的主要区别在于当程序调用start方法一个新线程将会被创建,并且在run方法中的代码将会在新线程上运行,然而在你直接调用run方法的时候,程序并不会创建新线程,run方法内部的代码将在当前线程上运行。大多数情况下调用run方法是一个bug或者变成失误。因为调用者的初衷是调用start方法去开启一个新的线程,这个错误可以被很多静态代码覆盖工具检测出来,比如与fingbugs.
如果你想要运行需要消耗大量时间的任务,你最好使用start方法,否则在你调用run方法的时候,你的主线程将会被卡住。
第二点:
一但一个线程被启动,你不能重复调用该thread对象的start方法,调用已经启动线程的start方法将会报IllegalStateException异常, 而你却可以重复调用run方法
12) Java中你怎样唤醒一个阻塞的线程?
这是个关于线程和阻塞的棘手的问题,它有很多解决方法。如果线程遇到了IO阻塞,我并且不认为有一种方法可以中止线程。如果线程因为调用wait()、sleep()、或者join()方法而导致的阻塞,你可以中断线程,并且通过抛出InterruptedException来唤醒它。我之前写的《How to deal with blocking methods in java》有很多关于处理线程阻塞的信息。
13)在Java中CycliBarriar和CountdownLatch有什么区别?
这个线程问题主要用来检测你是否熟悉JDK5中的并发包。这两个的区别是CyclicBarrier可以重复使用已经通过的障碍,而CountdownLatch不能重复使用。
CountDownLatch : 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。 CyclicBarrier : N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。
这样应该就清楚一点了,对于CountDownLatch来说,重点是那个“一个线程”, 是它在等待, 而另外那N的线程在把“某个事情”做完之后可以继续等待,可以终止。而对于CyclicBarrier来说,重点是那N个线程,他们之间任何一个没有完成,所有的线程都必须等待。
CountDownLatch 是计数器, 线程完成一个就记一个, 就像 报数一样, 只不过是递减的.
而CyclicBarrier更像一个水闸, 线程执行就想水流, 在水闸处都会堵住, 等到水满(线程到齐)了, 才开始泄流.
14) 什么是不可变对象,它对写并发应用有什么帮助?
另一个多线程经典面试问题,并不直接跟线程有关,但间接帮助很多。这个java面试问题可以变的非常棘手,如果他要求你写一个不可变对象,或者问你为什么String是不可变的。
15) 你在多线程环境中遇到的共同的问题是什么?你是怎么解决它的?
多线程和并发程序中常遇到的有Memory-interface、竞争条件、死锁、活锁和饥饿。问题是没有止境的,如果你弄错了,将很难发现和调试。这是大多数基于面试的,而不是基于实际应用的Java线程问题。
补充的其它几个问题:
1) 在java中绿色线程和本地线程区别?
2) 线程与进程的区别?
3) 什么是多线程中的上下文切换?
4)死锁与活锁的区别,死锁与馅饼的区别?
5) Java中用到的线程调度算法是什么?
6) 在Java中什么是线程调度?
7) 在线程中你怎么处理不可捕捉异常?
8) 什么是线程组,为什么在Java中不推荐使用?
9) 为什么使用Executor框架比使用应用创建和管理线程好?
10) 在Java中Executor和Executors的区别?
11) 如何在Windows和Linux上查找哪个线程使用的CPU时间最长?
http://www.mamicode.com/info-detail-517008.html
http://www.importnew.com/12773.html
1)现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?
这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉。这个多线程问题比较简单,可以用join方法实现。
sleep和yield有点相似都是让步,但是让法不一样
sleep表示,当前线程停 一定时间后,再往下执行。把机会让给别的线程。他不管优先级,反正哥就是让别的先走
yield表示给同优先级或更高优先级的线程一个运行的机会。至于有没有生效,不太确定。
join:表示等其它线程,它有一个时间参数,也可以没有时间参数。当没有的时候,是表示,等这个线程执行完才往下执行。
当有时间参数的时候,有两种情况:
先设:主线程为mainThread,分线程为aThread. 三个线程类似
mainThread里执行aThread.join(10000) ;表示mainThread等aThread执行10秒再说。但如果10秒里,aThread最多只要执行5秒就完事,那么,mainThread只要等5秒就会不再等了。因为aThread已经完事了。如果10秒里aThread要10秒以上的时间才能执行完。那会怎样呢?mainThread会不管你执行完没完,等你10秒就不等了。哥先走一步。
package join;
public class JoinTest
{
public static void main(String[] args) {
Thread t = new Thread(new MyRunable());
t.start();
try {
System.out.println("join前");
t.join(100000);
System.out.println("join后");
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyRunable implements Runnable {
@Override
public void run() {
try {
System.out.println("开始睡");
Thread.sleep(2000);
System.out.println("睡完");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
以上代码是等了10秒,但是分线程2秒就完事,所以,2秒后主线程就开始往下走了。大家可以修改时间感受一下,记住如果join()里没有时间参数,表示,无限等,等它完事才往下执行,比较痴情哦。杨过才16年呢
2)在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?
lock接口在多线程和并发编程中最大的优势是它们为读和写分别提供了锁,它能满足你写像ConcurrentHashMap这样的高性能数据结构和有条件的阻塞。Java线程面试的问题越来越会根据面试者的回答来提问。我强烈建议在你去参加多线程的面试之前认真读一下Locks,因为当前其大量用于构建电子交易终统的客户端缓存和交易连接空间。
http://blog.csdn.net/bestone0213/article/details/48974257 举例说明,但还是不清晰。。。。
http://blog.csdn.net/ghsau/article/details/7461369/ lock(写写互斥 读写互斥 读读不互斥) 和synchronized (写写互斥 读写互斥 读读hu'ch)
3)在java中wait和sleep方法的不同?
通常会在电话面试中经常被问到的Java线程面试问题。最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
获取对象锁进入运行状态。
什么意思呢?
举个列子说明:
1 /** 2 * 3 */ 4 package com.b510.test; 5 6 /** 7 * java中的sleep()和wait()的区别 8 * @author Hongten 9 * @date 2013-12-10 10 */ 11 public class TestD { 12 13 public static void main(String[] args) { 14 new Thread(new Thread1()).start(); 15 try { 16 Thread.sleep(5000); 17 } catch (Exception e) { 18 e.printStackTrace(); 19 } 20 new Thread(new Thread2()).start(); 21 } 22 23 private static class Thread1 implements Runnable{ 24 @Override 25 public void run(){ 26 synchronized (TestD.class) { 27 System.out.println("enter thread1..."); 28 System.out.println("thread1 is waiting..."); 29 try { 30 //调用wait()方法,线程会放弃对象锁,进入等待此对象的等待锁定池 31 TestD.class.wait(); 32 } catch (Exception e) { 33 e.printStackTrace(); 34 } 35 System.out.println("thread1 is going on ...."); 36 System.out.println("thread1 is over!!!"); 37 } 38 } 39 } 40 41 private static class Thread2 implements Runnable{ 42 @Override 43 public void run(){ 44 synchronized (TestD.class) { 45 System.out.println("enter thread2...."); 46 System.out.println("thread2 is sleep...."); 47 //只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。 48 TestD.class.notify(); 49 //================== 50 //区别 51 //如果我们把代码:TestD.class.notify();给注释掉,即TestD.class调用了wait()方法,但是没有调用notify() 52 //方法,则线程永远处于挂起状态。 53 try { 54 //sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程, 55 //但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。 56 //在调用sleep()方法的过程中,线程不会释放对象锁。 57 Thread.sleep(5000); 58 } catch (Exception e) { 59 e.printStackTrace(); 60 } 61 System.out.println("thread2 is going on...."); 62 System.out.println("thread2 is over!!!"); 63 } 64 } 65 } 66 }
运行效果:
enter thread1... thread1 is waiting... enter thread2.... thread2 is sleep.... thread2 is going on.... thread2 is over!!! thread1 is going on .... thread1 is over!!!
如果注释掉代码:
1 TestD.class.notify();
运行效果:
enter thread1... thread1 is waiting... enter thread2.... thread2 is sleep.... thread2 is going on.... thread2 is over!!!
且程序一直处于挂起状态。
4)用Java实现阻塞队列。
这是一个相对艰难的多线程面试问题,它能达到很多的目的。第一,它可以检测侯选者是否能实际的用Java线程写程序;第二,可以检测侯选者对并发场景的理解,并且你可以根据这个问很多问题。如果他用wait()和notify()方法来实现阻塞队列,你可以要求他用最新的Java 5中的并发类来再写一次。
https://my.oschina.net/bfleeee/blog/275227 两种方式实现阻塞队列 分别是synchronized 的wait notify 和lock 的condition
5)用Java写代码来解决生产者——消费者问题。
与上面的问题很类似,但这个问题更经典,有些时候面试都会问下面的问题。在Java中怎么解决生产者——消费者问题,当然有很多解决方法,我已经分享了一种用阻塞队列实现的方法。有些时候他们甚至会问怎么实现哲学家进餐问题。
http://blog.csdn.net/zll793027848/article/details/8680785
哲学家进餐问题>>>??????? 还不懂
6)用Java编程一个会导致死锁的程序,你将怎么解决?
这是我最喜欢的Java线程面试问题,因为即使死锁问题在写多线程并发程序时非常普遍,但是很多侯选者并不能写deadlock free code(无死锁代码?),他们很挣扎。只要告诉他们,你有N个资源和N个线程,并且你需要所有的资源来完成一个操作。为了简单这里的n可以替换为2,越大的数据会使问题看起来更复杂。通过避免Java中的死锁来得到关于死锁的更多信息。
7) 什么是原子操作,Java中的原子操作是什么?
非常简单的java线程面试问题,接下来的问题是你需要同步一个原子操作。
http://88250.b3log.org/java-atomic-conncurrent
8) Java中的volatile关键是什么作用?怎样使用它?在Java中它跟synchronized方法有什么不同?
自从Java 5和Java内存模型改变以后,基于volatile关键字的线程问题越来越流行。应该准备好回答关于volatile变量怎样在并发环境中确保可见性、顺序性和一致性。
??????????http://sakyone.iteye.com/blog/668091
9) 什么是竞争条件?你怎样发现和解决竞争?
这是一道出现在多线程面试的高级阶段的问题。大多数的面试官会问最近你遇到的竞争条件,以及你是怎么解决的。有些时间他们会写简单的代码,然后让你检测出代码的竞争条件。可以参考我之前发布的关于Java竞争条件的文章。在我看来这是最好的java线程面试问题之一,它可以确切的检测候选者解决竞争条件的经验,or writing code which is free of data race or any other race condition。关于这方面最好的书是《Concurrency practices in Ja
1.竞争条件:
在Java多线程中,当两个或以上的线程对同一个数据进行操作的时候,可能会产生“竞争条件”的现象。这种现象产生的根本原因是因为多个线程在对同一个数据进行操作,此时对该数据的操作是非“原子化”的,可能前一个线程对数据的操作还没有结束,后一个线程又开始对同样的数据开始进行操作,这就可能会造成数据结果的变化未知。
竞争条件参考以下的例子:
[java] view
plain copy
public class TestThread {
public static void main(String[] args) {
// new 出一个新的对象 t
MyThread t = new MyThread();
/**
* 两个线程是在对同一个对象进行操作
*/
Thread ta = new Thread(t, "Thread-A");
Thread tb = new Thread(t, "Thread-B");
ta.start();
tb.start();
}
}
class MyThread implements Runnable {
// 变量 a 被两个线程共同操作,可能会造成线程竞争
int a = 10;
@Override
public void run() {
for (int i = 0; i < 5; i++) {
a -= 1;
try {
Thread.sleep(1);
} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " → a = " + a);
}
}
}
最终运行结果如下:
Thread-A → a = 8
Thread-B → a = 7
Thread-A → a = 6
Thread-B → a = 5
Thread-A → a = 4
Thread-B → a = 3
Thread-A → a = 2
Thread-B → a = 1
Thread-A → a = 0
Thread-B → a = 0
从上面的结果中我们可以看到,在线程A对数据进行了操作之后,他还没有来得及数据进行下一次的操作,此时线程B也对数据进行了操作,导致数据a一次性被减了两次,以至于a为9的时候的值根本没有打印出来,a为0的时候却被打印了两次。
那么,我们要如何才能避免结果这种情况的出现呢?
2.线程锁
如果在一个线程对数据进行操作的时候,禁止另外一个线程操作此数据,那么,就能很好的解决以上的问题了。这种操作叫做给线程加锁。
[java] view
plain copy
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestThread {
public static void main(String[] args) {
// new 出一个新的对象 t
MyThread t = new MyThread();
/**
* 两个线程是在对同一个对象进行操作
*/
Thread ta = new Thread(t, "Thread-A");
Thread tb = new Thread(t, "Thread-B");
ta.start();
tb.start();
}
}
class MyThread implements Runnable {
// 声明锁
private Lock lock = new ReentrantLock();
// 变量 a 被两个线程共同操作,可能会造成线程竞争
int a = 10;
@Override
public void run() {
// 加锁
lock.lock();
for (int i = 0; i < 5; i++) {
a -= 1;
try {
Thread.sleep(1);
} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " → a = " + a);
}
// 解锁
lock.unlock();
}
}
上面的代码给出了给线程枷锁的方式,可以看到,在线程对数据进行操作之前先给此操作加一把锁,那么在此线程对数据进行操作的时候,其他的线程无法对此数据进行操作,只能“阻塞”在一边等待当前线程对数据操作结束后再对数据进行下一次的操作,当前线程在数据的操作完成之后会解开当前的锁以便下一个线程操作此数据。
加锁之后的运行结果如下所示,运行结果符合了我们一开始的要求了。
Thread-A → a = 9
Thread-A → a = 8
Thread-A → a = 7
Thread-A → a = 6
Thread-A → a = 5
Thread-B → a = 4
Thread-B → a = 3
Thread-B → a = 2
Thread-B → a = 1
Thread-B → a = 0
3.线程同步
从JDK1.0开始,Java中的每一个对象都拥有一个内部锁,如果一个方法用关键字"synchronized"声明,那么对象的锁将保护整个方法。synchronized关键字使得我们不需要再去创建一个锁对象,而只需要在声明一个方法时加上此关键字,那么方法在被一个线程操作时就会自动的被上锁,这种操作的结果和目的与手动创建Lock对象来对数据进行加锁的结果和目的相类似。
用synchronized关键字加锁来对方法进行加锁:
[java] view
plain copy
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestThread {
public static void main(String[] args) {
MyThread t = new MyThread();
Thread ta = new Thread(t, "Thread-A");
Thread tb = new Thread(t, "Thread-B");
ta.start();
tb.start();
}
}
class MyThread implements Runnable {
int a = 10;
// synchronized 关键字对方法进行加锁
@Override
public synchronized void run() {
for (int i = 0; i < 5; i++) {
a -= 1;
try {
Thread.sleep(1);
} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " → a = " + a);
}
}
}
其结果和第二节中的结果一致。
总结:
Java中的多线程,当多个线程对一个数据进行操作时,可能会产生“竞争条件”的现象,这时候需要对线程的操作进行加锁,来解决多线程操作一个数据时可能产生问题。加锁方式有两种,一个是申明Lock对象来对语句快进行加锁,另一种是通过synchronized 关键字来对方法进行加锁。以上两种方法都可以有效解决Java多线程中存在的竞争条件的问题。
10) 你将如何使用thread dump?你将如何分析Thread dump?
在UNIX中你可以使用kill -3,然后thread dump将会打印日志,在windows中你可以使用”CTRL+Break”。非常简单和专业的线程面试问题,但是如果他问你怎样分析它,就会很棘手。
11) 为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?
这是另一个非常经典的java多线程面试问题。这也是我刚开始写线程程序时候的困惑。现在这个问题通常在电话面试或者是在初中级Java面试的第一轮被问到。这个问题的回答应该是这样的,当你调用start()方法时你将创建新的线程,并且执行在run()方法里的代码。但是如果你直接调用run()方法,它不会创建新的线程也不会执行调用线程的代码。阅读我之前写的《start与run方法的区别》这篇文章来获得更多信息。
第一点:
start与run方法的主要区别在于当程序调用start方法一个新线程将会被创建,并且在run方法中的代码将会在新线程上运行,然而在你直接调用run方法的时候,程序并不会创建新线程,run方法内部的代码将在当前线程上运行。大多数情况下调用run方法是一个bug或者变成失误。因为调用者的初衷是调用start方法去开启一个新的线程,这个错误可以被很多静态代码覆盖工具检测出来,比如与fingbugs.
如果你想要运行需要消耗大量时间的任务,你最好使用start方法,否则在你调用run方法的时候,你的主线程将会被卡住。
第二点:
一但一个线程被启动,你不能重复调用该thread对象的start方法,调用已经启动线程的start方法将会报IllegalStateException异常, 而你却可以重复调用run方法
12) Java中你怎样唤醒一个阻塞的线程?
这是个关于线程和阻塞的棘手的问题,它有很多解决方法。如果线程遇到了IO阻塞,我并且不认为有一种方法可以中止线程。如果线程因为调用wait()、sleep()、或者join()方法而导致的阻塞,你可以中断线程,并且通过抛出InterruptedException来唤醒它。我之前写的《How to deal with blocking methods in java》有很多关于处理线程阻塞的信息。
13)在Java中CycliBarriar和CountdownLatch有什么区别?
这个线程问题主要用来检测你是否熟悉JDK5中的并发包。这两个的区别是CyclicBarrier可以重复使用已经通过的障碍,而CountdownLatch不能重复使用。
CountDownLatch : 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。 CyclicBarrier : N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。
这样应该就清楚一点了,对于CountDownLatch来说,重点是那个“一个线程”, 是它在等待, 而另外那N的线程在把“某个事情”做完之后可以继续等待,可以终止。而对于CyclicBarrier来说,重点是那N个线程,他们之间任何一个没有完成,所有的线程都必须等待。
CountDownLatch 是计数器, 线程完成一个就记一个, 就像 报数一样, 只不过是递减的.
而CyclicBarrier更像一个水闸, 线程执行就想水流, 在水闸处都会堵住, 等到水满(线程到齐)了, 才开始泄流.
14) 什么是不可变对象,它对写并发应用有什么帮助?
另一个多线程经典面试问题,并不直接跟线程有关,但间接帮助很多。这个java面试问题可以变的非常棘手,如果他要求你写一个不可变对象,或者问你为什么String是不可变的。
15) 你在多线程环境中遇到的共同的问题是什么?你是怎么解决它的?
多线程和并发程序中常遇到的有Memory-interface、竞争条件、死锁、活锁和饥饿。问题是没有止境的,如果你弄错了,将很难发现和调试。这是大多数基于面试的,而不是基于实际应用的Java线程问题。
补充的其它几个问题:
1) 在java中绿色线程和本地线程区别?
2) 线程与进程的区别?
3) 什么是多线程中的上下文切换?
4)死锁与活锁的区别,死锁与馅饼的区别?
5) Java中用到的线程调度算法是什么?
6) 在Java中什么是线程调度?
7) 在线程中你怎么处理不可捕捉异常?
8) 什么是线程组,为什么在Java中不推荐使用?
9) 为什么使用Executor框架比使用应用创建和管理线程好?
10) 在Java中Executor和Executors的区别?
11) 如何在Windows和Linux上查找哪个线程使用的CPU时间最长?
相关文章推荐
- android知识回顾------安全加密(金融类)
- Android基础知识回顾--Activity四种加载模式分析
- Java基础知识强化之多线程笔记01:多线程基础知识(详见Android(java)笔记61~76)
- [android异步]回顾Handler知识及HandlerThread应用及源码分析
- android 基础知识回顾
- 黑马程序员--java 知识回顾--多线程
- java基础知识回顾之java Thread类学习(五)--java多线程安全问题(锁)同步的前提
- android知识回顾-----自定义view
- Android知识回顾之事件分发机制
- Android多线程相关知识总结——源码分析
- android知识回顾----网络编程
- android知识回顾---消息机制
- Android基础知识回顾
- android知识回顾--drawable
- 多线程的深入(一,线程的一些基础知识简单回顾)
- Android开发基础知识整理之多线程与网络技术
- android知识回顾---UI和性能优化
- android知识回顾-------使用到的设计模式及举例
- java基础知识回顾之java Thread类学习(七)--java多线程安全问题(死锁)
- Java多线程基础知识回顾与总结;