面试总结2—多线程问题

面试总结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()方法的含义先说明一下:

  1. wait():指的是一个已经进入同步锁的线程,让出同步锁,以便其他等待此锁的线程可以得到锁并运行。(只有其他线程调用了notify()来通知等待队列的线程才可以解除wait状态)
  2. 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();
    }
}

猜你喜欢

转载自blog.csdn.net/qq_38238296/article/details/87869701