什么是Synchronized,有什么用?
Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
使用方式
1. 修饰对象方法(也就是非静态方法)
如果一个线程正在访问这个对象中synchronized修饰的方法时,那么其他试图访问
该对象
中任何
synchronized修饰的方法的线程都将被阻塞.
很抽象,没关系,继续往下.
/**
* 对象方法加synchronized关键字
*/
class SyncDemo {
synchronized void exec1() {
for (int i = 0; i < 5; i++) {
Thread.sleep(100);//省略try_catch
System.out.println("exec1-->" + Thread.currentThread().getName() + "-->" + i);
}
}
synchronized void exec2() {
for (int i = 0; i < 5; i++) {
Thread.sleep(100);//省略try_catch
System.out.println("exec2-->" + Thread.currentThread().getName() + "-->" + i);
}
}
}
同一个对象,同一个方法
public class SyncDemoTest {
public static void main(String[] args) {
//先将对象实例化,两个线程用的是一个对象
final SyncDemo syncDemo = new SyncDemo();
new Thread(new Runnable() {
@Override
public void run() {
syncDemo1.exec1();
}
}, "thread1").start();
new Thread(new Runnable() {
@Override
public void run() {
syncDemo1.exec1();
}
}, "thread2").start();
}
}
运行结果
exec1-->thread1-->0
exec1-->thread1-->1
exec1-->thread1-->2
exec1-->thread1-->3
exec1-->thread1-->4
exec1-->thread2-->0
exec1-->thread2-->1
exec1-->thread2-->2
exec1-->thread2-->3
exec1-->thread2-->4
同一个对象,不同方法
public class SyncDemoTest {
public static void main(String[] args) {
//先将对象实例化,两个线程用的是一个对象
final SyncDemo syncDemo = new SyncDemo();
new Thread(new Runnable() {
@Override
public void run() {
syncDemo1.exec1();
}
}, "thread1").start();
new Thread(new Runnable() {
@Override
public void run() {
syncDemo1.exec2();
}
}, "thread2").start();
}
}
运行结果
exec1-->thread1-->0
exec1-->thread1-->1
exec1-->thread1-->2
exec1-->thread1-->3
exec1-->thread1-->4
exec2-->thread2-->0
exec2-->thread2-->1
exec2-->thread2-->2
exec2-->thread2-->3
exec2-->thread2-->4
从结果中我们会发现,当exec1()执行的时候,exec2()必须要等待exec1()执行完成后才会执行.
说明: 如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕前,其他线程是无法访问该对象的任何synchronized方法的。
结论
当synchronized关键字修饰一个方法的时候,该方法叫做同步方法。
Java中的每个对象都有一个锁(lock),或者叫做监视器(monitor),当一个线程访问某个对象的synchronized方法时,将该对象上锁,其他任何线程都无法再去访问该对象的synchronized方法了(这里是指所有的同步方法,而不仅仅是同一个方法),直到之前的那个线程执行方法完毕后(或者是抛出了异常),才将该对象的锁释放掉,其他线程才有可能再去访问该对象的synchronized方法。
注意这时候是给对象上锁,如果是不同的对象,则各个对象之间没有限制关系。
one more thing
代码中构造两个线程时,分别在run方法里传入不同的SyncDemo1对象,那么两个线程的执行之间将没有什么制约关系。
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
new SyncDemo().exec1();
}
}, "thread1").start();
new Thread(new Runnable() {
@Override
public void run() {
new SyncDemo().exec2();
}
}, "thread2").start();
}
2. 修饰静态方法
当一个synchronized关键字修饰的方法同时又被static修饰,之前说过,非静态的同步方法会将对象上锁,但是静态方法不属于对象,而是属于类,它会将这个方法所在的类的Class对象上锁。
一个类不管生成多少个对象,它们所对应的是同一个Class对象。
/**
* 静态方法加synchronized关键字
*/
class SyncDemo {
//加入静态修饰
synchronized static void exec1() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("exec1-->" + Thread.currentThread().getName() + "-->" + i);
}
}
//加入静态修饰
synchronized static void exec2() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("exec2-->" + Thread.currentThread().getName() + "-->" + i);
}
}
}
public class SyncDemoTest {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
SyncDemo.exec1();
}
}, "thread1").start();
new Thread(new Runnable() {
@Override
public void run() {
SyncDemo.exec2();
}
}, "thread2").start();
new Thread(new Runnable() {
@Override
public void run() {
new SyncDemo().exec1();
}
}, "thread3").start();
new Thread(new Runnable() {
@Override
public void run() {
new SyncDemo().exec2();
}
}, "thread4").start();
}
}
运行结果
exec1-->thread1-->0
exec1-->thread1-->1
exec1-->thread1-->2
exec1-->thread1-->3
exec1-->thread1-->4
exec2-->thread4-->0
exec2-->thread4-->1
exec2-->thread4-->2
exec2-->thread4-->3
exec2-->thread4-->4
exec1-->thread3-->0
exec1-->thread3-->1
exec1-->thread3-->2
exec1-->thread3-->3
exec1-->thread3-->4
exec2-->thread2-->0
exec2-->thread2-->1
exec2-->thread2-->2
exec2-->thread2-->3
exec2-->thread2-->4
从结果中我们会发现,4个线程的调用时依次执行的
如果某个synchronized方法是static的,那么当线程访问该方法时,它锁的并不是synchronized方法所在的对象,而是synchronized方法所在的类所对应的Class对象。Java中,无论一个类有多少个对象,这些对象会对应唯一一个Class对象,因此当线程分别访问同一个类的两个对象的两个static,synchronized方法时,它们的执行顺序也是顺序的,也就是说一个线程先去执行方法,执行完毕后另一个线程才开始
3. 修饰代码块
如下,
对象锁
void exec() {
synchronized (this) {
for (int i = 0; i < 5; i++) {
Thread.sleep(100);
System.out.println("exec-->" + Thread.currentThread().getName() + "-->" + i);
}
}
}
其效果等同于修饰对象方法.this作为对象锁.
类锁
static void exec() {
synchronized (SyncDemo.class) {
for (int i = 0; i < 5; i++) {
Thread.sleep(100);
System.out.println("exec-->" + Thread.currentThread().getName() + "-->" + i);
}
}
}
其效果等同于修饰静态方法.
4. 混合修饰
class SyncDemo {
synchronized void exec() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("exec -->" + Thread.currentThread().getName() + "-->" + i);
}
}
synchronized static void exec1() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("exec1-->" + Thread.currentThread().getName() + "-->" + i);
}
}
synchronized static void exec2() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("exec2-->" + Thread.currentThread().getName() + "-->" + i);
}
}
}
public class SyncDemoTest {
public static void main(String[] args) {
final SyncDemo syncDemo = new SyncDemo();
new Thread(new Runnable() {
@Override
public void run() {
syncDemo.exec();
}
}, "thread ").start();
new Thread(new Runnable() {
@Override
public void run() {
syncDemo.exec1();
}
}, "thread1").start();
new Thread(new Runnable() {
@Override
public void run() {
syncDemo.exec2();
}
}, "thread2").start();
}
}
运行结果
exec -->thread -->0
exec1-->thread1-->0
exec -->thread -->1
exec1-->thread1-->1
exec -->thread -->2
exec1-->thread1-->2
exec -->thread -->3
exec1-->thread1-->3
exec -->thread -->4
exec1-->thread1-->4
exec2-->thread2-->0
exec2-->thread2-->1
exec2-->thread2-->2
exec2-->thread2-->3
exec2-->thread2-->4
从结果来看,对象锁的thread和类锁的thread1 两个线程交替进行.
说明对象锁跟类的锁之间没有相互制约关系
Tips
-
synchronized方法与synchronized代码块的区别
synchronized methods(){} 与synchronized(this){}之间没有什么区别,只是 synchronized methods(){} 便于阅读理解,而synchronized(this){}可以更精确的控制冲突限制访问区域,有时候表现更高效率
-
synchronized关键字是不能继承的
-
使用synchronized关键字解决线程的同步问题会带来一些执行效率上的问题。
JDK 5.0引入了这样一个包:java.util.concurrent:
简述
无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是类.
每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
类的锁跟对象的锁之间没有制约关系.