Java关键字synchronized的简单理解

参考链接:
https://blog.csdn.net/luoweifu/article/details/46613015
Java中并发编程使用中,最频繁和最简单的使用是synchronized关键字了吧,使用了synchronized关键字,代码或者对象只能同时被一个线程操作。synchronized可以分为以下几类:
1.同步代码块(synchronized修饰代码块)
2.同步方法(synchronized修饰方法)
3.同步静态方法(synchronized修饰静态方法)
4.同步类(synchronized修饰类)
5.同步对象(synchronized修饰对象)

1.同步代码块例子

public class SynchronizedCode implements Runnable {
    private static int count = 0;

    void functionA() {
        synchronized (this) {
            for (int i = 0; i < 5; i++) {
                count++;
                System.out.println(Thread.currentThread().getName() + ":"
                        + "count=" + count);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void run() {
        functionA();
    }

    public static void main(String[] args) {
        SynchronizedCode code = new SynchronizedCode();
        Thread thread1 = new Thread(code, "thread1");
        Thread thread2 = new Thread(code, "thread2");
        thread1.start();
        thread2.start();

    }
}

控制台输出:

thread1:count=1
thread1:count=2
thread1:count=3
thread1:count=4
thread1:count=5
thread2:count=6
thread2:count=7
thread2:count=8
thread2:count=9
thread2:count=10

分析:
可以看出,在同一个对象中,访问同步代码块时只能有一个线程thread1访问,另外一个线程thread2一直在等待,直到第一个线程完全执行完毕。
典型格式:

functionXXX() {
        synchronized (this) {
             //do sth
        }

2.同步方法例子

    synchronized void functionA() {
        for (int i = 0; i < 5; i++) {
            count++;
            System.out.println(Thread.currentThread().getName() + ":"
                    + "count=" + count);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

控制台输出:

thread1:count=1
thread1:count=2
thread1:count=3
thread1:count=4
thread1:count=5
thread2:count=6
thread2:count=7
thread2:count=8
thread2:count=9
thread2:count=10

分析:
可以看到,从代码上,和同步代码块极为类似,只是将synchronized挪到方法上了,这样synchronized的同步范围扩大为整个方法,但是实际作用和同步代码块基本一样。
典型格式:

    synchronized void functionXXX() {
        //do sth
    }

3.改进上述例子说明同步方法只锁方法,作用范围为同一个对象

对上述第二个例子简单修改如下:

public class SynchronizedCode implements Runnable {
    private static int count = 0;

    synchronized void functionA() {
        for (int i = 0; i < 5; i++) {
            count++;
            System.out.println(Thread.currentThread().getName() + ":"
                    + "count=" + count);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void run() {
        functionA();
    }

    public static void main(String[] args) {
        SynchronizedCode code1 = new SynchronizedCode();
        SynchronizedCode code2 = new SynchronizedCode();
        Thread thread1 = new Thread(code1, "thread1");
        Thread thread2 = new Thread(code2, "thread2");
        thread1.start();
        thread2.start();

    }
}

控制台输出:

thread1:count=1
thread2:count=2
thread2:count=4
thread1:count=4
thread2:count=5
thread1:count=5
thread2:count=7
thread1:count=7
thread1:count=9
thread2:count=9

分析:从log上看,好像synchronized不起作用了?输出为两个线程交替进行,但是仔细观察main函数,会发现此时有两个SynchronizedCode对象,而对于同步方法或者同步代码块,它们只同步一个对象中的对应同步范围,对于其他对象内部的执行逻辑,则管不到了。所以同步方法或者同步代码块是一个对象锁。

4.同步静态方法的例子

修改上述Demo中的方法,更改如下:

public class SynchronizedCode implements Runnable {
    private static int count = 0;

    static synchronized void functionA() {
        for (int i = 0; i < 5; i++) {
            count++;
            System.out.println(Thread.currentThread().getName() + ":"
                    + "count=" + count);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void run() {
        functionA();
    }

    public static void main(String[] args) {
        SynchronizedCode code1 = new SynchronizedCode();
        SynchronizedCode code2 = new SynchronizedCode();
        Thread thread1 = new Thread(code1, "thread1");
        Thread thread2 = new Thread(code2, "thread2");
        thread1.start();
        thread2.start();

    }
}

控制台输出:

thread1:count=1
thread1:count=2
thread1:count=3
thread1:count=4
thread1:count=5
thread2:count=6
thread2:count=7
thread2:count=8
thread2:count=9
thread2:count=10

分析:
观察main方法,可知,目前创建了SynchronizedCode的两个实例对象,各自有一个线程去访问SynchronizedCode类,可以看到线程1和线程2保持同步,为什么会这样呢?因为thread start调用了run方法,run里面的调用的functionA是一个静态同步方法,我们知道静态方法是属于类的,所以此时加的同步锁也是针对类的,同一时间,只能有一个SynchronizedCode类的对象来访问functionA方法,因此保持了线程的同步。

典型格式:

    static synchronized void functionA() {
        //do sth
    }

5.同步类(class)的例子

public class SynchronizedCode implements Runnable {
    private static int count = 0;

    void functionA() {
        synchronized (SynchronizedCode.class) {
            for (int i = 0; i < 5; i++) {
                count++;
                System.out.println(Thread.currentThread().getName() + ":"
                        + "count=" + count);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void run() {
        functionA();
    }

    public static void main(String[] args) {
        SynchronizedCode code1 = new SynchronizedCode();
        SynchronizedCode code2 = new SynchronizedCode();
        Thread thread1 = new Thread(code1, "thread1");
        Thread thread2 = new Thread(code2, "thread2");
        thread1.start();
        thread2.start();

    }

控制台输出:

thread1:count=1
thread1:count=2
thread1:count=3
thread1:count=4
thread1:count=5
thread2:count=6
thread2:count=7
thread2:count=8
thread2:count=9
thread2:count=10

分析:
可以对比例子三和例子四的代码和输出。这种加锁方式和同步静态方法类似,锁是加在类上,即同一个类的所有实例,一次只能有一个实例拥有锁,这是一把类锁。
典型格式:

    void functionXXX() {
        ...
        synchronized (XXX.class) {
            //do sth
        }
        ...
    }

6.同步对象的例子

此种情况稍显复杂,需要另外写个demo,如下

public class Acount {
    private String UserName;
    private float Money;

    public Acount(String userName, float money) {
        super();
        UserName = userName;
        Money = money;
    }

    public String getUserName() {
        return UserName;
    }

    public void setUserName(String userName) {
        UserName = userName;
    }

    public float getMoney() {
        return Money;
    }

    public void setMoney(float money) {
        Money = money;
    }

}
public class BankOperator implements Runnable {
    Acount a;

    // 存钱
    public void deposit(float money) {
        a.setMoney(a.getMoney() + money);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":"
                + a.getMoney());
    }

    // 取钱
    public void withdraw(float money) {
        a.setMoney(a.getMoney() - money);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":"
                + a.getMoney());
    }

    public BankOperator(Acount a) {
        super();
        this.a = a;
    }

    public static void main(String[] args) {
        Acount acount = new Acount("chj", 100);
        BankOperator b = new BankOperator(acount);
        Thread t1 = new Thread(b, "thread1");
        Thread t2 = new Thread(b, "thread2");
        Thread t3 = new Thread(b, "thread3");
        Thread t4 = new Thread(b, "thread4");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }

    public void run() {
        // 假设每个线程存50 取50
        withdraw(50);
        deposit(50);

    }

}

控制台输出:

thread1:withdraw-100.0
thread4:withdraw-50.0
thread3:withdraw0.0
thread2:withdraw50.0
thread1:deposit100.0
thread4:deposit100.0
thread3:deposit100.0
thread2:deposit100.0

分析:观察main方法,我们目前模拟4个操作员对chj账户进行操作,由于没有加锁,四个操作员可以同时操作账户,于是出现了-100的情况,如果我们需要一个操作员的存取动作完全结束才能操作,应该如何做呢?
最简单的方式是改为

    public void run() {
        // 假设每个线程存50 取50
        synchronized (a) {
            withdraw(50);
            deposit(50);
        }
    }

这样对于a账户,一个线程的存取操作就变成一个原子操作,需要一同执行完毕,
此时,控制台输出:

thread1:withdraw50.0
thread1:deposit100.0
thread4:withdraw50.0
thread4:deposit100.0
thread3:withdraw50.0
thread3:deposit100.0
thread2:withdraw50.0
thread2:deposit100.0

典型格式:

public void functionXXX()
{
   //obj 锁定的对象
   synchronized(obj)
   {
      // todo
   }
}

其他注意点

1.当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块,例子请看参考链接的Demo2 参考这里
2.synchronized方法可以被继承么?
答案是不可以,父类的synchronized修饰的方法是同步的,子类要想实现父类同名方法同步,第一可以使用super调用父类方法,第二只能自己继承方法后加上synchronized关键字
3.同步的弊端
a:性能下降,多线程的好处就是多个任务同时进行,但是synchronized的作用是一次只让一个线程操作一个对象或者方法,其他对象只能等待。过多的同步会导致性能的下降
b:导致死锁,某些情况同步还会导致死锁,比如:

    void test(){
        synchronized (a) {
            synchronized (b) {
                //do sth
            }
        }
        ...
    }

如果有两个线程访问test方法,线程1先给a对象加锁,线程2先给b加锁,接下来就好戏上场了,线程1等2释放b的锁,线程2等1释放a的锁,然后就over了。
4.

当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的对象来充当锁:

class Test implements Runnable
{
   private byte[] lock = new byte[0];  // 特殊的instance变量
   public void method()
   {
      synchronized(lock) {
         // todo 同步代码块
      }
   }

   public void run() {

   }
}

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

总结

synchronized锁加的同步锁分为两类:对象锁和类锁
修饰类和修饰静态方法的synchronized的锁是类锁,该类的所有实例拥有同一把锁。
修饰对象,修饰普通方法,修饰代码块的都是对象锁,只针对同一个对象,换了对象则不能对其他对象的内容起作用。

以上为原文https://blog.csdn.net/luoweifu/article/details/46613015的理解和整理,如有错误,请指出。

猜你喜欢

转载自blog.csdn.net/u011109881/article/details/79832370