欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

对synchronized的一点理解

程序员文章站 2024-01-01 09:34:16
...

定义

Java中具有通过synchronized实现的内置锁,内置锁获取锁和释放锁的过程是隐式的,进入synchronized修饰的代码就获得锁,离开相应的代码就释放锁。

作用

当它用来修饰一个方法或一个代码块时,能够保证在同一时刻最多只有一个线程执行该代码。

使用

synchronized主要有两种使用方法:synchronized方法和synchronized代码块。

  • synchronized方法:
public synchronized void foo1() {
    System.out.println("synchronized methoed");
}

注意:synchronized方法只能保证被修饰的方法为互斥访问,而不能保证未被synchronized方法互斥访问。

public class SynchronizedMethod {
    //synchronized方法A
    public synchronized void synchronizedMethodA() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " enter in synchronizedMethodA");
        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName() + " get out synchronizedMethodA");
    }
    //synchronized方法B
    public synchronized void synchronizedMethodB() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " enter in synchronizedMethodB");
        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName() + " get out synchronizedMethodB");
    }
    //非synchronized方法
    public void notSynchronizedMethod() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " enter in notSynchronizedMethod");
        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName() + " get out notSynchronizedMethod");
    }
}

public class SynchronizedTest {

    public static void main(String[] args){
        //设置两个对象
        SynchronizedMethod method = new SynchronizedMethod();
        SynchronizedMethod anotherMethod = new SynchronizedMethod();
        Thread t1 = new Thread(new MyRunnable(method,0), "thread1");
        Thread t2 = new Thread(new MyRunnable(method,1), "thread2");
        Thread t3 = new Thread(new MyRunnable(method,2), "thread3");
        //使用另一个对象作为对比
        Thread t4 = new Thread(new MyRunnable(anotherMethod,0), "thread4");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }

    static class MyRunnable implements Runnable{
        private SynchronizedMethod method;
        private int idx;
        public MyRunnable(SynchronizedMethod method, int idx){
            this.method = method;
            this.idx = idx;
        }
        @Override
        public void run() {
            try {
                if (idx %3 == 0)
                    method.synchronizedMethodA();
                else if (idx % 3 == 1)
                    method.notSynchronizedMethod();
                else
                    method.synchronizedMethodB();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
输出结果:
thread1 enter in synchronizedMethodA
thread2 enter in notSynchronizedMethod
thread4 enter in synchronizedMethodA
thread4 get out synchronizedMethodA
thread1 get out synchronizedMethodA
thread2 get out notSynchronizedMethod
thread3 enter in synchronizedMethodB
thread3 get out synchronizedMethodB

如上,根据结果,对比线程1和线程3可以知道synchronized方法保证了同一对象的互斥访问。对比线程1和线程2可以知道synchronized方法只会保证synchronized修饰的互斥访问。对比线程1和4可以知道,synchronized方法为对象锁,不能保证不同对象的互斥访问。

  • synchronized代码块:
public void foo2() {
    synchronized (this) {
        System.out.println("synchronized methoed");
    }
}

synchronized代码块中的this是指当前对象。也可以将this替换成其他对象,例如将list替换成obj,则foo2()在执行synchronized(obj)时获取的就是obj的同步锁。

public class InsertData {
    private ArrayList<Integer> list1 = new ArrayList<>();
    private ArrayList<Integer> list2 = new ArrayList<>();

    public void insertData1(Thread t){
        synchronized (this){
            for (int i = 0; i < 5; i ++){
                System.out.println(t.getName() + "在插入list1数据" + i);
                list1.add(i);
            }
        }
    }

    public void insertData2(Thread t){
        synchronized (this){
            for (int i = 0; i < 5; i ++){
                System.out.println(t.getName() + "在插入list2数据" + i);
                list2.add(i);
            }
        }
    }
}

public class SychronizedTest2 {

    public static void main(String[] args){
        InsertData insertData = new InsertData();
        Thread t1 = new Thread(new MyRunnable(insertData,0),"thread1");
        Thread t2 = new Thread(new MyRunnable(insertData,1),"thread2");
        t1.start();
        t2.start();
    }

    static class MyRunnable implements Runnable{
        private InsertData insertData;
        private int idx;

        public MyRunnable(InsertData insertData, int idx){
            this.insertData = insertData;
            this.idx = idx;
        }

        @Override
        public void run() {
            if (idx % 2 == 0)
                insertData.insertData1(Thread.currentThread());
            else
                insertData.insertData2(Thread.currentThread());
        }
    }

}
输出结果:
thread2在插入list2数据0
thread2在插入list2数据1
thread2在插入list2数据2
thread2在插入list2数据3
thread2在插入list2数据4
thread1在插入list1数据0
thread1在插入list1数据1
thread1在插入list1数据2
thread1在插入list1数据3
thread1在插入list1数据4

根据结果,我们可以看到两个线程时互斥访问InsertData对象的。如果我们只是希望list1和list2分别被互斥访问,而不是互斥访问InsertData对象,那么可以修改如下:

public class InsertData {
    private ArrayList<Integer> list1 = new ArrayList<>();
    private ArrayList<Integer> list2 = new ArrayList<>();

    public void insertData1(Thread t){
        synchronized (list1){
            for (int i = 0; i < 5; i ++){
                System.out.println(t.getName() + "在插入list1数据" + i);
                list1.add(i);
            }
        }
    }

    public void insertData2(Thread t){
        synchronized (list2){
            for (int i = 0; i < 5; i ++){
                System.out.println(t.getName() + "在插入list2数据" + i);
                list2.add(i);
            }
        }
    }
}

输出结果:
thread1在插入list1数据0
thread2在插入list2数据0
thread1在插入list1数据1
thread2在插入list2数据1
thread1在插入list1数据2
thread2在插入list2数据2
thread1在插入list1数据3
thread2在插入list2数据3
thread1在插入list1数据4
thread2在插入list2数据4

根据结果,我们可以看到两个线程是并行的。

建议:尽量使用synchronized代码块,因为synchronized代码块可以更精确地控制冲突限制访问区域,有时候表现地更高效。

public class SynchronizedTest3 {

    public synchronized void synMethod(){
        for (int i = 0; i < 1000000; i ++);
    }

    public void synBlock(){
        synchronized (this){
            for (int i = 0; i < 1000000; i ++);
        }
    }

    public static void main(String[] args){
        SynchronizedTest3 test3 = new SynchronizedTest3();
        long start,end;
        start = System.currentTimeMillis();
        test3.synMethod();
        end = System.currentTimeMillis();
        System.out.println("synMethod() takes " + (end - start) + " ms");
        start = System.currentTimeMillis();
        test3.synBlock();
        end = System.currentTimeMillis();
        System.out.println("synBlock() takes " + (end - start) + " ms");
    }

}

输出结果:
synMethod() takes 3 ms
synBlock() takes 2 ms

实例锁和全局锁

  • 实例锁:锁在某一个实例对象上。如果类是单例,那么该锁也具有全局锁的概念。其对应的是synchronized关键字。
  • 全局锁:该锁针对的是类,无论实例多少个对象,线程都共享该锁。全局锁对应的是static synchronized。

我们先来验证实例锁:对于同一实例必须互斥访问,而不同实例是可以并行。

public class SynchronizedMethod {
    //synchronized方法A
    public synchronized void synchronizedMethodA() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " enter in synchronizedMethodA");
        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName() + " get out synchronizedMethodA");
    }
    //static synchronized方法B
    public static synchronized void synchronizedMethodB() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " enter in synchronizedMethodB");
        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName() + " get out synchronizedMethodB");
    }
}

public class SynchronizedTest {

    public static void main(String[] args){
        //设置两个对象
        SynchronizedMethod method = new SynchronizedMethod();
        SynchronizedMethod anotherMethod = new SynchronizedMethod();
        //验证synchronized
        Thread t1 = new Thread(new MyRunnable(method), "thread1");
        Thread t2 = new Thread(new MyRunnable(method), "thread2");
        Thread t3 = new Thread(new MyRunnable(anotherMethod), "thread3");
        //验证static synchronized
        //Thread t4 = new Thread(new MyRunnable(method,1), "thread4");
        //Thread t5 = new Thread(new MyRunnable(anotherMethod,1), "thread5");
        t1.start();
        t2.start();
        t3.start();
        //t4.start();
        //t5.start();
    }

    static class MyRunnable implements Runnable{
        private SynchronizedMethod method;
        public MyRunnable(SynchronizedMethod method){
            this.method = method;
        }
        @Override
        public void run() {
            try {
                    method.synchronizedMethodA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

输出结果:
thread3 enter in synchronizedMethodA
thread1 enter in synchronizedMethodA
thread3 get out synchronizedMethodA
thread1 get out synchronizedMethodA
thread2 enter in synchronizedMethodA
thread2 get out synchronizedMethodA

我们可以看到线程1和3为不同实例,因此可以并行处理,而线程1和2是同一个实例,必须互斥访问。

接下来,我们再来验证全局锁

public class SynchronizedTest {

    public static void main(String[] args){
        //设置两个对象
        SynchronizedMethod method = new SynchronizedMethod();
        SynchronizedMethod anotherMethod = new SynchronizedMethod();
        //验证static synchronized
        Thread t4 = new Thread(new MyRunnable(method), "thread4");
        Thread t5 = new Thread(new MyRunnable(anotherMethod), "thread5");
        t4.start();
        t5.start();
    }

    static class MyRunnable implements Runnable{
        private SynchronizedMethod method;
        public MyRunnable(SynchronizedMethod method){
            this.method = method;
        }
        @Override
        public void run() {
            try {
                    method.synchronizedMethodB();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

输出结果:
thread5 enter in synchronizedMethodB
thread5 get out synchronizedMethodB
thread4 enter in synchronizedMethodB
thread4 get out synchronizedMethodB

我们可以看到虽然线程4和5为不同实例,但两者却无法进行并行处理。

基本原则

  • 当一个线程访问“某对象”的synchronized方法“或者”synchronized代码块“时,其他线程对”该对象“的”synchronized方法“或者”synchronized代码块“将被阻塞。
  • 当一个线程访问”某对象“的”synchronized方法“或者”synchronized方法块“时,其他线程可以访问”该对象“的非同步代码块。
  • 当一个线程访问”某对象“的”synchronized方法“或者”synchronized方法块“时,其他线程对”该对象“的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。

synchronized原理

上一篇:

下一篇: