在多线程中,难免会出现多个线程中对同一个对象的实例变量进行并发发访问的情况,如果不做正确的同步处理,那么产生的后果就是"脏读",也就是取到的数据其实是被更改过的。
synchronized锁机制:| 修饰类型 | 说明 | 持有的锁 |
| 修饰代码块 | 大括号括起来的代码,作用于调用的对象。 | 对象锁 |
| 修饰方法 | 整个方法,作用于调用对象。 | |
| 修饰静态方法 | 整个静态方法,作用于所有对象。 | 类锁 |
| 修饰类 | 括起来的部分,作用于所有对象。 |
关键字synchronized修饰非static方法或代码块取得的锁都是对象锁,两个synchronized方法或synchronized块之间具有互斥性,换句话说,synchronized块锁定的是整个对象,即如果线程A访问了一个对象O的synchronized方法或synchronized块,那么线程B对同一对象O的synchronized方法或synchronized块的访问将被阻塞。
synchronized修饰方法:A线程持有O对象的Lock锁,B线程如果在这时调用O对象中的synchronized类型的方法则需要等待,也就是同步。
public class MainTest00 {
public static void main(String[] args) throws InterruptedException {
DoMain00 doMain = new DoMain00();
MyThread001 myThread00 = new MyThread001(doMain);
MyThread002 myThread01 = new MyThread002(doMain);
myThread00.start();
myThread01.start();
}
}
class MyThread001 extends Thread {
private DoMain00 domain;
public MyThread001(DoMain00 domain) {
this.domain = domain;
}
@Override
public void run() {
domain.methodA();
}
}
class MyThread002 extends Thread {
private DoMain00 domain;
public MyThread002(DoMain00 domain) {
this.domain = domain;
}
@Override
public void run() {
domain.methodB();
}
}
class DoMain00 {
public synchronized void methodA() {
try {
System.out.println("调用A该方法的线程为: " + Thread.currentThread().getName() + "开始时间为: " + System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("调用A该方法的线程为: " + Thread.currentThread().getName() + "结束时间为: " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void methodB() {
try {
System.out.println("调用B该方法的线程为: " + Thread.currentThread().getName() + "开始时间为: " + System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("调用B该方法的线程为: " + Thread.currentThread().getName() + "结束时间为: " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果:
调用A该方法的线程为: Thread-0开始时间为: 1635035661099
调用A该方法的线程为: Thread-0结束时间为: 1635035666113
调用A该方法的线程为: Thread-1开始时间为: 1635035666113
调用A该方法的线程为: Thread-1结束时间为: 1635035671126
A线程持有Object对象的Lock锁,B线程可以以异步方式调用Object对象中的非synchronized类型的方法不需要同步,也就是异步。
public class MainTest01 {
public static void main(String[] args) throws InterruptedException {
DoMain01 doMain01 = new DoMain01();
MyThread011 myThread011 = new MyThread011(doMain01);
MyThread012 myThread002 = new MyThread012(doMain01);
myThread011.start();
myThread002.start();
}
}
class MyThread011 extends Thread {
private DoMain01 domain;
public MyThread011(DoMain01 domain) {
this.domain = domain;
}
@Override
public void run() {
domain.methodA();
}
}
class MyThread012 extends Thread {
private DoMain01 domain;
public MyThread012(DoMain01 domain) {
this.domain = domain;
}
@Override
public void run() {
domain.methodB();
}
}
class DoMain01 {
public synchronized void methodA() {
try {
System.out.println("调用A该方法的线程为: " + Thread.currentThread().getName() + "开始时间为: " + System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("调用A该方法的线程为: " + Thread.currentThread().getName() + "结束时间为: " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void methodB() {
try {
System.out.println("调用B该方法的线程为: " + Thread.currentThread().getName() + "开始时间为: " + System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("调用B该方法的线程为: " + Thread.currentThread().getName() + "结束时间为: " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果:
synchronized修饰代码块:调用A该方法的线程为: Thread-0开始时间为: 1635036204008
调用B该方法的线程为: Thread-1开始时间为: 1635036204008
调用B该方法的线程为: Thread-1结束时间为: 1635036209013
调用A该方法的线程为: Thread-0结束时间为: 1635036209013
当A线程访问对象的synchronized代码块的时候,B线程依然可以访问对象方法中其余非synchronized块的部分。
当A线程访问对象的synchronized代码块的时候,B线程如果要访问这段synchronized块,那么访问将会被阻塞。
public class MainTest02 {
public static void main(String[] args) throws InterruptedException {
DoMain02 doMain = new DoMain02();
MyThread021 myThread = new MyThread021(doMain);
MyThread022 mzThread = new MyThread022(doMain);
myThread.start();
mzThread.start();
}
}
class MyThread021 extends Thread {
private DoMain02 domain;
public MyThread021(DoMain02 domain) {
this.domain = domain;
}
@Override
public void run() {
domain.methodA();
}
}
class MyThread022 extends Thread {
private DoMain02 domain;
public MyThread022(DoMain02 domain) {
this.domain = domain;
}
@Override
public void run() {
domain.methodA();
}
}
class DoMain02 {
public void methodA() {
System.out.println("当前执行该方法的线程为:" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
synchronized (this) {
try {
System.out.println(Thread.currentThread().getName() + "正在执行同步代码块的代码..." + System.currentTimeMillis());
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "执行结束了... " + System.currentTimeMillis());
}
}
执行结果:
Synchronized方法与Synchronized块:当前执行该方法的线程为:Thread-0 1635037600650
Thread-0正在执行同步代码块的代码...1635037600650
当前执行该方法的线程为:Thread-1 1635037600650
Thread-0执行结束了... 1635037603657
Thread-1正在执行同步代码块的代码...1635037603657
Thread-1执行结束了... 1635037606669
线程A访问对象O的synchronized方法,线程B访问对象的synchronized代码块时将会被阻塞,线程C依然可以访问该对象的非synchronized方法或synchronized块。
public class MainTest03 {
public static void main(String[] args) throws InterruptedException {
DoMain doMain = new DoMain();
MyThread031 myThread031 = new MyThread031(doMain);
MyThread032 myThread032 = new MyThread032(doMain);
MyThread033 myThread033 = new MyThread033(doMain);
myThread031.start();
myThread032.start();
myThread033.start();
}
}
class MyThread031 extends Thread {
private DoMain domain;
public MyThread031(DoMain domain) {
this.domain = domain;
}
@Override
public void run() {
domain.methodC();
}
}
class MyThread032 extends Thread {
private DoMain domain;
public MyThread032(DoMain domain) {
this.domain = domain;
}
@Override
public void run() {
domain.methodA();
}
}
class MyThread033 extends Thread {
private DoMain domain;
public MyThread033(DoMain domain) {
this.domain = domain;
}
@Override
public void run() {
domain.methodB();
}
}
class DoMain {
public void methodA() {
System.out.println("当前执行A方法的线程为:" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
synchronized (this) {
try {
System.out.println( Thread.currentThread().getName() + "正在执行同步代码块的代码..." + System.currentTimeMillis());
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("方法A执行结束了... " + System.currentTimeMillis());
}
public synchronized void methodB() {
System.out.println("当前执行B方法的线程为:" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println( Thread.currentThread().getName() + "方法B执行结束了... " + System.currentTimeMillis());
}
public void methodC() {
System.out.println("当前执行c方法的线程为:" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println( Thread.currentThread().getName() + "方法c执行结束了... " + System.currentTimeMillis());
}
}
执行结果:
将任意对象作为对象监视器:当前执行A方法的线程为:Thread-1 1635043547219
当前执行B方法的线程为:Thread-2 1635043547219
当前执行c方法的线程为:Thread-0 1635043547219
Thread-2方法B执行结束了... 1635043550223
Thread-0方法c执行结束了... 1635043550223
Thread-1正在执行同步代码块的代码...1635043550223
方法A执行结束了... 1635043553227
Java还支持对"任意对象"作为对象监视器来实现同步的功能。这个"任意对象"大多数是实例变量或方法的参数,使用格式为synchronized(非this对象)。
多个线程持有"对象监视器"为同一个对象的前提下,同一时间只能有一个线程可以执行synchronized(非this对象x)代码块中的代码。
public class MainTest04 {
public static void main(String[] args) throws InterruptedException {
DoMain04 doMain = new DoMain04();
MyThread041 myThread041 = new MyThread041(doMain);
MyThread042 myThread042 = new MyThread042(doMain);
MyThread043 myThread043 = new MyThread043(doMain);
myThread041.start();
myThread042.start();
myThread043.start();
}
}
class MyThread041 extends Thread {
private DoMain04 domain;
public MyThread041(DoMain04 domain) {
this.domain = domain;
}
@Override
public void run() {
domain.methodA();
}
}
class MyThread042 extends Thread {
private DoMain04 domain;
public MyThread042(DoMain04 domain) {
this.domain = domain;
}
@Override
public void run() {
domain.methodA();
}
}
class MyThread043 extends Thread {
private DoMain04 domain;
public MyThread043(DoMain04 domain) {
this.domain = domain;
}
@Override
public void run() {
domain.methodB();
}
}
class DoMain04 {
private String anyThing = new String();
private String anythong = new String();
public void methodA() {
System.out.println("当前执行A方法的线程为:" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
synchronized (anyThing) {
try {
System.out.println(Thread.currentThread().getName() + "正在执行同步代码块的代码..." + System.currentTimeMillis());
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "方法执行结束了... " + System.currentTimeMillis());
}
public void methodB() {
System.out.println("当前执行B方法的线程为:" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
synchronized (anythong) {
try {
System.out.println(Thread.currentThread().getName() + "正在执行同步代码块的代码..." + System.currentTimeMillis());
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "方法B执行结束了... " + System.currentTimeMillis());
}
}
执行结果:
synchronized(非this对象x):当前执行A方法的线程为:Thread-0 1635043896425
当前执行B方法的线程为:Thread-2 1635043896425
当前执行A方法的线程为:Thread-1 1635043896425
Thread-2正在执行同步代码块的代码...1635043896425
Thread-0正在执行同步代码块的代码...1635043896425
Thread-0方法执行结束了... 1635043899436
Thread-2方法B执行结束了... 1635043899436
Thread-1正在执行同步代码块的代码...1635043899436
Thread-1方法执行结束了... 1635043902436
synchronized(非this对象x)格式的写法是将x对象本身作为对象监视器。
| 当多个线程同时执行synchronized(x){}同步代码块时呈同步效果。 |
| 当其他线程执行x对象中的synchronized同步方法时呈同步效果。 |
| 当其他线程执行x对象方法中的synchronized(this)代码块时也呈同步效果。 |
| 1 | 如果在一个类中有很多synchronized方法,这时虽然能实现同步,但会受到阻塞,从而影响效率。但如果同步代码块锁的是非this对象,则synchronized(非this对象x)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,大大提高了运行效率。 |
| 2 | synchronized(非this对象x),这个对象如果是实例变量的话,指的是对象的引用,只要对象的引用不变,即使改变了对象的属性,运行结果依然是同步的。 |
关键字synchronized拥有锁重入的功能,当一个线程得到一个对象锁后,再次请求此对象锁时可以再次得到该对象的锁。锁重入的机制,也支持在父子类继承的环境中。
public class MainTest05 {
public static void main(String[] args) throws InterruptedException {
DoMain05 doMain = new DoMain05();
MyThread051 myThread051 = new MyThread051(doMain);
myThread051.start();
}
}
class MyThread051 extends Thread {
private DoMain05 domain;
public MyThread051(DoMain05 domain) {
this.domain = domain;
}
@Override
public void run() {
domain.methodA();
}
}
class DoMain05 {
public synchronized void methodA() {
System.out.println("我被mythread线程调用...");
methodB();
}
public synchronized void methodB() {
System.out.println("我被methodA()调用...");
methodC();
}
public synchronized void methodC() {
System.out.println("我被methodB()调用...");
}
}
执行结果:
异常自动释放锁:我被mythread线程调用...
我被methodA()调用...
我被methodB()调用...
当一个线程执行的代码出现异常,并且没有捕获时,其所持有的锁会自动释放。
类锁与对象锁:public class MainTest06 {
public static void main(String[] args) throws InterruptedException {
DoMain06 doMain = new DoMain06();
MyThread061 myThread061 = new MyThread061();
MyThread062 myThread062 = new MyThread062();
MyThread063 myThread063 = new MyThread063(doMain);
myThread061.start();
myThread062.start();
myThread063.start();
}
}
class MyThread061 extends Thread {
@Override
public void run() {
DoMain06.methodA();
}
}
class MyThread062 extends Thread {
@Override
public void run() {
DoMain06.methodB();
}
}
class MyThread063 extends Thread {
private DoMain06 domain;
public MyThread063(DoMain06 domain) {
this.domain = domain;
}
@Override
public void run() {
domain.methodC();
}
}
class DoMain06 {
public synchronized static void methodA() {
System.out.println("当前执行A方法的线程为:" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
try {
System.out.println("正在执行同步代码块的代码..." + System.currentTimeMillis());
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("方法A执行结束了... " + System.currentTimeMillis());
}
public synchronized static void methodB() {
System.out.println("当前执行B方法的线程为:" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
try {
System.out.println("正在执行同步代码块的代码..." + System.currentTimeMillis());
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("方法B执行结束了... " + System.currentTimeMillis());
}
public synchronized void methodC() {
System.out.println("当前执行C方法的线程为:" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
try {
System.out.println("正在执行同步代码块的代码..." + System.currentTimeMillis());
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("方法C执行结束了... " + System.currentTimeMillis());
}
}
执行结果:
当前执行C方法的线程为:Thread-2 1635042326932
当前执行A方法的线程为:Thread-0 1635042326932
正在执行同步代码块的代码...1635042326932
正在执行同步代码块的代码...1635042326932
方法C执行结束了... 1635042329936
方法A执行结束了... 1635042329936
当前执行B方法的线程为:Thread-1 1635042329936
正在执行同步代码块的代码...1635042329936
方法B执行结束了... 1635042332943
methodA与methodB呈现同步效果,而与methodC呈现异步效果,说明静态同步方法和非静态同步方法持有的是不同的锁,前者是类锁,后者是对象锁。
关键字synchronized修饰static方法或类(xx.class),表示对当前.java文件对应的Class类加锁,即类锁。所谓类锁,举个具体的例子。假如一个类中有一个静态同步方法A,new出了两个类的实例B和实例C,线程D持有实例B,线程E持有实例C,只要线程D调用了A方法,那么线程E调用A方法必须等待线程D执行完A方法,尽管两个线程持有的是不同的对象。
synchronized关键字以及对象锁和类锁的区别:| 无论是修饰方法还是修饰代码块都是对象锁,当一个线程访问一个带synchronized方法时,由于对象锁的存在,所有加synchronized的方法都不能被访问(前提是在多个线程调用的是同一个对象实例中的方法)。 |
| 无论是修饰静态方法还是锁定某个class,都是类锁。一个class其中的静态方法和静态变量在内存中只会加载和初始化一份,所以,一旦一个静态的方法被申明为synchronized,此类的所有的实例化对象在调用该方法时,共用同一把锁,称之为类锁。 |
死锁是指两个或两个以上的进程(线程)在运行过程中因争夺资源而造成的一种僵局(Deadly-Embrace) ) ,若无外力作用,这些进程(线程)都将无法向前推进。
public class DeadLockTest implements Runnable {
public int flag = 1;
//静态对象是类的所有对象共享的
private static Object o1 = new Object(), o2 = new Object();
@Override
public void run() {
System.out.println("flag = " + flag);
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("准备获取o2的对象锁" + System.currentTimeMillis());
synchronized (o2) {
System.out.println("1");
}
}
}
if (flag == 0) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("准备获取o1的对象锁" + System.currentTimeMillis());
synchronized (o1) {
System.out.println("0");
}
}
}
}
public static void main(String[] args) {
DeadLockTest td1 = new DeadLockTest();
DeadLockTest td2 = new DeadLockTest();
td1.flag = 1;
td2.flag = 0;
//td1,td2都处于可执行状态,但JVM线程调度先执行哪个线程是不确定的。
//td2的run()可能在td1的run()之前运行
new Thread(td1).start();
new Thread(td2).start();
}
}
死锁必要条件:
| 互斥条件 | 进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。 |
| 不剥夺条件 | 进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。 |
| 请求与保持条件 | 进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。 |
| 环路等待条件 | 存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, …, pn},其中Pi等 待的资源被P(i+1)占有(i=0, 1, …, n-1),Pn等待的资源被P0占有。 |
常用避免死锁的技术:
| 加锁顺序(线程按照一定的顺序加锁); |
| 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁); |
| 死锁检测; |



