多线程面试题

*、易混概念:什么是java内存结构?什么是java内存模型?

  • java内存结构属于jvm内存分配;
  • java内存模型属于多线程可见性,决定一个线程对于另一个线程是否可见;
  • Java内存模型:主内存(存放共享的全局变量) 、私有本地内存(本地线程私有变量);

*、什么是线程安全?解释一下多线程的线程安全问题?

多个线程共享同一个全局变量,对全局变量进行修改的时候,可能会受到其它线程的干扰,导致数据发生错误。

解决多个线程数据安全问题 - 加锁: synchronized、lock;    

多线程(加锁)缺点: 降低程序效率、阻塞、抢锁、效率不高

*、多线程5中运行状态。

*、创建一个多线程:

class Thread2 implements Runnable {
	@Override
	public void run() {
		for (int i = 0; i < 30; i++) {
			System.out.println("子Thread1: +"+i);
		}
	}
	
}

public class Demo2 {
	public static void main(String[] args) {
		Thread2 t2 = new Thread2();
		//==implements Runnable 与  extends Thread 调用方式不同
		Thread t = new Thread(t2);
		t.start();
		for (int i = 0; i < 30; i++) {
			System.err.println("主Demo1: +"+i);
		}
	}
}

*、面试题:a、b、c三个线程,如何让a先执行完,再执行b 执行完,再执行c?

回答:使用 join() 方法;

a线程 调用 b线程的join(),a线程会等待b线程执行完毕之后再执行。

public static void main(String[] args) {
	Thread t1 = new Thread(new Runnable() {
		public void run() {
			for (int i = 0; i < 30; i++) {
				System.out.println("【a子线程】: +"+i);
			}
		}
	});
	Thread t2 = new Thread(new Runnable() {
		public void run() {
			for (int i = 0; i < 30; i++) {
				try {
					t1.join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("【bb子线程】: +"+i);
			}
		}
	});
	Thread t3 = new Thread(new Runnable() {
		public void run() {
			for (int i = 0; i < 30; i++) {
				try {
					t2.join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("【ccc子线程】: +"+i);
			}
		}
	});
	t1.start();
	t2.start();
	t3.start();
}

*、面试题:多线程写一个火车票程序。

class Th implements Runnable {
	
	private static AtomicInteger count = new AtomicInteger(0);

	@Override
	public void run() {
		while(count.intValue()<=50){
			synchronized ("ok") {
				if (count.incrementAndGet()<=50) {
					System.out.println(Thread.currentThread().getName()+"出售第"+count.intValue()+"张车票");
				} else {
					System.err.println(Thread.currentThread().getName()+"火车票卖完了");
				}
			}
		}
	}
}

public class 火车票 {

	public static void main(String[] args) {
		
		new Thread(new Th(), "【窗口1】").start();
		new Thread(new Th(), "【窗口2】").start();
		new Thread(new Th(), "【窗口3】").start();
		
	}
}

*、Lock与synchronized有以下区别:

  1. Lock是一个接口;可以知道线程有没有拿到锁;必须手动释放锁(通常在finally中释放);可以让等待锁的线程响应中断;Lock是块范围内的;
  2. synchronized是关键字;不知道线程有没有拿到锁;会自动释放锁(代码执行完毕或者抛出异常由jvm放弃锁),不能够手动释放锁;线程会一直等待下去;synchronized能锁住类、方法和代码块;

Lock能提高多个线程读操作的效率。

释放对象的锁有两种情况:

  • 程序执行完同步代码块会释放代码块。
  • 程序在执行同步代码块是出现异常,JVM会自动释放锁去处理异常。

*、多线程之间的通讯方式

回答一:

参考:https://www.cnblogs.com/lgyxrk/p/10404839.html

  1. 同步:这里讲的同步是指多个线程通过synchronized关键字这种方式来实现线程间的通信。
  2. while轮询的方式:线程A不断地改变条件,线程ThreadB不停地通过while语句检测这个条件(list.size()==5)是否成立 ,从而实现了线程间的通信。
  3. wait / notify机制:当条件未满足时(list.size() !=5),线程A调用wait() 放弃CPU,并进入阻塞状态;当条件满足时,线程B调用 notify()通知 线程A,所谓通知线程A,就是唤醒线程A,并让它进入可运行状态。
  4. 管道通信:使用java.io.PipedInputStream 和 java.io.PipedOutputStream进行通信

回答二:

参考:https://blog.csdn.net/jisuanji12306/article/details/86363390

  1. 使用 volatile 关键字
  2. 使用Object类的wait() 和 notify() 方法

  3. 使用JUC工具类 CountDownLatch

  4. 使用 ReentrantLock 结合 Condition

  5. 基本LockSupport实现线程间的阻塞和唤醒


*、Runnable接口和Callable接口的区别。

返回值:Runnable没有返回值,Callable有返回值;

异常:Runnable没有容错机制,意味着如果出现异常必须立即处理;Callable有容错机制,意味着出现异常之后可以向上抛出;

启动方式:Runnable可以通过Thread来启动,也可以通过线程池的execute.submit来处理;Callable线程只能通过线程池的submit来处理


*、Java锁的种类

悲观锁、乐观锁、分段锁、重入锁、读写锁、CAS无锁、自旋锁、排他锁、分布式锁

乐观锁 和 悲观锁:

  • 悲观锁: 每次取数据的时候,都会上锁;
  • 乐观锁: CAS无锁机制————版本标识;
  • 区别:  1、如果查询数量小,可以使用悲观锁;2、乐观锁使用版本控制;

重入锁 与 非重入锁 区别: 

  • 重入锁也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响;
  • 非重入锁 死锁;

读写锁的机制: ReentrantReadWriteLock

static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock r = rwl.readLock();
static Lock w = rwl.writeLock();

CAS无锁机制:与自旋锁配合使用;

CAS体系中的三个参数:CAS(V,E,N):

V表示要更新的变量,E表示预期值,N表示新值。

仅当V值等于E值时,才会将V的值设为N,

如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。

最后,CAS返回当前V的真实值。

自旋锁: 让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时 才能进入临界区。


*、线程池

线程池的核心是 ThreadPoolExecutor

创建线程池的方法:java.util.concurrent.Executors

  • Executors.newCachedThreadPool(),创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  • Executors.newSingleThreadScheduledExecutor(),创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
  • Executors.newFixedThreadPool(4),创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • Executors.newScheduledThreadPool(3),创建一个定长线程池,支持定时及周期性任务执行。

线程池配置多少合适?

  • 方法一: CPU密集:线程数和cpu核数相同;
  • 方法二:IO密集:2*cpu数;

*、说几个 java.util.concurrent.* 下的工具类(高级篇)

concurrent是 util 下的并发包。

java.util.concurrent.ConcurrentHashMap   

  • 比 HashMap 线程安全;

java.util.concurrent.CountDownLatch

  • 实现计数功能;可以使线程按顺序执行,比join()书写简便
/**
 * CountDownLatch 可以使线程按顺序执行,比join()书写简便
 *
 */
public class TestCountDownLatch {
	public static void main(String[] args) throws InterruptedException {
		CountDownLatch countDownLatch = new CountDownLatch(2);
		new Thread(new Runnable() {

			@Override
			public void run() {
				System.out.println("子线程," + Thread.currentThread().getName() + "开始执行...");
				countDownLatch.countDown();// 每次减去1
				System.out.println("子线程," + Thread.currentThread().getName() + "结束执行...");
			}
		}).start();
		new Thread(new Runnable() {

			@Override
			public void run() {
				System.out.println("子线程," + Thread.currentThread().getName() + "开始执行...");
				countDownLatch.countDown();
				System.out.println("子线程," + Thread.currentThread().getName() + "结束执行...");
			}
		}).start();
		System.out.println("等待子线程执行完毕...");

		countDownLatch.await();// 如果countDownLatch不为0,一直等待
		System.out.println("两个子线程执行完毕....");
		System.out.println("继续主线程执行..");
	}
}

java.util.concurrent.CyclicBarrier   

  • 始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。
  • 当线程数达到了这个数目时,所有进入等待状态的线程被唤醒并继续。
  • cyclicBarrier.await() 控制所有线程都执行完毕,然后才继续执行
/**
 * cyclicBarrier.await() 控制所有线程都执行完毕,然后才继续执行
 *
 */
class Write extends Thread {
	
	private CyclicBarrier cyclicBarrier;
	public Write(CyclicBarrier cyclicBarrier){
		 this.cyclicBarrier=cyclicBarrier;
	}
	
	@Override
	public void run() {
		System.out.println("线程" + Thread.currentThread().getName() + ",正在写入数据");
		try {
			Thread.sleep(3000);
		} catch (Exception e) {
			// TODO: handle exception
		}
		System.out.println("线程" + Thread.currentThread().getName() + ",写入数据成功.....");
		
		try {
			cyclicBarrier.await();
		} catch (InterruptedException | BrokenBarrierException e) {
			e.printStackTrace();
		}
		
		System.err.println("所有线程执行完毕..........");
	}
}
public class TestCyclicBarrier {

	public static void main(String[] args) {
		CyclicBarrier cyclicBarrier=new CyclicBarrier(5);
		for (int i = 0; i < 5; i++) {
			new Write(cyclicBarrier).start();
		}
			
	}
}

java.util.concurrent.Semaphore

  • new Semaphore(5); 可以用来构建一些对象池,
  • semaphore.acquire(); //申请资源
  • semaphore.release();// 释放资源
/**
 * new Semaphore(5); 可以用来构建一些对象池,
 * semaphore.acquire(); //申请资源
 * semaphore.release();// 释放资源
 */
class Parent implements Runnable {

	Semaphore wc;
	String name;

	public Parent(String name, Semaphore wc) {
		this.name = name;
		this.wc = wc;
	}

	@Override
	public void run() {
		try {
			// 剩下的资源(剩下的茅坑)
			int availablePermits = wc.availablePermits();
			if (availablePermits > 0) {
				System.out.println(name + "天助我也,终于有茅坑了...");
			} else {
				System.out.println(name + "怎么没有茅坑了...");
			}
			// 申请茅坑 如果资源达到3次,就等待
			wc.acquire();
			System.out.println(name + "终于轮我上厕所了..爽啊");
			Thread.sleep(new Random().nextInt(1000)); // 模拟上厕所时间。
			System.out.println(name + "厕所上完了...");
			wc.release();

		} catch (Exception e) {

		}

	}
}

public class TestSemaphore {
	public static void main(String[] args) {
		Semaphore semaphore = new Semaphore(3);
		for (int i = 1; i <= 10; i++) {
			Parent parent = new Parent("第" + i + "个人,", semaphore);
			new Thread(parent).start();
		}

	}
}

java.util.concurrent.ConcurrentLinkedDeque 和 BlockingQueue

  • ConcurrentLinkedDeque 不阻塞、无界;
  • BlockingQueue 可以阻塞、有界;
/**
 * peek();	//消费后不删除元素
 * poll();	//消费后删除元素
 */
public class TestConcurrentLinkedDeque {
	
	public static void main(String[] args) {
		//无界队列
		ConcurrentLinkedDeque<Object> quene = new ConcurrentLinkedDeque<>();
		
		quene.offer("码云");
		quene.offer("张杰");
		quene.offer("艾姐");
		
		System.out.println(quene.size());
		System.out.println(quene.peek());	//消费后不删除元素
		System.out.println(quene.poll());	//消费后删除元素
		System.out.println(quene.peek());
		System.out.println(quene.peek());
		System.out.println(quene.size());
		
//		BlockingQueue s = new BlockingQueue(3);
	}

}

*、Java并发解决方式?并发包有哪些?(高级篇)

原子类(D2 - D6)、lock、并发包;


*、new ThreadLocal()

ThreadLocal为每一个线程提供一个局部变量

//class ResNum {
//	public int num = 0;
//	
//	public String getNum(){
//		num = num + 1;
//		return num+"";
//	}
//}

class ResNum {
	public int num = 0;
	
	//ThreadLocal为每一个线程提供一个局部变量
	public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
		protected Integer setInitialValue() {
			return 0;
		}
	};
	
	public Integer getNum(){
		int num = threadLocal.get()+1;
		threadLocal.set(num);
		return num;
	}
}

class Thread2 extends Thread {
	
	private ResNum resNum;
	
	public Thread2(ResNum resNum) {
		this.resNum = resNum;
	}
	
	@Override
	public void run() {
		for (int i = 0; i < 5; i++) {
			System.err.println(getName()+"num:"+resNum.getNum());
		}
	}
}

public class TestThreadLocal {
	public static void main(String[] args) {
		ResNum res = new ResNum();
		
		new Thread2(res).start();
		new Thread2(res).start();
		new Thread2(res).start();
	}
}

*、wait与sleep区别?

wait用于同步中,可以释放锁资源; sleep 不会释放锁的资源;

wait可以被notify唤醒;sleep是时间到期及被唤醒;

sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。

wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。


*、wait() 和 notify()

wait让当前线程从运行状态 变成 休眠状态;

notify让当前线程从休眠状态 变成 运行状态;


*、什么多线程之间通讯?

多个线程 对 同一个共享资源,每个线程对共享资源的操作不同;

多线程通讯的应用: 生产者 与 消费者。 消息中间件。


*、三大特性之原子性和可见性 —— volatile关键字 和 AtomicInteger

volatile关键字: 保证线程之间可见,但不保证原子性、不安全;

new AtomicInteger():可以保证线程之间可见,并且保证原子性;

以下是 volatile 关键字实例:

class Thread1 extends Thread {
	
	private volatile boolean flag = true;	//volatile关键字,强制刷新
//	private boolean flag = true;

	public void setFlag(boolean flag){
		this.flag = flag;
	}
	
	private int i = 0;
	
	@Override
	public void run() {
		System.out.println("子线程开始执行,"+"此时flag:"+flag);
		while (flag) {
			System.err.println(i+"   flag: "+flag);
			i++;
		}
		System.out.println("子线程结束....,"+"此时flag:"+flag);
	}
	
}

public class VolatileTest {
	public static void main(String[] args) throws InterruptedException {
		Thread1 t = new Thread1();
		t.start();
		
		Thread.sleep(20);
		t.setFlag(false);
		
	}
}

以下是 AtomicInteger 实例:

class Thread3 extends Thread {
	
//	private volatile static int count = 0;	//volatile 不能保证数据原子性, 改为下面 AtomicInteger方式
	private static AtomicInteger count = new AtomicInteger(0);
	
	@Override
	public void run() {
		for (int i = 0; i < 1000; i++) {
//			count++;
			count.incrementAndGet();
		}
		System.out.println(count);
	}
	
}

public class AtomicIntegetTest {
	public static void main(String[] args) throws InterruptedException {
		Thread3 [] t = new Thread3[10];
		for (int i = 0; i < t.length; i++) {
			t[i] = new Thread3();
		}
		for (int i = 0; i < t.length; i++) {
			t[i].start();
		}
	}
}

*、三大特性之有序性 —— join()方法

主线程 调用 子线程的join(),主线程会等待子线程执行完毕之后  再执行。

public static void main(String[] args) {
	
	Thread t1 = new Thread(new Runnable() {
		public void run() {
			for (int i = 0; i < 30; i++) {
				try {
					Thread.sleep(300);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("【子线程】: +"+i);
			}
		}
	});
	t1.start();
	
	for (int i = 0; i < 10; i++) {
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		if (i==3) {
			try {
				t1.join();	//主线程 调用 子线程的join(),主线程会等待子线程执行完毕之后  再执行。
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.err.println("主线程: +"+i);
	}
	System.err.println("【主线程执行完毕....】");
}

*、多线程三大特性?

原子性、可见性、有序性。

  • 原子性: 独一无二,保证线程安全;
  • 可见性: Java 内存模型;
  • 有序性: join、wait、notify(多线程之间通讯)

*、什么是守护线程、非守护线程?

守护线程(gc线程)随着主线程一起销毁;非守护线程(用户线程)和主线程互不影响;

public static void main(String[] args) {
	
	Thread t = new Thread(new Runnable() {	//用户线程(非守护线程)
		public void run() {
			for (int i = 0; i < 30; i++) {
				try {
					Thread.sleep(300);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("【子线程】: +"+i);
			}
		}
	});
	t.start();
	
	for (int i = 0; i < 10; i++) {
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.err.println("主线程: +"+i);
	}
	System.err.println("【主线程执行完毕....】");
}

1、创建多线程的方法

  • 以下是继承 thread 类
class Thread1 extends Thread {
	
	@Override
	public void run() {
		for (int i = 0; i < 30; i++) {
			System.out.println("Thread1: +"+i);
		}
	}
}

public class Demo1 {
	
	public static void main(String[] args) {
		Thread1 t1 = new Thread1();
//		t1.run();	//直接调用,属于同步执行,没有开启线程  所以不会异步
		t1.start();
		for (int i = 0; i < 30; i++) {
			System.err.println("Demo1: +"+i);
		}
	}
}
  • 以下是实现 Runnable 接口:
class Thread2 implements Runnable {
	@Override
	public void run() {
		for (int i = 0; i < 30; i++) {
			System.out.println("子Thread1: +"+i);
		}
	}
	
}

public class Demo2 {
	public static void main(String[] args) {
		Thread2 t2 = new Thread2();
		//==implements Runnable 与  extends Thread 调用方式不同
		Thread t = new Thread(t2);
		t.start();
		for (int i = 0; i < 30; i++) {
			System.err.println("主Demo1: +"+i);
		}
	}
}
  • 以下是使用匿名内部类的方式创建多线程
public class Demo3 {
	public static void main(String[] args) {
		
		Thread t = new Thread(new Runnable() {
			public void run() {
				for (int i = 0; i < 30; i++) {
					System.out.println("子Thread1: +"+i);
				}
			}
		});
		t.start();
		
		for (int i = 0; i < 30; i++) {
			System.err.println("主Demo3: +"+i);
		}
	}
}

*、什么是线程安全?解释一下多线程的线程安全问题?

多个线程共享同一个全局变量,对全局变量进行修改的时候,可能会受到其它线程的干扰,导致数据发生错误。

解决多个线程数据安全问题 - 加锁: synchronized、lock;    

多线程(加锁)缺点: 降低程序效率、阻塞、抢锁、效率不高

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值