为什么要使用synchronized同步代码块?
为了防止多线程异步执行导致共享数据出现错误的情况。
语法及原理
synchronized(目标资源){
//业务代码
}
通过语法可以了解到synchronized同步代码块的原理其实很简单,当前线程必须要获得目标资源的锁才可以执行,如果一个线程无法获取资源锁,则进入阻塞状态,待到获取资源锁(占用资源锁的线程执行完之后会释放资源锁)之后方可进入运行状态执行任务。
我们将问题进一步简化,可以这样理解多个线程并行访问是同步还是异步就看当前的资源是一个还是多个,如果是一个资源,则必然是同步,即同一个时刻只能有一个线程获取锁执行任务,其他线程排队等待,等到占用锁的线程执行完毕释放资源后,下一个线程获取锁执行任务,如图,多个线程排队,挨个执行,我们称之为模型A。
如果是多个资源,即每个线程都对应一个资源,则必然是异步,多个线程不需要排队,每个线程都能获取对应资源的锁,都可以执行任务,即多个线程同时执行任务,则为异步,如图,我们称之为模型B。
理解了synchronized同步代码块的原理之后,我们开始写代码。
synchronized同步代码块可以锁定任意数据,运行时类,实例对象,成员变量都可以,我们来看不同的数据类型对应的代码。
运行时类
运行时类在内存中只有一份,所以很显然是模型A,即线程同步。
package com.southwind.thread;
public class SynchronizedTest {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
for (int i = 1; i <= 5; i++) {
new Thread(myRunnable,"线程"+i).start();
}
}
}
class MyRunnable implements Runnable{
private int num = 0;
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (MyRunnable.class) {
num++;
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"是第"+num+"位访客");
}
}
}
运行结果如图。
实例对象
实例对象的创建写在for循环以外,则内存中只有一份实例对象,属于模型A,线程同步。
package com.southwind.thread;
public class SynchronizedTest {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
for (int i = 1; i <= 5; i++) {
new Thread(myRunnable,"线程"+i).start();
}
}
}
class MyRunnable implements Runnable{
private int num = 0;
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (this) {
num++;
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"是第"+num+"位访客");
}
}
}
运行结果如图。
实例对象的创建写在for循环内部,则内存中有多份实例对象,属于模型B,线程异步。
package com.southwind.thread;
public class SynchronizedTest {
public static void main(String[] args) {
for (int i = 1; i <= 5; i++) {
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable,"线程"+i).start();
}
}
}
class MyRunnable implements Runnable{
private int num = 0;
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (this) {
num++;
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"是第"+num+"位访客");
}
}
}
运行结果如图。
成员变量
内存中成员变量的个数是一个还是多个,是通过调用equals()方法来判断的,如果equals()方法返回true,表示两个变量相等,内存中只有一份,如果equals()方法返回false,表示两个变量不相等,内存中有多份。
package com.southwind.thread;
public class SynchronizedTest {
public static void main(String[] args) {
Object obj = new Object();
MyRunnable myRunnable = new MyRunnable(obj);
Object o1 = myRunnable.obj;
for (int i = 1; i <= 5; i++) {
myRunnable.obj = new Object();
System.out.println(myRunnable.obj.equals(o1));
o1 = myRunnable.obj;
new Thread(myRunnable,"线程"+i).start();
}
}
}
class MyRunnable implements Runnable{
private int num = 0;
public Object obj;
public MyRunnable(Object obj) {
// TODO Auto-generated constructor stub
this.obj = obj;
}
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (obj) {
num++;
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"是第"+num+"位访客");
}
}
}
上述代码中每次循环创建线程对象时,都会重新给MyRunnable的成员变量obj赋值,所以成员变量是多个,equals方法返回false,为模型B,线程异步。
运行结果如图。
修改代码,去掉myRunnable.obj = new Object();
package com.southwind.thread;
public class SynchronizedTest {
public static void main(String[] args) {
Object obj = new Object();
MyRunnable myRunnable = new MyRunnable(obj);
Object o1 = myRunnable.obj;
for (int i = 1; i <= 5; i++) {
System.out.println(myRunnable.obj.equals(o1));
o1 = myRunnable.obj;
new Thread(myRunnable,"线程"+i).start();
}
}
}
class MyRunnable implements Runnable{
private int num = 0;
public Object obj;
public MyRunnable(Object obj) {
// TODO Auto-generated constructor stub
this.obj = obj;
}
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (obj) {
num++;
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"是第"+num+"位访客");
}
}
}
每次循环创建线程对象时,没有重新给MyRunnable的成员变量obj赋值,所以成员变量是一个,equals方法返回true,为模型A,线程同步。
运行结果如图。