前言

之前我们说到了Handler的用法,当然Android中还有很多异步消息通信的机制,后面有时间也会去学习并且一一补上,目前我了解到的常用的有:

  1. Handler
  2. HandlerThread
  3. IntentService
  4. runOnUIThread
  5. View.post
  6. AsyncTask

一下子想到就这些,之后再补补,也看看别的大神写的内容,边学边写。

今天先记一下synchronized的用法吧。

正文

synchronized是java中的保留关键字,用来加锁防止线程不安全的情况发生,所谓线程安全,就是说在发生资源抢夺时,多个线程在临界区争抢资源,在按部就班的处理后,得到的结果仍然是一个一个处理的,没有发生数据错乱的情况;对应的线程不安全就是说在多个线程对同一块资源进行操作时,发生了错乱操作导致结果非预期的情况。我写的是我的一家之言,是个人的理解,也无法确定所说的准确性,如果有误还请各位大佬多多指导。

用法

synchronized的用法比较简单,大概分为四种

1. 对指定对象加锁

synchronized (o1){
    //code                    
}
synchronized(this){
    //code
}

就是说要进入到synchronized修饰的代码块之前,要获取这个()中对象的锁,一般叫对象锁,获取到这个对象锁,就可以进入到这个区域进行操作。

2.对普通方法加锁

public  synchronized void go(){
        //CODE
    }

对普通方法加锁,就是对当前实例化的对象进行加锁,使用不同的线程对同一个实例化对象进行操作时,获取对象锁,会发生资源争抢,此时sychronized生效,多个线程发生互斥等待,如果是不同的对象,则是因为不同的线程获取到不同的对象锁,各管各的,不会发生线程安全问题。举个例子,家里有个手机,弟弟妹妹都想玩,就会发生手机的抢夺,弟弟抢到手机,则占有这个手机,妹妹就只能看着。此时爸爸妈妈又买了一个手机(实例化对象),就变成了弟弟玩之前的手机(获取到之前的对象锁),妹妹玩新手机(获取到新的实例化对象锁)。

3. 对静态方法加锁

public  static  synchronized void go(){
         //code
    }

大家都知道我们的静态方法是放在JVM的方法区中,方法区随着类的加载进行初始化,跟类的实例化没有关系,静态方法也是一样,隶属于整个类,给静态方法加synchronized关键字,就是给整个类加了锁。意思就是说,不管实例化多少个类,在操作这些类的时候,都会使用这个类中的synchronized修饰的方法。举个例子的话。。。没想到什么例子,想到了补充。总之,在多线程调用同一个类的静态对象时,都会去获取这个类的锁,所有实例化对象都通过获取这个类的锁来操作静态方法。

4.对类加锁

public class SynchronizedTest {
    public  void go(){
        synchronized (SynchronizedTest.class){
            //code
        }
    }
}

这样在代码中对类加锁,其实跟静态方法中加锁是一样的,加的锁属于类的锁,对所有的实例化对象生效。

例子

1.对指定对象/本类加锁

public class SynchronizedTest implements Runnable {
    private  int  x = 0;
    @Override
    public void run() {
        synchronized (this) {
            for (int i = 0; i < 5; i++) {
                x++;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + " : " + x);
            }
        }
    }

    public static void main(String[] args) {
        SynchronizedTest sync = new SynchronizedTest();
        SynchronizedTest sync2 = new SynchronizedTest();
        Thread thread = new Thread(sync,"Thread1");
        Thread thread2= new Thread(sync2,"thread2");
        Thread thread3 = new Thread(sync,"Thread3");
        thread.start();
        thread2.start();
        thread3.start();
    }
}

运行结果如下

Thread[thread2,5,main] : 1
Thread[Thread1,5,main] : 1
Thread[thread2,5,main] : 2
Thread[Thread1,5,main] : 2
Thread[thread2,5,main] : 3
Thread[Thread1,5,main] : 3
Thread[Thread1,5,main] : 4
Thread[thread2,5,main] : 4
Thread[thread2,5,main] : 5
Thread[Thread1,5,main] : 5
Thread[Thread3,5,main] : 6
Thread[Thread3,5,main] : 7
Thread[Thread3,5,main] : 8
Thread[Thread3,5,main] : 9
Thread[Thread3,5,main] : 10

可以看到,Thread1和Thread2在随机运行,而Thread3在Thread1,Thread2运行之后再运行,可以看出Thread3与前面的线程发生了资源争夺,事实上,Thread3和Thread1公用同一个对象,在Thread1先运行时,对对象sync进行加锁,就会导致Thread3变成阻塞状态,等待Thread1执行完毕后再次尝试获取对象锁。

Object o = new Object();
    private  int  x = 0;
    @Override
    public void run() {
        synchronized (o) {
            for (int i = 0; i < 5; i++) {
                x++;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + " : " + x);
            }
        }
    }

这样改的话结果也是一样的,只不过这里加锁的对象不是这个实例化的类对象,而是这个实例化的类中的o对象。

2.对指定方法加锁

对上面的代码进行修改,使用synchronized对run方法进行修饰,如此一来,有人要使用这个对象的run方法时,就会对这段代码块加锁,其他人想再次来调用这个方法时就要进入到等待状态,同样的,如果是不同的对象,自然是不受影响的。

public class SynchronizedTest implements Runnable {
    private  int  x = 0;
    @Override
    public synchronized void run() {
        for (int i = 0; i < 5; i++) {
            x++;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + " : " + x);
        }
    }


    public static void main(String[] args) {
        SynchronizedTest sync = new SynchronizedTest();
        SynchronizedTest sync2 = new SynchronizedTest();
        Thread thread = new Thread(sync,"Thread1");
        Thread thread2= new Thread(sync2,"thread2");
        Thread thread3 = new Thread(sync,"Thread3");
        thread.start();
        thread2.start();
        thread3.start();
    }
}

结果跟上面相同,同样的是对象锁。

这时候有个问题,对象锁的对象,说的是这个类对象,还是这个方法是个对象?如果是类对象,那同时如果有人在操作这个类中被synchronized修饰的方法,还可以不可以继续操作没有被修饰的方法呢?对这个情况做如下测试

public class SynchronizedTest implements Runnable {
    private  int  x = 0;
    @Override
    public synchronized void run() {
        for (int i = 0; i < 5; i++) {
            x++;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + " : " + x);
        }
    }

    public void go() throws InterruptedException {
        for (int i = 0 ; i< 5; i++){
            Thread.sleep(1000);
            System.out.println(Thread.currentThread()+ " :"+i);
        }
    }


    public static void main(String[] args) throws InterruptedException {
        SynchronizedTest sync = new SynchronizedTest();
        SynchronizedTest sync2 = new SynchronizedTest();
        new Thread(sync,"Thread1").start();
        sync.go();
    }
}

打印结果如下

Thread[Thread1,5,main] : 1
Thread[main,5,main] :0
Thread[Thread1,5,main] : 2
Thread[main,5,main] :1
Thread[Thread1,5,main] : 3
Thread[main,5,main] :2
Thread[Thread1,5,main] : 4
Thread[main,5,main] :3
Thread[main,5,main] :4
Thread[Thread1,5,main] : 5

可以看出,没有加synchronized关键字的方法调用并不会因为有人使用别的有synchronized关键字的方法而阻塞。所以这里的对象锁指的对象并不是类的实例化对象,感觉就像是对于这个方法有一个虚拟的对象专门为它加锁。

3.对静态方法加锁

模拟两个线程访问同一个静态方法区,使用不同的对象,代码如下

public class SynchronizedTest  {
    private int x=0;
    public static SynchronizedTest synchronizedTest = new SynchronizedTest();

    public  static void go() throws InterruptedException {
        for (int i = 0 ; i< 5; i++){
            synchronizedTest.x++;
            Thread.sleep(1000);
            System.out.println(Thread.currentThread()+ " :"+synchronizedTest.x);
        }
    }


    public static void main(String[] args) throws InterruptedException {
        SynchronizedTest sync = new SynchronizedTest();
        SynchronizedTest sync2 = new SynchronizedTest();
        new Thread(()->{
            try {
                sync.go();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{
            try {
                sync2.go();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

输出结果

Thread[Thread-0,5,main] :2
Thread[Thread-1,5,main] :3
Thread[Thread-0,5,main] :4
Thread[Thread-1,5,main] :5
Thread[Thread-0,5,main] :6
Thread[Thread-1,5,main] :7
Thread[Thread-0,5,main] :8
Thread[Thread-1,5,main] :9
Thread[Thread-1,5,main] :10
Thread[Thread-0,5,main] :10

可以看到线程1和线程2一直在轮着被系统调度,并没有被一个线程独占,如果对方法go()加上synchronized关键字,则变成如下输出

Thread[Thread-0,5,main] :1
Thread[Thread-0,5,main] :2
Thread[Thread-0,5,main] :3
Thread[Thread-0,5,main] :4
Thread[Thread-0,5,main] :5
Thread[Thread-1,5,main] :6
Thread[Thread-1,5,main] :7
Thread[Thread-1,5,main] :8
Thread[Thread-1,5,main] :9
Thread[Thread-1,5,main] :10

在这里两个线程按照顺序执行,先执行线程1,再执行线程2,可以看出是对我们的方法上了锁,才会出现这样的情况。

所以就算是不同的对象,在调用加了synchronized关键字的静态方法时,同样会遇到锁,这种锁一般称为类锁。

4. 对类加锁

上代码,修改上述的go方法如下

public  void go() throws InterruptedException {
        synchronized (SynchronizedTest.class){
            for (int i = 0 ; i< 5; i++){
                this.x++;
                Thread.sleep(1000);
                System.out.println(Thread.currentThread()+ " :"+this.x);
            }
        }
    }

输出

Thread[Thread-0,5,main] :1
Thread[Thread-0,5,main] :2
Thread[Thread-0,5,main] :3
Thread[Thread-0,5,main] :4
Thread[Thread-0,5,main] :5
Thread[Thread-1,5,main] :1
Thread[Thread-1,5,main] :2
Thread[Thread-1,5,main] :3
Thread[Thread-1,5,main] :4
Thread[Thread-1,5,main] :5

这里我们操作了不同的对象,里面的x的值相互之间并没有关系。

总结

小小的总结一下,在使用synchronized时,四种用法可以归结为两种锁

1. 对象锁,对象锁对实例化的对象加锁,或者对实例化对象的某个方法加锁,不同的线程在调用不同的对象时,并不会发生资源的争夺,所以也不会造成阻塞。

2. 类锁,对整个类加锁。不过是哪个实例化的对象,凡是调用这个类中的加锁部分,都会进入到资源争夺中。猜测其原因在与类锁属于类本身,初始化在方法区,所有的对象共用这块内存。

 

好的,先这样吧。