首先说一下Java的同步机制:保证在同一时间只有一个线程可以操作对应资源,避免多个线程同时访问相同资源发生冲突。synchronize是java中的一个关键字,它是一种同步锁,可以实现同步机制。
一:synchronized常用的几种方式:
1. synchronized修饰普通方法
2. synchronize修饰静态方法
3. synchronize修饰代码块
下面我把自己写的demo来进行说明
1:synchronized修饰普通方法
public class A {
int num = -1;
public A() {
num = 0;
}
public synchronized void a() {
for (int i = 0; i < 5; i++) {
num += 1;
try {
Thread.sleep(10); System.out.println(Thread.currentThread().getName() +">>>>>>>>>>"+ num);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class Main {
public static void main(String[] args) {
A a = new A();
new Thread(new Runnable() {
public void run() {
a.a();
}
}).start();
new Thread(new Runnable() {
public void run() {
a.a();
}
}).start();
}
}
打印结果:
Thread-0>>>>>>>>>>1
Thread-0>>>>>>>>>>2
Thread-0>>>>>>>>>>3
Thread-0>>>>>>>>>>4
Thread-0>>>>>>>>>>5
Thread-1>>>>>>>>>>6
Thread-1>>>>>>>>>>7
Thread-1>>>>>>>>>>8
Thread-1>>>>>>>>>>9
Thread-1>>>>>>>>>>10
恩,不错,确实是实现了在同一时间内,只有一个线程能访问对应的资源,其他的线程必须的等其结束了才能开始运行。
好,那么我修改一下main中的代码呢?
public class Main {
public static void main(String[] args) {
A a1 = new A();
A a2= new A();
new Thread(new Runnable() {
public void run() {
a1.a();
}
}).start();
new Thread(new Runnable() {
public void run() {
a2.a();
}
}).start();
}
}
打印出来的结果是:
Thread-0>>>>>>>>>>1
Thread-1>>>>>>>>>>1
Thread-0>>>>>>>>>>2
Thread-1>>>>>>>>>>2
Thread-0>>>>>>>>>>3
Thread-1>>>>>>>>>>3
Thread-1>>>>>>>>>>4
Thread-0>>>>>>>>>>4
Thread-0>>>>>>>>>>5
Thread-1>>>>>>>>>>5
咦,这个结果怎么是这样?可以看出两个线程开启后,开始进行交叉运行。这样不对啊!!!应该等第一个线程结束,第二个线程才能开始运行啊。那么问题就来了,那么我就开始说一下这个事情。
synchronize修饰普通的方法时,是对象锁。也就是说在多线程中使用同一个对象访问synchronized修饰的方法可以达到同步的效果。但是如果在不同的线程中使用不同对象去调用其修饰的方法则达不到同步的效果。
2.修饰静态方法
public class A {
static int num = -1;
byte[] lock = new byte[0];
public A() {
num = 0;
}
public static synchronized void a() {
for (int i = 0; i < 5; i++) {
num += 1;
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + ">>>>>>>>>>"
+ num);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class Main {
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
A.a();
}
}).start();
new Thread(new Runnable() {
public void run() {
A.a();
}
}).start();
}
}
打印结果:
Thread-0>>>>>>>>>>0
Thread-0>>>>>>>>>>1
Thread-0>>>>>>>>>>2
Thread-0>>>>>>>>>>3
Thread-0>>>>>>>>>>4
Thread-1>>>>>>>>>>5
Thread-1>>>>>>>>>>6
Thread-1>>>>>>>>>>7
Thread-1>>>>>>>>>>8
Thread-1>>>>>>>>>>9
synchronized修饰的静态方法,这种情况下的锁是包含这个方法的类的锁,也是这个类对象的锁。所以多线程访问时,可以达到同步的效果。
3.修饰代码块
public class A {
static int num = -1;
byte[] lock = new byte[0];
public A() {
num = 0;
}
public void printfLog() {
synchronized (lock) {
for (int i = 0; i < 5; i++) {
num += 1;
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()
+ ">>>>>>>>>>" + num);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
A a1 = new A();
A a2 = new A();
new Thread(new Runnable() {
public void run() {
a1.printfLog();
}
}).start();
new Thread(new Runnable() {
public void run() {
a2.printfLog();
}
}).start();
}
}
打印结果:
Thread-0>>>>>>>>>>2
Thread-1>>>>>>>>>>3
Thread-0>>>>>>>>>>4
Thread-1>>>>>>>>>>5
Thread-0>>>>>>>>>>6
Thread-1>>>>>>>>>>7
Thread-0>>>>>>>>>>8
Thread-1>>>>>>>>>>9
Thread-0>>>>>>>>>>10
Thread-1>>>>>>>>>>10
这中结果和synchronize修饰的普通方法类似。在多线程中不同对象的进行调用,不能实现同步的效果。
注意:
1.在多线程中同一个对象进行调用,可以实现同步的效果 .可自行尝试一下。
2.将lock换成this时,打印的效果是一样的。
那么我将代码改动一下呢?
public class A {
static int num = -1;
byte[] lock = new byte[0];
public A() {
num = 0;
}
public void printfLog() {
synchronized (A.class) {
for (int i = 0; i < 5; i++) {
num += 1;
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()
+ ">>>>>>>>>>" + num);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
打印结果如下:
Thread-0>>>>>>>>>>1
Thread-0>>>>>>>>>>2
Thread-0>>>>>>>>>>3
Thread-0>>>>>>>>>>4
Thread-0>>>>>>>>>>5
Thread-1>>>>>>>>>>6
Thread-1>>>>>>>>>>7
Thread-1>>>>>>>>>>8
Thread-1>>>>>>>>>>9
Thread-1>>>>>>>>>>10
所以这样的效果跟synchronize修饰静态方法一样。这样就可以在不同线程中不同的对象调用时实现同步的效果。
所以
synchronized 修饰方法 等价于 synchronized (this) { … } 都是对象锁
synchronized 修饰静态方法 等价于 synchronized(A.class){ … } 都是类的锁。
下面说一下synchronize修饰方法和代码块的区别:
synchronize修饰方法时,控制整个方法。
synchronize修饰代码块时,只控制代码块(只有代码块之内的才能实现同步效果)。而对于代码块之外的代码,则不受控制(也就是不能实现同步的效果)。
简单一点说就是:synchronize修饰代码块时,更加灵活精确。
二:synchronized支持可重入性
描述:一个同步方法调用另外一个同步方法(包括调用父类加锁的方法),能否得到锁?
可以,synchronized本身可支持重入
代码如下:
public class Demo {
synchronized void test1(){
System.out.println("test1 start.........");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
test2();
System.out.println("test1 end.........");
}
synchronized void test2(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test2 start.......");
}
public static void main(String[] args) {
Demo demo= new Demo();
demo.test1();
}
}
输出的结果如下:
test1 start…
test2 start…
test1 end…
3 synchronized 修饰普通方法时,如果出现异常,则会自动释放锁
证明如下:
public class Demo {
int count = 0;
synchronized void test(){
System.out.println(Thread.currentThread().getName() + " start......");
while (true) {
count ++;
System.out.println(Thread.currentThread().getName() + " count = " + count);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) {
//碰到异常的情况,如果没有处理,会自动释放锁,所以T2可以执行。
int i = 1/0;
}
}
}
public static void main(String[] args) {
Demo demo11 = new Demo();
Runnable r = () -> demo11.test();
new Thread(r, "t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r, "t2").start();
}
}
输出结果如下:
t1 start…
t1 count = 1
t1 count = 2
t1 count = 3
t1 count = 4
t1 count = 5
t2 start…
t2 count = 6
Exception in thread “t1” java.lang.ArithmeticException: / by zero
at com.luban.demo8.Demo.test(Demo.java:24)
at com.luban.demo8.Demo.lambda$main$0(Demo.java:32)
at java.lang.Thread.run(Thread.java:748)
t2 count = 7
t2 count = 8
t2 count = 9
t2 count = 10
… (t2会无线累加下去)