Java线程-知识点归纳

目录

1 能确保线程安全的代码写法?

1.1 为什么多线程并发不安全?

1.2 并发不安全的解决思路。

1.3 编码思路

2 三个线程t1、t2、t3如何顺序执行?

3 synchronized、volatile的原理?

3.1 synchronized是什么? 如何保证有序性?

3.2 volatile是什么?如何保证有序性?

4 无锁、偏向锁、轻量级锁、重量级锁的区别?

5 正确的启动和停止线程?

6 JDK提供了那种现成的线程池?


1 能确保线程安全的代码写法?

1.1 为什么多线程并发不安全?

A:

操作系统中进程拥有资源,而线程则不拥有资源。一个进程可以创建多个线程。这些线程共享进程中的资源。当多线程并发执行。对同一个数据进行修改,就可能会造成数据的不一致。
线程执行的数据是由“工作内存“”获取的。而工作内存的数据来源与“主内存”。其中工作内存起到的作用类似于“缓存”。假设现在有个任务需将data=0,执行“+1”操作。开启两个线程执行。正常执行的结果data应该为2。实际data存在1 或者 2。原因线程1执行获取的data数据由主内存加载到工作内存。此时线程2也执行同样的逻辑,线程各自执行完毕后将结果写入工作内存,再各自写回”主内存”,导致线程1的结果被线程2覆盖。因此出现结果为“1”。在并发编程中称为不可见性。

1.2 并发不安全的解决思路。

A:

(1)给共享资源加锁,使的资源在同一时间节点上只由一个线程占用。
(2) 让线程也拥有资源,不取共享进程中的资源。

1.3 编码思路

A:

(1)多实例、多副本(ThreadLocal) ThreadLocal可以为每个线程维护一个私有的本地变量。
(2)使用锁机制 synchronize、lock方式。
(3)使用JUC并发包的工具。

2 三个线程t1、t2、t3如何顺序执行?

public static void main(String[] args) {
    Thread t1 = new Thread(() -> System.out.println("t1"));
    Thread t2 = new Thread(() -> {
        try {
            // 引用t1线程,等待线程1执行完毕再执行
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t2");
    });
    Thread t3 = new Thread(() -> {
        try {
            // 引用t2线程,等待线程1执行完毕再执行
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t3");
    });
    // 顺序不强制
    t3.start();
    t1.start();
    t2.start();
}

3 synchronized、volatile的原理?

3.1 synchronized是什么? 如何保证有序性?

A:
(1)是阻塞式的同步,当一个线程获得对象锁,进入同步块,其他线程访问都必须阻塞在同步块外面等待,进行线程阻塞和唤醒的代价较高。
(2)synchronized经过编译,会在同步块的前后分别形成“monitorenter”、“monitorexit”两个字节码指令。在执行“monitorenter”指令时,首先尝试获取对象锁。如果对象没被锁定或当前线程已经拥有对象锁,把锁的计数器加1.相对的,在执行“monitorexit”指令时会将计数器减1,当计数器为0时,锁释放。

3.2 volatile是什么?如何保证有序性?

A:

volatile修饰的变量(类的成员变量、类的静态成员变量)具有两层语义。
(1)保证了不同线程对同一变量进行操作的可见性,即一个线程修改了变量值,新值对其他线程来说是可感知的,volatile关键字会强制将修改值写入主内存,并使其他线程的工作内存值失效。
(2)禁止指令重排。当程序执行到volatile修饰的变量在读、写操作时,在其前面的操作的更改必然全部执行,且结果对后面的操作可见,在其后面的操作肯定未执行。

4 无锁、偏向锁、轻量级锁、重量级锁的区别?

A:

从JDK1.6版本后,synchronized也在不断优化锁的机制,某些情况下也并非是很重量级的锁。优化机制包括“自适应锁”、“自旋锁”、“锁消除”、“锁粗化”、“轻量级锁”、“偏向锁”
锁的状态从低到高依次为 “无锁” -> ”偏向锁” -> “轻量级锁” -> “重量级锁”,升级的过程就是从低到高。

自旋锁:由于大部分时候,锁占用的时间很短。共享变量的锁定时间也很短。所以没有必要挂起线程。用户态和内核态的来回上下文切换影响性能。自旋的概念就是让线程执行一个忙循环,可以理解为就是啥也不干,防止从用户态转入内核态。自旋锁可以通过设置-XX:+UseSpining来开启,自旋的默认次数10次,可以使用-XX:PreBlockSpin设置。


锁消除:锁消除指的是JVM检测到一些同步的代码块,完全不存在数据竞争的情况,并不需要加锁,会进行锁消除。


自适应锁:自适应锁就是自适应的自旋锁,自旋的时间不是固定时间,而是由前一次在同一个锁上的自旋时间和锁的持有者状态来决定。

锁粗化:锁粗化指的是有很多操作都是对同一个对象进行加锁,就会把锁的同步范围扩展整个操作序列之外。

偏向锁:当线程访问同步块获取锁时,会在对象头和栈帧中的锁记录里存储偏向锁的线程ID,之后这个线程再次进入同步块时不再需要CAS来加锁和解锁。偏向锁会永远偏向第一个获得锁的线程,如果后续没有其他线程获得过这个锁,持有锁的线程就永远不需要进行同步,反之,担忧其他线程竞争偏向锁时,持有偏向锁的线程会释放偏向锁。通过设置-XX:+UseBiasedLocking开启偏向锁。

轻量级锁:JVM对象的对象头中包含一些锁的标志位,代码进入同步块的时候,JVM将会使用CAS方式来尝试获取锁,如果更新成功则会把对象头中的状态位标记为轻量级锁,如果更新失败,则线程尝试自旋获取锁。

5 正确的启动和停止线程?

A:
启动:Runneable和Thread调用的run()方法都是在当前线程执行,调用start()方法的时候,则是另外启动一个线程运行run()里的逻辑。
停止
(1)使用退出标志,使线程正常退出。
(2)使用stop方法强行终止线程(此方法与suspend、resume都可能造成资源未正常释放的问题),不推荐。
(3)使用interrupt方法中断线程。

6 JDK提供了那种现成的线程池?

A:
newSingleThreadExecutor:创建一个单线程的线程池,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
newFixedThreadPool:创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
newCachedThreadPool:创建一个大小无限的线程池,此线程池不会对象池大小做限制。线程池大小完全依赖于操作系统能够创建的最大线程大小。
newSingleThreadExecutor:此线程池支持定时以及周期性执行任务的需求。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值