Java中Synchronized关键字的基本使用方法
1.简介
Synchronized是java的关键字,synchronized可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作),synchronized可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到。
Synchronized关键字细分的话,主要的修饰对象有以下几种:
1)修饰一个代码块,其作用的范围是Synchronized后面{}括起来的代码,作用的对象是调用这个代码块的实例对象,对给定实例对象加锁,进入同步代码块之前要获得给定实例对象的锁。如:
public void test() {
// 同步代码块,this为给定实例对象
synchronized (this) {
// 同步代码块中的内容
}
}
2)修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的实例对象;如:
// 同步方法
public synchronized void test() {
}
3)修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有实例对象。如:
public synchronized static void test() {
}
4)修饰一个类,其作用的范围是synchronized后面{}括起来的部分,作用的对象是这个类的所有实例对象,类似于1与3的结合,与1相比,1只作用于对象的单个实例,而4则作用于对象的全部实例。如:
public void test() {
// 同步代码块,作用的对象为Test类的所有实例对象。
synchronized (Test.Class) {
// 同步代码块中的内容
}
}
2.Synchronized修饰代码块
public class SyncThread implements Runnable {
public static void main(String[] args) {
SyncThread syncThread = new SyncThread();
Thread thread1 = new Thread(syncThread, "SyncThread1");
Thread thread2 = new Thread(syncThread, "SyncThread2");
thread1.start();
thread2.start();
}
private static int count;
public SyncThread() {
count = 0;
}
public void run() {
// 其他代码。。。
//一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞
synchronized (this) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 其他代码。。。
}
public int getCount() {
return count;
}
}
当两个并发线程(thread1和thread2)访问同一个实例对象(syncThread)中的synchronized代码块时,在同一时间只能有一个线程可以执行, 另一个线程会收到阻塞,必须等待当前线程执行完整个代码块以后才能接着执行该代码块。
Thread1和thread2是互斥的,因为在执行synchronized代码块时会锁定当前的实例对象,只有执行完该代码块才能释放该对象锁,下一个线程才能的到并执行并锁定该对象。
我们可以做一个对比,接下来稍微修改一下实例对象(syncThread):
SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();
线程thread1和线程thread2分别创建了两个SyncThread的实例对象syncThread1和syncThread2,线程thread1执行的是syncThread1对象中的synchronized代码块,而线程thread2执行的是syncThread2对象中的synchronized代码块。
我们知道synchronized锁定的是实例对象,这时会有两把锁分别锁定SyncThread这个对象的syncThread1实例和syncThread2实例,这是两把锁,所以互不干扰的,不形成互斥,两个线程可以同时执行。
3.Synchronized修饰方法
public class SyncThread implements Runnable {
public static void main(String[] args) {
SyncThread syncThread = new SyncThread();
Thread thread1 = new Thread(syncThread, "SyncThread1");
Thread thread2 = new Thread(syncThread, "SyncThread2");
thread1.start();
thread2.start();
}
private static int count;
public SyncThread() {
count = 0;
}
public synchronized void method() {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void run() {
method();
}
public int getCount() {
return count;
}
}
synchronized修饰方法和synchronized修饰代码块本质其实没什么区别,synchronized修饰代码块是因为,在一些情况下,我们编写的方法,方法体可能比较大,同时可能存在一些比较耗时的操作,而需要同步的代码又只有一小部分,如果直接对整个方法进行同步操作,可能会造成不小的损失,所以我们使用同步代码块的方式对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作了。
synchronized修饰方法可以参照上文的synchronized修饰代码块。
4.Synchronized修饰静态方法
public class SyncThread implements Runnable {
public static void main(String[] args) {
SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();
}
private static int count;
public SyncThread() {
count = 0;
}
public static synchronized void method() {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void run() {
method();
}
public int getCount() {
return count;
}
}
当synchronized作用于静态方法时,就是相当于当前类的class对象锁,作用于类的所有实例对象,静态方法不专属于任何一个实例对象。
需要注意的是如果一个线程A调用一个实例对象的非static synchronized方法,而线程B需要调用这个实例对象所属类的静态synchronized方法,是允许的,不会发生互斥现象,因为访问静态synchronized方法占用的锁是类的class对象,而访问非静态synchronized方法占用的锁是当前类的实例对象锁。如:
public class SyncThread implements Runnable{
public static void main(String[] args) {
Thread thread1 = new Thread(new SyncThread(), "SyncThread1");
Thread thread2 = new Thread(new SyncThread(), "SyncThread2");
thread1.start();
thread2.start();
}
private static int count;
public SyncThread() {
count = 0;
}
public static synchronized void method() {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void test(){
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void run() {
String threadName = Thread.currentThread().getName();
if (threadName.equals("SyncThread1")) {
method();
} else if (threadName.equals("SyncThread2")) {
test();
}
}
}
5.Synchronized修饰类
public class SyncThread implements Runnable {
public static void main(String[] args) {
SyncThread syncThread = new SyncThread();
Thread thread1 = new Thread(syncThread, "SyncThread1");
Thread thread2 = new Thread(syncThread, "SyncThread2");
thread1.start();
thread2.start();
}
private static int count;
public SyncThread() {
count = 0;
}
public void method() {
synchronized (SyncThread.class) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public void run() {
method();
}
public int getCount() {
return count;
}
}
synchronized修饰类和synchronized修饰静态方法本质其实没什么区别,相当于synchronized修饰代码块,但是其作用范围是类的全部实例对象。
synchronized修饰类可以参照上文的synchronized修饰静态方法。
6.总结一下:
synchronized虽然细分修饰了4种对象:代码块,方法,静态方法,类
但是其本质锁住的对象只有两种:调用的类的实例对象,类的全部实例对象。
代码块和方法只锁住了调用的类的实例对象,我们可以同时调用该类中非synchronized方法。
静态方法和类锁住了类的全部实例对象,我们可以同时调用该类的非静态synchronized方法。