首页 > 编程笔记 > Java笔记 阅读:12

Java synchronized:实现线程同步(附带实例)

Java 提供 synchronized 关键字来实现同步机制,同步的主要作用是避免线程安全问题。

所谓同步是指控制多个线程对共享资源的访问,比如一次只能由一个线程访问,不能多个线程同时访问。在 synchronized 的作用范围内同一时间只有一个线程能进入,其他线程必须等待里面的线程出来以后才能依次进入。

synchronized 可以用来描述对象的方法,表示该对象的方法具有同步性。下面看一个实例:
public class ThreadSynchronizedTest {
    int count = 0;
    int count2 = 0;

    public void add() {
       count++;
    }

    public synchronized void add2() {
       count2++;
    }
   
    public static void main(String[] args) throws InterruptedException {
       ThreadSynchronizedTest2 test = new ThreadSynchronizedTest2();
       for (int i = i < i++)
           new Thread(() -> {
               for (int j = j < j++)
                   test.add();
           }).start();
       for (int i = i < i++)
           new Thread(() -> {
               for (int j = j < j++)
                   test.add2();
           }).start();
       Thread.sleep(3000);
       System.out.println("count = " + test.count);
       System.out.println("count2 = " + test.count2);
    }
}
程序中定义了 add() 和 add2() 两个方法,其中 add2() 方法声明为 synchronized,这两个方法都是让各自的变量完成自增操作。

在主线程中分别启动 10 个线程,并循环 10000 次去调用 add() 和 add2() 方法,然后让主线程休眠 3 秒,这是为了让所有线程都执行完毕。最终某次运行的输出结果为 count = 75608,count2 = 100000。

这里我们不关注 count 的具体值是多少,需要关注的是 count 的值肯定小于 100000,也就是说 add() 方法缺乏同步性而导致了线程安全 问题。而 count2=100000 则是正确的结果,因为 add2() 方法具备同步性。

有线程安全问题的 count 值为什么会小于 100000 呢?因为 count++ 等同于 count=count+1,假设某个时刻 10 个线程同时获取 count 的值为 100,那么 10 个线程中 count 的值加一后赋值给 count 变量的结果都为 101,而正确的结果应该是 110。

synchronized 也可以作用在对象的方法内部,即用 synchronized 来描述方法内部的某块逻辑,表示该块逻辑具有同步性。

我们看下面的程序:
public class ThreadSynchronizedTest {
    int count = 0;
    int count2 = 0;

    public void add() {
        synchronized (this) {
            count++;
        }
    }
   
    public synchronized void add2() {
        count2++;
    }
   
    public static void main(String[] args) throws InterruptedException {
        ThreadSynchronizedTest6 test = new ThreadSynchronizedTest6();
        for (int i = 0; i < 10; i++)
            new Thread(() -> {
                for (int j = 0; j < 10000; j++)
                    test.add();
            }).start();
        for (int i = 0; i < 10; i++)
            new Thread(() -> {
                for (int j = 0; j < 10000; j++)
                    test.add2();
            }).start();
        Thread.sleep(3000);
        System.out.println("count = " + test.count);
        System.out.println("count2 = " + test.count2);
    }
}
这里不像前面那样在方法上声明 synchronized,而是在 add() 方法内部通过 synchronized(this){xxx} 的形式来声明同步块。

这里的同步块包括了 count++ 操作,所以该操作具有同步性,也就能够避免线程安全问题。实际使用中同步块并不要求包含整个方法的所有代码,而可以是方法内的任意代码块。

在主线程上分别用 10 个线程循环调用 10000 次 add() 方法和 add2() 方法,最终运行的结果是 count = 100000,count2 = 100000。也就是说两个方法都获得了同步的效果,而且 add() 方法和 add2() 方法都使用了当前对象作为锁,所以这两个方法其实是共用一个锁的。

相关文章