java面试总结(4)之多线程

1.什么是线程

线程是操作系统运行调度的最小单元,是进程的元素,进程是由多个线程组合而来的。

每条线程有自己的栈内存,一个进程内的所有线程共享一片堆内存

多线程可以对运算密集型任务提速

2.简单描述一下线程安全

在多线程环境下运行的代码,如果存在多个线程资源共享就存在线程安全的问题,例如同时操作一个成员变量

3.Thread 类中的start() 和 run() 方法有什么区别

start是启动线程的方法,run方法内部会调用目标对象的run方法,目标对象的run方法是线程运行的载体

直接调用run方法也会运行,只是在当前线程中同步运行,并没有新的线程启动

4.Java多线程中调用wait() 和 sleep()方法有什么不同

sleep方法是Thread类提供的,wait方法是Object提供的

sleep只能等到休眠时间到,自己醒来,wait可以自己醒来,也可以被唤醒

sleep不会释放锁,wait会释放锁

wait必须在synchronized环境内使用,因为会释放锁,没有synchronized何来锁

5.notify和notifyAll的区别

当一个线程wait后,只有通过notify或notifyAll才能被唤醒,也就是重新获得了锁竞争的资格

notify只能唤醒某个线程,notifyAll可以唤醒所有的线程(所以如果多个线程被阻塞,须使用该方法)

注意获得竞争锁的资格不是获得了锁

6.为什么wait, notify 和 notifyAll这些方法不在thread类里面

这三个方法都是锁级别的操作,比如调用wait的时候需要去释放对象的锁,如果定义在thread里面,当前线程并不知道要释放的是哪个对象的锁,因此定义在Object里面,由被锁的对象去调用更为合理。

同理notify和notifyAll这两个方式是去唤醒被锁对象的阻塞线程,如果定义在thread里面,那么当前线程还是不得知需要唤醒的是哪个被锁对象的阻塞线程,因此也定义在Object里面

7.为什么wait和notify方法要在同步块中调用

如果在同步块外调用会抛异常:java.lang.IllegalMonitorStateException: current thread not owner

8.Thread类中的yield方法有什么作用

可以暂停当前线程,让其他线程获取更高的CPU执行优先级,但不保证当前线程一定后执行

9.Java线程池中submit() 和 execute()方法有什么区别

都可以提交线程,只是返回不同,submit定义在ExecutorService中,可以返回一个线程计算结果对象Future<V>,execute定义在Executor中,无返回值

10.如何强制启动一个线程

java中没有方法可以强制启动线程

11.interrupte、interrupted 和 isInterrupted的区别

interrupte是Thread的成员方法,中断线程

interrupted是Thread的静态方法,如果当前线程已被中断,则取消中断状态

isInterrupted是Thread的成员方法,判断当前线程是否中断

12.怎么检测一个线程是否拥有锁

Thread类提供了holdsLock(Object o)方法可以检测到o对象是否持有当前线程的锁,true表示有锁

13.如何让线程按既定顺序执行

t1,t2,t3,先启动t3,t3中启动t2,t2中启动t1,执行顺序t1>t2>t3

14.什么是竞态条件.

多个线程同时竞争一个共享资源,并且对该资源进行更新操作(例如线程A 对2 +1,线程B对变量2+1,那么想要的结果应该是4,但是其实是3),那么各个线程会发生覆盖的现象,称为静态条件

避免静态条件的发生,可以对资源加同步锁

15.什么是ThreadLocal变量

ThreadLocal变量是本地线程的变量,是一种对共享变量的副本的拷贝,可以实现各个线程操作共享变量的副本,即共享变量变成了当前线程的私有属性,各个线程间的更新操作互不影响

与synchronized的区别:同步是指多个线程再操作共享资源的时候,同步进行,但操作的还是共享资源,而ThreadLocal则是对共享资源的一份拷贝,多个线程各自操作各自的拷贝,互不干扰

使用示例:

*ThreadLocal变量使用示例

package cn.qu.td;

public class MyThread{
	
	private static ThreadLocal<Person> threadLocalPerson = new ThreadLocal<MyThread.Person>(){
		protected Person initialValue() {
			return new Person();
		};
	};
	
	static class Person {
		private Long id;
		private String name;
		public Long getId() {
			return id;
		}
		public void setId(Long id) {
			this.id = id;
		}
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		
		
	}

	public static void main(String[] args) {
		
		Thread t1 = new Thread("t1"){
			public void run() {
				Person person = threadLocalPerson.get();
				person.setId(10L);
				person.setName("Lucy");
				System.out.println("t1.id:" + person.getId() + " t1.name:" + person.getName());
			}
		};
		
		Thread t2 = new Thread("t2"){
			public void run() {
				Person person = threadLocalPerson.get();
				person.setId(20L);
				person.setName("Mary");
				System.out.println("t2.id:" + person.getId() + " t2.name:" + person.getName());
			}
		};
		
		t1.start();
		t2.start();
		
	}
	
}

*加同步锁的示例

package cn.qu.td;

public class MyThread{
	
	static class Person {
		private Long id;
		private String name;
		public Long getId() {
			return id;
		}
		public void setId(Long id) {
			this.id = id;
		}
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		
		
	}
	
	private static Person person = new Person();
	
	
	

	public static void main(String[] args) {
		
		Thread t1 = new Thread("t1"){
			public void run() {
				synchronized (person) {					
					person.setId(10L);
					person.setName("Lucy");
					System.out.println("t1.id:" + person.getId() + " t1.name:" + person.getName());
				}
			}
		};
		
		Thread t2 = new Thread("t2"){
			public void run() {
				synchronized (person) {					
					person.setId(20L);
					person.setName("Mary");
					System.out.println("t2.id:" + person.getId() + " t2.name:" + person.getName());
				}
			}
		};
		
		t1.start();
		t2.start();
		
	}
	
}

上述两段代码:控制台都会打印出:

t1.id:10 t1.name:Lucy
t2.id:20 t2.name:Mary

效果大致是一样的,但是实际上原理差的很大

*ThreadLocal一个典型的应用场景就是Hibernate中的session

16.描述一下volatile变量

*volatile是比synchronized更细粒度的一种同步机制

*volatile单词的意思是易变的,所以在多线程环境下如果对共享资源有很频繁的操作,且为了保证每个线程在操作资源时,都是最新的,那么可以使用volatile修饰符来修饰。

被volatile修饰的资源,各个线程操作该资源的时候,是直接在内存中进行操作,因此保证了,每个线程对资源的操作是对其他线程可见的。

用法示例:

package cn.qu.td;

public class MyThread{
	
	private volatile static Integer a = 10;

	public static void main(String[] args) {
		
		Thread t1 = new Thread("t1"){
			public synchronized void run() {
				for(int i = 1; i < 10; i ++){
					a++;
					System.out.println(Thread.currentThread().getName() + ": a = " + a);
				}
			}
		};
		
		Thread t2 = new Thread("t2"){
			public synchronized void run() {
				for(int i = 1; i < 10; i ++){
					a++;
					System.out.println(Thread.currentThread().getName() + ": a = " + a);
				}
			}
		};
		
		t1.start();
		t2.start();
		
	}
	
}

通过控制台打印的内容可以总结出:当t1线程打印a的时候,如果t2线程对a++,会在t1线程打印的时候反应出来

17.如何停止一个线程,join的用途

java中不能强制停止一个线程,只能等待其执行完毕后,自行停止

join的字面意思加入的意思,然而我们都知道他在java中的作用是阻塞主线程等待子线程执行结束,那么怎么理解加入这个意义呢

当子线程执行完毕后汇入到主线程,这下可以理解了jon的意思啦,详情参考:join详解

例如:

package cn.qu.td;

public class MyThread{

	private volatile static Integer a = 10;

	public static void main(String[] args) {
		
		Thread t1 = new Thread("t1"){
			public void run() {
				for(int i = 1; i <= 10; i ++){
					a++;
					System.out.println(Thread.currentThread().getName() + ": a = " + a);
				}
			}
		};
		
		Thread t2 = new Thread("t2"){
			public void run() {
				for(int i = 1; i <= 10; i ++){
					a++;
					System.out.println(Thread.currentThread().getName() + ": a = " + a);
				}
			}
		};
		
		t1.start();
		t2.start();
		
		try {
			t1.join();
			t2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println(a);
		
	}
	
}

可以看到控制台最后一行打印出的a是30,就是等待t1,t2两个线程结束后才执行的最后一行打印a的代码

18.同步块内的线程抛出异常会发生什么

会释放锁,交由其他线程来使用资源

19.多线程编程都注意些什么

20.实现线程的几种方式以及区别

继承Thread类方式:

public class MyThread extends Thread {
	
	private Integer ticket = 10;
	
	@Override
	public void run() {
		while (true){
			synchronized (this) {
				if(ticket < 1) break;
				ticket --;
				System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + ticket);
			}				
		}	
		
	}
	
	public static void main(String[] args) {
		new MyThread().start();
		new MyThread().start();
		new MyThread().start();
	}
	
}

实现Runnable接口方式:

public class MyThread implements Runnable {
	
	private Integer ticket = 10;
	
	public void run() {
		while (true){
			synchronized (this) {
				if(ticket < 1) break;
				ticket --;
				System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + ticket);
			}				
		}		
	}
	
	public static void main(String[] args) {
		MyThread t = new MyThread2();		
		new Thread(t,"t1").start();
		new Thread(t,"t2").start();
		new Thread(t,"t3").start();
	}
	
}

实现Callable<V>接口方式:

public class MyThread implements Callable<MyThread> {
	
	protected Integer ticket = 10;
	
	public MyThread call() throws Exception {
		while (true) {
			synchronized (this) {
				if(ticket < 1) break;	
				ticket --;
				System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + ticket);
                                //模拟线程1进来操作则抛出异常信息
				if("t1".equals(Thread.currentThread().getName()))throw new RuntimeException(Thread.currentThread().getName() + ".errortest");    
			}
		}
		
                //返回当前类的引用
		return this;
	}
	
	
	public static void main(String[] args) {

		MyThread t = new MyThread();		
		
                //创建一个线程池,并在线程池内部循环设置线程名称
		ExecutorService executorService = Executors.newCachedThreadPool(new ThreadFactory() {
			
                        //定义一个随方法调用的自增变量
			private AtomicInteger i = new AtomicInteger(1);
			
			public Thread newThread(Runnable r) {
				Thread thread = new Thread(r);
                                //设置线程名称
				thread.setName("t" + i.getAndIncrement());
				return thread;
			}
		});
		
                //声明一个线程执行完成后返回结果的存储集合
		List<Future<MyThread>> list = new ArrayList<Future<MyThread>>();

		for(int i = 0; i < 3; i ++){
                        //调用submit(Callable<V> c)启动线程
			Future<MyThread> future = executorService.submit(t);
                        //把线程运行的返回结果保存到集合中
			list.add(future);			
		}
		
                //线程运行完毕后,关闭阻塞
		executorService.shutdown();
		
		String error = "";
		
                //迭代线程运行的返回结果
		for (Future<MyThread> future : list) {
			try{			
				System.out.println("剩余票数:" + future.get().ticket);							
			}catch (Exception e) {
                                //如果某个线程抛出了异常则赋值给error
				error = e.getMessage();
			}
		}
		
                //打印出错线程的异常信息
		System.out.println(error);
		
	}
	
}

*Thread和Runnable的区别:

通过上述代码我们可以看到,使用Thread方式不方便线程间资源共享,当然了你可以把一个实现线程的Thread子类在包装给Thread创建的线程对象,这样便可以资源共享了,但这种方式很显然是为了支持Runnable创即线程而提供的构造,毕竟继承了Thread,就可以直接调用Thread的非私有属性和方法了,要是使用Thread包装一个子类,就显得多余,没意义了

Thread和Runnable的区别总结:

1,java不支持多继承,单支持多实现,因此使用Runnable方式比Thread方式更容易扩展

2,使用Runnable可以达到线程间资源共享的目的

*Runnable和Callable的区别:

两者都是接口,便于扩展,Callable的装饰类FutureTask也实现了Runnable,因此都支持线程间资源共享

Runnable是jdk1.0就有,Callable是jdk1.5才有的

Callable有返回值,返回值的类型既Callable的泛型类型,可以自定义返回值类型很方便,Runnable没有返回值

Callable可以把异常抛出去,并被主线程 在获取抛出异常线程的返回值时捕获到,Runnable抛出的异常,主线程无法捕获到

21.谈谈死锁

*死锁概述

多个线程间并发操作同一资源时,产生的相互等待对方的现象

*死锁产生的四个必要条件

互斥条件:一个资源只能同时被一个线程访问。即有同步

请求与保持条件:线程(因需要其他资源)出现等待,且未释放锁. 例如: sleep

不可抢占资源条件:当前线程需要等待的资源,被其他线程持有而且锁定可以理解为不可抢占,即需要等待的资源被加锁

循环等待条件:A线程锁定a资源需要b资源,B线程锁定b资源需要a资源,AB两个线程同时启动,就产生循环等待条件

*一个简单死锁示例

package cn.qu.td;

public class MyThread {

	final static Object o1 = new Object(), o2 = new Object();

	/*
	 * o1和o2中都加了同步,因此就     满足互斥条件:一个资源只能同时被一个线程占用
	 * Thread.sleep(500);  	满足请求与保持条件(阻塞且未释放锁,阻塞的目的通常是为了等待其他线程释放资源)
	 * t2占用o2资源后,t1中也需要o2资源,但是因为t2对o2加了锁,因此就   满足不可抢占资源条件
	 * t1对o1加锁>阻塞>t2对o2加锁>阻塞>t1获取不到o2等待>t2又获取到t1也等待     于是满足循环等待条件
	 */
	public static void main(String[] args) {
	
		new Thread(new Runnable() {
			
			public void run() {
				synchronized (o1) {
					System.out.println(o1);
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					synchronized (o2) {
						System.out.println(o2);
					}
				}			
			}
		}, "t1").start();
		
		new Thread(new Runnable() {
			
			public void run() {
				synchronized (o2) {
					System.out.println(o2);
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					synchronized (o1) {
						System.out.println(o1);
					}
				}			
			}
		}, "t2").start();
		
	}

}

23.写一个简单的生产这消费者示例

//商品类
public class Good{

	private String name;
	private Integer goodNumber;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Integer getGoodNumber() {
		return goodNumber;
	}
	public void setGoodNumber(Integer goodNumber) {
		this.goodNumber = goodNumber;
	}
	@Override
	public String toString() {
		return "Goods [name=" + name + ", goodNumber=" + goodNumber + "]";
	}
	
	//主函数中测试生产者消费者模型
	public static void main(String[] args) {
		List<Good> goods = new ArrayList<Good>();
		new Thread(new Producer(goods),"生产者").start();
		new Thread(new Consumer(goods),"消费者").start();
	}

}


//生产者
public class Producer implements Runnable {

	//商品容器
	private List<Good> goods;

	public Producer(List<Good> goods) {
		this.goods = goods;
	}
	
	//已生产的商品数量
	private Integer i = 0;
	
	public void run() {
		while(true){
			//休眠的目的是为了模拟动态演示生产和消费的效果
			try {
				Thread.sleep(500);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
			
			synchronized (goods) {
				//每次进来先唤醒其他的线程
				goods.notifyAll();
				//打印容器中商品的数量
				System.out.println(Thread.currentThread().getName() + "-" + "商品剩余数量:" + goods.size());
				
				if (goods.isEmpty()) {	//如果为空,则开始生产商品
					Good good = new Good();
					good.setName(UUID.randomUUID().toString());
					//把生产的商品添加到容器
					goods.add(good);
					
					//已生产商品数量加1
					i++;
					//打印当前生产的商品名称
					System.out.println(Thread.currentThread().getName() + "-" + "生产者生又产了一个商品:" + good);
					//打印生产者已经生产了多少个商品的数量
					System.out.println(Thread.currentThread().getName() + "-" + "生产者已生产的商品数量:" + i);
					
				} else {	//如果不为空,则阻塞当前线程
					try {
						goods.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}

}


//消费者
public class Consumer implements Runnable {

	//商品容器
	private List<Good> goods;

	public Consumer(List<Good> goods) {
		this.goods = goods;
	}
	
	//已消费的商品数量
	private Integer i = 0;

	public void run() {
		while (true) {
			//休眠的目的是为了模拟动态演示生产和消费的效果
			try {
				Thread.sleep(500);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
			
			synchronized (goods) {
				//每次进来先唤醒其他的线程
				goods.notifyAll();
				//打印容器中商品的数量
				System.out.println(Thread.currentThread().getName() + "-" + "商品剩余数量:" + goods.size());
				
				if (goods.isEmpty()) {	//如果为空,则阻塞当前线程
					try {
						goods.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}					
				} else {	//否则开始消费商品
					Good good = goods.get(0);
					goods.remove(good);
					
					//已消费商品的数量加1
					i++;
					//打印当前消费的商品名称
					System.out.println(Thread.currentThread().getName() + "-" + "消费者又消费了一个商品:" + good);
					//打印已消费的商品数量
					System.out.println(Thread.currentThread().getName() + "-" + "已消费商品的数量:" + i);					
				}
			}
		}
	}

}

24.什么是线程池? 为什么要使用它?

猜你喜欢

转载自blog.csdn.net/m0_37414960/article/details/81364309