synchronized是Java中的关键字,是java中利用锁机制实现同步的一种方式。
锁机制有如下两种特性:
-
互斥性:即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块(复合操作)进行访问。互斥性我们也往往称为操作的原子性。
-
可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作从而引起不一致。
为什么使用synchronized关键字?
在并发编程中存在线程安全问题,主要原因有:1.存在共享数据 2.多线程共同操作共享数据。关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile。
synchronized的使用方式
修饰方法和代码块(对象锁和类锁)
修饰方法:
对于普通同步方法,锁的是当前的对象
对于静态同步方法,锁的是当前类的所有实例对象
修饰代码块:
对于同步代码块,锁的是synchronized括号里配置的对象(当前对象或者是当前类的Class对象)
使用实例
1.修饰一个代码块
1.1锁当前实例对象
/**
*
* @author Eflying
* @desc synchronized(this)锁实例对象
*/
public class SyncExample implements Runnable {
private int count=0;
public void run() {
doSync();
}
private void doSync() {
synchronized (this) {
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+":"+(count++));
}
}
}
}
main方法测试同一实例对象:
public static void main(String[] args) {
//创建一个实例
SyncExample example = new SyncExample();
//两个并发线程访问同一个对象的同步代码块
Thread t1 = new Thread(example, "thread1");
Thread t2 = new Thread(example, "thread2");
t1.start();
t2.start();
}
执行结果:
thread1:0
thread1:1
thread1:2
thread1:3
thread1:4
thread2:5
thread2:6
thread2:7
thread2:8
thread2:9
从执行结果可以看出:当两个并发线程(thread1和thread2)访问SyncExample的同一个实例对象中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。Thread1和thread2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。
当类中还包含非同步代码块方法时,一个线程访问一个对象的synchronized代码块时,别的线程可以访问该对象的非synchronized代码块而不受阻塞
public class SyncExample implements Runnable {
private int count=0;
public void run() {
//线程1执行同步代码块方法
if("thread1".equals(Thread.currentThread().getName())){
doSync();
}else{
//线程2执行无同步代码块方法
doSync1();
}
}
private void doSync() {
synchronized (this) {
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+":"+(count++));
}
}
}
private void doSync1() {
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+":"+(count++));
}
}
public static void main(String[] args) {
//创建一个实例
SyncExample example = new SyncExample();
//两个并发线程访问同一个对象的同步代码块
Thread t1 = new Thread(example, "thread1");
Thread t2 = new Thread(example, "thread2");
t1.start();
t2.start();
}
}
执行结果:
thread1:0
thread2:1
thread2:2
thread2:3
thread2:4
thread2:5
thread1:6
thread1:7
thread1:8
thread1:9
从结果可以看出:当线程1访问对象的同步代码块方法,此时会对该对象加锁,但当线程2访问该对象的非同步代码块方法时没有被阻塞;
main方法测试不同实例对象:
public static void main(String[] args) {
//创建两个实例
SyncExample1 syncExample1 = new SyncExample1();
SyncExample1 syncExample2 = new SyncExample1();
//由于synchronized锁定的是对象,这时就会有两把锁分别锁定syncExample1对象和syncExample2对象
Thread t1 = new Thread(syncExample1, "thread1");
Thread t2 = new Thread(syncExample2, "thread2");
t1.start();
t2.start();
}
执行结果:
thread1:0
thread2:0
thread2:1
thread2:2
thread2:3
thread2:4
thread1:1
thread1:2
thread1:3
thread1:4
从执行结果可以看出:当两个并发线程(thread1和thread2)访问两个不同对象(SyncExample1)中的synchronized代码块时,由于synchronized锁定的是实例对象,这时就会有两把锁分别锁定syncExample1对象和syncExample2对象,而这两把锁是互不干扰的,不形成互斥,所以两个线程可以同时执行。
1.2锁当前类的所有实例对象
public class SyncExample2 implements Runnable {
private int count=0;
public void run() {
doSync();
}
private void doSync() {
//锁定SyncExample2类的所有对象,线程会被阻塞
synchronized (SyncExample2.class) {
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+":"+(count++));
}
}
}
public static void main(String[] args) {
//创建两个对象
SyncExample2 example1 = new SyncExample2();
SyncExample2 example2 = new SyncExample2();
//两个线程访问不同对象
Thread t1 = new Thread(example1, "thread1");
Thread t2 = new Thread(example2, "thread2");
t1.start();
t2.start();
}
}
执行结果:
thread1:0
thread1:1
thread1:2
thread1:3
thread1:4
thread2:0
thread2:1
thread2:2
thread2:3
thread2:4
从执行结果可以看出:由于synchronized锁定的是SyncExample2.class类,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。
同样,如果类存在非同步代码块的方法 ,那么一个线程访问一个对象的synchronized代码块时,别的线程可以访问该对象的非synchronized代码块而不受阻塞。
2.修饰一个方法
2.1修饰普通方法,锁当前实例对象
当synchronized修饰类的普通方法时,锁定的时当前实例对象
public class SyncExample3 implements Runnable {
private int count=0;
public void run() {
doSync();
}
private synchronized void doSync() {
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+":"+(count++));
}
}
}
同一个实例对象被多个线程执行同步代码方法时 会受到阻塞
public static void main(String[] args) {
//创建一个实例
SyncExample3 example = new SyncExample3();
//两个并发线程访问同一个对象的同步代码块
Thread t1 = new Thread(example, "thread1");
Thread t2 = new Thread(example, "thread2");
t1.start();
t2.start();
}
执行结果:
thread1:0
thread1:1
thread1:2
thread1:3
thread1:4
thread2:5
thread2:6
thread2:7
thread2:8
thread2:9
锁当前实例对象
同样,如果类存在非同步代码块的方法 ,那么一个线程访问一个对象的synchronized代码块时,别的线程可以访问该对象的非synchronized代码块而不受阻塞。
public static void main(String[] args) {
//创建两个实例对象
SyncExample4 example = new SyncExample4();
SyncExample4 example1 = new SyncExample4();
//两个线程访问不同对象的同步方法
Thread t1 = new Thread(example, "thread1");
Thread t2 = new Thread(example1, "thread2");
t1.start();
t2.start();
}
执行结果:
thread2:0
thread1:0
thread1:1
thread1:2
thread1:3
thread1:4
thread2:1
thread2:2
thread2:3
thread2:4
锁实例对象,不同对象,线程并发执行,两把锁是互不干扰的,不形成互斥,所以两个线程可以同时执行。
2.2修饰静态方法,锁当前类的所有实例对象
public class SyncExample5 implements Runnable {
private static int count=0;
public void run() {
doSync();
}
private synchronized static void doSync() {
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+":"+(count++));
}
}
public static void main(String[] args) {
//创建一个实例
SyncExample5 example = new SyncExample5();
SyncExample5 example1 = new SyncExample5();
//两个并发线程访问不同对象的同步代码块
Thread t1 = new Thread(example, "thread1");
Thread t2 = new Thread(example, "thread2");
t1.start();
t2.start();
}
}
执行结果:
thread1:0
thread1:1
thread1:2
thread1:3
thread1:4
thread2:5
thread2:6
thread2:7
thread2:8
thread2:9
静态方法是属于类的而不是属于对象的,所以synchronized修饰的静态方法锁定的是这个类的所有对象。
当两个并发线程(thread1和thread2)访问不同实例对象(SyncExample5)中的同步方法时,只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个方法以后才能执行该代码块。
总结:
对象锁:
锁定的是当前的对象,同一个对象被多线程执行,会被阻塞,同一时刻只能有一个线程得到执行,另一个线程受阻;不同对象之间的锁互不干扰,不形成互斥。
类锁:
锁的是类的所有对象,同一时刻类的所有实例之间的锁互斥,线程会受到阻塞,不能同时执行。
非同步方法或者代码块:
同一时间同一对象,可以在被一个线程执行同步方法/代码块的时候同步执行非同步方法/代码块而不受阻塞。