《Java 多线程编程核心技术》笔记——第2章 对象及变量的并发访问(二)

声明:

本博客是本人在学习《Java 多线程编程核心技术》后整理的笔记,旨在方便复习和回顾,并非用作商业用途。

本博客已标明出处,如有侵权请告知,马上删除。

2.2 synchronized 同步语句块

用关键字 synchronized 声明方法在某些情况下是有弊端的,比如 A 线程调用同步方法执行一个较长时间的任务,那么 B 线程必须等待比较长的时间。这种情况下可以尝试使用 synchronized 同步代码块来解决问题。

synchronized 方法是对当前对象进行加锁,而 synchronized 代码块是对某一个对象进行加锁

2.2.1 synchronized 方法的弊端

下面用一个示例来演示 synchronized 方法的弊端:

  1. 创建一个公共类

    public class Task {
        private String getData1;
        private String getData2;
    
        synchronized public void doLongTimeTask() {
            try {
                System.out.println("begin task");
                Thread.sleep(3000);
                getData1 = " 长时间处理任务后从远程返回的值1,threadName = " + Thread.currentThread().getName();
                getData2 = " 长时间处理任务后从远程返回的值2,threadName = " + Thread.currentThread().getName();
                System.out.println(getData1);
                System.out.println(getData2);
                System.out.println("end task");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    
  2. 创建一个工具类

    public class CommonUtils {
        public static long beginTime1;
        public static long beginTime2;
        public static long endTime1;
        public static long endTime2;
    }
    
    
  3. 创建两个自定义的线程类

    public class MyThread1 extends Thread {
        private Task task;
    
        public MyThread1(Task task) {
            this.task = task;
        }
    
        @Override
        public void run() {
            super.run();
            CommonUtils.beginTime1 = System.currentTimeMillis();
            task.doLongTimeTask();
            CommonUtils.endTime1 = System.currentTimeMillis();
        }
    }
    
    
    public class MyThread1_2 extends Thread {
        private Task task;
    
        public MyThread1_2(Task task) {
            this.task = task;
        }
    
        @Override
        public void run() {
            super.run();
            CommonUtils.beginTime2 = System.currentTimeMillis();
            task.doLongTimeTask();
            CommonUtils.endTime2 = System.currentTimeMillis();
        }
    }
    
    
  4. 测试类

    public class MyThread1Test {
        public static void main(String[] args) {
            Task task = new Task();
            MyThread1 myThread1 = new MyThread1(task);
            myThread1.start();
            MyThread1_2 myThread1_2 = new MyThread1_2(task);
            myThread1_2.start();
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long beginTime = CommonUtils.beginTime1;
            if (CommonUtils.beginTime1 > CommonUtils.beginTime2) {
                beginTime = CommonUtils.beginTime2;
            }
            long endTime = CommonUtils.endTime1;
            if (CommonUtils.endTime1 < CommonUtils.endTime2) {
                endTime = CommonUtils.endTime2;
            }
            System.out.println("耗时:" + (endTime - beginTime)/1000 + " 秒");
        }
    }
    
    

    运行结果

    begin task
     长时间处理任务后从远程返回的值1,threadName = Thread-0
     长时间处理任务后从远程返回的值2,threadName = Thread-0
    end task
    begin task
     长时间处理任务后从远程返回的值1,threadName = Thread-1
     长时间处理任务后从远程返回的值2,threadName = Thread-1
    end task
    耗时:6 秒
    

分析:在使用 synchronized 关键字来声明方法 synchronized public void doLongTimeTask() 时,从运行时间上来看,弊端很明显

2.2.2 synchronized 同步代码块的使用

当两个并发线程访问同一个对象 Object 中的 synchronized(this) 同步代码块时,一段时间只能有一个线程被执行,另一个线程必须等待当前线程执行完这个代码块后才执行该代码块

下面通过一个示例来演示 synchronized 同步代码块的使用:

  1. 创建一个公共类

    public class ObjectService {
        public void serviceMethod() {
            try {
                synchronized (this) {
                    System.out.println("begin time = "+ System.currentTimeMillis());
                    Thread.sleep(2000);
                    System.out.println("end time = "+ System.currentTimeMillis());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    
  2. 创建两个自定义的线程类

    public class MyThread2 extends Thread {
        private ObjectService objectService;
    
        public MyThread2(ObjectService objectService) {
            this.objectService = objectService;
        }
    
        @Override
        public void run() {
            super.run();
            objectService.serviceMethod();
        }
    }
    
    
    public class MyThread2_2 extends Thread {
        private ObjectService objectService;
    
        public MyThread2_2(ObjectService objectService) {
            this.objectService = objectService;
        }
    
        @Override
        public void run() {
            super.run();
            objectService.serviceMethod();
        }
    }
    
    
  3. 测试类

    public class MyThread2Test {
        public static void main(String[] args) {
            ObjectService objectService = new ObjectService();
            MyThread2 myThread2 = new MyThread2(objectService);
            myThread2.start();
            MyThread2_2 myThread2_2 = new MyThread2_2(objectService);
            myThread2_2.start();
        }
    }
    
    

    运行结果

    begin time = 1574418400128
    end time = 1574418402128
    begin time = 1574418402128
    end time = 1574418404129
    

2.2.3 用同步代码块解决同步方法的弊端

上面介绍了 synchronized 同步代码块的使用,但执行的效率还是没有提高,如何使用 synchronized 同步代码块解决同步方法的弊端呢?

下面通过一个示例来演示使用 synchronized 同步代码块解决同步方法的弊端:

  1. 修改 2.2.1 中的公共类

    public class Task {
        private String getData1;
        private String getData2;
    
        public void doLongTimeTask() {
            try {
                System.out.println("begin task");
                Thread.sleep(3000);
                String privateGetData1 = " 长时间处理任务后从远程返回的值1,threadName = " + Thread.currentThread().getName();
                String privateGetData2 = " 长时间处理任务后从远程返回的值2,threadName = " + Thread.currentThread().getName();
                synchronized (this) {
                    getData1 = privateGetData1;
                    getData2 = privateGetData2;
                }
                System.out.println(getData1);
                System.out.println(getData2);
                System.out.println("end task");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    
  2. 再次运行,得到运行结果如下

    begin task
    begin task
     长时间处理任务后从远程返回的值1,threadName = Thread-0
     长时间处理任务后从远程返回的值2,threadName = Thread-1
    end task
     长时间处理任务后从远程返回的值1,threadName = Thread-1
     长时间处理任务后从远程返回的值2,threadName = Thread-1
    end task
    耗时:3 秒
    

分析:当一个线程访问 object 的一个 synchronized(this) 同步代码块时,另一个线程仍然可以访问该 object 对象中的非 synchronized(this) 同步代码块

2.2.4 一半异步,一半同步

下面一个实验说明:不在 synchronized 代码块中就是异步执行,在 synchronized 代码块中就是同步执行

  1. 创建一个公共类

    public class Task2 {
        public void doLongTimeTask() {
            for (int i = 0; i < 10; i++) {
                System.out.println("no synchronized threadName = " + Thread.currentThread().getName() + " i = " + i);
            }
            synchronized (this) {
                for (int i = 0; i < 10; i++) {
                    System.out.println("synchronized threadName = " + Thread.currentThread().getName() + " i = " + i);
                }
            }
        }
    }
    
    
  2. 创建两个自定义的线程类

    public class MyThread3 extends Thread {
        private Task2 task2;
    
        public MyThread3(Task2 task2) {
            this.task2 = task2;
        }
    
        @Override
        public void run() {
            super.run();
            task2.doLongTimeTask();
        }
    }
    
    
    public class MyThread3_2 extends Thread {
        private Task2 task2;
    
        public MyThread3_2(Task2 task2) {
            this.task2 = task2;
        }
    
        @Override
        public void run() {
            super.run();
            task2.doLongTimeTask();
        }
    }
    
    
  3. 测试类

    public class MyThread3Test {
        public static void main(String[] args) {
            Task2 task2 = new Task2();
            MyThread3 myThread3 = new MyThread3(task2);
            myThread3.start();
            MyThread3_2 myThread3_2 = new MyThread3_2(task2);
            myThread3_2.start();
        }
    }
    
    

    运行结果

    no synchronized threadName = Thread-0 i = 0
    no synchronized threadName = Thread-0 i = 1
    no synchronized threadName = Thread-0 i = 2
    no synchronized threadName = Thread-0 i = 3
    no synchronized threadName = Thread-1 i = 0
    no synchronized threadName = Thread-1 i = 1
    no synchronized threadName = Thread-1 i = 2
    no synchronized threadName = Thread-1 i = 3
    no synchronized threadName = Thread-1 i = 4
    no synchronized threadName = Thread-1 i = 5
    no synchronized threadName = Thread-1 i = 6
    no synchronized threadName = Thread-1 i = 7
    no synchronized threadName = Thread-1 i = 8
    no synchronized threadName = Thread-1 i = 9
    synchronized threadName = Thread-1 i = 0
    synchronized threadName = Thread-1 i = 1
    synchronized threadName = Thread-1 i = 2
    synchronized threadName = Thread-1 i = 3
    synchronized threadName = Thread-1 i = 4
    synchronized threadName = Thread-1 i = 5
    synchronized threadName = Thread-1 i = 6
    no synchronized threadName = Thread-0 i = 4
    synchronized threadName = Thread-1 i = 7
    no synchronized threadName = Thread-0 i = 5
    synchronized threadName = Thread-1 i = 8
    synchronized threadName = Thread-1 i = 9
    no synchronized threadName = Thread-0 i = 6
    no synchronized threadName = Thread-0 i = 7
    no synchronized threadName = Thread-0 i = 8
    no synchronized threadName = Thread-0 i = 9
    synchronized threadName = Thread-0 i = 0
    synchronized threadName = Thread-0 i = 1
    synchronized threadName = Thread-0 i = 2
    synchronized threadName = Thread-0 i = 3
    synchronized threadName = Thread-0 i = 4
    synchronized threadName = Thread-0 i = 5
    synchronized threadName = Thread-0 i = 6
    synchronized threadName = Thread-0 i = 7
    synchronized threadName = Thread-0 i = 8
    synchronized threadName = Thread-0 i = 9
    
    

2.2.5 synchronized 代码块间的同步性

当一个线程访问 object 的一个 synchronized(this) 同步代码块时,其他线程对 object 中所有其它 synchronized(this) 同步代码块的访问将被阻塞。这说明 synchronized 使用的 “对象监视器” 是一个

下面通过一个示例来演示:

  1. 创建一个公共类

    public class ObjectService2 {
        public void serviceMethodA() {
            try {
                synchronized (this) {
                    System.out.println("A begin time = " + System.currentTimeMillis());
                    Thread.sleep(1000);
                    System.out.println("A end time = " + System.currentTimeMillis());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public void serviceMethodB() {
            synchronized (this) {
                System.out.println("B begin time = " + System.currentTimeMillis());
                System.out.println("B end time = " + System.currentTimeMillis());
            }
        }
    
    }
    
    
  2. 创建两个自定义的线程类

    public class MyThread4 extends Thread {
        private ObjectService2 objectService2;
    
        public MyThread4(ObjectService2 objectService2) {
            this.objectService2 = objectService2;
        }
    
        @Override
        public void run() {
            super.run();
            objectService2.serviceMethodA();
        }
    }
    
    
    public class MyThread4_2 extends Thread {
        private ObjectService2 objectService2;
    
        public MyThread4_2(ObjectService2 objectService2) {
            this.objectService2 = objectService2;
        }
    
        @Override
        public void run() {
            super.run();
            objectService2.serviceMethodB();
        }
    }
    
    
  3. 测试类

    public class MyThread4Test {
        public static void main(String[] args) {
            ObjectService2 objectService2 = new ObjectService2();
            MyThread4 myThread4 = new MyThread4(objectService2);
            myThread4.start();
            MyThread4_2 myThread4_2 = new MyThread4_2(objectService2);
            myThread4_2.start();
        }
    }
    
    

    运行结果

    A begin time = 1574423146307
    A end time = 1574423147309
    B begin time = 1574423147309
    B end time = 1574423147309
    

分析:两个同步代码块按顺序执行

2.2.6 验证同步 synchronized(this) 代码块是锁定当前对象的

和 synchronized 方法一样,synchronized(this) 代码块也是锁定当前对象的

下面通过一个示例来演示:

  1. 创建一个公共类

    public class Task3 {
        public void otherMethod() {
            System.out.println("run otherMethod...");
        }
    
        public void doLongTimeTask() {
            try {
                synchronized (this) {
                    for (int i = 0; i < 10; i++){
                        System.out.println(i);
                        Thread.sleep(500);
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    
  2. 创建两个自定义的线程类

    public class MyThread5 extends Thread {
        private Task3 task3;
    
        public MyThread5(Task3 task3) {
            this.task3 = task3;
        }
    
        @Override
        public void run() {
            super.run();
            task3.doLongTimeTask();
        }
    }
    
    
    public class MyThread5_2 extends Thread {
        private Task3 task3;
    
        public MyThread5_2(Task3 task3) {
            this.task3 = task3;
        }
    
        @Override
        public void run() {
            super.run();
            task3.otherMethod();
        }
    }
    
    
  3. 测试类

    public class MyThread5Test {
        public static void main(String[] args) {
            Task3 task3 = new Task3();
            MyThread5 myThread5 = new MyThread5(task3);
            myThread5.start();
            MyThread5_2 myThread5_2 = new MyThread5_2(task3);
            myThread5_2.start();
        }
    }
    
    

    运行结果

    0
    run otherMethod...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    

分析:myThread5 线程先持有了 Task3 对象的 Lock 锁,myThread5_2 线程可以以异步的方式调用 Task3 对象中的非 synchronized 类型的方法 otherMethod()

接下来把 Task3 对象中的非 synchronized 类型的方法 otherMethod() 上锁,再次运行:

  1. 把 Task3 对象中的非 synchronized 类型的方法 otherMethod() 上锁

    public class Task3 {
        synchronized public void otherMethod() {
            System.out.println("run otherMethod...");
        }
    
        public void doLongTimeTask() {
            try {
                synchronized (this) {
                    for (int i = 0; i < 10; i++){
                        System.out.println(i);
                        Thread.sleep(500);
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    
  2. 再次运行,运行结果如下

    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    run otherMethod...
    
    

分析:synchronized(this) 代码块和 synchronized 方法同步执行,这说明和 synchronized 方法一样,synchronized(this) 代码块也是锁定当前对象的

2.2.7 将任意对象作为对象监视器

在前面的学习中,使用 synchronized(this) 格式来同步代码块,其实 Java 还支持对 “任意对象” 作为 “对象监视器” 来实现同步的功能。这个 “任意对象” 大多数是实例变量及方法的参数,使用格式为 synchronized(非 this 对象)

在多个线程持有 “对象监视器” 为同一个对象的前提下,同一时间只有一个线程可以执行 synchronized(非 this 对象 x) 同步代码块中的代码,下面通过一个示例来演示:

  1. 创建一个公共类

    public class Service {
        private String username;
        private String password;
        private String anyString = new String();
    
        public void setUsernameAndPassword(String username, String password) {
            try {
                synchronized (anyString) {
                    System.out.println("threadName = " + Thread.currentThread().getName() +" begin time = " + System.currentTimeMillis());
                    this.username = username;
                    Thread.sleep(3000);
                    this.password = password;
                    System.out.println("threadName = " + Thread.currentThread().getName() +" end time = " + System.currentTimeMillis());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
        }
    }
    
    
  2. 创建两个自定义的线程类

    public class MyThread6 extends Thread {
        private Service service;
    
        public MyThread6(Service service) {
            this.service = service;
        }
    
        @Override
        public void run() {
            super.run();
            service.setUsernameAndPassword("A","AA");
        }
    }
    
    
    public class MyThread6_2 extends Thread {
        private Service service;
    
        public MyThread6_2(Service service) {
            this.service = service;
        }
    
        @Override
        public void run() {
            super.run();
            service.setUsernameAndPassword("B","BB");
        }
    }
    
    
  3. 测试类

    public class MyThread6Test {
        public static void main(String[] args) {
            Service service = new Service();
            MyThread6 myThread6 = new MyThread6(service);
            myThread6.setName("A");
            myThread6.start();
            MyThread6_2 myThread6_2 = new MyThread6_2(service);
            myThread6_2.setName("B");
            myThread6_2.start();
        }
    }
    
    

    运行结果

    threadName = A begin time = 1574427244475
    threadName = A end time = 1574427247476
    threadName = B begin time = 1574427247476
    threadName = B end time = 1574427250476
    

使用 synchronized(非 this 对象 x) 同步代码块时,对象监视器必须是同一个对象,否则运行的结果就是异步调用了,下面通过一个示例来演示:

  1. 修改 Service 类

    public class Service {
        private String username;
        private String password;
    
        public void setUsernameAndPassword(String username, String password) {
            try {
                String anyString = new String();
                synchronized (anyString) {
                    System.out.println("threadName = " + Thread.currentThread().getName() + " begin time = " + System.currentTimeMillis());
                    this.username = username;
                    Thread.sleep(3000);
                    this.password = password;
                    System.out.println("threadName = " + Thread.currentThread().getName() + " end time = " + System.currentTimeMillis());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
        }
    }
    
    
  2. 再次运行,运行结果如下

    threadName = A begin time = 1574428254678
    threadName = B begin time = 1574428254679
    threadName = A end time = 1574428257680
    threadName = B end time = 1574428257681
    

锁非 this 对象具有一定的优点:如果一个类中有很多个 synchronized 同步方法,这时虽然能实现同步,但会受到阻塞,所以影响运行效率;但如果使用同步代码块锁非 this 对象,则 synchronized(非 this 对象) 代码块的程序与 synchronized 同步方法是异步调用的。不与其他锁 this 同步方法争抢 this 锁,则可以大大提高运行效率

下面通过一个示例来演示 synchronized(非 this 对象) 与 synchronized 同步方法是异步调用的:

  1. 创建一个公共类

    public class Service2 {
        private String anyString = new String();
    
    
        public void a() {
            try {
                synchronized (anyString) {
                    System.out.println("a begin");
                    Thread.sleep(3000);
                    System.out.println("a end");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        synchronized public void b() {
            System.out.println("b begin");
            System.out.println("b end");
        }
    }
    
    
  2. 创建两个自定义的线程类

    public class MyThread7 extends Thread {
        private Service2 service2;
    
        public MyThread7(Service2 service2) {
            this.service2 = service2;
        }
    
        @Override
        public void run() {
            super.run();
            service2.a();
        }
    }
    
    
    public class MyThread7_2 extends Thread {
        private Service2 service2;
    
        public MyThread7_2(Service2 service2) {
            this.service2 = service2;
        }
    
        @Override
        public void run() {
            super.run();
            service2.b();
        }
    }
    
    
  3. 测试类

    public class MyThread7Test {
        public static void main(String[] args) {
            Service2 service2 = new Service2();
            MyThread7 myThread7 = new MyThread7(service2);
            myThread7.start();
            MyThread7_2 myThread7_2 = new MyThread7_2(service2);
            myThread7_2.start();
        }
    }
    
    

    运行结果

    a begin
    b begin
    b end
    a end
    

分析:由于对象监视器不同,所以运行结果就是异步的

使用 “synchronized(非 this 对象 x)同步代码块" 格式也可以解决 “脏读问题”。但在解决脏读问题之前,先做一个实验,实验的目标是验证多个线程调用同一个方法是随机的

  1. 创建一个公共类

    public class MyList {
        private List list = new ArrayList();
    
        synchronized public void add(String username) {
            System.out.println("ThreadName = " + Thread.currentThread().getName() +" 执行了 add 方法");
            list.add(username);
            System.out.println("ThreadName = " + Thread.currentThread().getName() +" 退出了 add 方法");
        }
    }
    
    
  2. 创建两个自定义的线程类

    public class MyThread8 extends Thread {
        private MyList myList;
    
        public MyThread8(MyList myList) {
            this.myList = myList;
        }
    
        @Override
        public void run() {
            super.run();
            for (int i = 0; i < 10; i++) {
                myList.add("ThreadA"+i);
            }
        }
    }
    
    
    public class MyThread8_2 extends Thread {
        private MyList myList;
    
        public MyThread8_2(MyList myList) {
            this.myList = myList;
        }
    
        @Override
        public void run() {
            super.run();
            for (int i = 0; i < 10; i++) {
                myList.add("ThreadB"+i);
            }
        }
    }
    
    
  3. 测试类

    public class MyThread8Test {
        public static void main(String[] args) {
            MyList myList = new MyList();
            MyThread8 myThread8 = new MyThread8(myList);
            myThread8.setName("A");
            myThread8.start();
            MyThread8_2 myThread8_2 = new MyThread8_2(myList);
            myThread8_2.setName("B");
            myThread8_2.start();
        }
    }
    
    

    运行结果

    ThreadName = A 执行了 add 方法
    ThreadName = A 退出了 add 方法
    ThreadName = B 执行了 add 方法
    ThreadName = B 退出了 add 方法
    ThreadName = A 执行了 add 方法
    ThreadName = A 退出了 add 方法
    ThreadName = B 执行了 add 方法
    ThreadName = B 退出了 add 方法
    ThreadName = B 执行了 add 方法
    ThreadName = B 退出了 add 方法
    ThreadName = A 执行了 add 方法
    ThreadName = A 退出了 add 方法
    ThreadName = B 执行了 add 方法
    ThreadName = B 退出了 add 方法
    ThreadName = B 执行了 add 方法
    ThreadName = B 退出了 add 方法
    ThreadName = A 执行了 add 方法
    ThreadName = A 退出了 add 方法
    ThreadName = B 执行了 add 方法
    ThreadName = B 退出了 add 方法
    ThreadName = B 执行了 add 方法
    ThreadName = B 退出了 add 方法
    ThreadName = A 执行了 add 方法
    ThreadName = A 退出了 add 方法
    ThreadName = B 执行了 add 方法
    ThreadName = B 退出了 add 方法
    ThreadName = A 执行了 add 方法
    ThreadName = A 退出了 add 方法
    ThreadName = A 执行了 add 方法
    ThreadName = A 退出了 add 方法
    ThreadName = B 执行了 add 方法
    ThreadName = B 退出了 add 方法
    ThreadName = A 执行了 add 方法
    ThreadName = A 退出了 add 方法
    ThreadName = A 执行了 add 方法
    ThreadName = A 退出了 add 方法
    ThreadName = A 执行了 add 方法
    ThreadName = A 退出了 add 方法
    ThreadName = B 执行了 add 方法
    ThreadName = B 退出了 add 方法
    

分析:从运行结果上来看,同步方法中的程序是同步执行的,但线程 A 和线程 B 的执行却是异步的,这就有可能出现脏读的环境。

下面通过一个示例来演示出现脏读的情况:

  1. 创建一个公共类

    public class MyList2 {
        private List list = new ArrayList();
    
        synchronized public void add(String username) {
            list.add(username);
        }
    
        synchronized public int getSize() {
            return list.size();
        }
    }
    
    
  2. 创建一个业务类

    public class MyService {
        public void addService(MyList2 myList2, String username) {
            try {
                if (myList2.getSize() < 1) {
                    Thread.sleep(2000);
                    myList2.add(username);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    
  3. 创建两个自定义的线程类

    public class MyThread9 extends Thread {
        private MyList2 myList2;
    
        public MyThread9(MyList2 myList2) {
            this.myList2 = myList2;
        }
    
        @Override
        public void run() {
            super.run();
            MyService myService = new MyService();
            myService.addService(myList2,"A");
        }
    }
    
    
    public class MyThread9_2 extends Thread{
        private MyList2 myList2;
    
        public MyThread9_2(MyList2 myList2) {
            this.myList2 = myList2;
        }
    
        @Override
        public void run() {
            super.run();
            MyService myService = new MyService();
            myService.addService(myList2,"B");
        }
    }
    
    
  4. 测试类

    public class MyThread9Test {
        public static void main(String[] args) throws InterruptedException {
            MyList2 myList2 = new MyList2();
            MyThread9 myThread9 = new MyThread9(myList2);
            myThread9.start();
            MyThread9_2 myThread9_2 = new MyThread9_2(myList2);
            myThread9_2.start();
            Thread.sleep(6000);
            System.out.println(myList2.getSize());
        }
    }
    
    

    运行结果

    2
    

分析:脏读出现了。出现原因是两个线程异步执行 addService 方法中的代码,解决办法就是 ”同步化“

下面通过一个示例解决脏读问题:

  1. 修改 MyService

    public class MyService {
        public void addService(MyList2 myList2, String username) {
            try {
                synchronized (myList2) {
                    if (myList2.getSize() < 1) {
                        Thread.sleep(2000);
                        myList2.add(username);
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    
  2. 再次运行,运行结果如下

    1
    

分析:由于 list 参数对象在项目中是一份实例,是单例的,而且也正需要对 list 参数的 getSize() 方法做同步的调用,所以就对 list 参数进行同步处理

2.2.8 细化验证 3 个结论

“synchronized(非 this 对象 x)” 格式的写法是将 x 对象本身作为 ”对象监视器“,这样就可以得出以下三个结论:

  1. 当多个线程同时执行 synchronized(x){} 同步代码块时呈同步效果

  2. 当其他线程执行 x 对象中的 synchronized 同步方法时呈同步效果

  3. 当其他线程执行 x 对象中的 synchronized(this) 方法时也呈现同步效果

下面验证第 1 个结论:

当多个线程同时执行 synchronized(x){} 同步代码块时呈同步效果

  1. 创建一个公共类

    public class MyObject {
    }
    
  2. 创建一个业务类

    public class Service3 {
        public void testMethod1(MyObject myObject) {
            synchronized (myObject) {
                try {
                    System.out.println("testMethod1 begin" + " run threadName = " + Thread.currentThread().getName());
                    Thread.sleep(2000);
                    System.out.println("testMethod1 end" + " run threadName = " + Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    
  3. 创建两个自定义的线程类

    public class MyThread10 extends Thread {
        private MyObject myObject;
        private Service3 service3;
    
        public MyThread10(MyObject myObject, Service3 service3) {
            this.myObject = myObject;
            this.service3 = service3;
        }
    
        @Override
        public void run() {
            super.run();
            service3.testMethod1(myObject);
        }
    }
    
    
    public class MyThread10_2 extends Thread {
        private MyObject myObject;
        private Service3 service3;
    
        public MyThread10_2(MyObject myObject, Service3 service3) {
            this.myObject = myObject;
            this.service3 = service3;
        }
    
        @Override
        public void run() {
            super.run();
            service3.testMethod1(myObject);
        }
    }
    
    
  4. 测试类

    public class MyThread10Test {
        public static void main(String[] args) {
            MyObject myObject = new MyObject();
            Service3 service3 = new Service3();
            MyThread10 myThread10 = new MyThread10(myObject, service3);
            myThread10.setName("A");
            myThread10.start();
            MyThread10_2 myThread10_2 = new MyThread10_2(myObject, service3);
            myThread10_2.setName("B");
            myThread10_2.start();
        }
    }
    
    

    运行结果

    testMethod1 begin run threadName = A
    testMethod1 end run threadName = A
    testMethod1 begin run threadName = B
    testMethod1 end run threadName = B
    

分析:同步的原因是因为使用了同一个对象监视器

下面验证第 2 个结论:

当其他线程执行 x 对象中的 synchronized 同步方法时呈同步效果

  1. 创建一个公共类

    public class MyObject2 {
        synchronized public void speedPrintString() {
            System.out.println("speedPrintString begin" + "run threadName = " + Thread.currentThread().getName());
            System.out.println("speedPrintString end" + "run threadName = " + Thread.currentThread().getName());
        }
    }
    
    
  2. 创建一个业务类

    public class Service4 {
        public void testMethod1(MyObject2 myObject2) {
            synchronized (myObject2) {
                try {
                    System.out.println("testMethod1 begin" + " run threadName = " + Thread.currentThread().getName());
                    Thread.sleep(2000);
                    System.out.println("testMethod1 end" + " run threadName = " + Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    
  3. 创建两个自定义的线程类

    public class MyThread11 extends Thread {
        private Service4 service4;
        private MyObject2 myObject2;
    
        public MyThread11(Service4 service4, MyObject2 myObject2) {
            this.service4 = service4;
            this.myObject2 = myObject2;
        }
    
        @Override
        public void run() {
            super.run();
            service4.testMethod1(myObject2);
        }
    }
    
    
    public class MyThread11_2 extends Thread {
        private MyObject2 myObject2;
    
        public MyThread11_2(MyObject2 myObject2) {
            this.myObject2 = myObject2;
        }
    
        @Override
        public void run() {
            super.run();
            myObject2.speedPrintString();
        }
    }
    
    
  4. 测试类

    public class MyThread11Test {
        public static void main(String[] args) {
            MyObject2 myObject2 = new MyObject2();
            Service4 service4 = new Service4();
    
            MyThread11 myThread11 = new MyThread11(service4, myObject2);
            myThread11.setName("A");
            myThread11.start();
            MyThread11_2 myThread11_2 = new MyThread11_2(myObject2);
            myThread11_2.setName("B");
            myThread11_2.start();
        }
    }
    
    

    运行结果

    testMethod1 begin run threadName = A
    testMethod1 end run threadName = A
    speedPrintString beginrun threadName = B
    speedPrintString endrun threadName = B
    

下面验证第 3 个结论:

当其他线程执行 x 对象中的 synchronized(this) 方法时也呈现同步效果

  1. 修改 MyObject2

    public class MyObject2 {
        public void speedPrintString() {
            synchronized (this) {
                System.out.println("speedPrintString begin" + "run threadName = " + Thread.currentThread().getName());
                System.out.println("speedPrintString end" + "run threadName = " + Thread.currentThread().getName());
            }
        }
    }
    
    
  2. 再次运行,运行结果如下

    testMethod1 begin run threadName = A
    testMethod1 end run threadName = A
    speedPrintString beginrun threadName = B
    speedPrintString endrun threadName = B
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bm1998

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值