通过8锁问题,可以大致清楚synchronized是锁的什么东西。通过对8锁问题的扩展,可以进一步理解synchronized。
8锁问题
1、一个对象,两个synchronized方法,一个方法睡眠
public class LockTest {
public static void main(String[] args) {
Phone phone = new Phone(); // 一个Phone对象
new Thread(() -> {
phone.sendSms();
},"A").start();
new Thread(() -> {
phone.call();
},"B").start();
}
}
class Phone{
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(1); // 休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
执行结果:1、发短信 2、打电话
原因:两个方法都被synchronized锁了,实际也就是锁的phone这个对象。A线程先拿到锁,sleep并不会释放锁。因此,只有当A线程执行完毕释放了锁,B拿到锁才能执行。
2、一对象,两synchronized方法,main方法里睡眠
public class LockTest {
public static void main(String[] args) {
Phone phone = new Phone(); // 一个Phone对象
new Thread(() -> {
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1); // 休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.call();
},"B").start();
}
}
class Phone{
public synchronized void sendSms(){
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
执行结果:1、发短信 2、打电话
原因:与第1中情况一样,锁的都是同一个对象,只有先拿到锁的才会先执行。
3、一对象,一synchronized方法,一普通方法
public class LockTest {
public static void main(String[] args) {
Phone phone = new Phone(); // 一个Phone对象
new Thread(() -> {
phone.sendSms();
},"A").start();
new Thread(() -> {
phone.qq();
},"B").start();
}
}
class Phone{
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(2); // 休眠2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public void qq(){
System.out.println("聊QQ");
}
}
执行结果:1、聊QQ 2、发短信
原因:普通方法不受锁的影响,被锁住的方法休眠了2秒,但普通方法不受锁的影响,所以它会先执行。
4、两对象,两synchronized方法
public class LockTest {
public static void main(String[] args) {
Phone phone1 = new Phone(); // phone1
Phone phone2 = new Phone(); // phone2
new Thread(() -> {
phone1.sendSms();
},"A").start();
new Thread(() -> {
phone2.call();
},"B").start();
}
}
class Phone{
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(2); // 休眠2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
执行结果:1、打电话 2、发短信
原因:调用两个方法的是两个不同的对象,synchronized锁的也是不同对象,它们互不影响。
5、一个对象,两个静态synchronized方法
public class LockTest {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sendSms();
},"A").start();
new Thread(() -> {
phone.call();
},"B").start();
}
}
class Phone{
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(2); // 休眠2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
执行结果:1、发短信 2、打电话
原因:静态方法属于Class,Class对象全局唯一,一个类只有一个Class对象,因此这个synchronized锁的是Class。所以还是谁先拿到锁,谁先执行。
6、两对象,两synchronized静态方法
public class LockTest {
public static void main(String[] args) {
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
phone1.sendSms();
},"A").start();
new Thread(() -> {
phone2.call();
},"B").start();
}
}
class Phone{
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(2); // 休眠2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
执行结果:1、发短信 2、打电话
原因:再次印证,静态方法锁的是Class。
7、一对象,一静态synchronized方法,一普通synchronized方法
public class LockTest {
public static void main(String[] args) {
Phone1 phone = new Phone();
new Thread(() -> {
phone1.sendSms();
},"A").start();
new Thread(() -> {
phone1.call();
},"B").start();
}
}
class Phone{
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(2); // 休眠2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
执行结果:1、打电话 2、发短信
原因:sendSms方法的synchronized是锁的Class对象,而call方法的synchronized是锁的phone1对象。因此两个方法在上诉代码中的调用,各自持有一把锁,互不影响。
8、两对象,一静态synchronized方法,一普通synchronized方法
public class LockTest2 {
public static void main(String[] args) {
Phone1 phone1 = new Phone1();
Phone1 phone2 = new Phone1();
new Thread(() -> {
phone1.sendSms();
},"A").start();
new Thread(() -> {
phone2.call();
},"B").start();
}
}
class Phone1{
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(2); // 休眠2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
执行结果:1、打电话 2、发短信
原因:再次印证static的synchronized锁的是Class对象
8锁结论
一个对象只有一把锁,加锁的普通方法,谁先抢到锁,谁先执行。
静态的加锁方法,锁的是Class对象,Class对象全局唯一。
synchronized的扩充问题
1、当被锁的对象发生变化的时候
public class Synchronized1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.changed();
},"A线程").start();
try{
TimeUnit.SECONDS.sleep(2);
}catch (InterruptedException e){
e.printStackTrace();
}
Thread thread = new Thread(() -> {
phone.changed();
},"B线程");
System.out.println(phone.object);
// object对象发生了变化
phone.object = new Object();
System.out.println(phone.object);
// 如果锁的对象发生改变,则相当于锁的是一个新的对象了
thread.start();
}
}
class Phone{
Object object = new Object();
public void changed(){
// 锁的是object对象
synchronized (object){
while (true){
try{
TimeUnit.SECONDS.sleep(2);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
}
当被锁的对象发生改变,则表示又锁了一个新的对象,应该避免将锁定对象的引用改变。如果只是改变对象的属性,则不会有影响。
2、以字符串常量作为锁定对象的时候
public class Synchronized1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.helloWorld();
},"A").start();
new Thread(() -> {
phone.worldHello();
},"B").start();
}
}
class Phone{
// 两个字符串引用都是指向常量池中的同一个字符串
String str1 = "hello";
String str2 = "hello";
public void helloWorld(){
synchronized (str1){
System.out.println("str1 运行!");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("str1 2秒后结束!");
}
}
public void worldHello(){
synchronized (str2){
System.out.println("str2 运行!");
}
}
}
因为str1和str2都是指向字符串常量池中的同一个字符串,所以不管锁的是str1还是str2,都是锁的同一个对象。
3、synchronized方法里调用synchronized方法
public class Synchronized1 {
public static void main(String[] args) {
Phone phone = new Phone();
phone.one();
}
}
class Phone{
synchronized void one(){
System.out.println("one func start...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
two();
}
synchronized void two(){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("two func start...");
}
}
可以调用到,因为synchronized是可重入锁。
4、继承情况下的重入锁
public class Synchronized1 {
public static void main(String[] args) {
MobilePhone mobilePhone = new MobilePhone();
mobilePhone.phone();
}
}
class Phone{
synchronized void phone(){
System.out.println("Phone Start...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Phone End...");
}
}
class MobilePhone extends Phone{
@Override
synchronized void phone() {
System.out.println("MobilePhone Start...");
super.phone();
System.out.println("MobilePhone End...");
}
}
5、异常情况下的synchronized
public class Synchronized1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.test();
},"A线程").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.test();
},"B线程").start();
}
}
class Phone{
int count = 0;
synchronized void test(){
System.out.println(Thread.currentThread().getName() + " Start...");
while(true){
count++;
System.out.println(Thread.currentThread().getName() + "的count = " + count);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count == 5){
try {
// 如果这里的异常没有被捕获处理,则会释放锁
int i = 1 / 0;
}catch (Exception e){
System.out.println("异常处理!");
}
}
}
}
}
如果synchronized修饰的块里有地方出现异常,如果不去捕获处理这个异常,synchronized锁会被主动释放掉。如果捕获处理这个异常之后,这个锁就不会主动释放。