java多线程

Volatile关键字

Volatile主要使用的场合是在多个线程之间感知实例变量的修改,并且可以获得最新值使用,当线程想要访问volatile修饰的变量时强制从公共堆栈中进行读取。

Volatile可以保证每次线程从主内存中刷新到最新的变量值,但是不能保证变量值被加载到线程内存之后对该变量做的修改操作是原子性的。Volatile变量在线程内存中被修改之后要立即同步回主内存中,以保证其他线程使用该volatile关键字修饰的变量时获取到的是最新的变量值。

也就是说,volatile关键字保证的是变量在不同线程之间的可见性,但是无法保证原子性,对于多个线程访问同一个实例变量还是需要加锁同步。

Synchronized关键字

Synchronized关键字保证在同一时刻,只有一个线程可以执行某个对象内某一个方法或某一段代码块。包含两个特征:互斥性和可见性。Synchronized可以解决一个线程看到对象处于不一致的状态,可以保证进入同步方法或者同步代码块的每个线程都可以看到由同一个锁保护之前所有的修改效果。

两者区别

  • 关键字volatile是线程同步的轻量级实现,volatile只能用于同步变量;而Synchronized可以用于修饰方法和代码块;
  • 多线程访问volatile不会发生阻塞,而Synchronized会发生阻塞;
  • volatile可保证数据的可见性,但是不能保证数据的一致性(原子性),而Synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步。
  • volatile关键字解决的是多线程之间的可见性,而Synchronized解决的是多个线程之间访问资源的同步性。

并发编程中的三个概念(原子、可见、有序)

Java内存模型是围绕着在并发过程中如何处理原子性、可见性和有序性三个特征建立的。

  • 原子性:一个操作要么全部执行完毕 ,要么根本就不执行。Java内存模型直接保证的原子性变量操作有read、load、assign、use、store和write。
  • 可见性:多个线程访问同一个变量时一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。Java语言中的volatile、synchronized和final三个关键字都可保证操作时变量的可见性。
  • 有序性:即程序执行的顺序按照代码的先后顺序执行。Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性。

总之,要想让并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。

常见的线程不安全的类

  • StringBuilder(StringBuffer是线程安全的)
  • SimpleDateFormat(JodaTime是线程安全的,且推荐使用)
  • ArrayList、HashSet、HashMap等

先行发生原则

先行发生是Java内存模型中定义的两项操作之间的偏序关系,如果说操作A先行发生于操作B,那么操作A产生的影响将会被操作B观察到,“影响”包括修改了内存中共享变量的值、发送了消息、修改了变量等。

Java内存模型中定义的一些“天然的”先行发生关系:① 程序次序原则(控制流中的先后顺序)、② 管程锁定原则(同一个锁的unlock发生在下一次lock)、③ volatile变量规则(禁止指令重排优化)、④ 线程启动原则(线程的start操作优先于线程内部的其他所有操作)、④ 线程终止规则(线程内部的所有操作优先于线程终止操作)、⑥ 线程中断原则(对线程interrupt()方法的调用先行发生于被中断检测代码检测到中断的发生)、⑦ 对象终结原则(对象初始化操作先行于finalize操作的发生)、⑧ 传递性(A先行发生于B,B先行发生于C,则A先行发生于C)。

时间先后顺序与先行发生原则之间基本没有太大关系,衡量并发问题的时候不要受到时间上先后发生的干扰,一切以先行发生原则为准。

线程调度

线程调度指的就是给线程分配使用处理器的过程。主要的调度方式有两种:协同式调度和抢占式调度。

协同式调度指的是线程完成自己的任务之后主动通知系统切换到另一个线程上。优点是实现简单,线程对于自己的切换是已知的,不存在线程同步的问题;缺点是如果一个线程一直阻塞占用处理器,则其他线程都会被阻塞。

抢占式调度是由系统来为线程分配使用处理器的时间片。优点:线程的执行时间是系统可控的,不会出现一个线程导致整个进程阻塞的问题。

Java使用的线程调度就是抢占式调度。

公平锁与非公平锁

公平锁是多个线程在等待同一个锁时,必须按照申请锁的时间顺序来一次获得锁(默认的是不公平锁);

非公平锁就是一种获取锁的抢占机制,是随机获得锁的,和公平锁不一样的就是先来的不一定先得到锁。

ThreadLocal

ThreadLocal类解决的是变量在不同线程间的隔离性,每个线程拥有自己的不同值。Java.lang.ThreadLocal类来实现线程本地存储的功能。ThreadLocal为每个使用该变量的线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。

每一个线程的Thread对象中都有一个ThreadLocalMap对象,这个对象存储了一组以ThreadLocal.threadLocalHashCode为键,以本地线程变量为值的K-V值对。ThreadLocal对象就是当前线程的ThreadLocalMap的访问入口,每一个ThreadLocal对象都包含了一个独一无二的threadLocalHashCode的值,使用这个值就可以在线程K-V值对中找回对应的本地线程变量。

get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本,initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法。操作的步骤都是先取得当前线程t,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。然后接着下面获取到<key,value>键值对。

总结:实际上通过ThreadLocal创建的副本是存储在每个线程自己内ThreadLocalMap类型对象中的。ThreadLocalMap的键值为ThreadLocal对象的原因是每个线程中可有多个ThreadLocal变量。

最常见的ThreadLocal使用场景是用来解决数据库连接、Session管理等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值