多线程面试题

1.什么是线程

线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之:一个软件中至少有一个应用程序,应用程序的一次运行就是一个进程,一个进程中至少有一个线程。

2.线程和进程有什么区别

  1. 进程是系统进⾏资源分配的基本单位,有独⽴的内存地址空间
  2. 线程是CPU独⽴运⾏和独⽴调度的基本单位,没有单独地址空间,有独⽴的栈,局部变量,寄存器, 程序计数器等。
  3. 创建进程的开销⼤,包括创建虚拟地址空间等需要⼤量系统资源
  4. 创建线程开销⼩,基本上只有⼀个内核对象和⼀个堆栈。
  5. ⼀个进程⽆法直接访问另⼀个进程的资源;同⼀进程内的多个线程共享进程的资源。
  6. 进程切换开销⼤,线程切换开销⼩;进程间通信开销⼤,线程间通信开销⼩。
  7. 线程属于进程,不能独⽴执⾏。每个进程⾄少要有⼀个线程,成为主线程

3.如何在java中实现线程

在这里插入图片描述

4.用Runnable还是Thread

class ThreadTest implements Runnable {
	private int tickets = 10;
 
	public void run() {
		while (true) {
			if (tickets > 0) {
				System.out.println(Thread.currentThread().getName()
						+ " is saling ticket " + tickets--);
			} else {
				break;
			}
		}
	}
}
 
public class ThreadDemo1 {
 
	public static void main(String[] args) {
		ThreadTest t = new ThreadTest();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
	}
}

在这里插入图片描述
解决方法很简单,一是用synchronized这种内置锁,二是用AtomicInteger这样的concurrent包里封装好的元素,简洁起见我用第二种实现如下:

import java.util.concurrent.atomic.AtomicInteger;
 
//public class MyThread extends Thread {  //两种写法一样
public class MyThread implements Runnable{
 
    private AtomicInteger tickets = new AtomicInteger(10);
 
    @Override
    public void run() {
        while (true) {
        	int ticketnum;
            if ((ticketnum = tickets.getAndDecrement()) > 0) {
                System.out.println(Thread.currentThread().getName() + " is saling ticket: "
                        + ticketnum);
            } else {
            	break;
            }
        }
    }
 
    public static void main(String[] args) {
 
        MyThread mt = new MyThread();
        Thread t1 = new Thread(mt, "Win1");
        Thread t2 = new Thread(mt, "Win2");
        Thread t3 = new Thread(mt, "Win3");
 
        t1.start();
        t2.start();
        t3.start();
    }
}

在这里插入图片描述

5.Thread类中的strat()和run()有什么区别

在这里插入图片描述

6.如何在Java中创建线程安全的Singleton?

饿汉式:在类初始化时就直接创建单例对象,而类初始化过程是没有线程安全问题的

形式一:

/*
public class HungryOne{
    public static final HungryOne INSTANCE = new HungryOne();
    private HungryOne(){}
}*/
public enum HungryOne{
    INSTANCE
}

形式二:

package com.atguigu.single.hungry;

public class HungrySingle {
    private static final HungrySingle INSTANCE = new HungrySingle();
    private HungrySingle(){}
    public static HungrySingle getInstance(){
        return INSTANCE;
    }
}

测试类:

package com.atguigu.single.hungry;

public class TestHungry {
    public static void main(String[] args) {
        HungryOne h1 = HungryOne.INSTANCE;
        HungryOne h2 = HungryOne.INSTANCE;
        System.out.println(h1 == h2);

        System.out.println("----------------------");
        HungrySingle s1 = HungrySingle.getInstance();
        HungrySingle s2 = HungrySingle.getInstance();
        System.out.println(s1 == s2);
    }
}

懒汉式:延迟创建对象,第一次调用getInstance方法再创建对象

形式一:

package com.atguigu.single.lazy;

public class LazyOne {
    private static LazyOne instance;

    private LazyOne(){}

    public static synchronized LazyOne getInstance(){
        if(instance == null){
            instance = new LazyOne();
        }
        return instance;
    }

    //有指令重排问题
/*    public static LazyOne getInstance(){
        if(instance == null){
            synchronized (LazyOne.class) {
                try {
                    Thread.sleep(10);//加这个代码,暴露问题
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(instance == null){
                    instance = new LazyOne();
                }
            }
        }

        return instance;
    }*/
}

形式二:

package com.atguigu.single.lazy;

public class LazySingle {
    private LazySingle instance;
    private LazySingle(){}
    private static class Inner{
        static final LazySingle INSTANCE = new LazySingle();
    }
    public static LazySingle getInstance(){
        return Inner.INSTANCE;
    }
}
package com.atguigu.single.lazy;

import org.junit.Test;

public class TestLazy {
    @Test
    public void test01(){
        LazyOne s1 = LazyOne.getInstance();
        LazyOne s2 = LazyOne.getInstance();

        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s1 == s2);
    }

    //把s1和s2声明在外面,是想要在线程的匿名内部类中为s1和s2赋值
    LazyOne s1;
    LazyOne s2;
    @Test
    public void test02(){
        Thread t1 = new Thread(){
            public void run(){
                s1 = LazyOne.getInstance();
            }
        };
        Thread t2 = new Thread(){
            public void run(){
                s2 = LazyOne.getInstance();
            }
        };

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s1 == s2);
    }


    LazySingle obj1;
    LazySingle obj2;
    @Test
    public void test03(){
        Thread t1 = new Thread(){
            public void run(){
                obj1 = LazySingle.getInstance();
            }
        };
        Thread t2 = new Thread(){
            public void run(){
                obj2 = LazySingle.getInstance();
            }
        };

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(obj1);
        System.out.println(obj2);
        System.out.println(obj1 == obj2);
    }
}

7.Java中的notify和notifyAll有什么区别

  1. notify:则选取所通知对象的 wait set 中的一个线程释放;
  2. notifyAll:则释放所通知对象的 wait set 上的全部线程。
    注意: notify可能会导致死锁,而notifyAll则不会

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

在这里插入图片描述

9.什么是线程池,为什么要使用它

线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用 new
线程而是直接去池中拿线程即可,节省了开辟子线程的时间,提高的代码执行效率

线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。
主要特点:线程复用;控制最大并发数:管理线程。
第一:降低资源消耗。通过重复利用己创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进 行统一的分配,调优和监控

10.如何写代码来解决生产者消费者问题

1、一个厨师一个服务员问题

案例:有家餐馆的取餐口比较小,只能放10份快餐,厨师做完快餐放在取餐口的工作台上,服务员从这个工作台取出快餐给顾客。现在有1个厨师和1个服务员。

package com.atguigu.thread5;

public class TestCommunicate {
	public static void main(String[] args) {
		// 1、创建资源类对象
		Workbench workbench = new Workbench();

		// 2、创建和启动厨师线程
		new Thread("厨师") {
			public void run() {
				while (true) {
					workbench.put();
				}
			}
		}.start();

		// 3、创建和启动服务员线程
		new Thread("服务员") {
			public void run() {

				while (true) {
					workbench.take();
				}
			}
		}.start();
	}

}

// 1、定义资源类
class Workbench {
	private static final int MAX_VALUE = 10;
	private int num;

	public synchronized void put() {
		if (num >= MAX_VALUE) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		num++;
		System.out.println(Thread.currentThread().getName() + "制作了一份快餐,现在工作台上有:" + num + "份快餐");
		this.notify();
	}

	public synchronized void take() {
		if (num <= 0) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		num--;
		System.out.println(Thread.currentThread().getName() + "取走了一份快餐,现在工作台上有:" + num + "份快餐");
		this.notify();
	}
}


2、多个厨师多个服务员问题

案例:有家餐馆的取餐口比较小,只能放10份快餐,厨师做完快餐放在取餐口的工作台上,服务员从这个工作台取出快餐给顾客。现在有多个厨师和多个服务员。

package com.atguigu.thread5;

public class TestCommunicate2 {
	public static void main(String[] args) {
		// 1、创建资源类对象
		WindowBoard windowBoard = new WindowBoard();

		// 2、创建和启动厨师线程
		// 3、创建和启动服务员线程
		Cook c1 = new Cook("张三",windowBoard);
		Cook c2 = new Cook("李四",windowBoard);
		Waiter w1 = new Waiter("小红",windowBoard);
		Waiter w2 = new Waiter("小绿",windowBoard);
		
		c1.start();
		c2.start();
		w1.start();
		w2.start();
	}

}
//1、定义资源类
class WindowBoard {
	private static final int MAX_VALUE = 10;
	private int num;

	public synchronized void put() {
		while (num >= MAX_VALUE) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		num++;
		System.out.println(Thread.currentThread().getName() + "制作了一份快餐,现在工作台上有:" + num + "份快餐");
		this.notifyAll();
	}

	public synchronized void take() {
		while (num <= 0) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		num--;
		System.out.println(Thread.currentThread().getName() + "取走了一份快餐,现在工作台上有:" + num + "份快餐");
		this.notifyAll();
	}
}


//2、定义厨师类
class Cook extends Thread{
	private WindowBoard windowBoard;
	
	public Cook(String name,WindowBoard windowBoard) {
		super(name);
		this.windowBoard = windowBoard;
	}

	public void run(){
		while(true) {
			windowBoard.put();
		}
	}
}


//3、定义服务员类
class Waiter extends Thread{
	private WindowBoard windowBoard;
	
	public Waiter(String name,WindowBoard windowBoard) {
		super(name);
		this.windowBoard = windowBoard;
	}

	public void run(){
		while(true) {
			windowBoard.take();
		}
	}
}

11.如何避免死锁

不同的线程分别锁住对方需要的同步监视器对象不释放,都在等待对方先放弃时就形成了线程的死锁。一旦出现死锁,整个程序既不会发生异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

public class TestDeadLock {
	public static void main(String[] args) {
		Object g = new Object();
		Object m = new Object();
		Owner s = new Owner(g,m);
		Customer c = new Customer(g,m);
		new Thread(s).start();
		new Thread(c).start();
	}
}
class Owner implements Runnable{
	private Object goods;
	private Object money;

	public Owner(Object goods, Object money) {
		super();
		this.goods = goods;
		this.money = money;
	}

	@Override
	public void run() {
		synchronized (goods) {
			System.out.println("先给钱");
			synchronized (money) {
				System.out.println("发货");
			}
		}
	}
}
class Customer implements Runnable{
	private Object goods;
	private Object money;

	public Customer(Object goods, Object money) {
		super();
		this.goods = goods;
		this.money = money;
	}

	@Override
	public void run() {
		synchronized (money) {
			System.out.println("先发货");
			synchronized (goods) {
				System.out.println("再给钱");
			}
		}
	}
}

12.Java中活锁和死锁有什么区别

在这里插入图片描述

13.怎样检测一个线程是否拥有锁

在这里插入图片描述

14.JVM中那个参数是用来控制线程的栈堆大小

java.lang.Thread#holdsLock 方法

15.java中synchronized和ReentrantLock有什么不同

在这里插入图片描述

16.多线程保证线程安全一般有几种方式?

三种方式

17.Synchronized加在方法上和加在对象上什么区别?

在这里插入图片描述

18.Synchronized加在静态方法 普通方法区别

在这里插入图片描述

19.Sleep 和 yeld区别

在这里插入图片描述

20.wait和sleep的区别,他们两个谁会释放锁

在这里插入图片描述

21.创建线程的方式哪些?创建线程池的方式哪些?参数是什么?

创建线程 :
(1)继承 Thread 类创建线程类

class Mythread extends Thread{


    @Override
    public void run() {
        System.out.println("使用继承Thread创建线程");
    }
}


public class TestDemo12 {

    public static void main(String[] args) {
        Thread thread=new Mythread();
        thread.start();

    }}

(2)通过 Runnable 接口创建线程类

class Mythread implements Runnable{


    @Override
    public void run() {
        System.out.println("使用实现Runnable接口创建线程");
    }
}


public class TestDemo12 {

    public static void main(String[] args) {
    
        Thread thread=new Thread(new Mythread());
        thread.start();
    }
    }

(3)通过 Callable 和 Future

class Mythread implements Callable {

    @Override
    public Object call() throws Exception {
        System.out.println("使用callable接口创建线程");
        return null;
    }
}
public class TestDemo12 {

    public static void main(String[] args) {
    
        Thread thread=new Thread(new FutureTask(new Mythread()));
        thread.start();

    }


4.使用匿名内部类创建,重写run()方法,这是比较常用的一种方式


    new Thread(){
        @Override
        public void run() {
            System.out.println("使用匿名内部类创建线程");
        }
    }.start();

还有一种启动方式


    Thread thread=new Thread(){
        @Override
        public void run() {
            System.out.println("不同的启动方式");
        }
    };
    thread.start();

JAVA线程池的三大创建方式和七大参数

22.线程的创建方式,怎样获取当前线程的某些状态或者值

继承Thread类;
实现Runnable接口
实现Callable接口;
线程池;

在这里插入图片描述
详情点击
42.多线程了解吗?假如有两个线程,子线程的异常父线程可以接收到吗?

 线程设计理念:“线程的问题应该由线程本身解决,而不应该委托到外部”
如果想要捕获到子线程的异常可以使用Thread的静态方法:
         Thread.setDefaultUncaughtExceptionHandle
        (new MyUncaughtExceptionHandle());

public class ThreadHandler {
    private volatile static boolean  flag = false;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{throw new NullPointerException();},"threadHandler");
        Thread.UncaughtExceptionHandler handler = (thread,e)->{
            flag = true;
            System.out.println("threadhandler:"+e);
        };
        t.setUncaughtExceptionHandler(handler);
        t.start();
        t.join();
        if(flag){
            System.out.println(true);
        }else {
            System.out.println(false);
        }
        System.out.println("===main===");
    }

}

在这里插入图片描述

23.进程和线程,能否多进程,进程和进程是如何通信的

进程和线程以及进程间通信的方式

24.线程的生命周期

观点1:5种状态(JDK1.5之前)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
观点2:6种状态(JDK1.5之后):
在这里插入图片描述
在这里插入图片描述

25.线程的状态和唤醒机制

在这里插入图片描述
在这里插入图片描述

26.java线程安全的类

引入百度文库

27.乐观锁和悲观锁有什么区别

在这里插入图片描述

28.threadlocal原理

在这里插入图片描述

29.runnable和callable区别

在这里插入图片描述

30.synchronized原理

在这里插入图片描述

31.多线程的状态种,能让线程停止执行的有哪些?

在这里插入图片描述

32.使用多线程可能带来什么问题?

在这里插入图片描述

33.请写出四种常用线程池,并说明用途?

引用于CSDN

34.多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么?

引入CSDN

35.多线程共用一个数据变量需要注意什么?

在这里插入图片描述

36.说下线程池执行任务的原理?

一文读懂线程池的工作原理

37.你们怎么处理多线程,高并发的问题?

在这里插入图片描述

38.线程的生命周期,线程有哪些状态?如何让线程进去阻塞?

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

39.你如何理解线程安全的?在这方面遇到过什么问题?线程安全问题是如何造成的?

引入CSDN

40.你在实际编码过程中如何避免线程安全问题?

/**
 * 通过 Synchronized 锁来保证线程安全
 */
public class SafeThreadServlet extends HttpServlet {
    //把PrintWriter定义为成员变量(这个servlet的不同线程将访问这同一个PrintWriter)
    private PrintWriter pw;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String name = req.getParameter("name");

        //因为PrintWriter对象是成员变量,是多线程共有的
        //所以同时运行的Servlet线程会抢占PrintWriter对象
        //在 getWriter() 之前加上 synchronized 代码块
        //可以保证在第一个Servlet线程在使用完PrintWriter之前,第二个Servlet线程不会抢占PrintWriter
        synchronized (this){
            pw = resp.getWriter();
            try{
                Thread.sleep(5000);
                pw.println(name);
                pw.flush();
                pw.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }   //synchronized 代码块结束
    }
}
/**
 * 通过 Synchronized 锁来保证线程安全
 */
public class SafeThreadServlet extends HttpServlet {
    //把PrintWriter定义为成员变量(这个servlet的不同线程将访问这同一个PrintWriter)
    private PrintWriter pw;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String name = req.getParameter("name");

        //因为PrintWriter对象是成员变量,是多线程共有的
        //所以同时运行的Servlet线程会抢占PrintWriter对象
        //在 getWriter() 之前加上 synchronized 代码块
        //可以保证在第一个Servlet线程在使用完PrintWriter之前,第二个Servlet线程不会抢占PrintWriter
        synchronized (this){
            pw = resp.getWriter();
            try{
                Thread.sleep(5000);
                pw.println(name);
                pw.flush();
                pw.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }   //synchronized 代码块结束
    }
}

41.线程池有什么状态?

在这里插入图片描述

42.线程安全说一下?

在这里插入图片描述

43.Java线程池中submit()和execute()方法有何区别?

在这里插入图片描述

44.线程的基本概念、线程的基本状态以及状态之间的关系?

在这里插入图片描述
在这里插入图片描述

45.描述下线程池的处理流程?

在这里插入图片描述

46.JAVA线程池有哪些参数,如果自己设计一个线程池要考虑哪些问题?

在这里插入图片描述

47.进程的通信方式有哪些?

48.为什么用线程池?

我们知道不用线程池的话,每个线程都要通过 new Thread(xxRunnable).start()的方 式来创建并运行一个线程,线程少的话这不会是问题,而真实环境可能会开启多个
13线程让系统和程序达到最佳效率,当线程数达到一定数量就会耗尽系统的 CPU 和 内存资源,也会造成 GC频繁收集和停顿,因为每次创建和销毁一个线程都是要消 耗系统资源的,如果为每个任务都创建线程这无疑是一个很大的性能瓶颈。所以, 线程池中的线程复用极大节省了系统资源,当线程一段时间不再有任务处理时它也 会自动销毁,而不会长驻内存。

49.多进程和多线程的区别?

在这里插入图片描述

50.多线程下读概率远远大于写概率,如何解决并发问题?

在这里插入图片描述
在这里插入图片描述

51.线程池有几种?

在这里插入图片描述
在这里插入图片描述

public class ThreadPoolDemo {

    public static void main(String[] args) {
        // 创建单一线程的连接池
        // ExecutorService threadPool = Executors.newSingleThreadExecutor();
        // ExecutorService threadPool = Executors.newFixedThreadPool(3);
        ExecutorService threadPool = Executors.newCachedThreadPool();

        try {
            for (int i = 0; i < 5; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName() + "执行了业务逻辑");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

52.说一些你知道的这几个线程池的区别,各自的特点。

在这里插入图片描述

53.怎么实现一个线程安全的计数器?

在java中volatile关键字可以保证共享数据的可见性,它会把更新后的数据从工作内存刷新进共享内存,并使其他线程中工作内存中的数据失效,进而从主存中读入最新值来保证共享数据的可见性,实现线程安全的计数器通过循环CAS操作来实现。就是先获取一个旧期望值值,再比较获取的值与主存中的值是否一致,一致的话就更新,不一致的话接着循环,直到成功为止.

54.线程的状态,如何避免死锁,如何让线程同步?

引入CSDN

55.谈一下 Threadlocal 和 ThreadLocalMap?

引入CSDN

56.怎么让等待的10个线程同时执行

//currency:线程数
//创建一个CyclicBarrier对象,下面调用他的方法,让线程等待,直到所有线程开启后一起执行方法,从而造成高并发。
CyclicBarrier cb = new CyclicBarrier(currency);
//CountDownLatch类也是同样的效果,区别在于,CountDownLatch每次线程开启,CountDownLatch.countDown(),申明减少一个。
//CountDownLatch cdn = new CountDownLatch(currency);

//多线程高并发模拟
for(int i = 0;i < currency;i++){
	new Thread(new Runnable(){
		//开启线程后执行的run()方法
		public void run(){
			System.out.println(Thread.currentThread().getName()+"------我准备好了------");
			//等待一起出发
			try{
				//CyckicBarrier等待一起出发
				cb.await();
				/*CountDownLatch的等待方法
				cdn.countDown();
				cdn.await();*/
			}catch(InterruptedException | BrokenBarrierException e){
				e.printStackTrace();
			}
			//调用业务
			//干什么活
			User.show();
		}
	}).start();//开启线程
}


57.runable跟callbale的区别。

在这里插入图片描述

58.线程池为什么能提高性能?

在这里插入图片描述

59.普通int类型的一个变量,使用十个线程操作++,结果是什么?

1,在多线程中我们知道一个问题就是i++操作时,我们得到的结果和我们想象的结果不一样,因为i++操作会被分成三步。分别是取值,++操作,赋值(读,改,写)。当线程执行到取值或者++操作时,线程突然切换,所以最终得到的结果可能有些奇怪!看看下面程序

创建是个线程对i进行++操作

public class AtomicTest {
	public static void main(String[] args) {
		AtomicDemo ad = new AtomicDemo();
		for(int i=0; i<10; i++) {
			new Thread(ad).start();
		}
	}
}
class AtomicDemo implements Runnable {
	private volatile int i = 0; //volatile保证可见性
	@Override
	public void run() {
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+":"+getI());
	}
	public int getI() {
		return i++;
	}
}

在这里插入图片描述
在这里插入图片描述

public class AtomicTest {
	public static void main(String[] args) {
		AtomicDemo ad = new AtomicDemo();
		for(int i=0; i<10; i++) {
			new Thread(ad).start();
		}
	}
}
class AtomicDemo implements Runnable {

	AtomicInteger a = new AtomicInteger(0);
	@Override
	public void run() {
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+":"+getI());
	}
	public int getI() {
		return a.getAndIncrement(); /类似/a++
	}

这样就可以保证i++问题了

60.什么是线程安全的List? 什么是线程安全的Map?

在这里插入图片描述

61.结合实际项目说说你如何设计一个线程池,用了哪些类?

在项目中,我们通常有两种方式创建线程池:

  • 第一种:静态方式
  • 第二种:使用Spring Boot创建线程池

比如说我们项目中需要处理用户登录日志,但是此时不想因为记录登录日志耽搁了登录。
如果我们使用同步的方式,可能会因为一些不太需要实时结果的,并且又耗时的业务可能会导致整个业务变慢:
在这里插入图片描述

耗时:200ms=100ms+100ms
如果使用线程池做了异步化后,直接创建个任务丢到线程池里,这样就减少了后面那100ms的等待时间。
在这里插入图片描述
在实际项目中,也有很多项目使用消息队列来做异步化,这个看项目情况来,比如:开发成本、后期运维成本等。
静态方式
静态方式就是当做一种工具类使用,代码实现如下:

package com.tianwc.myblog.pool;

import com.google.common.util.concurrent.ThreadFactoryBuilder;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
//这里的相关只是一个演示,大家在定参数时,还是要以具体情况来
public class ThreadPoolUtil {
    //获取CPU核数
    static int cpuNums = Runtime.getRuntime().availableProcessors();
    /** 线程池核心池的大小*/
    private static int corePoolSize = 10;
    /** 线程池的最大线程数*/
    private static int maximumPoolSize = cpuNums * 5;
    //阻塞队列容量
    private static int queueCapacity = 100;
    //活跃时间,
    private static int keepAliveTimeSecond = 300;

    public static ExecutorService httpApiThreadPool = null;

    static{
        System.out.println("创建线程数:"+corePoolSize+",最大线程数:"+maximumPoolSize);
        //建立10个核心线程,线程请求个数超过20,则进入队列等待
        httpApiThreadPool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTimeSecond,
                TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(queueCapacity),new ThreadFactoryBuilder().setNameFormat("PROS-%d").build());
    }
}

关于线程池参数设置,可以参考:面试小抄中并发编程部分有详细说明。

在业务代码中的使用:

ThreadPoolUtil.httpApiThreadPool.submit(new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("======== 登录日志记录=====start=======");
                    try {
                        // TODO: 2022/4/14 业务处理
                        Thread.sleep(1000L);
                        System.out.println("userId=" + userId);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("========登录日志记录------end=======");
                }
            }));

剩余部分引入知乎

62.线程池如何定义最大线程数,它的标准是什么?

在这里插入图片描述

63.多线程的创建方式, 锁有哪几种, synchronized和Lock的区别。

在这里插入图片描述
在这里插入图片描述

64.阿里巴巴Java开发手册[强制]线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式的原因是什么?

在这里插入图片描述

65.线程中sleep()和wait()有什么区别?

在这里插入图片描述

66.描述下线程池的处理流程?

在这里插入图片描述

67.线程池有几种状态,原理,一般配置多少?

详情请点击

68.线程间通信方式

69.threadLocal使用场景,存在的问题,底层实现。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

70.谈谈线程安全的常见解决方法

线程不安全:程序在多线程的执行环境下,程序的执行结果与与其结果不相符成为线程不安全。

一、导致线程不安全的原因

1.线程争抢,抢占式执行。

2.多个线程同时修改了同一个变量。

3.操作非原子性操作。

4.内存可见性问题。

5.指令重排序。

二、解决线程安全问题的方法

1.volatile解决指令重排序问题和内存可见性问题。

(1)volatile可以解决指令重排序问题和内存可见性问题,代码在写入volatile修饰变量的时候
改变线程工作内存的volatile变量副本的值 将改变后副本的值从工作内存刷新到主内 (2)代码在读取volatile修饰的变量的时候

从主内存中读取volatile变量最新值到线程的工作内存中 从工作内存中读取volatile变量的副本
2.使用锁解决线程安全问题

主要有两种锁:

内置锁synchroned (1)修饰静态方法

(2)修饰普通方法

(3)修饰代码块

lock()锁

71.守护线程和普通线程区别?怎么手动设置守护线程?

在这里插入图片描述

72.写出进程间数据共享的方式,至少三种。

引入CSDN

73.如何控制某个方法允许并发访问线程的个数

在这里插入图片描述

74.启动一个线程是用run()还是start()?

在这里插入图片描述

75.同步和异步有何异同,在什么情况下分别使用他们?举例说明。

在这里插入图片描述

76.当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?

在这里插入图片描述

77.请说出你所知道的线程同步的方法。

Synchronized 关键字,Lock 锁实现,分布式锁等。

78.多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么?

引入CSDN

79.stop()和suspend()方法为何不推荐使用?

在这里插入图片描述

80.线程的状态转换?怎么中断线程?volatile关键字作用。

Java线程的状态转换 & 如何停止线程
在这里插入图片描述

81.线程同步与阻塞的关系?同步一定阻塞吗?阻塞一定同步的吗?

在这里插入图片描述

82.线程有几个状态,就绪和阻塞有什么不同,知道线程池的实现原理不,核心参数讲一下,讲一下线程池分配线程的方法,项目中用过几种加锁的方法,threadlocal的实现原理。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
常见加锁的方法
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

public class TestWaitAndNotify {
    public static void main(String[] args) {
        Object obj = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    System.out.println("开始等待。。。。");
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("等待结束。。。。");
                }
            }
        }).start();
 
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    // 休眠两秒再唤醒线程
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    obj.notify();
                }
            }
        }).start();
    }
}
 

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

83.两线程对变量i进行加1操作,结果如何?为什么?怎么解决?

84.AQS有什么特点?

85.Java的线程都有哪几种状态

在这里插入图片描述

86.synchronized和volatile区别

引入CSDN

150.多线程,AtomicInteger底层用的啥?cas的原理,AtomicInteger用了Voliate么?voliate的原理,变量加Voliate被修改了其他线程能立刻知道么?

/**
 * @program: mybatis
 * @description: CAS
 * @author: Miller.FAN
 * @create: 2019-11-11 18:24
 **/
public class CASDome {
 
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(5);
        System.out.println(atomicInteger.compareAndSet(5,2018)+"/t current data " + atomicInteger.get());
    }
}
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
 
        return var5;
    }

上面这段代码就有意思了,他是Unsafe类种的一个方法,Unsafe中的方法都是直接调用操作系统底层资源执行相应的任务,即操作系统底层原语,不容许被打断,不会造成所谓的数据不一致的问题。

var5 = this.getIntVolatile(var1,
var2);中var1指当前对象,var2内存偏移地址。结果var5就是获得的内存中的数据。

do {
    var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));    // 拿一下主内存地址中的值与自己保存的快照对比,如果一致就修改,如果不一致放弃修改,再做一次var5 = this.getIntVolatile(var1, var2);,直到主内存中的值与自己的快照值一直,海海皮皮的进行修改跳出循环,return var5+1;

87.如果想实现一个线程安全的队列,可以怎么实现?

引入CSDN

88.哪些方法实现线程安全?

引入CSDN

89.Java 中的同步机制,synchronized 关键字,锁(重入锁)机制,其他解决同步的方 volatile 关键字 ThreadLocal 类的实现原理要懂。

90.synchronized 和 lock 区别

在这里插入图片描述

91.Java线程阻塞调用 wait 函数和 sleep 区别和联系,还有函数 yield,notify 等的作用。

1、sleep()

使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁。也就是说如果有synchronized同步快,其他线程仍然不能访问共享数据。注意该方法要捕捉异常。

例如有两个线程同时执行(没有synchronized)一个线程优先级为MAX_PRIORITY,另一个为MIN_PRIORITY,如果没有Sleep()方法,只有高优先级的线程执行完毕后,低优先级的线程才能够执行;但是高优先级的线程sleep(500)后,低优先级就有机会执行了。

总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。

2、join()

join()方法使调用该方法的线程在此之前执行完毕,也就是等待该方法的线程执行完毕后再往下继续执行。注意该方法也需要捕捉异常。

3、yield()

该方法与sleep()类似,只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级的线程有执行的机会。

4、wait()和notify()、notifyAll()

这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用。synchronized关键字用于保护共享数据,阻止其他线程对共享数据的存取,但是这样程序的流程就很不灵活了,如何才能在当前线程还没退出synchronized数据块时让其他线程也有机会访问共享数据呢?此时就用这三个方法来灵活控制。

wait()方法使当前线程暂停执行并释放对象锁标示,让其他线程可以进入synchronized数据块,当前线程被放入对象等待池中。当调用notify()方法后,将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,只有锁标志等待池中线程能够获取锁标志;如果锁标志等待池中没有线程,则notify()不起作用。

notifyAll()则从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。

注意 这三个方法都是java.lang.Object的方法。

二、run和start()

把需要处理的代码放到run()方法中,start()方法启动线程将自动调用run()方法,这个由java的内存机制规定的。并且run()方法必需是public访问权限,返回值类型为void。

三、关键字synchronized

该关键字用于保护共享数据,当然前提条件是要分清哪些数据是共享数据。每个对象都有一个锁标志,当一个线程访问到该对象,被Synchronized修饰的数据将被"上锁",阻止其他线程访问。当前线程访问完这部分数据后释放锁标志,其他线程就可以访问了。

四、wait()和notify(),notifyAll()是Object类的方法,sleep()和yield()是Thread类的方法。

(1)、常用的wait方法有wait()和wait(long timeout);

void wait() 在其他线程调用此对象的 notify() 方法或者 notifyAll()方法前,导致当前线程等待。

void wait(long timeout)在其他线程调用此对象的notify() 方法 或者
notifyAll()方法,或者超过指定的时间量前,导致当前线程等待。

wait()后,线程会释放掉它所占有的“锁标志”,从而使线程所在对象中的其他shnchronized数据可被别的线程使用。

wait()h和notify()因为会对对象的“锁标志”进行操作,所以他们必需在Synchronized函数或者 synchronized
block 中进行调用。如果在non-synchronized 函数或 non-synchronized block
中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。。

(2)、Thread.sleep(long millis)必须带有一个时间参数。

sleep(long)使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;

sleep(long)可使优先级低的线程得到执行的机会,当然也可以让同优先级的线程有执行的机会;

sleep(long)是不会释放锁标志的。

(3)、yield()没有参数

sleep
方法使当前运行中的线程睡眠一段时间,进入不可以运行状态,这段时间的长短是由程序设定的,yield方法使当前线程让出CPU占有权,但让出的时间是不可设定的。

yield()也不会释放锁标志。

实际上,yield()方法对应了如下操作;先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把CPU的占有权交给次线程,否则继续运行原来的线程,所以yield()方法称为“退让”,它把运行机会让给了同等级的其他线程。

sleep
方法允许较低优先级的线程获得运行机会,但yield()方法执行时,当前线程仍处在可运行状态,所以不可能让出较低优先级的线程此时获取CPU占有权。在一个运行系统中,如果较高优先级的线程没有调用sleep方法,也没有受到I/O阻塞,那么较低优先级线程只能等待所有较高优先级的线程运行结束,方可有机会运行。

yield()只是使当前线程重新回到可执行状态,所有执行yield()的线程有可能在进入到可执行状态后马上又被执行,所以yield()方法只能使同优先级的线程有执行的机会。

92.Java 中的锁是怎么实现的、有什么锁?

引入CSDN

93-,.concurrent包下面,都用过什么?

在这里插入图片描述

94.详细讲一下JUC包有哪些组件,用过哪些?

引入CSDN

95.ReentrantLock如何实现非公平锁的?重点是如何实现“非公平”,和“公平锁”有什么区别?

引入CSDN

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值