前言
体能状态先于精神状态,习惯先于决心,聚焦先于喜好。
synchronized 关键字
Java 提供了一种内置的锁机制来支持原子性:同步代码块(Synchronized Block).
使用 synchronized 关键字修饰一个方法,或者在一个方法里修饰一个代码块。
synchronized 和字节码
synchronized 关键字在编译之后会被体现在字节码中,其对应两个字节码 monitorenter和 monitorexit,而这两个字节码分别对应了Java内存模型中的 8个基本命令的 lock 和 unlock 命令。lock命令会锁定主内存中涉及到的变量,unlock则会解锁,锁定之后,其他线程无法操作与该对象内置锁相关的代码块或方法的代码,同时也无法从主内存同步涉及到的变量值,unlock命令执行前,当前线程会将工作内存的相关变量同步到主内存。
对象和内置锁(监视器锁)
每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁(Intrinsic Lock)或监视器锁(Monitor Lock).
当 synchronized 修饰一个方法时,那么其所以来的对象就是当前类的对象,只有通过这个对象调用该方法才可以同步不同的线程,当这个方法是static修饰的静态方法时,所有调用该方法的线程都将实现同步。
当synchronized 修饰一个方法中的代码块时需要明确指定一个对象,可以是单独声明的一个对象,也可以是 “this”表示本类,对于单独声明的对象来说,其如果被static 修饰,那么这个代码块可以启动同步任何调用该代码块的线程的作用,否则,其只能作用于拥有对应对象的线程的作用。
synchronized 需要借助于一个对象
synchronized 需要借助于一个Java 对象来获得内置锁。当这个对象被多个线程共享的时候,相关的线程在这个代码块是同步的。
所以务必注意,synchronized 修饰的方法或者代码块涉及的对象是否有被不同的线程关联到。一般而言,使用static 修饰的方法或者全局变量可以确保关联到同一个对象。
synchronized 修饰的方法可以重入
这个概念是和重入锁的概念比较的
你可以在一个 synchronized 修饰的方法中调用另一个 synchronized 修饰的方法,程序不会出现问题。
synchronized 的锁解除
一种是代码运行结束,unlock了,内置锁解除。
另一种是抛出异常,锁也会解除。
再一种是 Object.wait()方法,线程会释放锁,但是同时会等待其他线程Object.notify(),这种释放如果没有唤醒会造成线程永久阻塞。
但是没有其他主动解除锁的机制,比如锁中断机制——彻底的接触锁,而不是先释放然后再次尝试获取。
synchronized 内置锁的公平性
无法保证线程获得锁的公平性,也无法保证线程优先级。
wait 和 notify 必须在 synchronized 代码块中展示
wait 和 notify 必须在 synchronized 代码块中展示。
使用 wait 和 notify的前提是获取了对象锁,所以单独使用(而不是在synchronized 代码块)时,编译期不会报错,但是运行期会报错。
synchronized 使用举例
我们将使用线程池做几个不同的实验,感受不使用同步、使用不同方式的同步所带来的不同的效果。
没有 synchronized 同步的例子
可以从运行结果看到,结果是无序的,每个方法打印开始和结束是混杂在一起的。
- 代码
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SynchronizedTest {
private final static Object obj=new Object();
/**
* 使用 synchronized 修饰整个方法体
*
* @param name
*/
public static void blockMethod(String name){
System.out.println(name+" begin :"+System.currentTimeMillis());
try {
Random random=new Random();
Thread.sleep(random.nextInt(1000));
}catch(InterruptedException e){
System.out.println("exception msg:"+e);
}
System.out.println(name+" end :"+System.currentTimeMillis());
}
/**
* 一个内部类写一个线程
*/
static class MyThread extends Thread{
public MyThread(){}
public MyThread(String name){this.name=name;}
private String name;
@Override
public void run() {
//调用 synchronized 修饰的方法
blockMethod(name);
}
}
/**
*
* @param args
*/
public static void main(String [] args){
ExecutorService pool=Executors.newFixedThreadPool(3);
try{
for (int i=0;i<3;i++){
MyThread m=new MyThread("thread"+i);
pool.submit(m);
}
}finally{
pool.shutdown();
}
}
}
- 输出结果:每个线程的开始和结束混杂在一起
thread1 begin :1564148430029
thread0 begin :1564148430032
thread2 begin :1564148430033
thread1 end :1564148430275
thread0 end :1564148430730
thread2 end :1564148430730
使用 synchronized 修饰静态方法
- 代码
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SynchronizedTest {
private final static Object obj=new Object();
/**
* 使用 synchronized 修饰整个方法体
*
* @param name
*/
public static synchronized void blockMethod(String name){
System.out.println(name+" begin :"+System.currentTimeMillis());
try {
Random random=new Random();
Thread.sleep(random.nextInt(1000));
}catch(InterruptedException e){
System.out.println("exception msg:"+e);
}
System.out.println(name+" end :"+System.currentTimeMillis());
}
/**
* 一个内部类写一个线程
*/
static class MyThread extends Thread{
public MyThread(){}
public MyThread(String name){this.name=name;}
private String name;
@Override
public void run() {
//调用 synchronized 修饰的方法
blockMethod(name);
}
}
/**
* @param args
*/
public static void main(String [] args){
ExecutorService pool=Executors.newFixedThreadPool(3);
try{
for (int i=0;i<3;i++){
MyThread m=new MyThread("thread"+i);
pool.submit(m);
}
}finally{
pool.shutdown();
}
}
}
- 输出结果:每个线程的开始和结束是连接在一起的
thread1 begin :1564148603370
thread1 end :1564148603813
thread2 begin :1564148603813
thread2 end :1564148603941
thread0 begin :1564148603941
thread0 end :1564148604358
使用 synchronized 修饰代码块
这里使用全局静态对象作为内置锁对象,所以含synchronized 代码块的方法也必须是静态的。
- 代码
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SynchronizedTest {
private final static Object obj=new Object();
/**
* 使用 synchronized 修饰方法体
*
* @param name
*/
public static void blockMethod(String name){
synchronized (obj){
System.out.println(name+" begin :"+System.currentTimeMillis());
try {
Random random=new Random();
Thread.sleep(random.nextInt(1000));
}catch(InterruptedException e){
System.out.println("exception msg:"+e);
}
System.out.println(name+" end :"+System.currentTimeMillis());
}
}
/**
* 一个内部类写一个线程
*/
static class MyThread extends Thread{
public MyThread(){}
public MyThread(String name){this.name=name;}
private String name;
@Override
public void run() {
blockMethod(name);
}
}
/**
* 你可以尝试去除 blockMethod() 方法中 synchronized 后运行main方法,看看有什么不同
* @param args
*/
public static void main(String [] args){
ExecutorService pool=Executors.newFixedThreadPool(3);
try{
for (int i=0;i<3;i++){
MyThread m=new MyThread("thread"+i);
pool.submit(m);
}
}finally{
pool.shutdown();
}
}
}
- 结果
thread1 begin :1564151473516
thread1 end :1564151473625
thread2 begin :1564151473631
thread2 end :1564151473692
thread0 begin :1564151473693
thread0 end :1564151474385
synchronized(this)的正确和错误举例
使用 synchronized(this) 表示直接使用 该代码块所在方法对应的对象作为内置锁对象。
这样一般需要我们将内置锁对象通过线程的构造方法传递给线程。如果多个线程传递进去的是同一个对象,那么万事大吉,否则不同对象会因为对象不同而持有不同的内置锁,从而不再同步——线程不安全了。
安全和已读的角度看,一般就别用synchronized(this)了,而是用显示的 Object obj=new Object(); synchronized(obj)
正确用法:所有线程共享同一个对象
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SynchronizedTest {
private final static Object obj=new Object();
/**
* 使用 synchronized 修饰方法体
*
* @param name
*/
public void blockMethod(String name){
synchronized (this){
System.out.println(name+" begin :"+System.currentTimeMillis());
try {
Random random=new Random();
Thread.sleep(random.nextInt(1000));
}catch(InterruptedException e){
System.out.println("exception msg:"+e);
}
System.out.println(name+" end :"+System.currentTimeMillis());
}
}
/**
* @param args
*/
public static void main(String [] args){
ExecutorService pool=Executors.newFixedThreadPool(3);
try{
//只创建一个对象
SynchronizedTest s=new SynchronizedTest();
for (int i=0;i<3;i++){
MyThread m=new MyThread("thread"+i,s);
pool.submit(m);
}
}finally{
pool.shutdown();
}
}
}
/**
* 一个内部类写一个线程
*/
class MyThread extends Thread{
private String name;
private SynchronizedTest s;
public MyThread(){}
public MyThread(String name,SynchronizedTest s){
this.name=name;
this.s=s;
}
@Override
public void run() {
s.blockMethod(name);
}
}
- 结果
thread1 begin :1564153234192
thread1 end :1564153234459
thread2 begin :1564153234459
thread2 end :1564153235232
thread0 begin :1564153235232
thread0 end :1564153236279
错误用法:每个线程传入一个新对象
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SynchronizedTest {
private final static Object obj=new Object();
/**
* 使用 synchronized 修饰方法体
*
* @param name
*/
public void blockMethod(String name){
synchronized (this){
System.out.println(name+" begin :"+System.currentTimeMillis());
try {
Random random=new Random();
Thread.sleep(random.nextInt(1000));
}catch(InterruptedException e){
System.out.println("exception msg:"+e);
}
System.out.println(name+" end :"+System.currentTimeMillis());
}
}
/**
* @param args
*/
public static void main(String [] args){
ExecutorService pool=Executors.newFixedThreadPool(3);
try{
for (int i=0;i<3;i++){
//每个线程传入一个新对象
SynchronizedTest s=new SynchronizedTest();
MyThread m=new MyThread("thread"+i,s);
pool.submit(m);
}
}finally{
pool.shutdown();
}
}
}
/**
* 一个内部类写一个线程
*/
class MyThread extends Thread{
private String name;
private SynchronizedTest s;
public MyThread(){}
public MyThread(String name,SynchronizedTest s){
this.name=name;
this.s=s;
}
@Override
public void run() {
s.blockMethod(name);
}
}
- 结果
thread1 begin :1564152677663
thread0 begin :1564152677665
thread2 begin :1564152677665
thread2 end :1564152678085
thread1 end :1564152678284
thread0 end :1564152678376
synchronized实战举例
wait 和 notify 必须在 synchronized 代码块中展示
使用两个线程,交替打印字符A和B,各打印10次
synchronized(对象)
public static void main(String[] args) {
TestB testB = new TestB();
Thread t1 = new Thread() {
@Override
public void run() {
super.run();
for (int i = 0; i < 10; i++) {
synchronized (testB){
if(flag!=1){
try{
testB.wait();
}catch (InterruptedException e){
}
}
System.out.println("A"+i);
flag=2;
testB.notify();
}
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
super.run();
for (int i = 0; i < 10; i++) {
synchronized (testB){
if(flag!=2){
try{
testB.wait();
}catch (InterruptedException e){
}
}
System.out.println("B"+i);
flag=1;
testB.notify();
}
}
}
};
executorService.execute(t1);
executorService.execute(t2);
}
synchronized(类.class)
public static void main2(String[] args) {
Thread t1 = new Thread() {
@Override
public void run() {
super.run();
for (int i = 0; i < 10; i++) {
synchronized (TestB.class){
if(flag!=1){
try{
TestB.class.wait();
}catch (InterruptedException e){
}
}
System.out.println("A"+i);
flag=2;
TestB.class.notify();
}
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
super.run();
for (int i = 0; i < 10; i++) {
synchronized (TestB.class){
if(flag!=2){
try{
TestB.class.wait();
}catch (InterruptedException e){
}
}
System.out.println("B"+i);
flag=1;
TestB.class.notify();
}
}
}
};
executorService.execute(t1);
executorService.execute(t2);
}
synchronized(this)
this 本质还是对象,即代码块所在的对象,所以本场景下,为了保证两个线程使用同一个对象锁,第一个使用this,第二个线程需要使用第一个对象(即this代码块所在对象)进行 synchronized 同步
public static void main(String[] args) {
Thread t1 = new Thread() {
@Override
public void run() {
super.run();
for (int i = 0; i < 10; i++) {
//this 指的是当前线程
synchronized (this){
if(flag!=1){
try{
this.wait();
}catch (InterruptedException e){
}
}
System.out.println("A"+i);
flag=2;
this.notify();
}
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
super.run();
for (int i = 0; i < 10; i++) {
synchronized (t1){
if(flag!=2){
try{
t1.wait();
}catch (InterruptedException e){
}
}
System.out.println("B"+i);
flag=1;
t1.notify();
}
}
}
};
executorService.execute(t1);
executorService.execute(t2);
}
使用 ReentrantLock + condition 来实现
static ReentrantLock reentrantLock = new ReentrantLock();
static Condition c1 = reentrantLock.newCondition();
static Condition c2 = reentrantLock.newCondition();
static Condition c3 = reentrantLock.newCondition();
static volatile int flag =0 ;
public static void main(String[] args) {
Thread t1 =new Thread(()->{
for(int i=0;i<10;i++){
reentrantLock.lock();
try{
if(flag==0){
}else{
try {
c1.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("a");
flag=1;
c2.signal();
}finally{
reentrantLock.unlock();
}
}
});
Thread t2= new Thread(() -> {
for(int i=0;i<10;i++){
reentrantLock.lock();
try{
if(flag==1){
}else{
try {
c2.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("b");
flag=2;
c3.signal();
}finally{
reentrantLock.unlock();
}
}
});
Thread t3 = new Thread(() -> {
for(int i=0;i<10;i++){
reentrantLock.lock();
try{
if(flag==2){
}else{
try {
c3.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("c");
flag=0;
c1.signal();
}finally{
reentrantLock.unlock();
}
}
});
t1.start();
t2.start();
t3.start();
}
参考资料
[1]、Java 并发编程实战
[2]、Java 高并发程序设计
[3]、深入理解 Java 虚拟机
本文深入解析Java中的synchronized关键字,探讨其在并发编程中的作用、字节码层面的实现原理、对象锁与监视器锁的区别,以及如何正确使用synchronized避免线程安全问题。通过实例演示synchronized在不同场景下的应用,包括静态方法、代码块和内置锁的公平性。
1376

被折叠的 条评论
为什么被折叠?



