Java高并发编程 | 学习笔记(一)

1. 多线程基础回顾

1.1 synchronized关键字

为了防止并发编程引发的数据共享问题,最常见引用的锁机制就是利用synchronized关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* synchronized关键字
* 对某个对象加锁
*/

public class T {
private int count = 10;
private Object o = new Object();

public void m() {
synchronized(o) { // 任何线程要执行下面的代码,必须先拿到 o 的锁
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}

}

上述代码中synchronized关键字锁住的是堆内存中的Object o对象,而不是引用 o

1.2 synchronized关键字对当前类对象加锁

如上小节所示,每次锁对象的时候,都 new 一个要锁的对象,未免太麻烦,不如锁一个现成的对象:this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* synchronized关键字
* 对某个对象加锁
*/

public class T {
private int count = 10;

public void m() {
synchronized(this) { // 任何线程要执行下面的代码,必须先拿到this的锁
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}

上面代码与下面代码等同:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* synchronized关键字
* 对某个对象加锁
*/
public class T {
private int count = 10;

public synchronized void m() { // 等同于在方法的代码执行时要synchronized(this)
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}

1.3 synchronized 锁住静态方法的时候

当有static存在的时候,synchronized关键字锁住的是静态类的 .class 类对象(java.lang.Class 类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* synchronized关键字
* 对某个对象加锁
*/
public class T {
private static int count = 10;

public synchronized static void m() { //这里等同于synchronized(org.woodwhales.T.class)
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}

public static void mm() {
synchronized(T.class) { //考虑一下这里写synchronized(this)是否可以?
count --;
}
}
}
1.5 synchronized关键字解决线程重入问题

下面代码中,如果类 T 的 run() 方法没有 synchronized 锁机制保护方法体里的代码,那么会出现线程重入的问题而导致的数据不一致问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class T implements Runnable {
private int count = 10;

@Override
public /*synchronized*/ void run() {
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}

public static void main(String[] args) {
T t = new T();
for(int i=0; i<5; i++) {
new Thread(t, "THREAD" + i).start();
}
}
}

输出奇怪的结果:

1
2
3
4
5
THREAD0 count = 8
THREAD3 count = 6
THREAD2 count = 7
THREAD1 count = 8
THREAD4 count = 5

从上述示例可知:synchronized 代码块中的数据操作是原子操作,原子操作在多线程操作数据资源中是不可分割的,因为当某个线程得到锁的时候,其他无法获取到这把锁,而需要等待获得锁,获得的前提条件是:已获得锁的线程执行完锁代码块中的资源操作之后释放掉锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 对比上面一个小程序,分析一下这个程序的输出
* T 对象的 run() 方法使用了同步锁,因此不会出现多线程同步问题
*/

public class T implements Runnable {
private int count = 10;

public synchronized void run() {
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}

public static void main(String[] args) {
T t = new T();
for(int i=0; i<5; i++) {
new Thread(t, "THREAD" + i).start();
}
}
}

执行结果始终是:

1
2
3
4
5
THREAD0 count = 9
THREAD1 count = 8
THREAD3 count = 7
THREAD2 count = 6
THREAD4 count = 5
1.6 同步方法和非同步方法可以同时被多线程调用

下面示例中:m1() 方法是同步方法,m2() 方法是非同步方法,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* 同步和非同步方法是否可以同时调用?
* 可以
*/

public class T {
public synchronized void m1() {
System.out.println(Thread.currentThread().getName() + " m1 start...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " m1 end");
}

public void m2() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " m2 ");
}

public static void main(String[] args) {
T t = new T();

/*new Thread(()->t.m1(), "t1").start();
new Thread(()->t.m2(), "t2").start();*/

new Thread(t::m1, "t1").start();
new Thread(t::m2, "t2").start();

/* 上面代码在 JDK1.8 中等同于下面代码
new Thread(new Runnable() {
@Override
public void run() {
t.m1();
}
});
*/
}
}

执行结果:m1 打印的过程之中, m2 也打印了出来,因此可以得出结论,m1() 方法被线程运行的时候,其他的非同步方法可以被其他线程调用运行,也就是 synchronized 方法块在执行的过程中,非 synchronized 方法是可以被执行的。

1
2
3
t1 m1 start...
t2 m2
t1 m1 end
updated updated 2024-01-01 2024-01-01
本文结束感谢阅读

本文标题:Java高并发编程 | 学习笔记(一)

本文作者:woodwhales

原始链接:https://woodwhales.cn/2019/01/28/022/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

0%