一 修饰方法
Synchronized修饰一个方法很简单,就是在方法的前面加synchronized,synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数。
例如:
方法一
//Synchronized修饰方法时,锁住的是对象实例,如果是不同的对象实例,则锁不住
public synchronized void test_sync() throws InterruptedException {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
System.out.println(Thread.currentThread().getName()+"-进入synchronized修饰方法-"+df.format(new Date()));
Thread.sleep(5000);
}
方法二:
//定义方法体,锁住的是对象实例,如果是不同的对象实例,则锁不住
public void test_sync_code() throws InterruptedException {
synchronized (this){
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
System.out.println(Thread.currentThread().getName()+"-进入synchronize代码块-"+df.format(new Date()));
Thread.sleep(5000);
}
}
写法一修饰的是一个方法,写法二修饰的是一个代码块,但写法一与写法二是等价的,都是锁定了整个方法时的内容。
synchronized关键字不能继承。
虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。这两种方式的例子代码如下:
在子类方法中加上synchronized关键字
class Parent {
public synchronized void method() { }
}
class Child extends Parent {
public synchronized void method() { }
}
在子类方法中调用父类的同步方法
class Parent {
public synchronized void method() { }
}
class Child extends Parent {
public void method() { super.method(); }
}
1、在定义接口方法时不能使用synchronized关键字。
2、构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。
二 修饰一个代码块
1)一个线程访问一个对象中的synchronized(this)同步代码块或方法时,其他试图访问该对象的线程将被阻塞。
Demo:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;
/**
* @Author: thunder
* @Date: 2020/7/26 11:22
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestSynchronized {
@Test
public void test() throws InterruptedException {
//自定义线程池
ExecutorService executorService = new ThreadPoolExecutor(10,20,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>(10));
//定义线程计数器的初始数量,每一个线程完成,调用downLatch.countDown()减1
CountDownLatch latch = new CountDownLatch(2);
//创建TestData对象
TestData testdata = new TestData();
//TestData testdata1 = new TestData();
//TestData testdata2 = new TestData();
//创建线程实例
TestMythead t1 = new TestMythead("Thread-1",latch,testdata);
TestMythead t2 = new TestMythead("Thread-2",latch,testdata);
//提交任务到线程池
executorService.execute(t1);
executorService.execute(t2);
//线程等待,唤醒主线程,继续执行
latch.await();
//关闭线程池,释放资源
executorService.shutdown();
System.out.println(Thread.currentThread().getName()+"-hello,我又回来了");
}
}
class TestMythead implements Runnable{
private Thread t;
private String threadName;
private CountDownLatch downLatch;//线程计数器
private final TestData testData;
//构造方法
TestMythead(String name,CountDownLatch downLatch,TestData data){
this.threadName = name;
this.downLatch = downLatch;
this.testData = data;
}
//定义线程体
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-正在执行...");
try {
this.testData.test_sync();
} catch (InterruptedException e)
e.printStackTrace();
}
//完成任务计数器减1
this.downLatch.countDown();
}
//直接实现runable接口需要同时定义run()和start(),才能执行
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this,threadName);
t.start ();
}
}
}
//定义别的线程
class TestMythead2 extends Thread{
private String threadName;
private CountDownLatch downLatch;//线程计数器
private TestData testData;
//构造方法
TestMythead2(String name,CountDownLatch downLatch,TestData data){
this.threadName = name;
this.downLatch = downLatch;
this.testData = data;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-正在执行...");
try {
//可以并发访问没有Synchronized修饰的方法
testData.test_no_sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
//完成任务计数器减1
this.downLatch.countDown();
}
}
class TestData{
//Synchronized修饰方法时,锁住的是对象实例,如果是不同的对象实例,则锁不住
public synchronized void test_sync() throws InterruptedException {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
System.out.println(Thread.currentThread().getName()+"-进入synchronized修饰方法-"+df.format(new Date()));
Thread.sleep(5000);
}
//定义方法体,锁住的是对象实例,如果是不同的对象实例,则锁不住
public void test_sync_code() throws InterruptedException {
synchronized (this){
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
System.out.println(Thread.currentThread().getName()+"-进入synchronize代码块-"+df.format(new Date()));
Thread.sleep(5000);
}
}
//定义Synchronized修饰静态方法时,锁住的是class实例,class实例都会被锁住
public synchronized static void test_sync_static() throws InterruptedException {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
System.out.println(Thread.currentThread().getName()+"-进入synchronized修饰的静态方法-"+df.format(new Date()));
Thread.sleep(5000);
}
//定义没有Synchronized修饰的方法,别的线程可以访问该对象的非synchronized代码块而不受阻塞
public void test_no_sync() throws InterruptedException {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
System.out.println(Thread.currentThread().getName()+"-进入没有synchronized修饰的方法-"+df.format(new Date()));
Thread.sleep(5000);
}
}
注意下面两个程序的区别:
初始化线程,使用相同对象,锁住对象,线程阻塞
// TestSynchronized类中test()方法创建线程实例
TestData testdata = new TestData();
TestMythead t1 = new TestMythead("Thread-1",latch,testdata);
TestMythead t2 = new TestMythead("Thread-2",latch,testdata);
初始化线程,使用不同对象,线程并行执行,未进入阻塞状态:
// TestSynchronized类中test()方法创建线程实例
TestData testdata1 = new TestData();
TestData testdata2 = new TestData();
//创建线程实例
TestMythead t1 = new TestMythead("Thread-1",latch,testdata);
TestMythead t2 = new TestMythead("Thread-2",latch,testdata);
总结:synchronized作用于方法时,锁住的是对象的实例(this)。
2)一个线程访问一个对象的synchronized代码块时,别的线程可以访问该对象的非synchronized代码块而不受阻塞。
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestSynchronized {
@Test
public void test() throws InterruptedException {
//自定义线程池
ExecutorService executorService = new ThreadPoolExecutor(10,20,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>(10));
//定义线程计数器的初始数量,每一个线程完成,调用downLatch.countDown()减1
CountDownLatch latch = new CountDownLatch(3);
TestData testdata = new TestData();
//TestData testdata1 = new TestData();
//TestData testdata2 = new TestData();
//创建线程实例
TestMythead t1 = new TestMythead("Thread-1",latch,testdata);
TestMythead t2 = new TestMythead("Thread-2",latch,testdata);
//创建其他线程
TestMythead2 t3 = new TestMythead2("Thread-3",latch,testdata);
//提交任务到线程池
executorService.execute(t1);
executorService.execute(t2);
executorService.execute(t3);
//线程等待,唤醒主线程,继续执行
latch.await();
//关闭线程池,释放资源
executorService.shutdown();
System.out.println(Thread.currentThread().getName()+"-hello,我又回来了");
}
}
可以看到定义的其他线程,可以并发访问同一对象非synchronized修饰的代码。
3)指定要给某个对象加锁
class TestMythead implements Runnable{
private Thread t;
private String threadName;
private CountDownLatch downLatch;//线程计数器
private final TestData testData;
//构造方法
TestMythead(String name,CountDownLatch downLatch,TestData data){
this.threadName = name;
this.downLatch = downLatch;
this.testData = data;
}
//定义线程体
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-正在执行...");
//synchronized 作用于一个对象实例时,锁住的是**所有以该对象为锁的代码块**。如果其他代码块,没有以该对象为锁,则可以访问该对象的非synchronized 方法。
synchronized (testData){
System.out.println(Thread.currentThread().getName()+"-进入了修饰的代码块~");
try {
testData.test_no_sync();
//this.testData.test_sync();
//this.testData.test_sync_code();
//TestData.test_sync_static();//class实例直接调用静态方法
} catch (InterruptedException e) {
e.printStackTrace();
}
//完成任务计数器减1
this.downLatch.countDown();
}
}
//直接实现runable接口需要同时定义run()和start(),才能执行
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this,threadName);
t.start ();
}
}
}
//定义别的线程
class TestMythead2 extends Thread{
private String threadName;
private CountDownLatch downLatch;//线程计数器
private TestData testData;
//构造方法
TestMythead2(String name,CountDownLatch downLatch,TestData data){
this.threadName = name;
this.downLatch = downLatch;
this.testData = data;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-正在执行...");
//未以此对象为锁时,可以访问对象的非synchronized方法,已此对象为锁时,线程进入了阻塞状态
//synchronized (TestData.class) {
try {
//可以并发访问没有Synchronized修饰的方法
//testData.test_sync();
testData.test_no_sync();
//TestData.test_sync_static();//class实例直接调用静态方法
} catch (InterruptedException e) {
e.printStackTrace();
}
//完成任务计数器减1
this.downLatch.countDown();
// }
}
}
执行TestSynchronized.test()方法
其他线程未加对象锁时:
其他线程加对象锁时,锁住的是所有以该对象为锁的代码块,对应线程进入了阻塞状态:
synchronized 作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。如果其他代码块,没有以该对象为锁,则可以访问该对象的非synchronized 方法。
三、修饰一个静态的方法
public synchronized static void method() {
// todo
}
静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方法锁定的是这个类的所有对象。
/**
* 同步线程
*/
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public synchronized static 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 run() {
method();
}
}
public class Demo00{
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();
}
}
syncThread1和syncThread2是SyncThread的两个对象,但在thread1和thread2并发执行时却保持了线程同步。这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁。
四 、修饰一个类
Synchronized还可作用于一个类,用法如下:
class ClassName {
public void method() {
synchronized(ClassName.class) {
//todo
}
}
}
/**
* 同步线程
*/
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public static void method() {
//synchronized 作用于一个class实例时,锁住的是所有以该class实例为锁的代码块,如果其他线程未使用class实例锁,则可以访问synchronized方法,
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 synchronized void run() {
method();
}
}
本例的的给class加锁和上例的给静态方法加锁是一样的,是所有对象公用一把锁。
但是synchronized 作用于一个class实例时,锁住的是所有以该class实例为锁的代码块,如果其他线程未使用class实例锁,则可以访问此class实例的非synchronized方法。
总结
A、无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
B、每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
C、 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
Synchronized 作用范围
- 作用于方法时,锁住的是对象的实例(this);
- 当作用于静态方法时,锁住的是Class实例,又因为Class的相关数据存储在永久带PermGen
(jdk1.8 则是 metaspace),永久带是全局共享的,因此静态方法锁相当于类的一个全局锁,
会锁所有调用该方法的线程; - synchronized 作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。
- synchronized 作用于一个class实例时,锁住的是所有以该class为锁的代码块。