Java多线程

 

JMM内存模型?

Java内存模型是一种规范,用来解决多线程通过共享内存进行通信时数据不一致,编译器和处理器对代码和指令进行重排序而带来的问题,从而保证多线程场景下的原子性,可见性和有序性。共享数据存放在主内存中,每个线程都有一个本地内存,线程读取共享数据时,需要从主内存中拷贝数据副本,更新数据后写回主内存。

JMM有八大原子操作(lock, unlock,read, write,load,store,use,assign)

Volatile关键字的原理和作用?

Volatile的底层原理是缓存一致性协议。即多个cpu从主内存读取某数据到缓冲区中,该数据在执行lock前缀指令的cpu的缓冲区中锁定,那么数据锁定期间,其他cpu无法读写该数据。只有当该数据更新完被写回主内存后,其他cpu通过总线嗅探机制检测到该数据被修改了,然后及时失效自己缓存区中的数据,等到下一次需要用到该数据的时候,重新从主内存中读取该数据到缓冲区中。

Synchronized的原理?

Synchronized修饰代码块的时候,会在代码块的前后生成一个monitorenter和monitorexit指令,当执行monitorenter命令时就会去获取对象的锁,如果对象的锁空闲或者已经有对象锁了,就把锁的计数器+1,当执行monitorexit指令后就把锁的计数器-1,当锁的计数器为0的时候对象锁就被释放了。如果获取对象锁不成功,则进去阻塞等待状态,等待锁被释放后再去竞争对象锁。

Synchronized锁升级?

  1. 首先是无锁状态
  2. 当有一个线程尝试获取对象锁的时候,就会在对象头的markword中记录线程的id,表明这个对象锁偏向该线程。
  3. 当有另一个线程来竞争对象锁的时候,首先会撤销偏向锁,然后给原持有偏向锁的线程的栈中分配锁记录,然后拷贝对象头的markword到锁记录中,通过cas修改对象的锁记录指针指向该线程,这样就将偏向锁升级为了轻量级锁。另一个线程栈中分配锁记录,然后尝试通过自旋的方式cas操作修改对象头的markword的所记录指针指向该线程,如果修改成功,则表明该线程获得了轻量级锁。自旋一定次数后,就撤销轻量级锁,使用操作系统底层的mutex来实现重量级锁。

cas原理以及可能出现的问题?

Cas是基于乐观锁的思想来实现的,主要有三个参数,要更新的值,期望的值和更新后的值

只有当要更新的值等于期望的值时,就把要更新的值更新为更新后的值。

可能出现ABA问题

如何创建线程(四种方式)?

  1. 继承Thread类
  2. 实现Runnable接口
  3. Callable接口和futuretask配合使用
  4. 使用线程池创建

线程的状态和生命周期?

线程的状态:1.new -> runnable -> terminated -> waiting -> time_waiting -> blocked

  1.  

多线程都有哪些锁?

synchronized和ReentrantLock

Synchronized是jvm提供的

ReentrantLock是jdk提供的

ThreadLocal原理?

使用threadlocal存放对象后,对象是线程私有的,可以避免多线程竞争。

Threadlocalmap是threadlocal的静态内部类,entry就是threadlocalmap的静态内部类。

每个线程都有一个threadlocalmap类型的变量threadlocals,threadlocalmap又维护了一个Entry类型的数组。Entry继承WeakReference(ThreadLocal),Entry是一个键值对,键存放的是threadlocal的引用,值就是threadlocal对应的值。Threadlocal的set和get方法其实是对threadlocalmap的Entry进行操作,threadlocalmap相当于一个hash表,解决hash冲突的方法是线性探测。

内存泄漏问题:1.threadlocal推荐使用private static关键字修饰,那么它的生命周期跟线程一样,threadlocal不使用了长期得不到释放。2.threadlocal对象的强引用没了后,entry和threadlocal对象之间是弱引用,会被垃圾回收器回收,但是entry中的value不会被回收掉,所以threadlocal使用完毕需要调用remove方法。

锁和ThreadLocal分别适用什么场景?对于性能要求特别高的场景应该用哪个?

锁:以时间换空间:数据在线程间共享

ThreadLocal则是以空间换时间。:每个线程都有自己的副本。

Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

对于性能要求特别高的场景用threadlocal。

Reentrantlock原理底层实现?

Reentrantlock是基于AQS来实现的。

AQS?

AQS抽象队列同步器,当线程请求资源时,资源空闲,那么就把线程设置为工作状态,并把资源锁定。如果资源被占用,那么需要一套线程阻塞以及唤醒时锁分配机制。

Synchronized和ReentrantLock区别?

Synchronized和Reentrantlock都是加锁阻塞式同步。

  1. Synchronized是由jvm提供的,而reentrantlock则是由jdk提供的.
  2. Synchronized在老版本性能不高,优化升级后跟reentrantlock的性能差不多
  3. Synchronized不可中断(只有获取到锁之后才能中断,等待锁时不可中断。),而reentrantlock等待可以中断
  4. Reentrantlock可以用来实现公平锁。
  5. Reentrantlock配置Condition条件类,使用起来比synchronized更灵活。

Synchronized不可中断(只有获取到锁之后才能中断,等待锁时不可中断。):

  • 若线程被中断前,如果该线程处于非阻塞状态(未调用过wait,sleep,join方法),那么该线程的中断状态将被设为true, 除此之外,不会发生任何事。
  • 若线程被中断前,该线程处于阻塞状态(调用了wait,sleep,join方法),那么该线程将会立即从阻塞状态中退出,并抛出一个InterruptedException异常,同时,该线程的中断状态被设为false, 除此之外,不会发生任何事。

ReentrantLock实现原理:

ReentrantLock是基于AQS来实现的,AQS是一个抽象队列同步器,使用了模板设计模式,已经制定好了acquire,acquiredshared,release,releasedShared四个主要的模板方法。子类只需要实现它的tryacquire,tryacquredshared,tryrelease,tryreleasedShared即可。

以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。

 

java线程同步?

  1. synchronized同步方法
  2. Synchronized同步代码块
  3. Volatile关键字修饰变量+cas操作
  4. Reentrantlock
  5. Threadlocal
  6. 阻塞队列
  7. 使用原子变量实现线程同步

CountDownlatch的用法?

再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。

如何让一个线程终止?

  1. 使用退出标志。(推荐)
  2. thread.stop(可能会产生预料不到的结果)
  3. Thread.interrupt()1.线程处于阻塞状态,比如使用了sleep()则抛出异常。
  4. 使用while(!isInterrupted)来判断线程是否被中断。

BlockingQueue?

BlockingQueue是双缓冲队列。BlockingQueue内部使用两条队列,允许两个线程同时向队列一个存储,一个取出操作。在保证并发安全的同时,提高了队列的存取效率。

  • ArrayBlockingQueue(int i):规定大小的BlockingQueue,其构造必须指定大小。其所含的对象是FIFO顺序排序的。

  • LinkedBlockingQueue()或者(int i):大小不固定的BlockingQueue,若其构造时指定大小,生成的BlockingQueue有大小限制,不指定大小,其大小有Integer.MAX_VALUE来决定。其所含的对象是FIFO顺序排序的。

  • PriorityBlockingQueue()或者(int i):类似于LinkedBlockingQueue,但是其所含对象的排序不是FIFO,而是依据对象的自然顺序或者构造函数的Comparator决定。

  • SynchronizedQueue():特殊的BlockingQueue,对其的操作必须是放和取交替完成。

  • DelayedWorkQueue() 延时队列

ArrayBlockingQueue和LinkedBlockingQueue区别,使用的场景?

ArrayBlockingQueue和LinkedBlockingQueue最大的区别还是有界和无界的区别,所以ArrayBlockQueue适合任务量不多的场景比如人事系统人员变动后,其他依赖应用进行数据同步。(人员变动不是很频繁)

LinkedBlockQueue适合任务量大的场景比如邮件/短信提醒这种任务。

典型的线程池特点:

1.newFixedThreadPool(创建一个定长线程池)

corePoolSize = n,maximumPoolSize = n

keepAliveTime = 0,workQueue=LinkedBlockingQueue(无界阻塞队列)

适用:执行长期的任务,性能好很多

2.newSingleThreadExecutor(创建一个单线程的线程池)

corePoolSize = 1, maxinumPoolSize = 1,

keepAliveTime = 0, workQueue= LinkedBlockingQueue(无界阻塞队列)

适用:一个任务一个任务执行的场景

3.newCachedThreadPool(创建一个可缓存线程池)

corePoolSize = 0, maximumPoolSize = Integer.MAX_VALUE;

keepAliveTime=60L,workQueue=SynchronousQueue(同步队列)

适用:执行很多短期异步的小程序或者负载较轻的服务器

4.newScheduledThreadPool(创建一个定时或者延时执行任务的定长线程池)

corePoolSize = n, maximumPoolSize = Integer.MAX_VALUE,

keepAlivetime=0,workQueue = DelayedWorkQueue(延时队列)

适用:周期性执行任务的场景

线程在线程池中的状态?

  1. running 既接受新任务也处理已经添加的任务
  2. Shutdown 不接受新任务但处理已经添加的任务
  3. Stop 不接受新任务,也不处理已经添加的任务
  4. Tidying 所有任务终止
  5. Terminated 线程池终止

用户向线程池请求线程后线程池的处理过程?

线程池参数和作用,拒绝策略有哪些?

  1. 核心线程数
  2. 最大线程数
  3. 线程工厂
  4. 阻塞队列
  5. Keep alive time 空闲线程存活时间 (一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定)
  6. 时间单位unit
  7. 拒绝策略
  1. AbortPolicy 直接抛出拒绝异常
  2. CallerRunsPolicy 只要线程池未关闭,直接在调用者线程中运行当前被丢弃的任务。
  3. DiscardOldestPolicy,丢弃队列中最老的,然后再尝试提交新任务
  4. DiscardPolicy,丢弃无法加载的任务。

 

合理配置线程池中各个参数

1.corePoolSize:核心线程数(最重要)

对于Cpu密集型任务(大量需要cpu计算) corePoolSize = cpu核数 + 1

对于IO密集型任务(大量I/O操作,CPU利用率很低) 线程数可以尽可能的多 

推荐:corePoolsize = cpu核数 + 1或者corePoolSize = CPU核数/(1-阻塞系数 ) 阻塞系数在(0.8-0.9)之间

2.其他参数根据实际场景来设置

 

多线程手撕代码

1.打印奇偶数

方法1:
import java.util.concurrent.atomic.AtomicInteger;

public class Main{
    private static AtomicInteger num = new AtomicInteger(1);

    public static void main(String[] args) {
        new Thread(()->{
            while (num.intValue() < 100) {
                if (num.intValue() % 2 == 1) {
                    System.out.println("奇数线程:" + num.intValue());
                    num.incrementAndGet();
                }
            }
        }).start();

        new Thread(()->{
            while (num.intValue() < 100) {
                if (num.intValue() % 2 == 0) {
                    System.out.println("偶数线程:" + num.intValue());
                    num.incrementAndGet();
                }
            }
        }).start();
    }
}

方法2:
public class PrintABVolatile implements IPrintAB {
    private static final int MAX_PRINT_NUM = 100;
    private static volatile int count = 0;
​
    @Override
    public void printAB() {
        new Thread(() -> {
            while (count < MAX_PRINT_NUM) {
                if (count % 2 == 0) {
                    log.info("num:" + count);
                    count++;
                }
            }
        }).start();
​
        new Thread(() -> {
            while (count < MAX_PRINT_NUM) {
                if (count % 2 == 1) {
                    log.info("num:" + count);
                    count++;
                }
            }
        }).start();
    }
}

2.一个线程打印1-26,另一个线程打印A-Z

public class Main{

    private static BlockingDeque<Integer> bq1 = new LinkedBlockingDeque<>(1);
    private static BlockingQueue<Character> bq2 = new LinkedBlockingDeque<>(1);

    public static void main(String[] args) {
        new Thread(()->{
            for (int i = 1; i <= 26; i++) {
                try {
                    bq1.put(i);
                    System.out.print(bq2.take());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(()->{
            for (char i = 'A'; i <= 'Z'; i++) {
                try {
                    System.out.print(bq1.take());
                    bq2.put(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

3.消费者-生产者模式

public class Main{
    private static ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);

    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            while (true) {
                try {
                    Integer o = queue.take();
                    if (o != null) {
                        System.out.println(Thread.currentThread().getName() + "消费了" + o);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                if (queue.offer(i)) {
                    System.out.println(Thread.currentThread().getName() + "生产了" + i);
                }
            }
        });
        t1.start();
        t2.start();
    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值