临界区
一个程序运行多个线程是没问题的,问题出现在多个线程访问同一个共享资源
多线程访问同一个共享资源也没有问题,问题在于线程再执行读写操作时会有指令交错,那么就会出现问题
一段代码如果存在线程对共享资源的访问,那么就称这段代码块为临界区
static int counter = 0; static void increment() // 临界区 { counter++; } static void decrement() // 临界区 { counter--; }
竞争条件
多个线程在临界区执行时,它们的执行顺序是不清楚的,所以才会有产生竞争条件
synchronized互斥锁解决方案
应用之互斥
为了避免临界区的竞争条件发生,有多种手段可以达到目的
阻塞式的解决方案:synchronized、lock
非阻塞式的解决方案:原子变量
而synchronized(对象锁),采用了互斥的方式让同一时刻只有一个线程才能拥有对象锁,其他线程再想获取这个对象锁时就会阻塞住,这样就能保证拥有锁的线程可以安全执行临界区内的代码,不用担心线程上下文之间的切换
这里注意一个点:
互斥是保证临界区的竞争条件发生时,同一时刻只有一个线程去执行
同步是由于线程执行的先后顺序不同,需要一个线程等待其他线程运行到某个点
其实使用synchronized就是用对象锁来保证了临界区的代码块的原子性,临界区的代码对外是不可分割的,不会被线程切换所打断
如果把 synchronized(obj) 放在 for 循环的外面,如何理解?-- 原子性
如果 t1 synchronized(obj1) 而 t2 synchronized(obj2) 会怎样运作?-- 锁对象
如果 t1 synchronized(obj) 而 t2 没有加会怎么样?如何理解?-- 锁对象
变量的线程安全分析
成员变量和静态变量是否线程安全?
如果它们没有被共享,那么就线程安全;如果它们被共享了,并且只进行读操作,那么就是线程安全,如果进行写操作,就是线程不安全
局部变量是否线程安全?
局部变量是线程安全的;但局部对象引用的对象未必是线程安全的,如果该对象没有逃离方法的作用区域,那么就是线程安全的,如果逃离 方法的作用区域,就是线程不安全的
常见的线程安全类:
Wait&Notify
sleep和wait的区别:
1、sleep是Thread方法,而wait是Object方法
2、sleep不需要强制和synchronized一起使用,而wait需要和synchronized一起使用
3、sleep时线程不会释放锁,而wait会使线程释放锁
Park和UnPark:
它们是LockSupport类的方法,目的是暂停当前线程,当是不会释放锁,会阻塞其他同步线程运行
这里调用这些方法需要重新理解线程状态转换
ReentranLock
相对于synchronized它有这方面特点:
可中断、可设置超时时间、可设置为公平锁、支持多个条件变量
基本语法为:
// 获取锁 reentrantLock.lock(); try { // 临界区 } finally { // 释放锁 reentrantLock.unlock(); }
条件变量
synchronized只能有一个条件变量去控制线程的指向,当线程获取不了这个条件变量时,那么就会发生阻塞;而ReentranLock可以拥有多个
条件变量,