Java之使用synchronized解决多线程安全性问题

什么是线程安全呢?当多个线程并发访问某个Java对象(Object)时,无论系统如何调度这些线程,也无论这些线程将如何交替操作,这个对象都能表现出一致的、正确的行为,那么对这个对象的操作是线程安全的。如果这个对象表现出不一致的、错误的行为,那么对这个对象的操作不是线程安全的,发生了线程的安全问题。

平时生活中的买票场景就是一个典型的线程安全的场景;车票作为多个线程争抢的稀缺资源,如果我们不对车票资源的占用顺序进行限制,那么可能会出现重票或者错票的问题;

public void sellTicketTest() throws InterruptedException {
        class TicketOperator implements Runnable{
            int count = 100;

            @Override
            public void run() {
                while(count > 0){
                    System.out.println(Thread.currentThread().getName() + ":" + count);
                    count--;
                }
            }
        }

        var operator = new TicketOperator();
        var t1 = new Thread(operator);
        t1.setName("t1");
        var t2 = new Thread(operator);
        t2.setName("t2");
        var t3 = new Thread(operator);
        t3.setName("t3");

        t1.start();
        t2.start();
        t3.start();

        t1.join();
        t2.join();
        t3.join();
//        t2:100
//        t2:99
//        t2:98
//        t2:97
//        t2:96
//        t2:95
//        t2:94
//        t1:100
//        t3:100
//        t3:91
//        t3:90
//        t3:89
//        t2:93
//        t1:92
//        t1:86
//        t3:88
//        t2:87
//        t1:85
//        t3:84
//        t2:83
//        t1:82
//        t3:81
//        t2:80
//        t1:79
//        t1:76
//        t1:75
//        t1:74
//        t1:73
//        t1:72
//        t1:71
//        t1:70
//        t3:78
//        t2:77
//        t1:69
//        t1:66
//        t1:65
//        t1:64
//        t1:63
//        t3:68
//        t2:67
//        t1:62
//        t1:59
//        t1:58
//        t1:57
//        t1:56
//        t1:55
//        t1:54
//        t1:53
//        t3:61
//        t2:60
//        t2:50
//        t2:49
//        t2:48
//        t1:52
//        t3:51
//        t2:47
//        t1:46
//        t3:45
//        t3:42
//        t3:41
//        t3:40
//        t3:39
//        t2:44
//        t1:43
//        t3:38
//        t3:35
//        t3:34
//        t3:33
//        t3:32
//        t2:37
//        t1:36
//        t1:29
//        t1:28
//        t1:27
//        t1:26
//        t1:25
//        t1:24
//        t3:31
//        t3:22
//        t3:21
//        t2:30
//        t2:19
//        t2:18
//        t2:17
//        t2:16
//        t2:15
//        t2:14
//        t2:13
//        t2:12
//        t1:23
//        t3:20
//        t3:9
//        t2:11
//        t1:10
//        t1:6
//        t1:5
//        t1:4
//        t1:3
//        t3:8
//        t3:1
//        t2:7
//        t1:2
    }


Java提供了synchronized关键来实现同步锁的功能,synchronized添加到操作共享资源的代码块外边,来实现对共享资源的同步访问;
当一个线程进入到共享代码区,则其他的线程必须等待当前线程释放同步锁;
线程的同步在于线程获得了synchronized后边对象的锁,故这个对象对于所有的线程来说都必须是同一个对象;

public void lockSellTicketTest() throws InterruptedException {
        class TicketOperator implements Runnable{
            int count = 100;

            @Override
            public  void  run() {
                while(true){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }

                    synchronized (this){
                        if(count > 0){
                            System.out.println(Thread.currentThread().getName() + ":" + count);
                            count--;
                        }
                        else{
                            break;
                        }
                    }
                }
            }
        }

        var operator = new TicketOperator();
        var t1 = new Thread(operator);
        t1.setName("t1");
        var t2 = new Thread(operator);
        t2.setName("t2");
        var t3 = new Thread(operator);
        t3.setName("t3");

        t1.start();
        t2.start();
        t3.start();

        t1.join();
        t2.join();
        t3.join();
//        t3:100
//        t1:99
//        t2:98
//        t3:97
//        t1:96
//        t2:95
//        t1:94
//        t2:93
//        t3:92
//        t2:91
//        t3:90
//        t1:89
//        t3:88
//        t1:87
//        t2:86
//        t2:85
//        t3:84
//        t1:83
//        t2:82
//        t1:81
//        t3:80
//        t3:79
//        t2:78
//        t1:77
//        t3:76
//        t2:75
//        t1:74
//        t3:73
//        t1:72
//        t2:71
//        t2:70
//        t3:69
//        t1:68
//        t2:67
//        t1:66
//        t3:65
//        t1:64
//        t2:63
//        t3:62
//        t2:61
//        t1:60
//        t3:59
//        t3:58
//        t2:57
//        t1:56
//        t2:55
//        t3:54
//        t1:53
//        t1:52
//        t2:51
//        t3:50
//        t1:49
//        t2:48
//        t3:47
//        t1:46
//        t3:45
//        t2:44
//        t3:43
//        t2:42
//        t1:41
//        t2:40
//        t1:39
//        t3:38
//        t1:37
//        t2:36
//        t3:35
//        t3:34
//        t2:33
//        t1:32
//        t3:31
//        t1:30
//        t2:29
//        t2:28
//        t3:27
//        t1:26
//        t3:25
//        t2:24
//        t1:23
//        t1:22
//        t2:21
//        t3:20
//        t1:19
//        t2:18
//        t3:17
//        t1:16
//        t2:15
//        t3:14
//        t2:13
//        t1:12
//        t3:11
//        t1:10
//        t2:9
//        t3:8
//        t1:7
//        t3:6
//        t2:5
//        t2:4
//        t1:3
//        t3:2
//        t2:1
    }

synchronized可以直接使用在方法前边,从而实现方法体的同步访问;添加在方法前边时,其默认获得的锁对象跟具体的方法有关系,如果是实例方法则是this,如果是静态方法则是对应类的class对象;

@Test
    public void lockMethodSellTicketTest() throws InterruptedException {
        class TicketOperator implements Runnable{
            int count = 100;
            boolean saleOut = false;

            @Override
            public  void  run() {
                while(!saleOut){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }

                    sale();
                }
            }

            synchronized void sale(){
                if(count > 0){
                    System.out.println(Thread.currentThread().getName() + ":" + count);
                    count--;
                }
                else{
                    saleOut = true;
                }
            }
        }

        var operator = new TicketOperator();
        var t1 = new Thread(operator);
        t1.setName("t1");
        var t2 = new Thread(operator);
        t2.setName("t2");
        var t3 = new Thread(operator);
        t3.setName("t3");

        t1.start();
        t2.start();
        t3.start();

        t1.join();
        t2.join();
        t3.join();
//        t3:100
//        t1:99
//        t2:98
//        t3:97
//        t1:96
//        t2:95
//        t1:94
//        t2:93
//        t3:92
//        t2:91
//        t3:90
//        t1:89
//        t3:88
//        t1:87
//        t2:86
//        t2:85
//        t3:84
//        t1:83
//        t2:82
//        t1:81
//        t3:80
//        t3:79
//        t2:78
//        t1:77
//        t3:76
//        t2:75
//        t1:74
//        t3:73
//        t1:72
//        t2:71
//        t2:70
//        t3:69
//        t1:68
//        t2:67
//        t1:66
//        t3:65
//        t1:64
//        t2:63
//        t3:62
//        t2:61
//        t1:60
//        t3:59
//        t3:58
//        t2:57
//        t1:56
//        t2:55
//        t3:54
//        t1:53
//        t1:52
//        t2:51
//        t3:50
//        t1:49
//        t2:48
//        t3:47
//        t1:46
//        t3:45
//        t2:44
//        t3:43
//        t2:42
//        t1:41
//        t2:40
//        t1:39
//        t3:38
//        t1:37
//        t2:36
//        t3:35
//        t3:34
//        t2:33
//        t1:32
//        t3:31
//        t1:30
//        t2:29
//        t2:28
//        t3:27
//        t1:26
//        t3:25
//        t2:24
//        t1:23
//        t1:22
//        t2:21
//        t3:20
//        t1:19
//        t2:18
//        t3:17
//        t1:16
//        t2:15
//        t3:14
//        t2:13
//        t1:12
//        t3:11
//        t1:10
//        t2:9
//        t3:8
//        t1:7
//        t3:6
//        t2:5
//        t2:4
//        t1:3
//        t3:2
//        t2:1
    }

synchronized在实现多线程同步共享资源的同时,必然也限制了多线程的并发执行,增长了程序的执行时间;

posted @ 2023-07-04 07:27  无风听海  阅读(33)  评论(0编辑  收藏  举报