synchronized的8大应用场景分析
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象; ---对象锁
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象; --对象锁
3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象; ---类锁
4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。--类锁
场景一:两个线程同时访问同一个对象的同步方法。
这是经典的对象锁中的方法锁,两个线程争夺同一个对象锁,但是两个线程是属于同一对象的,所以会互相等待,是线程安全的。
package com.demo.thread;
public class SynchronizedTest implements Runnable{
static SynchronizedTest instance1 = new SynchronizedTest();
@Override
public void run() {
method();
}
// 用synchronized对method方法机进行同步
private synchronized void method(){
System.out.println("线程名" + Thread.currentThread().getName()+",开始运行");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + ",运行结束");
}
public static void main(String[] args) {
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance1);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()){
}
System.out.println("测试结束");
}
}
运行结果:
线程名Thread-0,开始运行
线程Thread-0,运行结束
线程名Thread-1,开始运行
线程Thread-1,运行结束
测试结束
场景二:两个线程同时访问两个对象的锁。
这个就是对象锁失效的场景,原因在访问的是两个对象的同步方法,那么这两个线程分别持有的是两个线程的锁,因此两线程之间是相互不影响的。这时加锁启不到任何作用。我们加锁的目的就是为了让多个线程竞争同一把锁。因此这种场景下是线程不安全的。
package com.demo.thread;
public class SynchronizedTest implements Runnable{
//创建两个不同的对象
static SynchronizedTest instance1 = new SynchronizedTest();
static SynchronizedTest instance2 = new SynchronizedTest();
@Override
public void run() {
method();
}
// 用synchronized对method方法机进行同步
private synchronized void method(){
System.out.println("线程名" + Thread.currentThread().getName()+",开始运行");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + ",运行结束");
}
public static void main(String[] args) {
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()){
}
System.out.println("测试结束");
}
}
运行结果:
线程名Thread-0,开始运行
线程名Thread-1,开始运行
线程Thread-0,运行结束
线程Thread-1,运行结束
测试结束
场景三:两个线程同时访问(一个或两个)对象的静态同步方法
这个场景即是场景二中的线程安全的解决方法,在方法上添加了static方法,作用对象是这个类的所有对象,所以是线程安全的。
package com.demo.thread;
/**
* @Author Zhangnana
* @DATE 2020/12/19 9:39
* @Version 1.0
*/
public class SynchronizedTest implements Runnable{
//创建两个不同的对象
static SynchronizedTest instance1 = new SynchronizedTest();
static SynchronizedTest instance2 = new SynchronizedTest();
@Override
public void run() {
method();
}
// 用synchronized对method方法机进行同步
private synchronized static void method(){
System.out.println("线程名" + Thread.currentThread().getName()+",开始运行");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + ",运行结束");
}
public static void main(String[] args) {
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()){
}
System.out.println("测试结束");
}
}
结果:
线程名Thread-0,开始运行
线程Thread-0,运行结束
线程名Thread-1,开始运行
线程Thread-1,运行结束
测试结束
场景四:两个线程分别同时访问一个或两个对象的同步方法和非同步方法
这个场景是指两个线程其中一个访问的是同步方法,另一个访问的是非同步方法,此时,线程是否安全呢?
我们可以很确定的说是线程不安全的。从结果可以看出是线程不安全的。
package com.demo.thread;
/**
* @Author Zhangnana
* @DATE 2020/12/19 9:39
* @Version 1.0
*/
public class SynchronizedTest implements Runnable{
//创建两个不同的对象
static SynchronizedTest instance1 = new SynchronizedTest();
static SynchronizedTest instance2 = new SynchronizedTest();
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")){
method0();
}
if (Thread.currentThread().getName().equals("Thread-1")){
method1();
}
}
// 同步方法
private synchronized static void method0(){
System.out.println("线程名" + Thread.currentThread().getName()+",开始运行");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + ",运行结束");
}
//普通方法
public void method1(){
System.out.println("线程名" + Thread.currentThread().getName()+",开始运行");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + ",运行结束");
}
public static void main(String[] args) {
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()){
}
System.out.println("测试结束");
}
}
结果
线程名Thread-0,开始运行
线程名Thread-1,开始运行
线程Thread-1,运行结束
线程Thread-0,运行结束
测试结束
场景五:两个对象访问同一个对象中的同步方法,且同步方法中调用非同步方法
这个场景是用两个线程去调用同步方法,同步方法中去调用非同步方法,在用一个线程直接去调用普通方法。从结果中可以看出,thread0和thread2的普通方法是并行运行的,此时是线程不安全的。若想做到线程安全,必须确保在同步方法中调用非同步方法时,没有别的线程对其进行操作。
package com.demo.thread;
/**
* @Author Zhangnana
* @DATE 2020/12/19 9:39
* @Version 1.0
*/
public class SynchronizedTest implements Runnable{
//创建两个不同的对象
static SynchronizedTest instance1 = new SynchronizedTest();
static SynchronizedTest instance2 = new SynchronizedTest();
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")){
method1();
}else {
method0();
}
}
// 同步方法
private synchronized void method0(){
System.out.println("线程名" + Thread.currentThread().getName()+"同步方法,开始运行");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + "同步方法,运行结束。开始调用普通方法");
method1();
}
//普通方法
public void method1(){
System.out.println("线程名" + Thread.currentThread().getName()+"普通方法,开始运行");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + "普通方法,运行结束");
}
public static void main(String[] args) {
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance1);
Thread thread3 = new Thread(instance1);
thread1.start();
thread2.start();
thread3.start();
while (thread1.isAlive() || thread2.isAlive() || thread3.isAlive()){
}
System.out.println("测试结束");
}
}
线程名Thread-2同步方法,开始运行
线程名Thread-0普通方法,开始运行
线程Thread-2同步方法,运行结束。开始调用普通方法
线程名Thread-2普通方法,开始运行
线程Thread-0普通方法,运行结束
线程Thread-2普通方法,运行结束
线程名Thread-1同步方法,开始运行
线程Thread-1同步方法,运行结束。开始调用普通方法
线程名Thread-1普通方法,开始运行
线程Thread-1普通方法,运行结束
测试结束
场景六:两个线程同时访问同一个对象的不同的同步方法
此时获得的是对象锁,同一对象中,访问不同的同步方法是线程安全的。若是访问的是不同对象中的不同的同步方法时线程不安全的。根据synchronized修饰的具体内容,判断获取的是哪种锁。
package com.demo.thread;
/**
* @Author Zhangnana
* @DATE 2020/12/19 9:39
* @Version 1.0
*/
public class SynchronizedTest implements Runnable{
//创建两个不同的对象
static SynchronizedTest instance1 = new SynchronizedTest();
static SynchronizedTest instance2 = new SynchronizedTest();
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")){
method1();
}
if (Thread.currentThread().getName().equals("Thread-1")){
method0();
}
}
// 同步方法
private synchronized void method0(){
System.out.println("线程名" + Thread.currentThread().getName()+"同步方法,开始运行");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + "同步方法,运行结束。开始调用普通方法");
method1();
}
//普通方法
public synchronized void method1(){
System.out.println("线程名" + Thread.currentThread().getName()+"普通方法,开始运行");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + "普通方法,运行结束");
}
public static void main(String[] args) {
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance1);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()){
}
System.out.println("测试结束");
}
}
线程名Thread-0普通方法,开始运行
线程Thread-0普通方法,运行结束
线程名Thread-1同步方法,开始运行
线程Thread-1同步方法,运行结束。开始调用普通方法
线程名Thread-1普通方法,开始运行
线程Thread-1普通方法,运行结束
测试结束
场景七:两个线程分别同时访问静态synchronized和非静态synchronized
方法
这个场景的本质是两个线程获取的是不是同一把锁的问题,我们知道静态synchronized方法获取的类锁,非静态获取得是对象锁,两个线程拿到的锁是不同的锁,所有不会互相等待,是线程不安全的。
package com.demo.thread;
/**
* @Author Zhangnana
* @DATE 2020/12/19 9:39
* @Version 1.0
*/
public class SynchronizedTest implements Runnable{
//创建两个不同的对象
static SynchronizedTest instance1 = new SynchronizedTest();
static SynchronizedTest instance2 = new SynchronizedTest();
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")){
method1();
}
if (Thread.currentThread().getName().equals("Thread-1")){
method0();
}
}
// 同步方法
private synchronized static void method0(){
System.out.println("线程名" + Thread.currentThread().getName()+",开始运行");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + "运行结束。");
}
//普通方法
public synchronized void method1(){
System.out.println("线程名" + Thread.currentThread().getName()+",开始运行");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + ",运行结束");
}
public static void main(String[] args) {
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()){
}
System.out.println("测试结束");
}
}
线程名Thread-0,开始运行
线程名Thread-1,开始运行
线程Thread-1运行结束。
线程Thread-0,运行结束
测试结束
场景八:同步方法抛出异常后,JVM会自定释放锁的情况
只有当同步方法执行完成或执行时抛出异常这两种情况,才会释放锁。