面试总结2—多线程问题
1.经典问题一次打印ABC 5次
public class WaitThread implements Runnable {
private String name;
private Object prev;
private Object self;
private WaitThread(String name, Object prev, Object self) {
this.name = name;
this.prev = prev;
this.self = self;
}
public void run() {
int count = 5;
while (count > 0) {
synchronized (prev) {
synchronized (self) {
System.out.print(name);
count--;
self.notify();
}
try {
prev.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws Exception {
Object a = new Object();
Object b = new Object();
Object c = new Object();
WaitThread pa = new WaitThread("A", c, a);
WaitThread pb = new WaitThread("B", a, b);
WaitThread pc = new WaitThread("C", b, c);
new Thread(pa).start();
Thread.sleep(100);//保证顺序!ABC
new Thread(pb).start();
Thread.sleep(100);
new Thread(pc).start();
Thread.sleep(100);
}
}
prev:表示的是前一个线程持有的锁对象。
self:表示的是当前线程持有的锁对象。
首先必须明白我们这里锁的是实例对象!pa,pb,pc代表的是不同的实例对象!
wait()和notify()方法的含义先说明一下:
- wait():指的是一个已经进入同步锁的线程,让出同步锁,以便其他等待此锁的线程可以得到锁并运行。(只有其他线程调用了notify()来通知等待队列的线程才可以解除wait状态)
- notify():通知等待队列中的线程(任意一个)参与竞争锁资源。(notify并不会释放锁!!!!)
等待队列:调用wait()的线程就会被放入等待队列。
wait的前提是:必须持有锁资源,否则无法释放!所以说wait和notify必须在同步代码块(方法)中使用。
条件:从代码可以看出每个线程必须有两个锁资源才能运行!
代码解释:首先pa.start(),A线程启动持有了c,a锁,self.notify()通知等待队列中的线程来竞争a锁,但是此时等待队列并没有线程!然后将c放入等待队列,打印A;对A线程来说
pb.start(),B线程启动持有a,b锁,self.notify()通知等待队列中的线程来竞争b锁,此时等待队列有c,但是c并不会被唤醒,因为notify的是b锁!然后将a放入等待队列,打印B;
pc.start(),C线程启动持有b,c锁,self.notify()通知等待队列中的线程来竞争c锁,此时等待队列中有c,a,c被成功notify(那么此时那个线程被启动呢?),将b放入等待队列,打印C。
这里需要明白一点pa,pb,pc是3个不同的实例对象!他们之间的变量和方法不共享,run()方法是不共享的!
那么就很容易理解:
对A线程来说:它释放了c锁,保留a锁,所以只要有c.notify那么就能运行。
对B线程来说:它释放了a锁,保留b锁,所以只要有a.notify那么就能运行。
对C线程来说:它释放了b锁,保留c锁,所以只要有b.notify那么就能运行。
所以说上面的pc.start()之后是A线程启动,A线程启动后又a.notify(),所以B线程启动;B启动后b.notify(),C启动。一次循环!直到每个对象中的count=0。
2.有关synchronize的问题
1.为什么要使用wait()和notify()?
若使用简单的synchonized机制实现互斥,会导致线程主动发起轮询,若N次轮询没有成功,就产生了N次的CPU空间浪费;如果加上了 wait/notify机制,就可以避免这些无谓的轮询,节省CPU的消耗。wait/notify/notifyAll可以控制线程执行与不执行。
2.为什么wait/notify/notifyAll方法一定要写在synchronized里面呢?
因为第一点已经说了wait/notify/notifyAll的作用是为了避免轮询带来的性能损失,而产生轮询的条件是多个线程对同一个资源进行操作。
3.为什么wait/notify/notifyAll方法定义在Object类里面呢?
因为wait/notify/notifyAll必须写在synchronized里面,而synchronized的对象锁可以是任意对象,所以wait/notify/notifyAll方法定义在Object类里面呢。
调用wait/notify/notifyAll方法的对象,必须和synchronized()的对象锁一致。
3.继承Thread类和实现Runnable接口的区别
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
举个例子来说明:
方式1:继承Thread
public class Test extends Thread{
public void run() {
System.out.println("A");
}
public static void main (String args[]) {
Test t=new Test();
t.start();
//一个实例对象只能启动一个线程。
}
}
方式2:实现Runnable
public class Test implements Runnable{
public void run() {
System.out.println("A");
}
public static void main (String args[]) {
Test t=new Test();
new Thread(t).start();
new Thread(t).start();
//一个实例对象能启动多个线程。
}
}
从上面代码可以看出单继承的局限
线程分为两部分,一部分线程对象,一部分线程任务。继承Thread类,线程对象和线程任务耦合在一起。一旦创建Thread类的子类对象,既是线程对象,有又有线程任务。实现runnable接口,将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。Runnable接口对线程对象和线程任务进行解耦。
4.死锁实例
pubic class DieLock implements Runnable{
private boolean falg;
private static final Object objA=new Object();
private static final Object objB=new Object();
public DieLock(boolean flag){
this.flag=flag;
}
public void run(){
if(flag){
synchronized(objA){
System.out.println("if A");
}
synchronized(objB){
System.out.println("if B");
}
}else{
synchronized(objB){
System.out.println("else B");
}
synchronized(objA){
System.out.println("else A");
}
}
}
public static void main (String args[]){
DieLock dl1=new DieLock(true);
DieLock dl2=new DieLock(false);
new Thread(dl1).start();
new Thread(dl2).start();
}
}
5.生产者和消费者
Student.java
public class Student{
private String name;
private int age;
private boolean flag;
public synchronized void set(String name,int age){
//这个锁的是实例对象(也就是s)
if(this.flag){
try{
this.wait()
}catch(InterruptedException e){
e.printStackTrace();
}
}
this.name=name;
this.age=age;
this.flag=false;
this.notify();
}
public synchronized void get(){
if(!this.flag){
try{
this.wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
System.out.println(this.name+"-----"+this.age);
this.flag=false;
this.notify();
}
}
生产者SetStudent.java
public class SetStudent implements Runnable{
private Student s;
public SetStuedent(Student s){
this.s=s;
}
public void run(){
while(true){
s.set("张三",18);
}
}
}
消费者GetStudenet.java
public class GetStudent implements Runnable{
private Student s;
public GetStudent(Student s){
this.s=s;
}
public void run(){
while(true){
s.get();
}
}
}
测试StudentDemo.java
/*
* 思想:
* 生产者SetStudent:先看是否有数据,如果没有就生产,生产完之后设置为有,并唤醒消费者消费,有就等待。
* 消费者GetStudnt:先看是否有数据,如果有就消费,消费完之后设置为没有,并唤醒生产者生产,没有就等待。
*/
public class StudentDemo{
public void main(String args[]){
//设置一个Student对象s保证数据和锁的同步;
Student s=new Student();
SetStudent ss=new SetStudent(s);
GetStudent gs=new GetStudent(s);
Thread t1=new Thread(ss);
Thread t2=new Thread(gs);
t1.start();
t2.start();
}
}