多线程面试

多线程

1.进程间的常见通信

  1. 无名管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。例如:Linux中的cat 后面加|(就是匿名管道),前一部分的输出作为后一部分的输入。
  2. 命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
  3. 消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  4. 共享内存+信号量:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间 。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
  5. 信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  6. 套接字Socket:套解口也是一种进程间通信机制,更多的是用于网络之间的进程传输。
  7. 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。(例如可以告诉CPU,硬盘不要处理其他东西了,马上处理出问题的东西)。

2.Java中sleep,wait,yield,join的区别

方法作用
sleep()方法sleep()在指定时间内让当前正在执行的线程暂停执行,使线程进入阻塞状态,但是不会释放锁,释放CPU
wait()方法在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的“锁标志”,释放CPU
yield方法暂停当前正在执行的线程对象,yield()只是使当前线程重新回到就绪状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行,yield()只能使同优先级或更高优先级的线程有执行的机会,不释放放锁,释放cpu.
join方法等待调用join方法的线程结束,再继续执行。释放锁,释放cpu.

使用:

sleep(1000);
yield();
thread1.wait();
thread1.notifyAll();
thread1.notify();
thread1.join();

join方法

等待该线程终止。

等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测。

publicclass Test {
        publicstaticvoid main(String[] args) {
                Thread t1 = new MyThread1();
                t1.start(); 

                for (int i = 0; i < 20; i++) {
                        System.out.println("主线程第" + i +"次执行!");
                        if (i > 2)try { 
                                //t1线程合并到主线程中,主线程停止执行过程,转而执行t1线程,直到t1执行完毕后继续。
                                t1.join(); 
                        } catch (InterruptedException e) {
                                e.printStackTrace(); 
                        } 
                } 
        } 
} 

class MyThread1 extends Thread { 
        publicvoid run() {
                for (int i = 0; i < 10; i++) {
                        System.out.println("线程1第" + i + "次执行!");
                } 
        } 
}

join(long),能够释放当前线程的锁,其他线程可以调用次线程中的同步方法。

sleep(long)不能释放锁。

## 3.内核态与用户态

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

3.1内核态和用户态

内核态:当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。

用户态:当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。即此时处理器在特权级最低的(3级)用户代码中运行。

简单来讲一个进程由于执行系统调用而开始执行内核代码,我们称该进程处于内核态中.例如:调用read( ),write( ),open( )等系统函数就是系统调用。

一个进程执行应用程序自身代码则称该进程处于用户态.

intel x86 架构的 CPU 分为好几个运行级别,从 0–3 , 0 为最高级别, 3 为最低级别

当操作系统刚引导时, CPU 处于实模式,这时就相当于是 0 级,于是操作系统就自动得到最高权限,然后切到保护模式时就是0级,这时操作系统就占了先机,成为了最高级别的运行者,由于你的程序都是由操作系统来加载的,所以当它把你加载上来后,就把你的运行状态设为 3 级

3.2从用户态如何转化到内核态

  1. 系统调用
  2. 异常
  3. 外围设备的中断

这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的

3.3用户空间和内核空间

用户空间就是用户进程所在的内存区域,相对的,系统空间就是操作系统占据的内存区域。用户进程和系统进程的所有数据都在内存中。

4.ThreadLocal

ThreadLocal的作用主要是做数据隔离填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,如何防止自己的变量被其它线程篡改。

  1. ThreadLocal针对的是一个线程的全局变量,而不是针对整个程序的全局变量。

ThreadLocal使用:

public class Main1 {
    static ThreadLocal threadLocal = new ThreadLocal();

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(()->{
            System.out.println(threadLocal.get());
            threadLocal.set(0);
            System.out.println(threadLocal.get());
        });

        Thread thread2 = new Thread(()->{
            System.out.println(threadLocal.get());
            threadLocal.set(1);
            System.out.println(threadLocal.get());
        });
        thread1.start();
        thread1.join();;
        thread2.start();

    }
}
//result
/*
null 
0
null
1
*/

ThreadLocal底层原理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DL26k24c-1636120957686)(/Users/suhang/Library/Application Support/typora-user-images/image-20210828215402136.png)]

  1. 每次使用完要移除:例如:t1.remove();
  2. ThreadLocalMap中的Entry节点类是继承于弱引用的,gc的时候就会被清除,防止内存溢出。

ThreadLocal在保存的时候会把自己当做Key存在ThreadLocalMap中,正常情况应该是key和value都应该被外界强引用才对,但是现在key被设计成WeakReference弱引用了。

这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。

就比如线程池里面的线程,线程都是复用的,那么之前的线程实例处理完之后,出于复用的目的线程依然存活,所以,ThreadLocal设定的value值被持有,导致内存泄露。

如何解决内存泄露:

在代码的最后使用remove就好了,我们只要记得在使用的最后用remove把值清空就好了。

 static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

使用场景ThreadLocal使用场景:

1.解决权限问题:

@Component
public class MyFilter extends OncePerRequestFilter {
    public static ThreadLocal<Integer> auth = new ThreadLocal<>();
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        auth.set(0);
        if (httpServletRequest.getHeader("Authorization").equals("1")){
            auth.set(1);
        }
        doFilter(httpServletRequest,httpServletResponse,filterChain);
    }
}
@GetMapping("/")
public ResponseEntity index(){
    try{
        if(MyFilter.auth.get()==0){
            return ResponseEntity.ok("没有权限");
        }else{
            return ResponseEntity.ok("有权限");
        }finally{
            MyFilter.auth.remove();//必须要清除,在线程池中,下次线程在使用的时候,会访问前世的内容。
        }
        }
}

5.线程池的四种创建方式

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

5.1 newCachedThreadPool executor [ɪɡˈzekjətər]

newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程:

public class test11 {
    public static void main(String[] args) {
        //1.创建可缓存的线程池,可重复利用
        ExecutorService executor = Executors.newCachedThreadPool();
        //创建10个线程
        for(int i=0;i<10;i++){
            int temp=i;
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名:"+Thread.currentThread().getName()+" ,i:"+temp);
                }
            });
        }
        executor.shutdown();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0rGHrTMb-1636120957686)(/Users/suhang/Library/Application Support/typora-user-images/image-20210829105807178.png)]

可以看到本来创建了10个线程池,这里只用了5个,因为newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程

5.2 newFixedThreadPool

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

public class test22 {
    public static void main(String[] args) {
        //1.创建可缓存的线程池,可重复利用
        ExecutorService executor = Executors.newFixedThreadPool(3);
        //创建10个线程
        for(int i=0;i<10;i++){
            int temp=i;
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名:"+Thread.currentThread().getName()+" ,i:"+temp);
                }
            });
        }
        executor.shutdown();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XOj0cSQn-1636120957687)(/Users/suhang/Library/Application Support/typora-user-images/image-20210829111948403.png)]

可以看到newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

5.3 newScheduledThreadPool [ˈskedʒuːld]

创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下:

public static void main(String[] args) {
        //1.创建可定时线程池
        ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            newScheduledThreadPool.schedule(new Runnable() {
                public void run() {
                    System.out.println("i:" + temp);
                }
            }, 3, TimeUnit.SECONDS);
        }

    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bFeAwUBw-1636120957688)(/Users/suhang/Library/Application Support/typora-user-images/image-20210829112901051.png)]

表示延迟3秒执行。

5.4 newSingleThreadExecutor

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:

public class test44 {
    public static void main(String[] args) {
        //1.创建单线程
        ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            newSingleThreadExecutor.execute(new Runnable() {

                @Override
                public void run() {
                    System.out.println("index:" + index);
                    try {
                        Thread.sleep(200);
                    } catch (Exception e) {
                        // TODO: handle exception
                    }
                }
            });
        }
        newSingleThreadExecutor.shutdown();
    }

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QghEj83r-1636120957688)(/Users/suhang/Library/Application Support/typora-user-images/image-20210829113330585.png)]

注意: 结果依次输出,相当于顺序执行各个任务。当shutdown时停止线程

但是这四种是阿里巴巴不推荐使用的。

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 3, 20, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>());

6.顺序输出ABC问题

6.1单例线程池实现

public class test44 {
    public static class thread11 implements Runnable{
        @Override
        public void run() {
            char [] ch= {'A','B','C'};
            for (int i = 0; i < 100; i++) {
                final int index = i;
                int temp = i;
                System.out.println("index:" + ch[temp %3]);
//                try {
//                    Thread.sleep(200);
//                } catch (Exception e) {
//                    // TODO: handle exception
//                }
            }
        }
    }
    public static void main(String[] args) {
        //1.创建单线程
        thread11 thread = new thread11();
        new Thread(thread).start();
        ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
        newSingleThreadExecutor.execute(thread);
        newSingleThreadExecutor.shutdown();
    }

}

6.2线程计数器

public class ThreadDemo4 implements Runnable{
    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(3);  //线程计数器

    private static Integer currentCount = 0;

    private static final Integer MAX_COUNT = 30;

    private static String [] chars = {"a", "b", "c"};

    private String name;

    public ThreadDemo4(String name) {
        this.name =  name;
    }

    @Override
    public void run() {
        while(currentCount<MAX_COUNT){
            while(this.name.equals(chars[currentCount%3]))
                printAndPlusOne(this.name + "\t" + currentCount);
            try {
                cyclicBarrier.await();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    public void printAndPlusOne(String name){
        System.out.println(name);
        currentCount ++;
    }

    public static void main(String [] args){
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 3, 20, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>());
        threadPoolExecutor.execute(new ThreadDemo4("a"));
        threadPoolExecutor.execute(new ThreadDemo4("b"));
        threadPoolExecutor.execute(new ThreadDemo4("c"));
        threadPoolExecutor.shutdown();
    }
}

计算机网络
Java基础
垃圾回收
IO流
Mysql
异常
Spring

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值