Synchronized关键字的使用

前面介绍了多线程的诸多优点,好用归好用,同时也出现了一个问题就是线程的同步。当不做线程的同步控制,多个线程跑起来并对一个共享变量做改动,执行完毕后,你会发现共享变量的值不如你愿。因为这些线程没能实现线程安全。关于线程安全和线程非安全,看下百度的解释:

线程安全&线程非安全

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

保证线程安全,java提供了以下几种同步策略:

  1. synchronized关键字。
  2. volatile关键字。
  3. JUC包中的Lock接口。
    4.ThreadLocal管理变量。
    本篇主要介绍一下synchronized关键字的使用。
    通俗点描述synchronized,它就像是挂在一个衣柜上的锁。记得大学和兄弟们打完球,一起去澡堂冲凉,走进更衣室,会看见有很多衣柜,一个个衣柜就如同一个个共享变量,如果一个衣柜上有锁(synchronized),说明这个柜(共享变量)正在被人(线程)使用,你如果非要用这个柜子,就得等正在使用这个柜子的人用完柜子,释放了锁,你再使用。
    先示范一把线程非安全的例子:
public class Test5 implements Runnable {
    private  int count = 0;
    @Override
    public void run() {
        for(int i = 0; i < 8; i++){
            System.out.println(Thread.currentThread().getName()+ ":" + count++);
        }
    }
    public static void main(String[] args) {
        Test5 test5 = new Test5();
        Thread thread1 = new Thread(test5,"thread1");
        Thread thread2 = new Thread(test5,"thread2");
        thread1.start();
        thread2.start();
    }
}

运行结果:(本想着count值为15,执行结果却是14,且thread1和thread2交叉着修改着count)


Synchronized关键字的使用_第1张图片
synchronized修饰一个方法

如果想让上面例子线程安全,且最后的count为15,使用synchronized修饰方法。修改上述代码的run()方法,两种写法:
①直接在方法访问修饰符后面加synchronized

@Override
public synchronized void run() {
    for(int i = 0; i < 8; i++){
        System.out.println(Thread.currentThread().getName()+ ":" + count++);
    }
 }

②在方法内部使用synchronized修饰方法代码块

@Override
public void run() {
     synchronized (this){
         for(int i = 0; i < 8; i++){
             System.out.println(Thread.currentThread().getName()+ ":" + count++);
         }
     }
}

两种结果的执行结果是一致的,可以看出:当两个线程访问同一个对象(test5)的synchronized方法时,同一时刻只能有一个线程执行,另一个阻塞,两个线程是互斥的,只有当第一个执行完毕释放了锁,第二个线程才能获取锁重新锁定对象。

Synchronized关键字的使用_第2张图片

继续玩Test5,将其main方法内部做下修改,修改如下:

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

执行结果如下:

Synchronized关键字的使用_第3张图片

这是怎么回事,不是已经用synchronized修饰了run(),锁定对象了吗,怎么两个线程交叉执行,且count都是7,难道使用的姿势不对???



原因:synchronized锁定的是对象,new Test5()两次会创建两个独立的对象,所以会有两把锁锁定两个对象,thread1执行着对象1的run(),thread2执行着对象2的run(),所以两个线程互不影响,各执行各的。

synchronized(this),this为何物?
public class Test5 implements Runnable {
    private static int count = 0;
    @Override
    public void run() {
        synchronized (this){
            for(int i = 0; i < 8; i++){
                System.out.println(Thread.currentThread().getName()+ ":" + count++);
            }
        }
    }
    public static void main(String[] args) {
        Test5 test5 = new Test5();
        Thread thread1 = new Thread(test5,"thread1");
        Thread thread2 = new Thread(test5,"thread2");
        thread1.start();
        thread2.start();
    }
}

执行结果(线程安全):

Synchronized关键字的使用_第4张图片

修改main方法如下:

public static void main(String[] args) {
        Thread thread1 = new Thread(new Test5(),"thread1");
        Thread thread2 = new Thread(new Test5(),"thread2");
        thread1.start();
        thread2.start();
}
Synchronized关键字的使用_第5张图片

从这个例子就可以看出:这里的this是指当前对象,不管是对象A还是对象B,刚开始运行这段代码的时候会给这段代码加个锁,这样即使运行到中间被替换了,另一个线程也不会执行这段代码,因为这段代码加锁了,而钥匙在给代码加锁的那个线程手里,只有加锁的线程运行完这段代码,才会给代码解锁.然后其他线程才能执行这段代码。

修饰一个静态方法

静态方法是属于整个类的,所以synchronized修饰静态方法锁定的就是这个类的所有对象。

public class Test5 implements Runnable {
    private static int count = 0;
    @Override
    public void run() {
        addCount();
    }
    public static synchronized void addCount(){
        for(int i = 0; i < 8; i++){
            System.out.println(Thread.currentThread().getName()+ ":" + count++);
        }
    }
    public static void main(String[] args) {
        Test5 test5 = new Test5();
        Thread thread1 = new Thread(test5,"thread1");
        Thread thread2 = new Thread(test5,"thread2");
        thread1.start();
        thread2.start();
    }
}

执行结果:


Synchronized关键字的使用_第6张图片

修改Test5的main(),再次执行

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

可以发现执行结果还是上图,new Test5()创建了两个不同的对象,但thread1、thread2的执行确实线程安全的,原因是synchronized修饰的是静态方法,静态方法是属于类的,两个对象用的是同一个锁,所以同步。

修饰一个类

修改Test5的addCount()方法,修改如下:

public static void addCount(){
    synchronized (Test5.class){
        for(int i = 0; i < 8; i++){
            System.out.println(Thread.currentThread().getName()+ ":" + count++);
        }
    }
}

执行结果(线程安全的):

Synchronized关键字的使用_第7张图片
synchronized不支持继承

synchronized可以修饰方法,但不属于方法定义的一部分,不能够被继承。如果父类的一个方法使用了synchronized,子类中重写了此方法,那么在子类中的这个方法默认是不同步的。如果要达到同步效果,一是在子类方法前也加上synchronized,二是在子类方法中调用父类的同步方法。

总结

synchronized是java中的关键字,是种同步锁。
①对于同步的方法或代码块,要先获得对象锁才能进入同步方法或代码块进行操作;
②synchronized可修饰方法,分为静态方法和非静态方法,修饰静态方法锁定的是整个类,修饰非静态方法锁定的是一个对象;
③synchronized可修饰类,修饰的是这个类的所有对象;
④代码块synchronized(a),对象锁就是a。
⑤synchronized修饰方法不支持继承。

略陈固陋,如有不当之处,欢迎各位看官批评指正!

你可能感兴趣的:(Synchronized关键字的使用)