synchronized的四种用法

一 修饰方法

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 作用范围

  1. 作用于方法时,锁住的是对象的实例(this);
  2. 当作用于静态方法时,锁住的是Class实例,又因为Class的相关数据存储在永久带PermGen
    (jdk1.8 则是 metaspace),永久带是全局共享的,因此静态方法锁相当于类的一个全局锁,
    会锁所有调用该方法的线程;
  3. synchronized 作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。
  4. synchronized 作用于一个class实例时,锁住的是所有以该class为锁的代码块。
  • 7
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值