小编给大家分享一下Synchronized怎么用,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!
synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
1. 普通同步方法,锁是当前实例对象
2. 静态同步方法,锁是当前类的class对象
3. 同步方法块,锁是括号里面的对象
synchronized 获取的锁,在方法抛出异常的时候会自动解锁
利用javap工具查看生成的class文件信息来分析Synchronize的实现
从上面可以看出,同步代码块是使用monitorenter和monitorexit指令实现的,同步方法(在这看不出来需要看JVM底层实现)依靠的是方法修饰符上的ACC_SYNCHRONIZED实现。
同步代码块:monitorenter指令插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置,JVM需要保证每一个monitorenter都有一个monitorexit与之相对应。任何对象都有一个monitor与之相关联,当且一个monitor被持有之后,他将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁;
同步方法:synchronized方法则会被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令,在VM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象。
同步方法就是在方法前加关键字synchronized,然后被同步的方法一次只能有一个线程进入,其他线程等待。
而同步块则是在方法内部使用大括号使得一个代码块得到同步。同步块会有一个同步的”目标“,使得同步块更加灵活一些(同步块可以通过”目标“决定需要锁定的对象)。一般情况下,如果此”目标“为this,那么同步方法和同步块没有太大的区别。
synchronized 加到 static 方法前面是给class 加锁,即类锁;而synchronized 加到非静态方法前面是给对象上锁。这两者的区别我用代码来演示下:
对象锁和类锁是不同的锁,所以多个线程同时执行这2个不同锁的方法时,是异步的。
在Task2 中定义三个方法 doLongTimeTaskA和doLongTimeTaskB是类锁,而doLongTimeTaskC是对象锁。
public class Task2 {
public synchronized static void doLongTimeTaskA() {
System.out.println("name = " + Thread.currentThread().getName() + ", begain");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("name = " + Thread.currentThread().getName() + ", end");
}
public synchronized static void doLongTimeTaskB() {
System.out.println("name = " + Thread.currentThread().getName() + ", begain");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("name = " + Thread.currentThread().getName() + ", end");
}
public synchronized void doLongTimeTaskC() {
System.out.println("name = " + Thread.currentThread().getName() + ", begain");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("name = " + Thread.currentThread().getName() + ", end");
}
三个线程的代码如下:
class ThreadA extends Thread{
private Task2 mTask2;
public ThreadA(Task2 tk){
mTask2 = tk;
}
public void run() {
mTask2.doLongTimeTaskA();
}
}
class ThreadB extends Thread{
private Task2 mTask2;
public ThreadB(Task2 tk){
mTask2 = tk;
}
public void run() {
mTask2.doLongTimeTaskB();
}
}
class ThreadC extends Thread{
private Task2 mTask2;
public ThreadC(Task2 tk){
mTask2 = tk;
}
public void run() {
mTask2.doLongTimeTaskC();
}
}
main函数中执行代码如下:
Task2 mTask2 = new Task2();
ThreadA ta = new ThreadA(mTask2);
ThreadB tb = new ThreadB(mTask2);
ThreadC tc = new ThreadC(mTask2);
ta.setName("A");
tb.setName("B");
tc.setName("C");
ta.start();
tb.start();
tc.start();
}
执行的结果如下:
name = A, begain, time = 1487311199783
name = C, begain, time = 1487311199783
name = C, end, time = 1487311200784
name = A, end, time = 1487311200784
name = B, begain, time = 1487311200784
name = B, end, time = 1487311201784
可以看出由于 doLongTimeTaskA和doLongTimeTaskB都是类锁,即同一个锁,所以 A和B是按顺序执行,即同步的。而C是对象锁,和A/B不是同一种锁,所以C和A、B是 异步执行的。(A、B、C代指上面的3中方法)。
我们知道对象锁要想保持同步执行,那么锁住的必须是同一个对象。下面就修改下上面的来证明:
Task2.java不变,修改ThreadA 和 ThreadB 如下:
class ThreadA extends Thread{
private Task2 mTask2;
public ThreadA(Task2 tk){
mTask2 = tk;
}
public void run() {
mTask2.doLongTimeTaskC();
}
}
class ThreadB extends Thread{
private Task2 mTask2;
public ThreadB(Task2 tk){
mTask2 = tk;
}
public void run() {
mTask2.doLongTimeTaskC();
}
}
main方法如下:
Task2 mTaska = new Task2();
Task2 mTaskb = new Task2();
ThreadA ta = new ThreadA(mTaska );
ThreadB tb = new ThreadB(mTaskb );
ta.setName("A");
tb.setName("B");
ta.start();
tb.start();
结果如下:
name = A, begain, time = 1487311905775
name = B, begain, time = 1487311905775
name = B, end, time = 1487311906775
name = A, end, time = 1487311906775
从结果看来,对象锁锁的对象不一样,分别是mTaska , mTaskb,所以线程A和线程B调用 doLongTimeTaskC 是异步执行的。
但是,类锁可以对类的所有对象的实例起作用。只需修改ThradA
和 ThreadB,main 方法不做改变,修改如下:
class ThreadA extends Thread{
private Task2 mTask2;
public ThreadA(Task2 tk){
mTask2 = tk;
}
public void run() {
//mTask2.doLongTimeTaskC();
mTask2.doLongTimeTaskA();
}
}
class ThreadB extends Thread{
private Task2 mTask2;
public ThreadB(Task2 tk){
mTask2 = tk;
}
public void run() {
//mTask2.doLongTimeTaskC();
mTask2.doLongTimeTaskA();
}
}
结果如下:
name = A, begain, time = 1487312239674
name = A, end, time = 1487312240674
name = B, begain, time = 1487312240674
name = B, end, time = 1487312241674
可以看出 在线程A执行完doLongTimeTaskA方法后,线程B才会获得该类锁接着去执行doLongTimeTaskA。也就是说,类锁对所有的该类对象都能起作用。
总结:
1. 如果多线程同时访问同一类的 类锁(synchronized 修饰的静态方法)以及对象锁(synchronized 修饰的非静态方法)这两个方法执行是异步的,原因:类锁和对象锁是2中不同的锁。
2. 类锁对该类的所有对象都能起作用,而对象锁不能。
之前谈到过,每个锁关联一个线程持有者和一个计数器。当计数器为0时表示该锁没有被任何线程持有,那么任何线程都都可能获得该锁而调用相应方法。当一个线程请求成功后,JVM会记下持有锁的线程,并将计数器计为1。此时其他线程请求该锁,则必须等待。而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增。当线程退出一个synchronized方法/块时,计数器会递减,如果计数器为0则释放该锁。
以上是“Synchronized怎么用”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注亿速云行业资讯频道!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。