先从无synchronized 说起
观察如下代码
public class StatefulTest {
public static void main(String[] args) {
Runnable stateOfX = () ->{//实现Runnable接口
/*
成员变量被多个线程共享 ,如果一个对象有可被修改的成员变量,就称它为有状态的对象,
反之,如果一个对象没有可被修改的成员变量,就称之为无状态的对象
*/
int x = 0;
while(x!=40){
System.out.println("x: "+x++);
try {
Thread.sleep((long) (Math.random()*100));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//通过两个线程同时访问stateOfX
new Thread(stateOfX).start();
new Thread(stateOfX).start();
}
}
复制代码
- 运行结果
- 从结果可知
- 两个线程会同时得到x = 0这个状态,说明x可被多个线程共享
- 相同数字只会出现两次
- 一个线程修改了值,其他线程可以得到被修改后的值
用一用synchronized
代码
- 创建一个实例对象
public class HelloAndWorld {
public static void main(String[] args) throws InterruptedException {
Test test = new Test();
Runnable run1 = test::hello;//引用test的实例方法
Runnable run2 = test::world;
new Thread(run1).start();
Thread.sleep((long) (Math.random() * 200));//线程sleep,run1一定先启动执行
new Thread(run2).start();
}
}
class Test{//该类有两个synchronized
public synchronized void hello() {
try {
Thread.sleep((long) (Math.random()*1000));
System.out.println("hello");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void world(){
System.out.println("world");
}
}
复制代码
- 创建两个实例对象
public class HelloAndWorld {
public static void main(String[] args) throws InterruptedException {
Test test = new Test();
Test test2 = new Test();//创建两个实列对象
Runnable run1 = test::hello;
Runnable run2 = test2::world;//引用第二个实列对象的方法
new Thread(run1).start();
Thread.sleep((long) (Math.random() * 200));//线程sleep,run1一定先启动执行
new Thread(run2).start();
}
}
class Test{
public synchronized void hello() {
try {
Thread.sleep((long) (Math.random()*1000));
System.out.println("hello");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void world(){
System.out.println("world");
}
}
复制代码
以上两个例子想说明
- 一个线程正在执行一个实列对象的一个
synchronized
方法,另外一个线程也不能执行没有线程执行的synchronized
方法- 一个线程执行到synchronized,就获得了对象的锁,其他线程无法执行任何其他被
synchronized
修饰的方法,不管获得当前对象锁的线程是否在执行该方法
- 一个线程执行到synchronized,就获得了对象的锁,其他线程无法执行任何其他被
- 当一个线程执行完一个
synchronized
方法,一定会释放锁让其他线程有机会获得当前实例对象的锁 - 一个线程正在执行一个实列对象的一个
synchronized
方法,另外一个线程可以执行另外一个实例对象的synchronized
方法- 不同的实例对象有不同的锁,即一个实例对象有一把锁
- 值得一提的是,
synchronized static
方法属于Class
对象,并不属于实例对象它的锁得另外算
从字节码的角度分析synchronized关键字
例一,修饰一个代码块时
public class MyTest {
Object object = new Object();
public void method(){
synchronized (object){
System.out.println("hello world");
}
}
}
复制代码
- 用
Javap
查看字节码指令
- 小结
- 用
synchronized
关键字修饰代码块时,括号中的实例对象可以任意,前提是存在的对象,无论你括号中是什么对象,其将同步的代码块不会受影响- 用
synchronized
关键字修饰代码块时,之所以一个monitorenter
对应两个monitorexit
,是因为一个用来当代码块正常结束让线程释放锁,一个用来当代码块异常结束时让线程释放锁
例二,手动让同步代码块抛异常
- 通过例一可知一个
monitorenter
对应两个monitorexit
,其中一个monitorexit
用来预备当代码块异常结束时也能释放锁 - 试一试主动让同步代码块抛异常
public class MyTest {
Object object = new Object();
public void method2(){
synchronized (object){
System.out.println("hello world");
throw new RuntimeException();//直接抛异常
}
}
}
复制代码
- 字节码
- 小结
- 例一释放锁的方式有异常结束和正常结束两种可能,所以有两个
monitorexit
- 例二释放锁的方式只有一个就是抛出异常(异常结束),来释放锁,所以只有一个
monitorexit
- 总之,系统总会想方设法让线程能释放对象的锁
例三,用synchronized修饰方法
public class MyTest {
public synchronized void method3(){
System.out.println("hello world");
}
}
复制代码
- 字节码
- 小结
- 对于
synchronized
关键字修饰方法来说,并没有出现monitorenter
与monitorexit
指令,而是出现了一个ACC_SYNCHRONIZED
标志。- JVM使用了
ACC_SYNCHRONIZBD
访问标志来区分一个方法是否为同步方法;当方法被调用时,调用指令会检查该方法是否拥有ACC_SYNCHRONIZBD
标志。- 如果有
ACC_SYNCHRONIZBD
标志,那么执行线程将会先持有方法所在对象的Monitor
,然后再去执行方法体,在该方法执行期间,其他任何线程均无法再获取到这个Monitor
,当线程执行完该方法后,它会释放掉这个Monitor
,换句话说只要方法执行完就会释放锁(Monitor
)
例四,用synchronized static修饰方法(同步静态方法)
public class MyTest {
public synchronized static void method4(){
System.out.println("hello world");
}
}
复制代码
- 字节码
- 小结
- 和例三类似,不再赘述
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END