java多线程面试题

多线程面试

进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程。(进程是资源分配的最小单位)

进程间通信主要包括管道, 系统IPC(包括消息队列,信号,共享存储), 套接字(SOCKET).
管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)

线程可以创建另外一个线程
进程有独立的虚拟地址空间
同一进程内的线程共享内存等系统资源

线程间同步的方式主要由以下三种:

  1. 互斥量,采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问
  2. 信号量(临界区),它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量。当需要一个计数器来限制可以使用某共享资源的线程数目时,可以使用“信号量”对象
  3. 事件(信号),通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作。即事件机制允许一个线程在处理完一个任务后,主动唤醒另外一个线程执行任务
    进程间通信的方法:半双工管道,FIFO(命名管道),消息队列,信号量,共享内存,socket
    在这里插入图片描述

1、多线程有什么用?

(1)发挥多核CPU的优势 资源利用率提升,程序处理效率提高
(2)防止阻塞,单核CPU我们还是要应用多线程,就是为了防止阻塞
(3)便于建模,分别建立程序模型,并通过多线程分别运行这几个任务

2、java中要想实现多线程,主要有两种手段

1.一种是继续Thread类
class Thread1 extends Thread
覆写run()
调用: Thread1 mTh2=new Thread1(“B”);
mTh2.setName(“名字”);
mTh1.start();
2.另外一种是实现Runable接口 实现接口的方式比继承类的方式更灵活,也能减少程序之间的耦合度,面向接口编程也是设计模式6大原则的核心。
3. 其实准确来讲,应该有三种,还有一种是实现Callable接口,并与Future、线程池结合使用
4. 使用Executor框架创建线程池。Executor框架是juc里提供的线程池的实现。

3、start()方法和run()方法的区别

只有调用了start()方法,才会表现出多线程的特性,不同线程的run()方法里面的代码交替执行。如果只是调用run()方法,那么代码还是同步执行的,必须 等待一个线程的run()方法里面的代码全部执行完毕之后,另外一个线程才可以执行其run()方法里面的代码。

4、Runnable接口和Callable接口的区别

Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;
Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。

这其实是很有用的一个特性,因为多线程相比单线程更难、更复杂的一个重要原因就是因为多线程充满着未知性,某条线程是否执行了?某条线程执行了多久?某条线程执行的时候我们期望的数据是否已经赋值完毕?无法得知,我们能做的只是等待这条多线程的任务执行完毕而已。而Callable+Future/FutureTask却可以获取多线程运行的结果,可以在等待时间太长没获取到需要的数据的情况下取消该线程的任务,真的是非常有用。

//实现了callable
ThreadDemo td = new ThreadDemo();
 //1.执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。
FutureTask<Integer> result = new FutureTask<>(td);
new Thread(result).start();
 //2.接收线程运算后的结果
try {
    Integer sum = result.get();  //FutureTask 可用于 闭锁 类似于CountDownLatch的作用,在所有的线程没有执行完成之后这里是不会执行的
    System.out.println(sum);
    System.out.println("------------------------------------");
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

  
  

5. 现在有线程 T1、T2 和 T3。你如何确保 T2 线程在 T1 之后执行,并且 T3 线程在 T2 之后执行?

可以用 Thread 类的 join 方法实现这一效果。

  • t.join()方法阻塞调用此方法的线程(calling thread),直到线程t完成,此线程再继续
  • join方法必须在线程start方法调用之后调用才有意义。这个也很容易理解:如果一个线程都没有start,那它也就无法同步了。
  • join方法的原理就是调用相应线程的wait方法进行等待操作的,例如A线程中调用了B线程的join方法,则相当于在A线程中调用了B线程的wait方法,当B线程执行完(或者到达等待时间),B线程会自动调用自身的notifyAll方法唤醒A线程,从而达到同步的目的。
public class JoinThreadDemo02 {
	public static void main(String[] args) {
		Thread t1 = new Thread(new Runnable() {
			public void run() {
				for (int i = 0; i < 20; i++) {
					System.out.println("t1,i:" + i);
				}
			}
		});
		Thread t2 = new Thread(new Runnable() {
			public void run() {
				try {
					t1.join();
				} catch (Exception e) {
					// TODO: handle exception
				}
				for (int i = 0; i < 20; i++) {
					System.out.println("t2,i:" + i);
				}
			}
		});
		Thread t3 = new Thread(new Runnable() {
			public void run() {
				try {
					t2.join();
				} catch (Exception e) {
					// TODO: handle exception
				}
				for (int i = 0; i < 20; i++) {
					System.out.println("t3,i:" + i);
				}
			}
		});
		t1.start();
		t2.start();
		t3.start();
	}
}

6. Java 中新的 Lock 接口相对于同步代码块(synchronized block)有什么优势?如果让你实现一个高性能缓存,支持并发读取和单一写入,你如何保证数据完整性。

多线程和并发编程中使用 lock 接口的最大优势是它为读和写提供两个单独的锁,可以让你构建高性能数据结构,比如 ConcurrentHashMap 和条件阻塞。
1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;

2)Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

1.用法不一样。synchronized既可以加在方法上,也可以加载特定的代码块上,括号中表示需要锁的对象。而Lock需要显示地指定起始位置和终止位置。synchronzied是托管给jvm执行的,Lock锁定是通过代码实现的。 2.在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。 3.锁的机制不一样。synchronized获得锁和释放的方式都是在块结构中,而且是自动释放锁。而Lock则需要开发人员手动去释放,并且必须在finally块中释放,否则会引起死锁问题的发生。 4.Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现; 5.synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁; 6.Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。Lock可以提高多个线程进行读操作的效率。

详情请点击
详情请点击

7. Java 中 wait 和 sleep 方法有什么区别?

sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,
将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复(线程回到就绪状态,请参考第66题中的线程状态转换图)。
wait()是Object类的方法,调用对象的wait()方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的notify()方法或notifyAll()方法时才能唤醒等待池中的线程进入等锁池(lockpool),如果线程重新获得对象的锁就可以进入就绪状态。

8.1 线程同步互斥的5种方式

https://blog.csdn.net/hard_working1/article/details/52760729

8.2 进程同步互斥的5种方式

临界区(Critical Section):适合一个进程内的多线程访问公共区域或代码段时使用
互斥量 (Mutex):适合不同进程内多线程访问公共区域或代码段时使用,与临界区相似。
事件(Event):通过线程间触发事件实现同步互斥
信号量(Semaphore):与临界区和互斥量不同,可以实现多个线程同时访问公共区域数据,原理与操作系统中PV操作类似,先设置一个访问公共区域的线程最大连接数,每有一个线程访问共享区资源数就减一,直到资源数小于等于零。

9.线程并发库

JDK5增加了并发库Douglea, java.util.current包中提供了对线程的优化和管理

10.线程池

https://blog.csdn.net/qq_36762677/article/details/82904121

待完善 参考原文 ImportNew.com - 一杯哈希不加盐

多线程安全问题
  • 当多个线程共享同一个全局变量或静态变量时,做写操作的时候,可能受到其他线程的干扰,导致数据有问题
  • 必须在线程使用共享资源时给资源“上锁”,以阻挡其他线程的訪问。

问:如何解决多线程之间线程安全问题?
答:使用多线程之间同步synchronized或使用锁(lock)。

目录

(同步保证原子性)synchronized用法|
lock|
死锁|
内存模型

synchronized能保证同一时刻最多只有一个线程执行该代码,以保证完全并发的效果 (通过锁来控制只有一个线程)
对象锁
-方法锁(默认锁对象是this当前实例
-同步代码块锁(自己指定锁对象
类锁
-synchronized修饰的静态方法或指定锁为Class对象

1.synchronized同步代码块

synchronized(处理全局、静态变量时加锁 )
使用synchronized时出现异常虚拟机自动帮你释放锁
[互斥的实现]

import java.util.concurrent.*;
import java.util.*;

public class ThreadTest {
public void g() {
//只能有一个线程访问,必须拿到锁才能访问||多个线程想同步,必须用[同一把锁]
synchronized (this) {//声明方式||此处上锁this
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}//执行完毕,释放锁,抛出异常也会释放锁
}

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">final</span> ThreadTest t <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ThreadTest</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	Thread t1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Runnable</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
		<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
			t<span class="token punctuation">.</span><span class="token function">g</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
		<span class="token punctuation">}</span>
	<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"A"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	Thread t2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Runnable</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
		<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
			t<span class="token punctuation">.</span><span class="token function">g</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
		<span class="token punctuation">}</span>
	<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"B"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	t1<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	t2<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}
/*

  • Output: A0 A1 A2 A3 A4 B0 B1 B2 B3 B4
    */

当一个线程在访问object对象的一个synchronized(this)同步代码块时,其他线程仍然能够访问此对象的非同步代码块。

2.synchronized同步方法

同步方法使用的this锁

import java.util.concurrent.*;
import java.util.*;

public class ThreadTest implements Runnable {
public synchronized void run() { //声明方式
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
public static void main(String[] args) {
ThreadTest t = new ThreadTest();
Thread t1 = new Thread(t, “A”);
Thread t2 = new Thread(t, “B”);
t1.start();
t2.start();
}
}
//Output: A0 A1 A2 A3 A4 B0 B1 B2 B3 B4

要注意的是,全部对象都自己主动含有单一的锁(也称为监视器)。所以当ThreadTest对象调用synchronized方法时,此对象被加锁,这意味着。其它线程不能调用此对象的全部synchronized方法。 可是假设把一个复杂的方法声明为synchronized,会减少程序执行的效率,所以synchronized块是非常好的解决方法。
静态同步代码块用的锁不是this锁,使用当前字节码文件

3.Lock

Lock和synchronized的差别就是:
synchronized:当A和B同一时候要訪问C资源,而A获得了对象C的锁。B将一直等待A释放对C的锁,不能被中断。
Lock:B等待一定时间后,A还不释放C,B就会中断等待。
它的基本使用方法:

	Lock l = new ReentrantLock();
	l.lock();
	try {
	    // access the resource protected by this lock
	} finally {
	    l.unlock();
	}

 
 

从代码也能够看出,l.unlock()在finally{}中。这表示终于会被解锁。

4.死锁

是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去
(1) 因为系统资源不足。
(2) 进程运行推进顺序不合适。
(3) 资源分配不当等。

死锁四个必要条件
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
防止进程在处于等待状态的情况下占用资源。因此,对资源的分配要给予合理的规划。
避免死锁破坏条件之一,最简单的方法就是线程都是以同样的顺序加锁和释放锁,也就是破坏了第四个条件。
面试题请写出一个死锁:

public class DeadLockDemo {
	private static String A="A";
	private static String B="B";
	public static void main(String[] args){
		new DeadLockDemo().deadLock();
	} 
	private void deadLock(){
		Thread threadA=new Thread(new Runnable(){
			@Override
			public void run(){
				synchronized(A){
					try {
						Thread.currentThread().sleep(2000);
					} catch (InterruptedException e) { 
						e.printStackTrace();
					}
					synchronized(B){
						System.out.println("AB");
					}
				}
			}
		});
		Thread threadB=new Thread(new Runnable(){
			@Override
			public void run(){
				synchronized(B){
					try {
						Thread.currentThread().sleep(2000);
					} catch (InterruptedException e) { 
						e.printStackTrace();
					}
					synchronized(A){
						System.out.println("BA");
					}
				}
			}
		});
		threadA.start();
		threadB.start();
	}
}

 
 

5.java内存模型jmm

java内存结构 jvm内存分配
java内存模型:JMM决定一个线程对共享变量的写入时,能对另一个线程可见
从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:

线程之间的共享变量存储在主内存(main memory)中,
每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。

本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
这里写图片描述
多线程三大特性:原子性(保证线程),可见性,有序性(join,wait,notify)

6.volatile关键字

volatile关键字,使用场景?原子性的理解?先行发生原则?
Volatile 关键字的作用是变量在多个线程之间可见。
不保证原子性.
举例说明::

class ThreadVolatileDemo extends Thread {
	//flag变量可以设成volatile
	public    boolean flag = true;
	@Override
	public void run() {
		System.out.println("开始执行子线程....");
		while (flag) {
		}
		System.out.println("线程停止");
	}
	public void setRuning(boolean flag) {
		this.flag = flag;
	}

}

public class ThreadVolatile {
public static void main(String[] args) throws InterruptedException {
ThreadVolatileDemo threadVolatileDemo = new ThreadVolatileDemo();
threadVolatileDemo.start();
Thread.sleep(3000);
threadVolatileDemo.setRuning(false);
System.out.println(“flag 已经设置成false”);
Thread.sleep(1000);
System.out.println(threadVolatileDemo.flag);

<span class="token punctuation">}</span>

}
//output:
//开始执行子线程…
//flag 已经设置成false

已经将结果设置为fasle为什么?还一直在运行呢。
原因:线程之间是不可见的,读取的是副本,没有及时读取到主内存结果。
解决办法使用Volatile关键字将解决线程之间可见性, 强制线程每次读取该值的时候都去“主内存”中取值
仅靠volatile不能保证线程的安全性。(原子性)使用AtomicInteger原子类

public class VolatileNoAtomic extends Thread {
   static int count = 0;
   private static AtomicInteger atomicInteger = new AtomicInteger(0);

@Override
public void run() {
for (int i = 0; i < 1000; i++) {
//等同于i++
atomicInteger.incrementAndGet();
}
System.out.println(count);
}

public static void main(String[] args) {
// 初始化10个线程
VolatileNoAtomic[] volatileNoAtomic = new VolatileNoAtomic[10];
for (int i = 0; i < 10; i++) {
// 创建
volatileNoAtomic[i] = new VolatileNoAtomic();
}
for (int i = 0; i < volatileNoAtomic.length; i++) {
volatileNoAtomic[i].start();
}
}

}

https://www.cnblogs.com/dolphin0520/p/3958019.html


两个线程A,B。A要等待B运行完(或者A超时10秒)再运行


多线程出现问题如何排查
  1. 通过top命令查看当前系统CPU使用情况,定位CPU使用率超过100%的进程ID;
  2. 通过ps aux | grep PID命令进一步确定具体的线程信息;
    https://www.cnblogs.com/zyhxhx/p/4564953.html
        </div>

转自:https://blog.csdn.net/qq_36762677/article/details/82387699

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值