【线程 锁】synchronized所有用法汇总(包含this语法)

1. synchronized的用法

在java代码中使用synchronized可是使用在代码块和方法中,根据Synchronized用的位置可以有这些使用场景:
在这里插入图片描述

如图,synchronized可以用在方法上也可以使用在代码块中,其中方法是实例方法静态方法分别锁的是该类的实例对象和该类的对象。而使用在代码块中也可以分为三种,具体的可以看上面的表格。

2. synchronized作用于实例方法

模板:

public synchronized void method{
}

2.1 多个线程访问同一个对象的同一个synchronized方法

每个实例对应一个lock,线程获得该含有synchronized方法的实例的锁才可以执行,否则阻塞。方法一旦执行,则一直到方法返回才可以释放锁。此后被阻塞的线程才能获得该锁。

对于一个实例,其声明为synchronized的方法显然只有一个能处于执行状态。从而避免了类访问变量的冲突。

下面我们来看个例子,创建一个ObjectLock的实例,被2个线性t1和t2同时调用:

public class ObjectLock {
    private int value; // 成员属性

    public synchronized void increment() { // synchronized修饰实例方法
        System.out.println(Thread.currentThread().getName() + " 开始执行");

        for (int j = 0; j < 100; j++) {
            try {
                // 增加执行时间总时间,保证多个线程周期重叠,避免执行过快,比如B线程尚未开始,A线程已经结束
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            value++; // 递增+1
        }
        System.out.println(Thread.currentThread().getName() + " 执行完毕");

    }

    public int getValue() {
        return value;
    }

}


public class SynchronizedTest implements Runnable {
    ObjectLock lock = new ObjectLock();

    public SynchronizedTest(ObjectLock lock) {

        this.lock = lock;
    }

    @Override
    public void run() {
        this.lock.increment();

    }

    public static void main(String[] args) throws InterruptedException {
        ObjectLock lock = new ObjectLock(); // 创建一个实例对象
        Thread t1 = new Thread(new SynchronizedTest(lock)); // 线程12引用lock实例
        Thread t2 = new Thread(new SynchronizedTest(lock)); // 线程t2引用lock实例
        t1.start();
        t2.start();

        t1.join(); // 保证t1线程执行结束,再执行main线程的System.out.println
        t2.join(); // 保证t2线程执行结束,再执行main线程的System.out.println
        System.out.println(lock.getValue());
    }

}

执行结果:

Thread-0 开始执行
Thread-0 执行完毕
Thread-1 开始执行    //说明Thread-1是在Thread-0执行完后才执行的
Thread-1 执行完毕
200

分析:实例对象lock只有一把锁 ,Thread-0先拿到锁,此时Thread-1就只能阻塞等待,直至Thread-0执行完,释放锁后,Thread-1才能拿到锁并执行。

2.2 多个线程访问同一个对象的不同的synchronized方法

此外,如果实例对象有多个synchronized方法,则只要一个线程访问了任何一个synchronized方法,其他线程不能同时访问任何一个该对象的synchronized方法(synchronized作用于对象,且每个对象只有一个锁)。

public class ObjectLock2 {
    public synchronized void method1() {  //method1被synchronized 修饰
        System.out.println("Method 1 start");
        try {
            System.out.println("Method 1 execute");
            Thread.sleep(3000);  //保证多个线程周期重叠
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 1 end");
    }

    public synchronized void method2() {   //method2被synchronized 修饰
        System.out.println("Method 2 start");
        try {
            System.out.println("Method 2 execute");
            Thread.sleep(1000);   //保证多个线程周期重叠
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 2 end");
    }

}

public class SynchronizedTest2 {

    public static void main(String[] args) throws InterruptedException {
        ObjectLock2 lock = new ObjectLock2(); // 创建一个实例对象
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 线程1引用lock实例
                lock.method1();
            }
        }

        );
        Thread t2 = new Thread(new Runnable() {

            @Override
            public void run() {
                // 线程t2引用lock实例
                lock.method2();
            }
        });

        t1.start();
        t2.start();

    }
}

执行结果:

Method 1 start
Method 1 execute
Method 1 end
Method 2 start    //等待method1调用完后,才轮到method2被调用
Method 2 execute
Method 2 end

分析:可以看出当t1持有lock实例上的锁,其他线程t2来访问synchronized修饰的其他方法时需要等待线程t1先把锁释放,method1和method2上的锁是同一个锁。

2.3 一个线程获取了该对象的锁之后,其他线程来访问其他非synchronized实例方法

任意线程任意情况下(不管是否持有锁)都可以访问非synchronized方法

public class ObjectLock3 {
    public synchronized void method1() {    //被synchronized 修饰
        System.out.println("Method 1 start");
        try {
            System.out.println("Method 1 execute");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 1 end");
    }

    public void method2() {     // !!! 未synchronized 修饰
        System.out.println("Method 2 start");
        try {
            System.out.println("Method 2 execute");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 2 end");
    }

}

public class SynchronizedTest3 {
    public static void main(String[] args) {
        ObjectLock3 lock = new ObjectLock3(); // 创建一个实例对象
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 线程1引用lock实例
                lock.method1();
            }
        }

        );
        Thread t2 = new Thread(new Runnable() {

            @Override
            public void run() {
                // 线程t2引用lock实例

                lock.method2();
            }
        });

        t1.start();
        t2.start();
    }
}

结果:

Method 1 start
Method 1 execute
Method 2 start   //method2和method1同时被调用
Method 2 execute
Method 2 end
Method 1 end

分析:t1线程持有锁,在同一时刻,t2访问非 synchronized 方法,是可以的。

2.4 当多个线程作用于不同的实例对象

不同的对象,拥有不同的锁,因此多个线程作用于不同实例对象时,是可以同时进行的

public class ObjectLock4 {
    public synchronized void method1() {   //仅有一个synchronized 方法
        System.out.println(Thread.currentThread().getName() + " Method 1 start");
        try {
            System.out.println(Thread.currentThread().getName() + " Method 1 execute");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " Method 1 end");
    }

}

public class SynchronizedTest4 {
    public static void main(String[] args) {
        ObjectLock4 lock = new ObjectLock4(); // 创建一个实例对象
        ObjectLock4 lock2 = new ObjectLock4(); // 创建一个实例对象
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 线程1引用lock实例
                lock.method1();
            }
        }

        );
        Thread t2 = new Thread(new Runnable() {

            @Override
            public void run() {
                // 线程t2引用lock2实例,注意和线程t1引用的不是不同实例

                lock2.method1();
            }
        });

        t1.start();
        t2.start();
    }
}

执行结果:

Thread-1 Method 1 start
Thread-0 Method 1 start   //Thread-0执行时,Thread-1也在执行
Thread-1 Method 1 execute
Thread-0 Method 1 execute
Thread-1 Method 1 end
Thread-0 Method 1 end

分析:线程t1引用lock实例,持有lock1上的锁,线程t2引用lock2实例,持有lock2实例上的锁,两个线程引用的是不同的实例,持有的是不同的锁,虽然方法名称都相同,但是可以同时调用。

3. synchronized作用于静态方法

synchronized修饰的静态方法,即使是不同的实例,但是只存在一个锁,一旦被持有,就会排斥其他的线程。

public class ObjectLock5 {
    public static synchronized void method1() {  //static方法,且是synchronized 修饰
        System.out.println(Thread.currentThread().getName() + " Method 1 start");
        try {
            System.out.println(Thread.currentThread().getName() + " Method 1 execute");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " Method 1 end");
    }

}

public class SynchronizedTest5 {
    public static void main(String[] args) {
        ObjectLock5 lock = new ObjectLock5(); // 创建一个实例对象 lock
        ObjectLock5 lock2 = new ObjectLock5(); // 创建一个实例对象 lock2
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 线程1引用lock实例
                ObjectLock5.method1();
            }
        }

        );
        Thread t2 = new Thread(new Runnable() {

            @Override
            public void run() {
                // 线程t2引用lock2实例

                lock2.method1();
            }
        });

        t1.start();
        t2.start();
    }
}


执行结果:

Thread-0 Method 1 start
Thread-0 Method 1 execute
Thread-0 Method 1 end
Thread-1 Method 1 start   //阻塞等待Thread-0释放锁
Thread-1 Method 1 execute
Thread-1 Method 1 end

分析:虽然线程t1和t2引用不同的对象,但是由于是静态的方法,全局就一个锁,一旦t1持有锁,t2就只能等待。

4. Synchronized作用于代码块

synchronized关键字用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

代码块中有种不同的场景,我们一次来看下

4.1 作用在对象实例上

4.1.1 作用在对象实例上例子

该对象实例拥有一个锁,谁拿到这个锁谁就可以运行它所控制的那段代码。

class Foo implements Runnable {
     private Object lock = new Object(); // instance变量    
     Public void methodA() {      
       synchronized(lock) { 
          dosomething1();
          dosomething2();
       }
    }
 }   

如果有2个线程A和B,A先持有锁,则A拥有执行该代码块的权限,如果B也想尝试该代码,则会阻塞,直至A执行完 dosomething2()后释放锁,B才能获取锁。

public class ObjectLock6 {   //锁对象
}

public class SynchronizedTest6 {

    ObjectLock6 lock;      //lock实例

    public SynchronizedTest6(ObjectLock6 lock) {
        this.lock = lock;
    }

    public void method() {    //方法本身没加锁
        synchronized (lock) {   //在代码块中,对lock实例加锁
            System.out.println(Thread.currentThread().getName() + " Method  start");
            try {
                System.out.println(Thread.currentThread().getName() + " Method  execute");
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " Method  end");
        }

    }

    public static void main(String[] args) {
        ObjectLock6 lock = new ObjectLock6();
        SynchronizedTest6 test = new SynchronizedTest6(lock);

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 线程1引用SynchronizedTest6 实例的method()
                test.method();
            }
        }

        );
        Thread t2 = new Thread(new Runnable() {

            @Override
            public void run() {
                // 线程t2也引用SynchronizedTest6 实例的method()
                test.method();
            }
        });

        t1.start();
        t2.start();
    }
}

执行结果:

Thread-0 Method  start
Thread-0 Method  execute
Thread-0 Method  end
Thread-1 Method  start   //t1是在t0执行完代码块,并释放或后才执行
Thread-1 Method  execute
Thread-1 Method  end

分析:2个线程t1和t2同时调用SynchronizedTest6 的method方法,该方法没加锁,但是在代码块中加锁了,效果显示加锁的效果,t1是在t0执行完代码块,并释放或后才执行。

4.1.2 作用在对象实例上时,锁住了谁?

此时有个一疑问,锁是在SynchronizedTest6实例test 上还是在ObjectLock6实例lock上?
答案:锁是在lock上。

在这里插入图片描述

我们来验证下:

public class ObjectLock7 {
    public synchronized void method1() { // 加锁的方法
        System.out.println(Thread.currentThread().getName() + " Method 1 start");
        try {            
            Thread.sleep(3000); // sleep 3s
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " Method 1 end");
    }

}

public class SynchronizedTest7 {

    ObjectLock7 lock;

    public SynchronizedTest7(ObjectLock7 lock) {
        this.lock = lock;
    }

    public void method() {
        synchronized (lock) { // 代码块加锁,该锁在lock上,不是SynchronizedTest7上
            System.out.println(Thread.currentThread().getName() + " Method  start");
            try {
                Thread.sleep(3000); // sleep 3s
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " Method  end");
        }

    }

    public synchronized void anotherMethod() { // 加锁了,锁在SynchronizedTest7实例上
        System.out.println(Thread.currentThread().getName() + " anotherMethod  start");
    }

    public static void main(String[] args) {
        ObjectLock7 lock = new ObjectLock7();
        SynchronizedTest7 test = new SynchronizedTest7(lock);

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 线程1引用SynchronizedTest7的实例test的method(),代码块中加锁,锁在lock对象上
                test.method();
            }
        }

        );
        Thread t2 = new Thread(new Runnable() {

            @Override
            public void run() {
                // 线程t2引用lock实例的方法,该方法加锁了

                lock.method1();
            }
        });

        Thread t3 = new Thread(new Runnable() {

            @Override
            public void run() {
                // 线程t3引用SynchronizedTest7的实例test的anotherMethod(),是个加锁的方法

                test.anotherMethod();
            }
        });

        t1.setName("Thread1");
        t1.start();
        t2.setName("Thread2");
        t2.start();

        try {
            Thread.sleep(1000); // sleep 1s,确保t2先执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t3.setName("Thread3");
        t3.start();

    }
}


执行结果:

Thread1 Method  start
Thread3 anotherMethod  start  //t3打印夹在t1过程中,说明二者不冲突,锁不在SynchronizedTest7上
Thread1 Method  end
Thread2 Method 1 start   //t1执行完后,t2才会打印
Thread2 Method 1 end

我们创建了3个线程,t1和t3都是访问SynchronizedTest7实例的不同方法,结果显示二者可以同时执行,说明没有锁住SynchronizedTest7自身的方法;

t1调用的方法在代码块中对lock实例加锁,此时t2尝试访问lock实例的另一个加锁方法,结果方法二者存在先后顺序,t2等t1执行完才能获取锁执行,说明锁在lock实例上。

4.1.3 最佳实践,最小的锁对象
class Foo implements Runnable {
     private byte[] lock = new byte[0]; // instance变量    
     Public void methodA() {      
       synchronized(lock) { //… }
    }
 }   

注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码

4.2 作用在this上

this 代表当前实例:

public void method {
   synchronized(this) {
    for (int j = 0; j < 100; j++) {
		i++;
    }
}

等价于下面的模板,本质是在当前类的实例的方法上加锁:

public synchronized void method {
    for (int j = 0; j < 100; j++) {
		i++;
    }
}

例子就不单独列举了,简单来说,与SynchronizedTest7改造一下,把SynchronizedTest7的method()替换为:

 public void method() {
        synchronized (this) { // 代码块加锁,该锁在SynchronizedTest7实例上
         ...
        }

    }

执行结果:

//已经剔除了t2的相关信息,因为和lock实例无关了
Thread1 Method  start

Thread1 Method  end
Thread3 anotherMethod  start    //t3此时依赖t1了,因为锁在SynchronizedTest7实例上了

4.3 作用在 .class 上

public void method() {
  synchronized(AccountingSync.class) {
    for (int j = 0; j < 100; j++) {
        i++;
    }
}

等价于下面的模板,作用在所有类的静态方法上的锁:

public static synchronized void method() {
    for (int j = 0; j < 100; j++) {
        i++;
    }
}

例子就不单独列举了。





参考:
Synchronized的使用参考其例子

让你彻底理解Synchronized 参考其场景图

  • 6
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值