Java Thread系列线程安全

Posted binarylei

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java Thread系列线程安全相关的知识,希望对你有一定的参考价值。

Java Thread系列(三)线程安全

一、什么是线程安全

线程安全概念:当多个线程访问某一个类(对象或方法)时,这个类始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的。

线程安全来说,需要满足以下两个特性:

  • 原子性

  • 可见性

public class MyThread extends Thread {

    private int count = 5;

    //synchronized加锁 同步锁
    public /*synchronized*/ void run () {
        System.out.printf("线程%s执行,count:%s\n", this.currentThread().getName(),--count);
    }

    public static void main(String[] args) {
        /**
         * 当多个线程访问 MyThread 的 run 方法时,以排队的方式进行处理(这里的排序是按照 CPU 分配的先后顺序而定的)
         * 一个线程要执行 synchronized 修饰的方法时
         *  1. 尝试获得锁,如果拿到锁则执行该方法
         *  2. 这个线程就会不断尝试获得这把锁,直到拿到为止,而且是多个线程同时去竞争这把锁。(也就是会有锁竞争的问题)
         */
        MyThread thread = new MyThread();
        new Thread(thread, "t1").start();
        new Thread(thread, "t2").start();
        new Thread(thread, "t3").start();
        new Thread(thread, "t4").start();
        new Thread(thread, "t5").start();
        new Thread(thread, "t6").start();
    }
}

执行结果:

//MyThread 的 run() 方法不加锁 synchronized
线程t1执行,count:4
线程t4执行,count:1
线程t5执行,count:1
线程t3执行,count:2
线程t2执行,count:3

//run() 加锁 synchronized,期待的结果 
线程t1执行,count:4
线程t2执行,count:3
线程t5执行,count:2
线程t4执行,count:1
线程t3执行,count:0

由此可见:

  1. 多个线程要执行 synchronized 修饰的方法时,必须获取对象锁,如果得不到这把锁,就处于等待状态,直到获取这把锁才执行这个方法。

二、什么是锁:对象锁和类锁

多个线程多个锁,多个线程,每个线程都可以拿到自己指定的锁,分别获得锁后执行 synchronized 修辞的方法。

  • 同步(synchronized)的概念就是共享,我们要牢牢记住“共享”这两个字,如果不是共享的资源,就没有必要进行同步。

  • 异步(asynchronized)的概念就是独立,相互之间不受任何制约。eg: Ajax

public class MutiThread {

    private /*static*/ int num = 0;

    /**
     * synchronized:对象锁,两个对象,线程获得的就是两个不同的锁,互不影响
     * static synchronized:表示类级别的锁,即便多个对象也是相同的锁
     */
    public /*static*/ synchronized void printNum (String tag) {
        try {
            if("a".equalsIgnoreCase(tag)) {
                System.out.printf("tag %s 设置成功\n", tag);
                num = 100;
                Thread.sleep(1000);
            } else {
                System.out.printf("tag %s 设置成功\n", tag);
                num = 200;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.printf("tag %s , num=%s\n", tag, num);
    }

    public static void main(String[] args) {
        
        final MutiThread m1 = new MutiThread();
        final MutiThread m2 = new MutiThread();
        new Thread(new Runnable() {
            public void run() {
                m1.printNum("a");
            }
        }).start();
        new Thread(new Runnable() {
            public void run() {
                m1.printNum("b");
            }
        }).start();
    }
}

执行结果:

//run()方法用synchronized修辞
tag b 设置成功
tag b , num=200
tag a 设置成功
tag a , num=100

//run()方法用static synchronized修辞
tag a 设置成功
tag a , num=100
tag b 设置成功
tag b , num=200

由此可见:

  1. static synchronized 修辞的方法,属于类级别的锁,多个对象共享同一把锁。

  2. synchronized 修辞的方法,属于对象锁,一个对象一把锁。

三、脏读

对于对象的同步和异步的方法,一定要考虑问题的整体性,不然就会出现数据不一致的错误,很经典的错误就是脏读(dirtyread)

public class DirtyRead {

    private String username;
    private String password;

    public /*synchronized*/ void setValue(String username, String password) {
        this.username = username;

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            //e.printStackTrace();
        }
        this.password = password;
        System.out.printf("username=%s; password=%s\n", username, password);
    }

    public /*synchronized*/ void getValue() {
        System.out.printf("username=%s; password=%s\n", username, password);
    }

    public static void main(String[] args) throws InterruptedException {
        final DirtyRead dirtyRead = new DirtyRead();
        new Thread(new Runnable() {
            public void run() {
                dirtyRead.setValue("admin", "admin");
            }
        }).start();
        Thread.sleep(1000);
        dirtyRead.getValue();
        //username=admin; password=null 不加锁,产生脏读
        //username=admin; password=admin 加锁
    }
}

执行结果:

  1. setValue() 执行要 2s,而主程序 1s 时调用 getValue() ,这时username 已经赋值,而 password 仍未赋值,产生了脏读,username=admin; password=null

  2. setValue() 和 getValue() 都对 username 和 password 操作,所以要避免产生脏读,需要对这两个方法都加锁 synchronized。


每天用心记录一点点。内容也许不重要,但习惯很重要!

以上是关于Java Thread系列线程安全的主要内容,如果未能解决你的问题,请参考以下文章

Java Thread系列线程状态

java线程

Thread专题 - 线程安全

Java中通过Runnable与Thread创建线程的区别

Java Thread系列线程创建

Thread专题 - 取消和关闭