Java多线程编程核心技术的笔记之2

半个读书人 2019-04-25 19:40  阅读 205 views 次 评论 0 条
站长的个人作品

为什么要学多线程同步?

因为多个线程同时访问共享资源时会导致非线程安全.

非线程安全主要是指:多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改和值不同步的情况,进而影响程序执行流程.

因此多线中的同步是学习多线程的重要部分.

PS:非线程安全问题是存在于实体变量中,如果是方法内部的私有变量,则不存在非线程安全问题.

实例1:方法内部私有变量

    //
    private class Students {

        public void showResult(String name, int score) {

            try {
                //方法内部的私有变量
                String result;

                if (score >= 60) {
                    result = "Good";
                    Thread.sleep(1000);//delay 1s
                } else {
                    result = "Filed";
                }

                Log.d(TAG, name + "--------" + score + "-----:" + result);
            } catch (InterruptedException e) {
            }
        }
    }

    //Thread A
    private class Thread_A extends Thread {

        private Students students = null;

        Thread_A(Students s) {
            students = s;
        }

        @Override
        public void run() {
            super.run();
            students.showResult("A", 88);
        }
    }

    // Thread  B
    private class Thread_B extends Thread {

        private Students students;

        Thread_B(Students s) {
            students = s;
        }

        @Override
        public void run() {
            super.run();
            students.showResult("B", 55);
        }
    }


        //在Main()方法中启动线程A和线程B,并同时调用同一个Students实例对象
        Students students = new Students();
        Thread_A thread_a = new Thread_A(students);
        thread_a.start();
        Thread_B thread_b = new Thread_B(students);
        thread_b.start();

此时是正常显示结果:

B--------55-----:Filed
A--------88-----:Good

当score大于60时候,我们延迟了1秒

实例2:实例变量

在上面代码的基础上修改,我们只是把result变量变为实例变量,如下:

    //
    private class Students {

        String result;

        public void showResult(String name, int score) {

            try {

                if (score >= 60) {
                    result = "Good";
                    Thread.sleep(1000);//delay 1s
                } else {
                    result = "Filed";
                }

                Log.d(TAG, name + "--------" + score + "-----:" + result);
            } catch (
                    InterruptedException e) {
            }
        }
    }

此时显示的结果就出行了错误:

B--------55-----:Filed
A--------88-----:Filed

也就发生了非线程安全问题,数据产生了脏读

解决非线程安全问题就是考虑要同步.

1、synchronized同步方法

关键字Synchronized修饰的方法就是同步方法

如果线程A调用有synchronized修饰的方法时会对改方法进行加锁,直到此方法执行结束才会释放锁.

如果线程B要调用被加锁的方法,必须等待解锁后才能使用.

我们在实例2的showResult()方法前添加关键字synchronized,如下:

    private class Students {

        String result;

        public synchronized void showResult(String name, int score) {

            try {

                if (score >= 60) {
                    result = "Good";
                    Thread.sleep(1000);//delay 1s
                } else {
                    result = "Filed";
                }

                Log.d(TAG, name + "--------" + score + "-----:" + result);
            } catch (
                    InterruptedException e) {
            }
        }
    }

打印结果如下:

A--------88-----:Good
B--------55-----:Filed

对比上两个结果,我们发现这次顺序一定是先完成线程A的调用,然后才完成了线程B的调用.而且结果都是正确的.

PS:

  1. 如果多个线程访问同一个实例对象,谁先获取该对象的锁,其他线程就必须等待.
  2. synchronized修饰的方法获取的锁是对象锁.

当如果在Students类中新增一个非synchronized的一个方法showName(),线程B改为调用showName(),修改后的代码如下:

    private class Students {

        String result;
        //synchronized的方法
        public synchronized void showResult(String name, int score) {

            try {

                if (score >= 60) {
                    result = "Good";
                    Thread.sleep(1000);//delay 1s
                } else {
                    result = "Filed";
                }

                Log.d(TAG, name + "--------" + score + "-----:" + result);
            } catch (
                    InterruptedException e) {
            }
        }
        //新增非synchronized的方法
        public void showName(String name){
            Log.d(TAG, "----------name:"+ name);
            return ;
        }
    }

    //Thread A
    private class Thread_A extends Thread {

        private Students students = null;

        Thread_A(Students s) {
            students = s;
        }

        @Override
        public void run() {
            super.run();
            students.showResult("A", 88);
        }
    }

    // Thread  B
    private class Thread_B extends Thread {

        private Students students;

        Thread_B(Students s) {
            students = s;
        }

        @Override
        public void run() {
            super.run();
              //调用非synchronized
              students.showName("B");
        }
    }

        //启动线程A和线程B
        Students students = new Students();
        Thread_A thread_a = new Thread_A(students);
        thread_a.start();
        Thread_B thread_b = new Thread_B(students);
        thread_b.start();

显示的结果:

--------B
A--------88-----:Good
本段小结:

1) 线程A先持有Students对象的锁,但线程B可以以异步的方式调用Students对象中的非synchronized修饰的方法

2) 线程A先持有Students对象的锁,线程B在调用synchronized修饰的方法时需要等待,这就是同步.

synchronized的一些相关特性

  1. synchronized锁可重入:也就是说,当一个线程获取对象锁后,可以再次获取改对象的锁
  2. 出现异常,锁自动释放
  3. 同步不可以继承

2、synchronized代码块

synchronized方法在某些情况下有弊端,比如线程A调用同步方法执行一个时间很长的任务,那么线程B必须等待比较长的时间.

解决这问题可以使用synchronized代码块.

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

一: synchronized(this)

我们在上面代码上就行修改,去除修饰方法的synchronized关键字,取代的是使用synchronized代码快

    private class Students {

        String result;

        public void showResult(String name, int score) {
            //synchronized代码块
            synchronized (this) {
                try {
                    if (score >= 60) {
                        result = "Good";
                        Thread.sleep(2000);//delay 1s
                    } else {
                        result = "Filed";
                    }
                } catch (
                        InterruptedException e) {
                }
                Log.d(TAG, name + "-----" + score + "-----:" + result);
            }
        }
    }

    //Thread A
    private class Thread_A extends Thread {

        private Students students = null;

        Thread_A(Students s) {
            students = s;
        }

        @Override
        public void run() {
            super.run();
            students.showResult("A", 88);
        }
    }

    // Thread  B
    private class Thread_B extends Thread {

        private Students students;

        Thread_B(Students s) {
            students = s;
        }

        @Override
        public void run() {
            super.run();
            students.showResult("B", 55);
        }
    }

启动代码一样,没有附上.

执行结果:

A-----88-----:Good
B-----55-----:Filed

我们发现这个也同步了.这个好像和synchronized修饰方法一样呢.

对,我们使用的是synchronized(this),而这个this就是指当前对象,也就是说线程A获取的是此对象的对象锁,线程B只能等待线程A执行完成才可以执行.

经过测试,得出如下结论:synchronized同步方法和synchronized(this)同步代码块作用一样.

1) 线程A先调用Students实例对象中带有synchronize(this)的方法,其他线程调用含有synchronize修饰的方法或无synchronize(this)代码块的方法都必须等待,直到线程A解锁

2) 线程A先调用Students实例对象中带有synchronize(this)的方法,其他线程调用其他无synchronize修饰的方法或无synchronize(this)代码块的方法时无需等待

 

PS:以上是写的时间有点久,可能存在误差。多谢指正

温馨提示:文章内容系作者个人观点,不代表博客志对观点赞同或支持。
版权声明:本文为投稿文章,感谢 125啦读书导航(125la.com) 的投稿,欢迎分享本文,转载请保留出处!
站长的个人作品
125la导航_独立博客导航平台

发表评论


表情

或者微信联系我