Synchroinzed 中文意思是同步的意思,也被称为是同步锁,其作用是保证在同一时刻,被Synchroinzed关键字修饰的方法或者是代码块只会有一个线程执行,从而达到并发安全的效果。Synchroinzed关键字是在Java中解决并发问题的最常用的方法,当然也是最简单的方式。下面我们就来看看在面试中被问及Synchroinzed关键字应该如何回答?
Synchroinzed 的使用
Synchroinzed有三种使用方式。
- 修饰实例方法:用于对当前实例进行加锁操作
- 修饰静态方法:用于对当前类对象进行加锁
- 修饰方法代码块:用于指定加锁对象,对给定对象加锁
修饰方法
Synchroinzed 非常简单,直接在方法前面加上synchronized关键字即可。synchronized修饰方法和修饰代码块是类似的,只是其对应的作用范围是不一样的。修饰代码块作用范围是整个被大括号括起来的范围,修饰方法则是标识整个的方法函数。
方法一,用来修饰一个方法
public synchronized void method()
{
// todo
}
方法二,用来修饰一个代码块
public void method()
{
synchronized(this) {
// todo
}
}
synchronized关键字是不能被继承的,虽然我们使用了synchronized关键字修饰了方法,但是synchronized并不是属于方法的一部分,所以,synchronized关键字是不能被继承的。如果在父类方法中使用了synchronized关键字修饰了方法,那么如果我们在其子类代码中对该方法进行了覆盖,那么如果要让子类方法实现同步的效果,那么就必须在子类方法上也加上synchronized关键字。
public class Parent {
public synchronized void method() { }
}
public class Child extends Parent {
public synchronized void method() { }
}
当然如果我们在子类的方法中调用了父类中的同步方法,那么虽然子类方法是不同步的,但是由于它调用的是父类的同步方法,这样一来,子类方法也相当于同步的。
public class Parent {
public synchronized void method() { }
}
public class Child extends Parent {
public void method() { super.method(); }
}
这里需要有两点注意
- 第一、在定义接口方法的时候不能使用synchronized关键字进行修饰
- 第二、在构造方法上不能使用synchronized进行修饰,但是可以在构造方法内部使用synchronized来修饰代码块。
修饰代码块
一个线程访问对象中的同步代码块的情况
在上面的代码中中我们编写了一段如下的代码,并且我们强调,当一个线程访问一个对象中的代码块的时候,如果这个时候有其他的对象线程也需要访问那么就会被阻塞。
public void method()
{
synchronized(this) {
// todo
}
}
下面我们来看一下如下的一段代码,通过实现Runnbale接口来实现一段多线程代码,并且在run()方法中引入了同步代码块。在代码块中对外部静态变量进行自增操作。
public class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public void run() {
synchronized(this) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public int getCount() {
return count;
}
}
我们知道,在调用这个多线程对象的时候有如下的两种实现方式。如下所示
public class Test {
public static void main(String args[]){
//调用方式一
SyncThread s1 = new SyncThread();
SyncThread s2 = new SyncThread();
Thread t1 = new Thread(s1);
Thread t2 = new Thread(s2);
//调用方式二
SyncThread s = new SyncThread();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
t1.start();
t2.start();
}
}
调用方式一中,两个线程是同时执行的,这个是因为synchronized关键字只是锁定了对象,并且每一个对象只有一个关联的锁。而在调用方式一种,创建了两个对象,并且两个对由两个线程分别执行,所以不会出现阻塞。
调用方式二中,让两个线程并发访问同一个SyncThread对象,并且在SyncThread代码中还有一块同步代码块,也就是说在同一时刻只能由一个线程执行同步代码块中的内容,另一个线程则会阻塞等待当前线程执行完成之后再去尝试获取锁,也就是说两个线程t1和t2互斥的。
同步代码块与非同步代码块的访问
如下代码所示。
public class Counter implements Runnable{
private int count;
public Counter() {
count = 0;
}
public void countAdd() {
synchronized(this) {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//非synchronized代码块,未对count进行读写操作,所以可以不用synchronized
public void printCount() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + " count:" + count);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void run() {
String threadName = Thread.currentThread().getName();
if (threadName.equals("A")) {
countAdd();
} else if (threadName.equals("B")) {
printCount();
}
}
}
public class Test{
public static void main(String args[]){
Counter counter = new Counter();
Thread thread1 = new Thread(counter, "A");
Thread thread2 = new Thread(counter, "B");
thread1.start();
thread2.start();
}
}
可以看到在代码中,B线程的是非同步代码,并且没有影响到A线程对于同步代码的调用。从上面的运行结果来看,如果一个线程访问一个对象的synchronized 代码块的时候,别的线程如果访问了非synchronized代码块,这个是可以被允许的,也就是说不会受到阻塞。
对象加锁
代码如下,如果在某个方法调用的过程中,我们指定了一个加锁的对象,那么当一个线程访问这个加锁对象的时候,如果其他线程想要尝试访问该对象则会产生阻塞,一直到当前线程对该对象的访问结束。也就是说谁获取的了这个对象锁,谁就可以操作对应的代码。
public void method3(SomeObject obj)
{
//obj 锁定的对象
synchronized(obj)
{
// todo
}
}
在有些情况下如果我们没有制定明确的对象锁,我们也可以创建一个特殊类型的对象来充当对象锁。
class Test implements Runnable
{
private byte[] lock = new byte[0]; // 特殊的instance变量
public void method()
{
synchronized(lock) {
// todo 同步代码块
}
}
public void run() {
}
}
修饰静态方法
如下所示synchronized也可以用来修饰一个静态方法。
public synchronized static void method() {
// todo
}
在之前的时候,我们知道如果一个方法被static修饰那么这个静态方法就是属于整个类的,而不是属于对象的。也就是说如果一个静态方法被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 Test{
public static void main(String args[]){
SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "A");
Thread thread2 = new Thread(syncThread2, "B");
thread1.start();
thread2.start();
}
}
Sync的两个对象,由t1和t2并发进行调用,这是由于在run()方法中调用了静态方法method。而我们之前提到过静态方法是属于类的,这个时候Sync的两个对象相当于获取的是同一把锁。所以上面这种调用中,两个对象是属于同步的。
修饰类
前面我们提到synchronized关键字还可以修饰一个类。当然这里的修饰一个类并不是说在public class 前面去加上一个修饰,而是通过如下的方式来实现。
class ClassName {
public void method() {
synchronized(ClassName.class) {
// todo
}
}
}
通过上面这种方式实现对于类的加锁,其实根据上面我们分析的内容来讲,给类加锁,与给类中的静态方法加锁是一样的。所使用的都是同一把类锁。
总结
到这里,关于synchronized锁的使用就介绍完了,对于synchronized底层原理的实现。我们会在后续的分享中来给大家分享,这里大家先来吸收一下如果在面试中被问到关于synchronized的实现或者是出现了上面的实现之后运行的结果是什么,并且如何进行分析回答。关于synchronized底层实现原理。我们在后面的分享中给大家详细说明,敬请期待。
本文暂时没有评论,来添加一个吧(●'◡'●)