synchronized关键字

学习synchronized关键字,首先要明白几个多线程的概念。

  • 线程安全:当多个线程访问某一个类(对象或方法)始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的。
  • 互斥区或临界区:synchronized可以在任意对象及方法上加锁,而加锁的这段代码称为“互斥区”或“临界区”。

synchronized的锁级别

  • 对象锁:关键字synchronized加在非static方法上(或synchronized锁代码块,直接用对象或者本身this加锁),取得的锁都是对象锁,而不是把一段代码(方法)当作锁,所以代码中哪个线程先执行synchronized关键字的方法,哪个线程就持有该方法所属的对象锁(lock)。
  • 类级别锁:在静态方法上加synchronized关键字(或synchronized锁代码块,直接用 类名.class 加锁),表示锁定.class类,类级别的锁(独占.class类)。

    对象锁

  1. synchronized关键字加在非静态方法上,对象锁。

    1
    2
    public synchronized void method (){
    }
  2. synchronized关键字加在代码块上,用this或者其他对象加锁,都是对象锁。

    1
    2
    3
    4
    public void method(){
    synchronized (this) { //对象锁
    }
    }

类级别锁

  1. synchronized关键字加在 static方法上,类级别锁,独占.class。

    1
    2
    public static synchronized void method(){
    }
  2. synchronized关键字加在代码块上,用 本类名.class加锁,类级别锁,独占.class。

    1
    2
    3
    4
    public void method(){ //类锁
    synchronized (ObjectLock.class) {
    }
    }

多个线程多个锁

        不同的线程,对象锁不同。

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
public class MyThread02 {
private int num = 0;
/** static */
public synchronized void printNum(String tag){
try {
if(tag.equals("a")){
num = 100;
System.out.println("tag a, set num over!");
Thread.sleep(1000);
} else {
num = 200;
System.out.println("tag b, set num over!");
}
System.out.println("tag " + tag + ", num = " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//注意观察run方法输出顺序
public static void main(String[] args) {
//俩个不同的线程对象,synchronized加在非static方法,对应的对象锁也不同。解决:方法加static
final MyThread02 m1 = new MyThread02();
final MyThread02 m2 = new MyThread02();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
m1.printNum("a");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
m2.printNum("b");
}
});
t1.start();
t2.start();
}
}

锁对象问题

  • 注意1:不要使用String常量加锁,会造成死循环。String常量池的缓存功能。
  • 注意2:修改锁对象
             1. 当使用一个对象进行加锁的时候,要注意对象本身发生改变的时候,那么持有的锁对象就不同。(可以理解为:对象的地址值不发生改变)
             2. 如果对象本身不发生改变,那么依然是同步,即使是对象的属性发生了改变。
  • 注意3:死锁问题,在设计程序时就应该避免双方相互持有对方的锁的情况。

    锁的同步和异步

    相关概念

  • 同步(synchronized):同步的概念就是共享,如果不是共享资源就没有必要进行同步。
  • 异步(asynchronized):异步的概念就是独立,相互之间不受到任何制约。比如,学习http的时候,在页面发起aj加锁的时候ax请求,我们还可以继续浏览
  • 同步的目的:为了线程安全。其实对于线程安全来说,需要满足两个特性,原子性(同步)和可见性。
  • 总结:
             1. A线程现持有Object对象的Lock锁,B线程如果在这个时候调用对象中的同步(synchronized)方法则需要等待,也就是同步。
             2. A线程现持有Object对象的Lock锁,B线程可以以异步的方式调用对象中的非synchronized修饰的方法。

    【问题】脏读

            对于对象的同步和异步的方法,在设计程序的时候,一定要考虑问题的整体,不然就会出现数据不一致的错误,很经典的错误就是脏读(dirtyread)。
  • 以下代码分析:
    场景1:setValue 加synchronized,getValue不加synchronized 结果:会出现线程安全问题(脏读)
    场景2:setValue 加synchronized,getValue加synchronized 结果:等执行setValue,才会执行 getValue,设值取值一致。
  • 分析:
            在我们对一个对象的方法加锁的时候,需要考虑业务的整体性,即为setValue和getValue方法同时加锁synchronized,保证业务的原子性,不然会出现业务错误(也从侧面保证业务的一致性)
    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
    public class MyThread04 {
    private String username = "lzp";
    private String password = "123";
    public synchronized void setValue(String username, String password){
    this.username = username;
    try {
    Thread.sleep(2000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    this.password = password;
    System.out.println("setValue最终结果:username = " + username + " , password = " + password);
    }
    // synchronized
    public synchronized void getValue(){
    System.out.println("getValue方法得到:username = " + this.username + " , password = " + this.password);
    }
    public static void main(String[] args) throws Exception{
    final MyThread04 dr = new MyThread04();
    Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
    dr.setValue("z3", "456");
    }
    });
    t1.start();
    Thread.sleep(1000);
    dr.getValue();
    }
    }

锁重入和异常

锁重入

  1. 同类的同步方法之间的互相调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class SyncDubbo1 {
    public synchronized void method1(){
    System.out.println("method1..");
    method2();
    }
    public synchronized void method2(){
    System.out.println("method2..");
    method3();
    }
    public synchronized void method3(){
    System.out.println("method3..");
    }
    public static void main(String[] args) {
    final SyncDubbo1 sd = new SyncDubbo1();
    Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
    sd.method1();
    }
    });
    t1.start();
    }
    }
  2. 子类同步方法调用父类的同步方法

    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
    public class SyncDubbo2 {
    static class Main {
    public int i = 10;
    public synchronized void operationSup(){
    try {
    i--;
    System.out.println("Main print i = " + i);
    Thread.sleep(100);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    static class Sub extends Main {
    public synchronized void operationSub(){
    try {
    while(i > 0) {
    i--;
    System.out.println("Sub print i = " + i);
    Thread.sleep(100);
    this.operationSup();
    }
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    public static void main(String[] args) {
    Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
    Sub sub = new Sub();
    sub.operationSub();
    }
    });
    t1.start();
    }
    }

异常

        对于web应用程序,异常释放锁的情况,如果不及时处理,很可能对你的应用程序业务逻辑产生严重的错误,比如你现在执行一个队列任务,很多对象都去在等待第一个对象正确执行完毕再去释放锁,但是第一个对象由于异常的出现,导致业务逻辑没有正常执行就释放了锁,那么可想而知后续的对象执行的都是错误的逻辑。

  • 同步方法或者代码块里出现异常-分析:
  • 代码里出现异常,用try…catch…,try 出现运行期异常,比如:Integer.parseInt(“a”); 或者throw new RuntimeException();
    1. 处理1:catch (InterruptedException e) 则线程到此结束(打断异常)
    2. 处理2:catch里throw new RuntimeException(); 则线程到此结束
    3. 处理3:catch (Exception e) 则线程继续

线程间通信

        synchronized实现线程间的通信,后期再补充Lock锁方式的线程间通信。

相关概念

  • 线程通信:线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体。线程间通信就成为整体的必用方式之一。当线程存在通信指挥,系统间的交互性会更强大,在提高CPU利用率的同时还会使开发人员对线程任务在处理过程中进行有效的把控与监督。
  • 使用wait/notify方法实现线程间的通信。(注意:这两个方法都是Object类的方法,换句话说,Java为所有对象都提供了这两个方法)
    1. wait和notify必须配合synchronized关键字使用。
    2. wait方法释放锁,notify方法不释放锁,sleep方法不释放锁。

wait和notify实现通信

  • 启动3个线程(包括主线程),循环打印出A,B,C,100次
    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
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    public class ThreadSignal {
    private int count = 1;
    public void printA() throws Exception {
    synchronized (this) {
    while(count != 1) {
    wait();
    }
    System.out.println("A");
    Thread.sleep(500);
    count = 2;
    notifyAll();
    }
    }
    public void printB() throws Exception{
    synchronized (this) {
    while(count != 2) {
    wait();
    }
    System.out.println("B");
    Thread.sleep(500);
    count = 3;
    notifyAll();
    }
    }
    public void printC() throws Exception{
    synchronized (this) {
    while(count != 3) {
    wait();
    }
    System.out.println("C");
    Thread.sleep(500);
    count = 1;
    notifyAll();
    }
    }
    public static void main(String[] args) throws Exception {
    final ThreadSignal signal = new ThreadSignal();
    new Thread(new Runnable() {
    @Override
    public void run(){
    for (int i = 1 ;i <= 100;i++) {
    try {
    signal.printA();
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }
    }).start();
    new Thread(new Runnable() {
    @Override
    public void run() {
    for (int i = 1 ;i <= 100;i++) {
    try {
    signal.printB();
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }
    }).start();
    for (int i = 1 ;i <= 100;i++) {
    signal.printC();
    }
    }
    }
文章目录
  1. 1. synchronized的锁级别
    1. 1.1. 对象锁
    2. 1.2. 类级别锁
    3. 1.3. 多个线程多个锁
    4. 1.4. 锁对象问题
  2. 2. 锁的同步和异步
    1. 2.1. 相关概念
    2. 2.2. 【问题】脏读
  3. 3. 锁重入和异常
    1. 3.1. 锁重入
    2. 3.2. 异常
  4. 4. 线程间通信
    1. 4.1. 相关概念
    2. 4.2. wait和notify实现通信
|