文章目录
1.synchronized是什么
synchronized是java里的一个关键字,可以用来给对象和方法或者代码块加锁,当它锁定一个方法或者代码块的时候,同一时刻最多只有一条线程执行这段代码
synchronized修饰的对象:
- 修饰方法,被修饰的方法被称为同步方法
- 修饰代码块,被修饰的代码块被称为同步代码块
2.synchronized锁的是什么
未加锁状态下线程执行:
// 未加锁的普通方法测试
public class TestMethod {
public static void main(String[] args) {
Date date = new Date();
// 开启一个A线程执行 fun1() 方法
new Thread(()->{
try {
date.fun1();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
// 主线程休眠1秒
Thread.sleep(1000);
// 开启一个B线程执行 fun2() 方法
new Thread(()->{
try {
date.fun2();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
}
}
class Date{
// 未加synchronized修饰
public void fun1() throws InterruptedException {
// 延迟3秒后继续运行
Thread.sleep(3000);
System.out.println("fun1......");
}
// 未加synchronized修饰
public void fun2() throws InterruptedException {
// 直接运行
System.out.println("fun2......");
}
}
执行结果:
显而易见,fun2()由于没有延迟3秒,于是fun2()先打印
1、修饰普通方法
1.1、两个普通方法都加锁
当锁的是普通方法(非静态方法)时,锁的是方法的调用者,也就是下面代码中date这个对象
// 两个普通方法都加锁的测试
public class TestMethod2 {
public static void main(String[] args) {
Date2 date = new Date2();
// 开启一个A线程执行 fun1() 方法
new Thread(()->{
try {
date.fun1();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
// 主线程休眠1秒
Thread.sleep(1000);
// 开启一个B线程执行 fun2() 方法
new Thread(()->{
try {
date.fun2();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
}
}
class Date2{
// 加synchronized修饰
public synchronized void fun1() throws InterruptedException {
// 延迟3秒后继续运行
Thread.sleep(3000);
System.out.println("fun1......");
}
// 加synchronized修饰
public synchronized void fun2() throws InterruptedException {
// 直接运行
System.out.println("fun2......");
}
}
执行结果:
从结果可以看出,由于锁的是方法的调用者,也就是date这个对象,所以A线程先来执行fun1()方法的时候锁定date,导致属于date里面的fun2()方法也被锁定,B线程必须等A线程执行完fun1()方法之后,A线程释放锁,B线程才能重新锁定date,并执行date里的fun2()方法
1.2、一个普通方法加锁,一个普通方法未加锁
普通方法加锁,锁的是调用者,而不加锁的普通方法执行是不需要锁的
// 一个普通方法加锁,一个普通方法未加锁的测试
public class TestMethod2 {
public static void main(String[] args) throws InterruptedException {
Date2 date = new Date2();
// 开启一个A线程执行 fun1() 方法
new Thread(()->{
try {
date.fun1();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
// 主线程休眠1秒
Thread.sleep(1000);
// 开启一个B线程执行 fun2() 方法
new Thread(()->{
try {
date.fun2();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
}
}
class Date2{
// 加synchronized修饰
public synchronized void fun1() throws InterruptedException {
// 延迟3秒后继续运行
Thread.sleep(3000);
System.out.println("fun1......");
}
// 普通方法
public void fun2() throws InterruptedException {
// 直接运行
System.out.println("fun2......");
}
}
执行结果:
从结果来看是fun2()先输出,这是因为fun1()有线程同步,所以fun1()的执行需要锁定date这个对象,而fun2()不是线程同步的,所以fun2()的执行不需要锁定对象就能执行,所以fun2()的执行不需要等fun1()释放锁住的对象,所以fun2()先输出
1.3、两个对象分别执行两个都加锁的普通方法
两个不同的对象,锁的是方法的调用者,所以两个对象分别执行两个普通方法的话,互不干扰
// 两个对象分别执行两个都加锁的普通方法的测试
public class TestMethod2 {
public static void main(String[] args) throws InterruptedException {
Date2 date1 = new Date2();
Date2 date2 = new Date2();
// 开启一个A线程执行 fun1() 方法
new Thread(()->{
try {
date1.fun1();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
// 主线程休眠1秒
Thread.sleep(1000);
// 开启一个B线程执行 fun2() 方法
new Thread(()->{
try {
date2.fun2();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
}
}
class Date2{
// 加synchronized修饰
public synchronized void fun1() throws InterruptedException {
// 延迟3秒后继续运行
Thread.sleep(3000);
System.out.println("fun1......");
}
// 加synchronized修饰
public synchronized void fun2() throws InterruptedException {
// 直接运行
System.out.println("fun2......");
}
}
执行结果:
从结果看出,fun2()先输出,因为当date1执行fun1()方法的时候,锁的是date1,date2执行fun2()的时候,锁的是date2,锁的是两个不同的对象,不存在竞争,所以fun2()先输出
2、修饰静态方法
2.1、两个对象分别执行两个都加锁的静态方法
静态方法锁定的是类,不是对象,所以不管多少个对象锁的都是一个类
// 两个对象分别执行两个都加锁的静态方法的测试
public class TestMethod2 {
public static void main(String[] args) throws InterruptedException {
Date2 date1 = new Date2();
Date2 date2 = new Date2();
// 开启一个A线程执行 fun1() 方法
new Thread(()->{
try {
date1.fun1();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
// 主线程休眠1秒
Thread.sleep(1000);
// 开启一个B线程执行 fun2() 方法
new Thread(()->{
try {
date2.fun2();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
}
}
class Date2{
// 加synchronized修饰的静态方法
public synchronized static void fun1() throws InterruptedException {
// 延迟3秒后继续运行
Thread.sleep(3000);
System.out.println("fun1......");
}
// 加synchronized修饰的静态方法
public synchronized static void fun2() throws InterruptedException {
// 直接运行
System.out.println("fun2......");
}
}
执行结果:
从结果来看,fun1()先输出,因为静态方法锁的是类,而类只有一个,所以fun2()需要等待fun1()执行完后释放锁,fun2()才能执行
2.2、两个对象分别执行一个加锁、一个未加锁的静态方法
静态方法加锁锁定的是类,而fun2()未加锁,不需要锁定类就能直接执行
// 两个对象分别执行一个加锁、一个未加锁的静态方法的测试
public class TestMethod2 {
public static void main(String[] args) throws InterruptedException {
Date2 date1 = new Date2();
Date2 date2 = new Date2();
// 开启一个A线程执行 fun1() 方法
new Thread(()->{
try {
date1.fun1();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
// 主线程休眠1秒
Thread.sleep(1000);
// 开启一个B线程执行 fun2() 方法
new Thread(()->{
try {
date2.fun2();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
}
}
class Date2{
// 加synchronized修饰
public synchronized static void fun1() throws InterruptedException {
// 延迟3秒后继续运行
Thread.sleep(3000);
System.out.println("fun1......");
}
// 未加synchronized修饰的静态方法
public static void fun2() throws InterruptedException {
// 直接运行
System.out.println("fun2......");
}
}
执行结果:
从结果来看,fun2先输出,这是因为fun1执行的时候锁的是类,而fun2没有加锁,所以fun2无需锁定就可以进行直接执行,也就是说fun2()无需等待fun1()释放锁资源,所以fun2()先输出
3、修饰代码块
未加锁的状态下的测试:
// 未加锁的状态测试
public class TestCode {
public static void main(String[] args) {
Date3 date3 = new Date3();
// 启动五条线程
for (int i = 0; i < 5; i++) {
new Thread(()->{
try {
date3.fun();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
class Date3 {
// 未加锁的状态
public void fun() throws InterruptedException {
System.out.println("start......");
// 休眠1秒
Thread.sleep(1000);
System.out.println("end......");
}
}
执行结果:
从结果看出,正常情况下,在五条线程执行完start…的时候都会停下等待1秒,然后全部执行完
修饰代码块,锁定的是传入的对象
3.1、代码块加锁,传入this
当加锁的代码块传入的是this的时候,锁的也是方法的调用者,也就是date3这个对象
// 代码块加锁,传入this的测试
public class TestCode {
public static void main(String[] args) {
Date3 date3 = new Date3();
// 启动五条线程
for (int i = 0; i < 5; i++) {
new Thread(()->{
try {
date3.fun();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
class Date3 {
public void fun() throws InterruptedException {
// 代码块加锁,传入this
synchronized (this) {
System.out.println("start......");
// 休眠1秒
Thread.sleep(1000);
System.out.println("end......");
}
}
}
执行结果:
从结果来看,是排队输出的,因为传入的是this,所以锁的是date3这个对象,所以每个线程在执行的时候都在等待上一个线程释放对象(锁),这个线程才能继续执行
3.2、代码块加锁,传入this,五个不同对象
代码块加锁,传入this,锁的方法的调用者,由于是五个不同的对象,所以互不干扰
// 代码块加锁,传入this,五个不同对象的测试
public class TestCode {
public static void main(String[] args) {
// 启动五条线程
for (int i = 0; i < 5; i++) {
// 每次循环创建一个新的对象
Date3 date3 = new Date3();
new Thread(()->{
try {
date3.fun();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
class Date3 {
public void fun() throws InterruptedException {
// 代码块加锁,传入this
synchronized (this) {
System.out.println("start......");
// 休眠1秒
Thread.sleep(1000);
System.out.println("end......");
}
}
}
执行结果:
从结果来看,五个线程基本是一起输出的,因为,代码块加锁,传入this,锁的是方法的调用者,所以五个线程之间互不干扰,一起输出
3.3、代码块加锁,传入类(.class),五个不同对象
代码块加锁,当传入的是类(.class) 的时候,锁定的自然就是类了
// 代码块加锁,传入类(.class),五个不同对象的测试
public class TestCode {
public static void main(String[] args) {
// 启动五条线程
for (int i = 0; i < 5; i++) {
Date3 date3 = new Date3();
new Thread(()->{
try {
date3.fun();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
class Date3 {
public void fun() throws InterruptedException {
// 代码块加锁,传入类(.class)
synchronized (Date3.class) {
System.out.println("start......");
// 休眠1秒
Thread.sleep(1000);
System.out.println("end......");
}
}
}
执行结果:
从结果来看,线程是一条一条排队输出的,因为当代码块锁中传入的是类的时候,锁定的就是类,而类只有一个,所以即使是不同的对象,也必须等待锁的释放
3.4、代码块加锁,传入Integer的对象,五个不同对象
Integer有一个特殊之处,当直接定义Integer而不用构造器,Integer的值是在-128~127之间的情况下,Integer其实都是存在常量池的,所以即使看起来是五个不同的对象那个,但其实是同一个对象
// 代码块加锁,传入Integer的对象,五个不同对象的测试
public class TestCode {
public static void main(String[] args) {
// 启动五条线程
for (int i = 0; i < 5; i++) {
Date3 date3 = new Date3();
new Thread(()->{
try {
date3.fun();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
class Date3 {
public void fun() throws InterruptedException {
// 代码块加锁,传入普通Integer的对象
Integer num = 1;
synchronized (num) {
System.out.println("start......");
// 休眠1秒
Thread.sleep(1000);
System.out.println("end......");
}
}
}
执行结果:
从结果来看,线程依然是排队输出的,这是因为即使看起来是五条线程中每次都回生成一个新的Integer对象,但是Integer有一个特殊之处,当直接定义Integer而不用构造器,Integer的值是在-128~127之间的情况下,Integer其实都是存在常量池的,所以即使看起来是五个不同的对象那个,但其实是同一个对象。测试的方法也很简单,只需把Integer num = 1;
换成Integer num = new Integer(1);
或者Integer num = 128
就可以看出区别
注意:即使在同步代码块里加一句num++;
锁的依然是同一个对象,需要排队