synchronized
synchronized关键字可以保证在同一时间只有一个线程可以执行某个方法或者某个代码块,同时synchronized关键字可以保证一个线程的变化可见——可以代替volatile
常见使用场景
主要用于处理线程安全方面的问题
- 1、代码中涉及到共享数据时
- 2、使用多线程共同操作共享数据时
实现原理
- 1、synchronized关键字可以保证方法和代码块运行时,同一时刻只有一个线程进入到临界区,同时还可以保证共享变量的内存可见性。
三种应用方式
Java中的每个对象都可以作为锁——synchronized实现同步的基础
- 1、普通同步方法(实例方法):锁是当前实例对象,进入同步代码前要获得当前实例的锁。
样例:多个线程访问同一个对象的方法:
public class SynchronizedTest01 implements Runnable {
static int i = 0;
/**
* synchronized修饰实例方法
*/
public synchronized void increase(){
i++;
}
@Override
public void run() {
for(int j = 0;j < 3;j++){
increase();
}
}
public static void main(String[] args) throws Exception {
SynchronizedTest01 test01 = new SynchronizedTest01();
Thread t1 = new Thread(test01);
Thread t2 = new Thread(test01);
Thread t3 = new Thread(test01);
//启动线程
t1.start();
t2.start();
t3.start();
//join方法礼让线程
t1.join();
t2.join();
t3.join();
//最后输出i的值
System.out.println(i);
}
}
样例输出:
样例说明:
当多个线程通过对一个对象的方法进行操作时,只有一个线程才能抢到锁,因为一个对象只有一把锁,只要一个线程获得了该对象的锁之后,其他的线程就只能等待该线程执行完之后(同时也不能执行该对象的其他synchronized实例方法)才然后所有线程在一起抢该对象的锁,
public class SynchronizedTest02 {
/**
* synchronized修饰实例方法:方法一
*/
public synchronized void test01(){
System.out.println("test01--start");
System.out.println("test01-execute");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test01--end");
}
public synchronized void test02(){
System.out.println("test02--start");
System.out.println("test02-execute");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test02--end");
}
public static void main(String[] args) throws Exception {
SynchronizedTest02 test02 = new SynchronizedTest02();
new Thread(new Runnable() {
@Override
public void run() {
test02.test01();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test02.test02();
}
}).start();
}
}
样例输出:
test01--start
test01-execute
test01--end
test02--start
test02-execute
test02--end
可见:
-
1、其他线程想要访问该对象的synchronized修饰的方法必须等到该对象的锁释放后才能再次执行。
-
2、当该对象的锁被占用后,只有拥有该对象的锁才能访问该对象的synchronized修饰的实例方法,但是其他线程可以访问该对象的其他实方法(没有被synchronized修饰的方法)—— 将上述代码方法test02的synchronized关键字去掉后再次运行即可验证:当线程1还在执行时,线程2也开始执行了。
-
3、当多个线程获取的锁的对象都不相同时,则它们之间的运行互不影响。2中的样例可以说明。
为什么分布式环境下synchronized失效?如何解决这种情况?
详解见另一篇博客传送门 -
2、静态同步方法:锁是当前类的class对象,进入同步代码前要获得当前类对象的锁。
多个线程实例化多个不同的对象,但是访问的方法是静态方法,因此多个线程之间就会发现线程互斥,即其中一个线程如果占有锁之后,其他的线程就只能等待占有锁的线程执行完之后才能继续执行。—— 当synchronized修饰静态方法之后,锁是class对象。
- 3、同步方法块:锁是括号里的对象,对给定对象加锁,进入同步代码块前要先获得给定对象的锁。
样例:
package synchronized_test;
public class SynchronizedTest03 implements Runnable {
static SynchronizedTest03 test03 = new SynchronizedTest03();
static int i = 0;
@Override
public void run() {
//同步块外包含其他的大量代码
//使用同步代码块对i++的操作进行控制,锁对象为test03
synchronized (test03){
for(int j = 0;j < 10000;j++){
i++;
}
}
}
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(test03);
Thread t2 = new Thread(test03);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
分析: 将synchronized关键字用于一个指定的实例对象的test03,每次当线程进入该同步代码块时都需要持有test03实例对象锁,如果当前有多个线程持有该对象锁,那么新到的线程就必须等待已经在执行的线程执行完毕后才能继续执行,从而保证每次只有一个线程执行同步代码块。除了可以将指定的实例对象作为锁之外,也可以使用this对象或者当前类的class对象作为锁。
以上博客内容参见:
【Java并发编程之深入理解】Synchronized的使用