Java多线程编程-线程安全和锁Synchronized概念

线程安全

线程安全概念:多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。

线程安全问题都是由全局变量及静态变量引起的, 局部变量逃逸也可能导致线程安全问题。

若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。

 

synchronized 关键字总结

  • 静态方法的锁属于类锁, 一个类中所有加锁的静态方法共用该锁, 多个 Share 类之间锁共用
  • 非静态方法属于对象锁, 一个对象中所有加锁的非静态方法共用该锁, 多个 Share 对象实例之间互不影响
  • 对于同一个类, 类锁和对象(类的实例)锁互不影响
    • 一个加锁的静态方法执行不会影响一个加锁的非静态方法的执行.
    • 一个加锁的静态方法执行, 另一个加锁的静态方法不能执行, 要等待持有锁的线程释放.
    • 一个加锁的非静态方法执行, 另一个加锁的非静态方法不能执行, 要等待持有锁的线程释放.

 

程序验证

执行入口

public class AnswerApp {
    public static void main(String[] args) throws InterruptedException {
        Sharer sharer = new Sharer();
        ThreadA threadA = new ThreadA(sharer);
        threadA.setName("ThreadA");
        ThreadB threadB = new ThreadB(sharer);
        threadB.setName("ThreadB");
        ThreadC threadC = new ThreadC(sharer);
        threadC.setName("ThreadC");

        threadA.start();
        threadB.start();
        threadC.start();
    }
}

class ThreadA extends Thread {
    private Sharer sharer;

    public ThreadA(Sharer sharer) {
        this.sharer = sharer;
    }

    @Override
    public void run() {
        sharer.printA();
    }
}

class ThreadB extends Thread {
    private Sharer sharer;

    public ThreadB(Sharer sharer) {
        this.sharer = sharer;
    }

    @Override
    public void run() {
        sharer.printB();
    }
}

class ThreadC extends Thread {
    private Sharer sharer;

    public ThreadC(Sharer sharer) {
        this.sharer = sharer;
    }

    @Override
    public void run() {
        sharer.printC();
    }
}

synchronized 类锁

public class Sharer {
    private final static DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
	
	// Sharer 类锁
    synchronized
    public static void printA() {
        System.out.println(MessageFormat.format("{0} 线程=[{1}]进入同步 printA 方法",
                LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(MessageFormat.format("{0} 线程=[{1}]离开同步 printA 方法",
                LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
    }

	// Sharer 类锁
    synchronized
    public static void printB() {
        System.out.println(MessageFormat.format("{0} 线程=[{1}]进入同步 printB 方法",
                LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(MessageFormat.format("{0} 线程=[{1}]离开同步 printB 方法",
                LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
    }

	// Sharer 类锁
    public void printC() {
        synchronized (Sharer.class) {
            System.out.println(MessageFormat.format("{0} 线程=[{1}]进入同步 printC 方法",
                    LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(MessageFormat.format("{0} 线程=[{1}]离开同步 printC 方法",
                    LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
        }
    }
}

运行结果输出

2019-10-15 11:19:08 线程=[ThreadA]进入 printA 方法
2019-10-15 11:19:10 线程=[ThreadA]离开 printA 方法
2019-10-15 11:19:10 线程=[ThreadC]进入 printC 方法
2019-10-15 11:19:12 线程=[ThreadC]离开 printC 方法
2019-10-15 11:19:12 线程=[ThreadB]进入 printB 方法
2019-10-15 11:19:14 线程=[ThreadB]离开 printB 方法

结论

public synchronized static void myFun() {
	// ...
}
// 等价于
public void myFun() {
	synchronized (类名.class) {
		// ...
	}
}

由程序运行结果输出得: printA、printC、printB 先后执行, 可知 三个方法共用一个锁(类锁)

 

synchronized 类锁 和对象锁

public class Sharer {
    private final static DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
	
	// Sharer 类锁
    synchronized
    public static void printA() {
        System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printA 方法",
                LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printA 方法",
                LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
    }

    // Sharer 对象锁
    synchronized
    public void printB() {
        System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printB 方法",
                LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printB 方法",
                LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
    }

    // Sharer 对象锁
    public void printC() {
        synchronized (this) {
            System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printC 方法",
                    LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printC 方法",
                    LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
        }
    }
}

程序运行结果

2019-10-15 11:21:01 线程=[ThreadB]进入 printB 方法
2019-10-15 11:21:01 线程=[ThreadA]进入 printA 方法
2019-10-15 11:21:03 线程=[ThreadB]离开 printB 方法
2019-10-15 11:21:03 线程=[ThreadC]进入 printC 方法
2019-10-15 11:21:03 线程=[ThreadA]离开 printA 方法
2019-10-15 11:21:05 线程=[ThreadC]离开 printC 方法

结论

public synchronized void myFun() {
	// ...
}
// 等价于
public void myFun() {
	synchronized (this) {
		// ...
	}
}

由程序运行结果输出得: printA、printB 方法同时执行, 可知 printB 和 printC 共用一个锁, 即 Sharer 对象锁

 

synchronized 类锁和多对象锁

public class Sharer {
    private final static DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
	// Object 可用其他对象, 如: Sharer 等 
    private final static Object OBJECT = new Object();

	// Sharer 类锁
    synchronized
    public static void printA() {
        System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printA 方法",
                LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printA 方法",
                LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
    }

	// Sharer 对象锁
    synchronized
    public void printB() {
        System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printB 方法",
                LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printB 方法",
                LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
    }
	
	// OBJECT 对象锁
    public void printC() {
        synchronized (OBJECT) {
            System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printC 方法",
                    LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printC 方法",
                    LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
        }
    }
}

程序运行结果

2019-10-15 11:25:33 线程=[ThreadB]进入 printB 方法
2019-10-15 11:25:33 线程=[ThreadC]进入 printC 方法
2019-10-15 11:25:33 线程=[ThreadA]进入 printA 方法
2019-10-15 11:25:35 线程=[ThreadC]离开 printC 方法
2019-10-15 11:25:35 线程=[ThreadB]离开 printB 方法
2019-10-15 11:25:35 线程=[ThreadA]离开 printA 方法

结论

由程序运行结果输出得: printA、printB、printC 三个函数同时执行, 可知三个锁属于不同锁

 

不同对象实例共用类锁

public class AnswerApp {
    public static void main(String[] args) throws InterruptedException {
    	// ThreadA、ThreadB、ThreadC 传入参数为不同对象
        ThreadA threadA = new ThreadA(new Sharer());
        threadA.setName("ThreadA");
        ThreadB threadB = new ThreadB(new Sharer());
        threadB.setName("ThreadB");
        ThreadC threadC = new ThreadC(new Sharer());
        threadC.setName("ThreadC");

        threadA.start();
        threadB.start();
        threadC.start();
    }
}
public class Sharer {
    private final static DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

	// Sharer 类锁
    synchronized
    public static void printA() {
        System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printA 方法",
                LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printA 方法",
                LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
    }

	// Sharer 类锁
    synchronized
    public static void printB() {
        System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printB 方法",
                LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printB 方法",
                LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
    }

	// Sharer 类锁
    public void printC() {
        synchronized (Sharer.class) {
            System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printC 方法",
                    LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printC 方法",
                    LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
        }
    }
}

程序运行结果

2019-10-15 11:44:13 线程=[ThreadA]进入 printA 方法
2019-10-15 11:44:15 线程=[ThreadA]离开 printA 方法
2019-10-15 11:44:15 线程=[ThreadC]进入 printC 方法
2019-10-15 11:44:17 线程=[ThreadC]离开 printC 方法
2019-10-15 11:44:17 线程=[ThreadB]进入 printB 方法
2019-10-15 11:44:19 线程=[ThreadB]离开 printB 方法

结论

由程序运行结果输出得: 由于ThreadA、ThreadB、ThreadC 传入参数为不同对象实例, 可知不同对象实例共用一个类锁

不同对象实例不共用对象锁

public class Sharer {
    private final static DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
	// Sharer 对象锁
    synchronized
    public void printA() {
        System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printA 方法",
                LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printA 方法",
                LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
    }

	// Sharer 对象锁
    synchronized
    public void printB() {
        System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printB 方法",
                LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printB 方法",
                LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
    }

	// Sharer 对象锁
    public void printC() {
        synchronized (this) {
            System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printC 方法",
                    LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printC 方法",
                    LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
        }
    }
}

程序运行结果

2019-10-15 11:48:29 线程=[ThreadC]进入 printC 方法
2019-10-15 11:48:29 线程=[ThreadA]进入 printA 方法
2019-10-15 11:48:29 线程=[ThreadB]进入 printB 方法
2019-10-15 11:48:31 线程=[ThreadC]离开 printC 方法
2019-10-15 11:48:31 线程=[ThreadA]离开 printA 方法
2019-10-15 11:48:31 线程=[ThreadB]离开 printB 方法

结论

由程序运行结果输出得: 由于ThreadA、ThreadB、ThreadC 传入参数为不同对象实例, 可知 不同对象之间的 对象锁 互不影响

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jaemon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值