Java并发之synchronized

本文详细探讨了Java中的`synchronized`关键字,包括它如何实现线程同步,以及修饰方法和代码块时的不同行为。通过实例展示了非静态、静态方法及代码块的同步效果,解释了锁的对象及其对并发执行的影响。总结了`synchronized`的作用域和线程同步原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

synchronized

作用:实现线程同步,让多个线程排队依次获取某个资源,保证数据不会出错

关键:synchronized 修饰什么?锁的是什么元素?

修饰方法

  • 非静态方法,锁的是方法的调用者(对象)
  • 静态方法,锁的是类

修饰代码块, (synchronized (parm)) 锁的是传入的对象(parm )

测试

1 正常方法测试

class Data {
    public void fun1() {
        try {
            // sleep 3秒
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("fun1..1.");
    }

    public void fun2() {
        try {
            // sleep 1秒
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("fun2..2.");
    }
}

public class Test {
    public static void main(String[] args) {
        Data data = new Data();
        // 新建一个线程A
        new Thread(() -> {
            data.fun1();
        },"A").start();
		// 新建一个线程B
        new Thread(() -> {
            data.fun2();
        },"B").start();
    }
}

程序输出

fun2..2.
fun1..1.

解析

主函数中新建两个线程,同时启动,互不影响。线程A暂停3秒才输出,线程B暂停1秒才输出。

所以输出结果是先输出 fun2..2.后2秒,才输出 fun1..1.

2 测试修饰非静态方法

class Data {
    public synchronized void fun1() {
        try {
            // sleep3秒
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("fun1..1.");
    }

    public synchronized void fun2() {
        try {
            // sleep3秒
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("fun2..2.");
    }
}
public class Test {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(() -> {
            data.fun1();
        }, "A").start();

        new Thread(() -> {
            data.fun2();
        }, "B").start();
    }
}

输出结果

fun1..1.
fun2..2.

解析

由于使用了 synchronized关键字修饰非静态方法,synchronized锁的是方法的调用者,由于两个方法fun1fun2 是被同一个对象data调用,所以两个线程不能同时占有对象data,得依次排队。由于是线程A先占有资源,所以B必须排队,等到线程A执行完线程B才能执行,

所以结果就是线程A先等待3秒输出 fun1..1.线程B在等待1秒输出fun2..2.

下面试试不是同一个调用者,输出又会怎样?

class Data {
    public synchronized void fun1() {
        try {
            // sleep3秒
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("fun1..1.");
    }

    public synchronized void fun2() {
        try {
            // sleep3秒
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("fun2..2.");
    }
}
public class Test {
    public static void main(String[] args) {
        Data data = new Data();
        Data data2 = new Data();
        new Thread(() -> {
            data.fun1();
        }, "A").start();

        new Thread(() -> {
            data2.fun2();
        }, "B").start();
    }
}

结果输出

fun2..2.
fun1..1.

解析

上面的代码分别创建了两个对象,分别是datadata2。线程A中对象data调用fun1, 线程B对象data2调用fun2, 由于两个方法调用不是同一调用者(对象)。所以结果就是两个线程同时进行,不用排队等候。

所以输出就是:先线程B等待1秒输出fun2..2.线程A再等待2秒输出fun1.1

3 测试修饰静态方法fun2

class Data {
    public static synchronized void fun1() {
        try {
            // sleep3秒
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("fun1..1.");
    }

    public static synchronized void fun2() {
        try {
            // sleep3秒
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("fun2..2.");
    }
}

public class Test {
    public static void main(String[] args) {
        Data data = new Data();
        Data data2 = new Data();
        new Thread(() -> {
            data.fun1();
        }, "A").start();

        new Thread(() -> {
            //data2.fun2();
            data2.fun2();
        }, "B").start();
    }
}

结果输出

fun1..1.
fun2..2.

解析

由于synchronized修饰的是静态代码块,synchronized锁的是类。线程A中对象data调用fun1, 线程B对象data2调用fun2,由于对象data和对象data2 都是属于同一个类Data,所以两个线程锁的东西是一样的,所以线程B必须等线程A执行完之后再执行。

所以结果就是线程A先等待3秒输出 fun1..1.线程B在等待1秒输出fun2..2.

4 测试修饰代码块

4.1 先来个正常的测试试试(没有加synchronized),然后比较一下

public class Test {
    public static void main(String[] args) {
        Data2 data2 = new Data2();
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                data2.fun();
            }).start();
        }
    }
}
class Data2 {
    public void fun() {
        {
            System.out.println("start...");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end...");
        }
    }
}

程序输出

start...
start...
start...
start...
start...
end...
end...
end...
end...
end...

解析

由于5个线程同时几乎同时启动,没有任何的限制,所有5个线程同时进行同时执行。

所以结果就是输出5个start…先,再输出5个end…

4.2 ``synchronized`修饰静态代码块进行测试

public class Test {
    public static void main(String[] args) {
        Data2 data2 = new Data2();
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                data2.fun();
            }).start();
        }
    }
}
class Data2 {
    public void fun() {
        synchronized (this) {
            System.out.println("start...");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end...");
        }
    }
}

结果输出

start...
end...
start...
end...
start...
end...
start...
end...
start...
end...

解析

由于 synchronized (this)这里的 this 表示对象,5个线程都是用对象data2进行测试,所以当多个线程执行中,如果有线程已经占有了该对象(锁住了)。其余线程需要排队等候,等到已经占有的该对象的线程释放到资源,后面的线程才能依次执行。

所以先输出第一个线程的执行输出,然后在第二个线程,第三个线程,依次排队。

所以第1个线程输出start...后等待1秒然后输出end...,释放掉占有的资源,第二个线程类似执行…

4.3 换种方法又测试

public class Test {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            Data2 data2 = new Data2();
            new Thread(() -> {
                data2.fun();
            }).start();
        }
    }
}
class Data2 {
    public void fun() {
        synchronized (this) {
            System.out.println("start...");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end...");
        }
    }
}

输出

start...
start...
start...
start...
start...
end...
end...
end...
end...
end...

解析

由于现在5个进行调用的函数的对象不一样,所以锁的东西也不一样,没有出现争夺资源的情况。所以5个进程同时进行,同时输出start... 后然后再同时输出end...

4.4 修饰代码块能不能锁点别的?只能是this对象吗?

public class Test {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            Data2 data2 = new Data2();
            new Thread(() -> {
                data2.fun(100);
            }).start();
        }
    }
}
class Data2 {
    public void fun(Integer id) {
        synchronized (id) {
            System.out.println("start...");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end...");
        }
    }
}

输出

start...
end...
start...
end...
start...
end...
start...
end...
start...
end...

解析

由于 synchronized (id)修饰的是 ididInteger对象,由于每次传进去的对象100都是同一个对象,所有``synchronized `锁的id也是同一个。所以当有的线程先占有了id这个资源,当别的线程来访问的时候,必须排队等候。

所以第1个线程输出start...后等待1秒然后输出end...,释放掉占有的资源,第二个线程类似执行…

4.5 测试锁class对象

public class Test {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                Data2 data2 = new Data2();
                data2.fun();
            }).start();
        }
    }
}

class Data2 {
    public void fun() {
        synchronized (Data2.class) {
            System.out.println("start...");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end...");
        }
    }
}

结果输出

start...
end...
start...
end...
start...
end...
start...
end...
start...
end...

解析

由于锁的是Data2.class,所以只要是属于这个Data.class类的都是同一个资源。很明显,都是由类 Data2 声明的对象,所以是同一个资源。

总结

1 搞清楚 synchronized 的作用域是啥(锁的是什么元素?)

2 判断是不是多个线程同时访问synchronized锁的元素?如果是,则后面的线程必须排队等候已经占有资源的线程释放,然后才能有机会执行。如果不是,各个线程互不影响,各走各的阳光大道。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值