Java面试题-高并发多线程相关问题

高并发多线程类

高并发多线程类

为什么要使用并发编程
	提升多核CPU的利用率:一般来说一台主机上的会有多个CPU核心,我们可以创建多个线程,理论上讲操作系统可以将多个线程分配给不同的CPU去执行,每个CPU执行一个线程,这样就提高了CPU的使用效率,如果使用单线程就只能有一个CPU核心被使用。
	比如当我们在网上购物时,为了提升响应速度,需要拆分,减库存,生成订单等等这些操作,就可以进行拆分利用多线程的技术完成。面对复杂业务模型,并行程序会比串行程序更适应业务需求,而并发编程更能吻合这种业务拆分 。
	总结就是:
            充分利用多核CPU的计算能力;
            方便进行业务拆分,提升应用性能 
多线程操作安全性处理,出现线程安全问题的原因一般都是三个原因
    1.线程切换带来的原子性问题 解决办法:使用多线程之间同步synchronized或使用锁(lock)。
    2.缓存导致的可见性问题 解决办法:synchronized、volatile、LOCK,可以解决可见性问题
    3.编译优化带来的有序性问题 解决办法:Happens-Before 规则可以解决有序性问题 
创建线程的四种方式
	承 Thread 类;
public class MyThread extends Thread {
		@Override
		public void run() {
			System.out.println(Thread.currentThread().getName() + " run()方法正在执行...");
	} 
	实现 Runnable 接口;
public class MyRunnable implements Runnable {
	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName() + " run()方法执行中...");
	}
	实现 Callable 接口; 
	public class MyCallable implements Callable<Integer> {
		@Override
		public Integer call() {
			System.out.println(Thread.currentThread().getName() + " call()方法执行中...");
			return 1;
		}
	}
	使用匿名内部类方式
public class CreateRunnable {
		public static void main(String[] args) {
			//创建多线程创建开始
			Thread thread = new Thread(new Runnable() {
				public void run() {
					for (int i = 0; i < 10; i++) {
						System.out.println("i:" + i);
					}
				}
			});
			thread.start();
		}
	}

说一下 runnable 和 callable 有什么区别

相同点:
都是接口
都可以编写多线程程序
都采用Thread.start()启动线程
主要区别:
Runnable 接口 run 方法无返回值;
Callable 接口 call 方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;
Callable 接口 call 方法允许抛出异常,可以获取异常信息 注:Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。

线程的 run()和 start()有什么区别?

每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,run()方法称为线程体。
通过调用Thread类的start()方法来启动一个线程。
start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。
run() 可以重复调用,而 start()只能调用一次。
start()方法来启动一个线程,真正实现了多线程运行。
调用start()方法无需等待run方法体代码执行完毕,可以直接继续执行其他的代码; 此时线程是处于就绪状态,并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, run()方法运行结束, 此线程终止。然后CPU再调度其它线程。run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。

为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用run() 方法?

new 一个 Thread,线程进入了新建状态。调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到 时间片 后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行run() 方法的内容,这是真正的多线程工作。而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。

线程的状态

1.新建(new):新创建了一个线程对象。
2.就绪(可运行状态)(runnable):线程对象创建后,当调用线程对象的 start()方法,该线程处于就绪状态,等待被线程调度选中,获取cpu的使用权。
3.运行(running):可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。
	注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
4.阻塞(block):处于运行状态中的线程由于某种原因,暂时放弃对 CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。
	阻塞的情况分三种:
		(一). 等待阻塞:运行状态中的线程执行 wait()方法,JVM会把该线程放入等待队列(waittingqueue)中,使本线程进入到等待阻塞状态;
		(二). 同步阻塞:线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),,则JVM会把该线程放入锁池(lock pool)中,线程会进入同步阻塞状态;
		(三). 其他阻塞: 通过调用线程的 sleep()或 join()或发出了 I/O 请求时,线程会进入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时者 I/O 处理完毕时,线程重新转入就绪状态。
 5.死亡(dead)(结束):线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

sleep() 和 wait() 有什么区别?

两者都可以暂停线程的执行
类的不同:sleep() 是 Thread线程类的静态方法,wait() 是 Object类的方法。
是否释放锁:sleep() 不释放锁;wait() 释放锁。
用途不同:Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
用法不同:wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify()	
或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用wait(long timeout)超时后线程会自动苏醒。

在 Java 程序中怎么保证多线程的运行安全?

方法一:使用安全类,比如 java.util.concurrent 下的类,使用原子类AtomicInteger
方法二:使用自动锁 synchronized。
方法三:使用手动锁 Lock。
手动锁 Java 示例代码如下:
	Lock lock = new ReentrantLock();
	lock. lock();
	try {
		System. out. println("获得锁");
	} catch (Exception e) {
		// TODO: handle exception
	} finally {
		System. out. println("释放锁");
		lock. unlock();
	}

多线程的常用方法

sleep() 	|  强迫一个线程睡眠N毫秒|
isAlive() |  	判断一个线程是否存活。|
join()|  	等待线程终止。|
activeCount()	|  程序中活跃的线程数。|
enumerate()|  	枚举程序中的线程。|
currentThread()	|  得到当前线程。|
isDaemon()	|  一个线程是否为守护线程。|
setDaemon()|  	设置一个线程为守护线程。|
setName()	|  为线程设置一个名称。|
wait()	|  强迫一个线程等待。|
notify()	|  通知一个线程继续运行。|
setPriority()|  	设置一个线程的优先级。

如果你提交任务时,线程池队列已满,这时会发生什么

有俩种可能:
(1)如果使用的是无界队列 LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为 LinkedBlockingQueue 可以近乎认为是一个无穷大的队列,可以无限存放任务
(2)如果使用的是有界队列比如 ArrayBlockingQueue,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue 满了,会根据maximumPoolSize 的值增加线程数量,如果增加了线程数量还是处理不过来,ArrayBlockingQueue 继续满,那么则会	使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是 AbortPolicy

什么是线程同步和线程互斥,有哪几种实现方式?

当一个线程对共享的数据进行操作时,应使之成为一个”原子操作“,即在没有完成相关操作之前,不允许其他线程打断它,否则,就会破坏数据的完整性,必然会得到错误的处理结果,这就是线程的同步。
在多线程应用中,考虑不同线程之间的数据同步和防止死锁。当两个或多个线程之间同时等待对方释放资源的时候就会形成线程之间的死锁。为了防止死锁的发生,需要通过同步来实现线程安全。	线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使	某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。
线程间的同步方法大体可分为两类:用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。内核模式下的方法有:事件,信号量,互斥量。
实现线程同步的方法	
	同步代码方法:sychronized 关键字修饰的方法
	同步代码块:sychronized 关键字修饰的代码块
	使用特殊变量域volatile实现线程同步:volatile关键字为域变量的访问提供了一种免锁机制
	使用重入锁实现线程同步:reentrantlock类是可冲入、互斥、实现了lock接口的锁他与sychronized方法具有相同的基本行为和语义

同步方法和同步块,哪个是更好的选择?

同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对象)。
同步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通常会导致他们停止执行并需要等待获	得这个对象上的锁。
同步块更要符合开放调用的原则,只在需要锁住的代码块锁住相应的对象,这样从侧面来说也可以避免死锁。
原则:同步的范围越小越好。

Java 中你怎样唤醒一个阻塞的线程?

首先 ,wait()、notify() 方法是针对对象的,
调用任意对象的 wait()方法都将导致线程阻塞,阻塞的同时也将释放该对象的锁,相应地,调用任意对象的 notify()方法则将随机解除该对象阻塞的线程,但它需要重新获取该对象的锁,直到获取成功才能往下执行;
其次,wait、notify 方法必须在 synchronized 块或方法中被调用,
并且要保证同步块或方法的锁对象与调用 wait、notify 方法的对象是同一个,如此一来在调用 wait 之前当前线程就已经成功获取某对象的锁,执行 wait 阻塞后当前线程就将之前获取的对象锁释放。

为什么 wait(), notify()和 notifyAll()必须在同步方法或者同步块中被调用?

当一个线程需要调用对象的 wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的notify()方法。
同样的,当一个线程需要调用对象的 notify()方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。

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

使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。
当前线程到了就绪状态,那么接下来哪个线程会从就绪状态变成执行状态呢?可能是当前线程,也可能是其他线程,看系统的分配了。

为什么 Thread 类的 sleep()和 yield ()方法是静态的?

Thread 类的 sleep()和 yield()方法将在当前正在执行的线程上运行。所以在其他处于等待状态的线
程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线
程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。

线程的 sleep()方法和 yield()方法有什么区别?

(1)sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;
yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
(2)线程执行 sleep()方法后转入阻塞(blocked)状态,而执行 yield()方法后转入就绪(ready)状态;
(3)sleep()方法声明抛出 InterruptedException,而 yield()方法没有声明任何异常;
(4)sleep()方法比 yield()方法(跟操作系统 CPU 调度相关)具有更好的可移植性,通常不建议使用yield()方法来控制并发线程的执行。

如何停止一个正在运行的线程?

在java中有以下3种方法可以终止正在运行的线程:
	使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
	使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法。
	使用interrupt方法中断线程。 

并发关键字 synchronized ?

在 Java 中,synchronized 关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized 代码段不被多个线程同时执行。synchronized 可以修饰类、方法、变量。
另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。
如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。

说说自己是怎么使用 synchronized 关键字,在项目中用到了吗

synchronized关键字最主要的三种使用方式:
修饰实例方法: 作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
修饰静态方法: 也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个
实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。修饰代码块: 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。总结: synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class类上锁。synchronized 关键字加到实例方法上是给对象实上锁。尽量不要使用 synchronized(Stringa) 因为JVM中,字符串常量池具有缓存功能!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值