目录
- 共享问题
- synchronized
- 线程安全分析
- Monitor
- wait / notify
- 线程状态转换
- Lock
Java 并发 两种解决思路:共享模型、非共享模型

共享问题
/**
* 共享问题,由于分时系统
*/
public class Demo1 {
static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (int i = 0; i < 5000; i++) {
count ++;
}
});
Thread t2 = new Thread(()->{
for (int i = 0; i < 5000; i++) {
count --;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);// 结果不一定是个啥
}
}
解析:
Java 中对静态变量的自增/自减操作并不是原则操作,例如对静态变量 i ,进行 i++ 操作,实际会产生如下的 JVM 字节码指令:getstatic i // 获取静态变量 i 的值 iconst_1 // 准备常量 1 iadd // 自增 putstatic i // 将修改后的值存入静态变量 i
Java 内存模型如下,完成静态变量的自增/自减需要在主存和工作内存进行数据交换。如果是单线程,字节码是顺序执行,不会交错,没有问题;



临界区 Critical Section
一段代码块内如果存在对共享资源 的多线程读写操作,称这段代码块为临界区static int counter = 0; static void increment(){// 临界区 counter++; } static void decrement(){// 临界区 counter; }竞态条件
多个线程在临界区内执行,由于代码执行序列不同而导致结果无法预测,称为竞态条件
synchronized
应用之互斥
为了避免临界区的竞态条件发生,有多种手段:
- 阻塞式:synchronized、Lock
- 非阻塞时:原子变量
【对象锁】,采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其他线程要想获取这个【对象锁】时会阻塞住(blocked)。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换。执行完毕后释放【对象锁】并唤醒要获得该锁的阻塞的线程。
/**
* 共享问题 synchronized
*/
public class Demo2 {
static int count = 0;
static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (int i = 0; i < 5000; i++) {
synchronized (lock){
count ++;
}
}
});
Thread t2 = new Thread(()->{
for (int i = 0; i < 5000; i++) {
synchronized (lock){
count --;
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);// 结果是0
}
}



思考
synchronized 实际是用 对象锁 保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换锁打断。
- 如果把 synchronized(obj) 放在 for 循环的外面,如何理解?结果正确。--原子性
- 如果 t1 synchronized(obj1) 而 t2 synchronized(obj2) 会怎样运作?--锁对象(进了不同的房间)
- 如果 t1 synchronized(obj) 而 t2 没有加会怎样?发生上下文切换时,t2 不会去获取对象锁。
/**
* 共享问题 synchronized 面向对象改造
*/
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
Room room = new Room();
Thread t1 = new Thread(()->{
for (int i = 0; i < 5000; i++) {
room.increament();
}
});
Thread t2 = new Thread(()->{
for (int i = 0; i < 5000; i++) {
room.decreament();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(room.getCount());// 结果是
}
}
class Room {
private int count = 0;
public void increament(){
synchronized (this){
count ++;
}
}
public void decreament(){
synchronized (this){
count --;
}
}
public int getCount() {
synchronized (this){
return count;
}
}
}
/**
* 共享问题 synchronized 加在方法上
*/
public class Demo4 {
private static int count = 0;
//加在非静态方法上
public synchronized void increament(){
count ++;
}
//相当于
public void increament1(){
synchronized (this){
count ++;
}
}
//加在静态方法上
public synchronized static void decreament(){
count --;
}
//相当于
public void decreament1(){
synchronized (Demo4.class){
count --;
}
}
}
线程八锁
考察锁的是哪个对象
/**
* 线程八锁一:
* 结果:1 2 或 2 1
* 说明:synchronized 加在 Number 的非静态方法上,锁的是 this 对象,线程t1 t2 使用的同一个 n1 对象;
*/
public class Demo1 {
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{n1.a();},"t1").start();
new Thread(()->{n1.b();},"t2").start();
}
}
class Number {
public synchronized void a(){
System.out.println("1");
}
public synchronized void b(){
System.out.println("2");
}
}
/**
* 线程八锁二:
* 结果:睡眠1s 1 2 或 2 睡眠1s 1
* 说明:synchronized 加在 Number 的非静态方法上,锁的是 this 对象,线程t1 t2 使用的同一个 n1 对象;
*/
public class Demo2 {
public static void main(String[] args) {
Number2 n1 = new Number2();
new Thread(()->{n1.a();},"t1").start();
new Thread(()->{n1.b();},"t2").start();
}
}
class Number2 {
public synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1");
}
public synchronized void b(){
System.out.println("2");
}
}
/**
* 线程八锁三:
* 结果:3 1s 12
* 32/23 1s 1
* 说明:
*/
public class Demo3 {
public static void main(String[] args) {
Number3 n1 = new Number3();
new Thread(()->{n1.a();},"t1").start();
new Thread(()->{n1.b();},"t2").start();
new Thread(()->{n1.c();},"t3").start();
}
}
class Number3 {
public synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1");
}
public synchronized void b(){
System.out.println("2");
}
public void c(){
System.out.println("3");
}
}
/**
* 线程八锁四:
* 结果:2 1s 1
* 说明:线程 t1 t2 锁的不是同一个对象,不存在互斥,并行执行
*/
public class Demo4 {
public static void main(String[] args) {
Number4 n1 = new Number4();
Number4 n2 = new Number4();
new Thread(()->{n1.a();},"t1").start();
new Thread(()->{n2.b();},"t2").start();
}
}
class Number4 {
public synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1");
}
public synchronized void b(){
System.out.println("2");
}
}
/**
* 线程八锁五:
* 结果:2 1s 1
* 说明:synchronized 加在 static 方法上,锁的是Number5.class对象,所以 t1 t2 并行执行
*/
public class Demo5 {
public static void main(String[] args) {
Number5 n1 = new Number5();
new Thread(()->{n1.a();},"t1").start();
new Thread(()->{n1.b();},"t2").start();
}
}
class Number5 {
public synchronized static void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1");
}
public synchronized void b(){
System.out.println("2");
}
}
/**
* 线程八锁六:
* 结果:2 1s 1 或 1 1s 2
* 说明:synchronized 加在 static 方法上,锁的是Number5.class对象
*/
public class Demo6 {
public static void main(String[] args) {
Number6 n1 = new Number6();
new Thread(()->{n1.a();},"t1").start();
new Thread(()->{n1.b();},"t2").start();
}
}
class Number6 {
public synchronized static void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1");
}
public synchronized static void b(){
System.out.println("2");
}
}
/**
* 线程八锁七:
* 结果:2 1s 1
* 说明:t1 锁 Number7.class 对象,t2 锁 n2 对象
*/
public class Demo7 {
public static void main(String[] args) {
Number7 n1 = new Number7();
Number7 n2 = new Number7();
new Thread(()->{n1.a();},"t1").start();
new Thread(()->{n2.b();},"t2").start();
}
}
class Number7 {
public synchronized static void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1");
}
public synchronized void b(){
System.out.println("2");
}
}
/**
* 线程八锁八:
* 结果:1 1s 2 或 2 1s 1
* 说明:线程 t1 t2 锁对象都是类对象 Number8.class ,类对象只有一个,存在互斥
*/
public class Demo8 {
public static void main(String[] args) {
Number8 n1 = new Number8();
Number8 n2 = new Number8();
new Thread(()->{n1.a();},"t1").start();
new Thread(()->{n2.b();},"t2").start();
}
}
class Number8 {
public synchronized static void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1");
}
public synchronized static void b(){
System.out.println("2");
}
}
线程安全分析

成员变量和静态变量是否线程安全?
- 如果他们没有共享,则线程安全
- 如果他们被共享了,则根据他们的状态是否能够改变,分两种情况
- 如果只有读操作,则线程安全
- 如果有读写操作,则这段代码是临界区,需要考虑线程安全
局部变量是否线程安全?
- 局部变量时线程安全的
- 但局部变量引用的对象则未必(局部变量指向的是堆中的对象,堆中对象有可能被共享)
- 如果该对象没有逃离方法的作用访问,则是线程安全的
- 如果该对象逃离方法的作用范围,则需要考虑线程安全问题(比如用 return 逃离)
局部变量线程安全分析
public static void test1(){
int i = 10;
i++;
}
每个线程调用 test1() 方法时,局部变量 i,会在每个线程的栈帧内存中被创建多分,不存在共享


局部变量的引用稍有不同
/**
* 不安全的成员变量的例子
*/
public class Demo5 {
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args) {
UnsafeThread unsafeThread = new UnsafeThread();
for (int i = 0; i < THREAD_NUMBER; i++) {
new Thread(()->{
unsafeThread.method1(LOOP_NUMBER);
},"Thread-"+(i+1)).start();
}
}
}
class UnsafeThread{
ArrayList<String> list = new ArrayList<>();
public void method1(int loopNumber){
for (int i = 0; i < loopNumber; i++) {//临界区,存在竞态条件
method2();
method3();
}
}
private void method2(){
list.add("1");
}
private void method3(){
list.remove(0);
}
}

/**
* 安全的局部变量的例子
*/
public class Demo6 {
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args) {
SafeThread safeThread = new SafeThread();
for (int i = 0; i < THREAD_NUMBER; i++) {
new Thread(()->{
safeThread.method1(LOOP_NUMBER);
},"Thread-"+(i+1)).start();
}
}
}
class SafeThread{
public void method1(int loopNumber){
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < loopNumber; i++) {//临界区,存在竞态条件
method2(list);
method3(list);
}
}
private void method2(List<String> list){
list.add("1");
}
private void method3(List<String> list){
list.remove(0);
}
}

/**
* 安全的局部变量的例子
* 修改1:将 method2 method3 的修饰符 private 改成 public
* 情况1:在其他线程调用 method2 method3,线程安全,list 对象都是每个线程自己的,不存在共享;
* 情况2:在 情况1 的基础上,添加 SafeThread7 的子类,重写 method3 方法。会存在 list 共享;
*/
public class Demo7 {
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args) {
SafeThreadSubClass safeThread = new SafeThreadSubClass();
for (int i = 0; i < THREAD_NUMBER; i++) {
new Thread(()->{
safeThread.method1(LOOP_NUMBER);
},"Thread-"+(i+1)).start();
}
}
}
class SafeThread7{
public void method1(int loopNumber){
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < loopNumber; i++) {//临界区,存在竞态条件
method2(list);
method3(list);
}
}
public void method2(List<String> list){
list.add("1");
}
public void method3(List<String> list){
list.remove(0);
}
}
class SafeThreadSubClass extends SafeThread7{
@Override
public void method3(List<String> list) {
new Thread(()->{
list.remove(0);
}).start();
}
}
从这个例子可以看出关键字 private 或 final 提供【安全】的意义所在,体会开闭原则中的【闭】。private 不让其他线程直接调用、不能被重写,final 不能被继承。
常见线程安全类
String Integer StringBuffer Random Vector(线程安全的 List 实现)Hashtable(线程安全的 Map 实现)java.util.concurrent 包下的类
这里说他们是线程安全的是指,多个线程调用他们同一个实例的某个方法时是安全的。他们的每个方法时原子的,但他们多个方法的组合不是原子的。
Hashtable table = new Hashtable();
//线程1 线程2
if(table.get("key") == null){
table.put("key",value);
}

不可变类线程安全性
String、Integer 等都是不可变类,因为他们内部的状态不可以改变,因此他们的方法都是线程安全的。(replace等方法是创建返回了新对象)
实例分析
/*
一、servelet 在中间件 Tomcat 中只有一份存在多线程共享
*/
public class MyServlet extends HttpServlet{
//是安全的吗?不是,HashMap 不是线程安全的
Map<String,Object> map = new HashMap<>();
//是安全的吗? 是,String Integer 都是不可变类,内部状态/属性不能被改变
String s1 = "...";
//是安全的吗?是,String Integer 都是不可变类,内部状态/属性不能被改变
final String s2 = "...";
//是安全的吗?不是,Date 不是线程安全的类
Date d1 = new Date();
//是安全的吗?不是,final 修饰符只保证了 d2 的引用不能被改变,但他的指向对象有可能被修改
final Date d2 = new Date();
public void doGet(HttpServletRequest request,HttpServletRequest request){
}
}
/*
二、
*/
public class MyServlet extends HttpServlet{
//是安全的吗?不安全
private UserService userService = new UserServiceImpl();
public void doGet(HttpServletRequest request,HttpServletRequest request){
}
}
public class UserServiceImpl implements UserService{
// 记录调用次数
private int count = 0;
public void update(){
//临界区
count++;
}
}

说明:
1、spring AOP 默认都是单例模式
2、不安全
3、改进1:不用单例模式也不安全,前置时是一个对象,后置时可能又是另一个对象了
4、改进2:改成环绕通知,不用成员变量,改成局部变量

说明:
1、UserDaoImpl 中没有成员变量,不存在共享,线程安全
2、UserServiceImple 中 虽然 userDao 是成员变量,共享,但这个对象内部是无状态的,不可改变,线程安全
3、MyServlet 中 userService 是成员变量,共享,但这个对象类中的 userDao 是 private 的,不可变,线程安全

说明:
1、MyServlet userService userDao 都只有一份,存在共享,但都不可变,线程安全
2、UserDaoImpl 中,conn 是成员变量,存在共享,线程1 在执行 conn.close() 之前的操作时,发生上下文切换,线程2 执行了 conn.close() 就会发生问题

说明:
1、UserServiceImpl 中 userDao 是局部变量,每个线程都是自己的,线程安全
2、虽然线程安全,但是不推荐

虽然 sdf 是局部变量,但是可能会对外暴露,并且 SimpleDateFormat 不是线程安全的。foo 的行为是不确定的,可能导致不安全的发生,被称之为外星方法

思考:String 类 为什么是 final 的?
String 类是不可变类,每个方法都是返回新对象,是线程安全的,如果不设置成 final 的 就有可能被继承,方法被覆盖掉,变成线程不安全的。