多线程

147 阅读3分钟

这是我的第一篇掘金博客,开启掘金写作之路。

Java中的volatile 变量是什么?

volatile是一个特殊的修饰符,只有成员变量才能使用它。在Java并发程序缺少同步类的情况下,多线程对成员变量的操作对其它线程是透明的。volatile变量可以保证下一个读取操作会在前一个写操作之后发生。线程都会直接从内存中读取该变量并且不缓存它。这就确保了线程读取到的变量是同内存中是一致的。

如何在两个线程间共享数据?

  • 如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据。
  • 使用ThreadLocal

例子

import java.util.Random;
​
/**
 * @author libin
 * @Date 2021/5/22 10:47
 **/
public class ThreadLocalTest {
​
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
​
    public static void main(String[] args) {
​
​
        for (int i = 0; i < 2; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int data = new Random().nextInt();
                    System.out.println(Thread.currentThread().getName() + " put random data:" + data);
                    threadLocal.set(data);
                    new A().get();
                    new B().get();
​
                }
            }).start();
​
        }
​
    }
​
    static class A {
        public void get() {
            int data = threadLocal.get();
​
            System.out.println("A from " + Thread.currentThread().getName() + " get data:" + data);
​
        }
    }
​
    static class B {
        public void get() {
            int data = threadLocal.get();
            System.out.println("B from " + Thread.currentThread().getName() + " get data:" + data);
​
        }
    }
}

Java中notify 和 notifyAll有什么区别?

这又是一个刁钻的问题,因为多线程可以等待单监控锁,Java API 的设计人员提供了一些方法当等待条件改变的时候通知它们,但是这些方法没有完全实现。notify()方法不能唤醒某个具体的线程,

Java中interrupted 和 isInterruptedd方法的区别?

interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除而后者不会。Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。当中断线程调用静态方法Thread.interrupted()来检查中断状态时,中断状态会被清零。而非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛出InterruptedException异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其它线程调用中断来改变。

如何避免死锁?

Java多线程中的死锁

死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。这是一个严重的问题,因为死锁会让你的程序挂起无法完成任务,死锁的发生必须满足以下四个条件:

  • 互斥条件:一个资源每次只能被一个进程使用。
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

怎么检测一个线程是否拥有锁?

在java.lang.Thread中有一个方法叫holdsLock(),它返回true如果当且仅当当前线程拥有某个具体对象的锁。

Java中ConcurrentHashMap的并发度是什么?

ConcurrentHashMap把实际map划分成若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度获得的,它是ConcurrentHashMap类构造函数的一个可选参数,默认值为16,这样在多线程情况下就能避免争用。

Java多线程中调用wait() 和 sleep()方法有什么不同?

Java程序中wait 和 sleep都会造成某种形式的暂停,它们可以满足不同的需要。wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时它会释放锁,而sleep()方法仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁。需要注意的是,sleep()并不会让线程终止,一旦从休眠中唤醒线程,线程的状态将会被改变为Runnable,并且根据线程调度,它将得到执行。

创建线程池

第一种 CustomizableThreadFactory Spring 框架提供的 CustomizableThreadFactory。

ThreadFactory springThreadFactory = new CustomizableThreadFactory("springThread-pool-");
    
​
ExecutorService exec = new ThreadPoolExecutor(1, 1,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(10),springThreadFactory);
exec.submit(() -> {
    logger.info("--记忆中的颜色是什么颜色---");
});

第二种 ThreadFactoryBuilder Google guava 工具类 提供的 ThreadFactoryBuilder ,使用链式方法创建。

 ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("retryClient-pool-").build();
        ExecutorService singleThreadPool = new ThreadPoolExecutor(5,10,10L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>(1024),threadFactory,new ThreadPoolExecutor.AbortPolicy());
        //调用生成pdf
        singleThreadPool.execute(()->{
          //业务逻辑
            }
        });
        singleThreadPool.shutdown();

第三种 BasicThreadFactory Apache commons-lang3 提供的 BasicThreadFactory.

    ThreadFactory basicThreadFactory = new BasicThreadFactory.Builder()
            .namingPattern("basicThreadFactory-").build();
​
    ExecutorService exec = new ThreadPoolExecutor(1, 1,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>(10),basicThreadFactory );
    exec.submit(() -> {
        logger.info("--记忆中的颜色是什么颜色---");
    });

线程池中submit()和execute()方法有什么区别

1、接收的参数不一样。exucute只能执行实现Runnable接口的线程,submit可以执行实现Runnable接口或Callable接口的线程

2、submit有返回值,而execute没有

jdk自带的四种拒绝策略

(1)ThreadPoolExecutor.AbortPolicy 丢弃任务,并抛出 RejectedExecutionException 异常。

(2)ThreadPoolExecutor.CallerRunsPolicy:该任务被线程池拒绝,由调用 execute方法的线程执行该任务。

(3)ThreadPoolExecutor.DiscardOldestPolicy : 抛弃队列最前面的任务,然后重新尝试执行任务。

(4)ThreadPoolExecutor.DiscardPolicy,丢弃任务,不过也不抛出异常。

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略。

阻塞队列

队列

从有界无界上分

常见的有界队列为

ArrayBlockingQueue 基于数组实现的阻塞队列

LinkedBlockingQueue 其实也是有界队列,但是不设置大小时就是无界的。

ArrayBlockingQueue 与 LinkedBlockingQueue 对比一哈

ArrayBlockingQueue 实现简单,表现稳定,添加和删除使用同一个锁,通常性能不如后者

LinkedBlockingQueue 添加和删除两把锁是分开的,所以竞争会小一些

SynchronousQueue 比较奇葩,内部容量为零,适用于元素数量少的场景,尤其特别适合做交换数据用,内部使用 队列来实现公平性的调度,使用栈来实现非公平的调度,在Java6时替换了原来的锁逻辑,使用CAS代替了

常见的无界队列

ConcurrentLinkedQueue 无锁队列,底层使用CAS操作,通常具有较高吞吐量,但是具有读性能的不确定性,弱一致性——不存在如ArrayList等集合类的并发修改异常,通俗的说就是遍历时修改不会抛异常

PriorityBlockingQueue 具有优先级的阻塞队列

DelayedQueue 延时队列,使用场景