Synchronized关键字

案例引入

多个线程访问同一个对象,实现两个线程,t1,t2同时对变量i进行++运算,预想最后的结果是20000

public class SynchronizedDemo1 implements Runnable {
    static  SynchronizedDemo1 ins = new SynchronizedDemo1();
    static int i = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(ins);
        Thread t2 = new Thread(ins);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
    @Override
    public void run() {
        for (int j = 0; j < 10000; j++) {
            i++;
        }
    }
}

代码运行结果

由结果可以看出,每次运行都会得到不同的结果,那么这是什么原因导致的呢?

i++,它看上去只是一个操作,实际上包含了三个动作:

1.读取 i
2.将 i 加一
3.将 i 的值写入到内存中

由于线程并发执行,三个步骤中任何一个步骤,被其他线程打断都可能会导致值丢失,因此当第二个线程取数据时,取到还是原来的数据,所以导致了结果的不准确,这种行为我们称之为线程不安全。

synchronized

作用

官方定义

Synchronized methods enable a simple strategy for preventing thread interference and memory consistency errors: if an object is visible to more than one thread, all reads or writes to that object'svariables are done through synchronized methods.

自己总结

能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果。

Synchronized的两种方法

对象锁

包括方法锁(默认锁对象为this当前实例对象)和同步代码块锁)(自己指定锁对象)

  • 代码块形式:手动指定锁对象
    public class SynchronizedDemo2 implements Runnable {
    static SynchronizedDemo2 sy = new SynchronizedDemo2();
    @Override    public void run() {
    synchronized (this){
    System.out.println("对象锁代码块形式,线程:"+Thread.currentThread().getName()+", 开始运行");
    try {
    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    System.out.println(Thread.currentThread().getName() + "运行结束");
            }
        }
    public static void main(String[] args) {
    Thread t1 = new Thread(sy);
    Thread t2 = new Thread(sy);
    t1.start();
    t2.start();
    while (t1.isAlive() || t2.isAlive()){}
    System.out.println("finished ...");
        }
    }
  • 方法锁形式:synchronized修饰普通方法,锁对象默认为this,或者自己创建一个对象锁
    public class SynchronizedDemo3 implements Runnable {
    
        static SynchronizedDemo3 sy  = new SynchronizedDemo3();
        @Override
        public void run() {
            methon();
        }
    
        public static void main(String[] args) {
            Thread t1 = new Thread(sy);
            Thread t2 = new Thread(sy);
            t1.start();
            t2.start();
            while (t1.isAlive() || t2.isAlive()){}
            System.out.println("finished ...");
        }
    
        public  synchronized void methon() {
            System.out.println("线程对象为:" + Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
        }
    
    }

类锁

指synchronized修饰静态的方法或指定锁为Class对象。

  • 只有一个Class对象:Java类可能会有很多个对象,但是只有1个Class对象。
  • 本质∶所以所谓的类锁,不过是Class对象的锁而已。------概念上的存在
  • 用法和效果:类锁只能在同一时刻被一个对象拥有。

类锁的两种形式

  • 形式1: synchronized加在static方法上
    public class SynchronizedDemo4 implements Runnable{
        @Override
        public void run() {
                methon();
        }
        static SynchronizedDemo4 sy1 = new SynchronizedDemo4();
        static SynchronizedDemo4 sy2 = new SynchronizedDemo4();
    
        public static void main(String[] args) {
            Thread t1 = new Thread(sy1);
            Thread t2 = new Thread(sy2);
            t1.start();
            t2.start();
            while (t1.isAlive() || t2.isAlive()){}
            System.out.println("finished ...");
        }
    
        public static  synchronized void methon() {
            System.out.println("线程对象:" + Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
        }
    }
  • 形式2: synchronized ( *.class )代码块
public class SynchronizedDemo5 implements Runnable{
@Override    public void run() {
            methon();
    }
static SynchronizedDemo5 sy1 = new SynchronizedDemo5();
static SynchronizedDemo5 sy2 = new SynchronizedDemo5();
public static void main(String[] args) {
Thread t1 = new Thread(sy1);
Thread t2 = new Thread(sy2);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){}
System.out.println("finished ...");
    }
public  void methon() {
synchronized (SynchronizedDemo5.class){
System.out.println("线程对象:" + Thread.currentThread().getName());
try {
Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
        }
    }
}

常见问题总结

1.两个线程同时访问一个对象的同步方法

 1 public class SynchronizedDemo4 implements Runnable{
 2     @Override
 3     public void run() {
 4             methon();
 5     }
 6     static SynchronizedDemo4 sy1 = new SynchronizedDemo4();
 7     static SynchronizedDemo4 sy2 = new SynchronizedDemo4();
 8 
 9     public static void main(String[] args) {
10         Thread t1 = new Thread(sy1);
11         Thread t2 = new Thread(sy2);
12         t1.start();
13         t2.start();
14         while (t1.isAlive() || t2.isAlive()){}
15         System.out.println("finished ...");
16     }
17 
18     public static  synchronized void methon() {
19         System.out.println("线程对象:" + Thread.currentThread().getName());
20         try {
21             Thread.sleep(2000);
22         } catch (InterruptedException e) {
23             e.printStackTrace();
24         }
25         System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
26     }
27 }
代码

由代码运行结果可以看出,其运行结果为串行,这是因为修饰的锁为this对象,为同一把锁,必然相互等待只能由一个线程所有。

2.两个线程访问的是两个对象的同步方法

 1 public class SynchronizedDemo6 implements Runnable{
 2     @Override
 3     public void run() {
 4             methon();
 5     }
 6     static SynchronizedDemo6 sy1 = new SynchronizedDemo6();
 7     static SynchronizedDemo6 sy2 = new SynchronizedDemo6();
 8 
 9     public static void main(String[] args) {
10         Thread t1 = new Thread(sy1);
11         Thread t2 = new Thread(sy2);
12         t1.start();
13         t2.start();
14         while (t1.isAlive() || t2.isAlive()){}
15         System.out.println("finished ...");
16     }
17 
18     public void methon() {
19         synchronized (this){
20             System.out.println("线程对象:" + Thread.currentThread().getName());
21             try {
22                 Thread.sleep(2000);
23             } catch (InterruptedException e) {
24                 e.printStackTrace();
25             }
26             System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
27         }
28     }
29 }
代码

  由上结果可以看出,两个线程为并行执行,因为两个线程所用的锁对象this不相同,两个线程互不干扰,呈现了并行执行的效果。

3.两个线程访问的是synchronized的静态方法

public class SynchronizedDemo4 implements Runnable{
    @Override
    public void run() {
            methon();
    }
    static SynchronizedDemo4 sy1 = new SynchronizedDemo4();
    static SynchronizedDemo4 sy2 = new SynchronizedDemo4();

    public static void main(String[] args) {
        Thread t1 = new Thread(sy1);
        Thread t2 = new Thread(sy2);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){}
        System.out.println("finished ...");
    }

    public static  synchronized void methon() {
        System.out.println("线程对象:" + Thread.currentThread().getName());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
    }
}
View Code

 

 

 

 

 由执行结果可以看出为串行,会一个一个执行,由此可以把看出锁生效。

4.同时访问同步方法与非同步方法

public class SynchronizedDemo7 implements Runnable {
    @Override
    public void run() {
        // 使线程再同一时刻访问同一个方法
        if (Thread.currentThread().getName().equals("Thread-0")) {
            methon1();
        } else {
            methon2();
        }
    }

    static SynchronizedDemo7 sy1 = new SynchronizedDemo7();
    static SynchronizedDemo7 sy2 = new SynchronizedDemo7();

    public static void main(String[] args) {
        Thread t1 = new Thread(sy1);
        Thread t2 = new Thread(sy2);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()) {
        }
        System.out.println("finished ...");
    }

    public void methon1() {
        synchronized (this) {
            System.out.println("同步线程对象:" + Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
        }
    }

    public void methon2() {
        System.out.println("非同步线程对象:" + Thread.currentThread().getName());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
    }

}
View Code

 

 

 由执行结果可以看出,synchronized只执行于被修饰的方法,其他方法并不受到影响。所以非同步方法不受到影响

5.访问同一个对象的不同的普通同步方法

public class SynchronizedDemo8 implements Runnable {
    @Override
    public void run() {
        // 使线程再同一时刻访问同一个方法
        if (Thread.currentThread().getName().equals("Thread-0")) {
            methon1();
        } else {
            methon2();
        }
    }
    static SynchronizedDemo8 sy1 = new SynchronizedDemo8();
    public static void main(String[] args) {
        Thread t1 = new Thread(sy1);
        Thread t2 = new Thread(sy1);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()) {
        }
        System.out.println("finished ...");
    }

    public synchronized void methon1() {
            System.out.println("同步线程1对象:" + Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
    }

    public synchronized  void methon2() {
        System.out.println("同步线程2对象:" + Thread.currentThread().getName());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
    }
}
View Code

 

 访问同一个对象的不同的普通方法,由于时同一个对象,所以可以推断出锁对象this指向为同一个锁,所以会串行执行。

6.同时访问静态synchronized和非静态synchronized方法

 

public class SynchronizedDemo9 implements Runnable {
    @Override
    public void run() {
        // 使线程再同一时刻访问同一个方法
        if (Thread.currentThread().getName().equals("Thread-0")) {
            methon1();
        } else {
            methon2();
        }
    }
    static SynchronizedDemo9 sy1 = new SynchronizedDemo9();
    public static void main(String[] args) {
        Thread t1 = new Thread(sy1);
        Thread t2 = new Thread(sy1);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()) {
        }
        System.out.println("finished ...");
    }

    public static synchronized void methon1() {
            System.out.println("同步静态线程1对象:" + Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
    }

    public synchronized  void methon2() {
        System.out.println("同步非静态线程2对象:" + Thread.currentThread().getName());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
    }
}
View Code

 

 

 

 由结果可以看出为串行。这是因为被static修饰之后就变成了类锁,锁的对象为class;而没有被static修饰的锁的对象时方法对象本身;他们之间锁不一样,所以互不干扰。

7.方法抛异常后,会释放锁

说明:synchronizatied出现异常会自动释放锁,而lock需要再findly中手动释放锁。

通过展示不抛出异常前和抛出异常后的对比:一旦抛出了异常,第二个线程会立刻进入同步方法,意味着锁已经释放。

手动抛出异常

 1 public class SynchronizedDemo10 implements Runnable {
 2     @Override
 3     public void run() {
 4         // 使线程再同一时刻访问同一个方法
 5         if (Thread.currentThread().getName().equals("Thread-0")) {
 6             methon1();
 7         } else {
 8             methon2();
 9         }
10     }
11     static SynchronizedDemo10 sy1 = new SynchronizedDemo10();
12     public static void main(String[] args) {
13         Thread t1 = new Thread(sy1);
14         Thread t2 = new Thread(sy1);
15         t1.start();
16         t2.start();
17         while (t1.isAlive() || t2.isAlive()) {
18         }
19         System.out.println("finished ...");
20     }
21 
22     public  synchronized void methon1() {
23             System.out.println("同步线程1对象:" + Thread.currentThread().getName());
24             try {
25                 Thread.sleep(2000);
26             } catch (InterruptedException e) {
27                 e.printStackTrace();
28             }
29             throw  new RuntimeException();
30 //            System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
31     }
32 
33     public synchronized  void methon2() {
34         System.out.println("同步线程2对象:" + Thread.currentThread().getName());
35         try {
36             Thread.sleep(2000);
37         } catch (InterruptedException e) {
38             e.printStackTrace();
39         }
40         System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
41     }
42 }
手动抛出异常

 

 总结

1.一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待(对应第1、5种情况);

2.每个实例都对应有自己的一把锁,不同实例之间互不影响; 例外:锁对象是*.class以及synchronized修饰的是static方法的时候,所有对象共用同一把类锁(对应第2、3、4、6种情况) ;

3.无论是方法正常执行完毕或者方法抛出异常,都会释放锁(对应第7种情况)

synchronizatied的性质

 1.可重入

什么是可重入︰指的是同一线程的外层函数获得锁之后,内层函数可以直接再次获取该锁(也叫做递归锁)

好处:避免死锁、提升封装性

2.不可中断

一旦这个锁已经被别人获得了,如果我还想获得,我只能选择等待或者阻塞,直到别的线程释放这个锁。如果别人永远不释放锁,那么我只能永远地等下去。

相比之下,未来会介绍的Lock类,可以拥有中断的能力,第一点,如果我觉得我等的时间太长了,有权中断现在已经获取到锁的线程的执行;第二点,如果我觉得我等待的时间太长了不想再等了,也可以退出。

加锁和释放锁的原理:现象、时机、深入JVM看字节码

现象:每个类都有一个锁,不同的线程都要获取到相应的锁才能执行,否则只能阻塞;而释放、加锁等功能由jvm管理。

获取释放锁的时机:内置锁

加锁和释放锁的 等价代码

public class SynchronizedDemo11 {
    Lock lock = new ReentrantLock();
    public static void main(String[] args) {
        SynchronizedDemo11 sy1 = new SynchronizedDemo11();
        sy1.methon1();
        sy1.methon2();
    }

    // method1 等价于 method2
    public synchronized void methon1() {
        System.out.println("synchronized lock method");
    }

    public void methon2() {
        lock.lock();  // 加锁
        try {
            System.out.println("lock method");
        } finally {
            lock.unlock();  // 释放锁
        }
    }
}
View Code

 

深入JVM看字节码

字节码中哪里体现的某个线程已经获取了该对象的锁:monitorenter 和 monitorexit

反编译流程

首先新建一个Java类

package concurrency_code;
public class SynchronizedDemo12  {
    private Object object = new Object();
    public  void  insert(Thread thread){
        synchronized (object){

        }
    }
}
Java类

 

接着通过Javac 编译成字节码文件

 

 

接着使用 javap -verbose SynchronizedDemo12.class 命令查看字节码文件

 

反编译后可以看到 第六行的 monitorenter  和 第8行的 monitorexit

 

 

 

 

可重入原理∶加锁次数计数器


保证可见性的原理︰内存模型

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2021-05-26 09:36  老鲜肉  阅读(46)  评论(0编辑  收藏  举报