美文网首页Android技术知识Android文章Android开发
Android小知识-Java多线程相关(同步方法与同步代码块)

Android小知识-Java多线程相关(同步方法与同步代码块)

作者: 爱读书的顾先生 | 来源:发表于2018-11-16 22:53 被阅读8次

本平台的文章更新会有延迟,大家可以关注微信公众号-顾林海,如果大家想获取最新教程,请关注微信公众号,谢谢!

“非线程安全”是指在多个线程对同一个对象中的实例变量进行并发访问,导致读取到的数据与预期不符,也就是“脏读”,而“线程安全”就是指获得的实例变量的值是经过同步处理的,不会出现“脏读”现象。

如果是方法内的私有变量就不会存在“非线程安全”问题,也就说“非线程安全”的问题存在于“实例变量”中,我们看下面这段代码:

public class Task {

    private String name = "bill";

    public void setName(int index) {
        try {
            Thread.sleep(2000);
            switch (index) {
                case 1:
                    name = "jack";
                    break;
                case 2:
                    name = "rose";
                    break;
                default:
                    name = "default";
                    break;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("name="+name);
    }
}

Task这个类很简单,内部只有一个setName方法,传入一个整型参数,如果是1,name就被赋值为jack,如果是2,name就被赋值为rose,最后打印name。接下来就把这个Task实例交给两个线程去处理。

public class ThreadFirst extends Thread {

    private Task mTask;

    public ThreadFirst(Task task) {
        this.mTask = task;
    }

    @Override
    public void run() {
        super.run();
        mTask.setName(1);
    }
}
public class ThreadSecond extends Thread {

    private Task mTask;

    public ThreadSecond(Task task) {
        this.mTask = task;
    }

    @Override
    public void run() {
        super.run();
        mTask.setName(2);
    }
}

两个线程类都差不多,唯一不同的地方就是调用mTask的setName方法分别传入1和2,如果执行这两个线程,是不是输出两个不同的name值,我们看Client代码:

public class Client {

    public static void main(String[] args) {
        Task task=new Task();
        Thread threadFirst=new ThreadFirst(task);
        Thread threadSecond=new ThreadSecond(task);
        threadFirst.start();
        threadSecond.start();
    }

}

看看打印结果:

name=jack
name=jack

发现输出的结果和我们预期不一样,这就是“非线程安全”问题,如何解决呢,可以按照上一节Android小知识-关于多线程的基础知识了解下中提到的给setName方法前加上关键字synchronized,代码如下:

public class Task {

    private String name = "bill";

    synchronized public void setName(int index) {
        try {
            Thread.sleep(2000);
            switch (index) {
                case 1:
                    name = "jack";
                    break;
                case 2:
                    name = "rose";
                    break;
                default:
                    name = "default";
                    break;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("name=" + name);
    }
}

打印结果:

name=jack
name=rose

添加关键字synchronized后,这个setName方法就是一个同步方法,多个线程执行该方法时是排队执行的。

现在Task类中只有一个 同步方法,再添加一个非同步方法:

public class Task {

    private String name = "bill";

    synchronized public void setName(int index) {
        try {
            Thread.sleep(2000);
            switch (index) {
                case 1:
                    name = "jack";
                    break;
                case 2:
                    name = "rose";
                    break;
                default:
                    name = "default";
                    break;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("[" + Thread.currentThread().getName() + "]" + "name=" + name);
    }

    public void getName() {
        System.out.println("[" + Thread.currentThread().getName() + "]" + "name=" + name);
    }
}

新添加了一个getName方法,打印的时候连线程名一起打印,方便我们查看,第一个线程代码不变,两个线程类如下:

public class ThreadFirst extends Thread {

    private Task mTask;

    public ThreadFirst(Task task) {
        this.mTask = task;
    }

    @Override
    public void run() {
        super.run();
        mTask.setName(1);
    }
}
public class ThreadSecond extends Thread {

    private Task mTask;

    public ThreadSecond(Task task) {
        this.mTask = task;
    }

    @Override
    public void run() {
        super.run();
        mTask.getName();
    }
}

Client代码如下:

public class Client {

    public static void main(String[] args) {
        Task task=new Task();
        Thread threadFirst=new ThreadFirst(task);
        threadFirst.setName("ThreadFirst");
        Thread threadSecond=new ThreadSecond(task);
        threadSecond.setName("ThreadSecond");
        threadFirst.start();
        threadSecond.start();
    }

}

代码没什么变化,创建一个Task实例,交由两个线程处理。

打印结果:

[ThreadSecond]name=bill
[ThreadFirst]name=jack

按照预期应该是先执行setName方法打印jack,再执行getName方法打印jack,现在是先执行了getName方法,再执行setName方法,也就是说,ThreadFirst线程先持有了object对象的Lock锁,ThreadSecond线程可以以异步的方式调用objec对象中的非synchronized类型的方法。

现在我们给getName方法前也加上关键字synchronized:

    synchronized public void getName() {
        System.out.println("[" + Thread.currentThread().getName() + "]" + "name=" + name);
    }

执行程序,打印:


[ThreadFirst]name=jack
[ThreadSecond]name=jack

这样的话ThreadFirst先持有object对象的Lock锁,ThreadSecond线程如果在这时调用object对象中的synchronized类型的方法则需等待,也就是同步。

通过多个线程调用同一个方法时,为了避免数据出现交叉的情况,使用synchronized关键字来进行同步,虽然在赋值时进行了同步,但在取值时有可能出现“脏读”,发生“脏读”的情况是在读取实例变量时,此值已经被其它线程更改过了,看下面代码:

public class Task {

    private String name = "bill";
    private String password="12345";

    synchronized public void setName(String name,String password) {
        try {
            this.name=name;
            Thread.sleep(5000);
            this.password=password;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("[" + Thread.currentThread().getName() + "]" + "name=" + name+"  password="+password);
    }

    public void getInfo(){
        System.out.println("[" + Thread.currentThread().getName() + "]" + "name=" + name+"  password="+password);
    }

}

setName是一个同步方法,传入姓名和密码,在赋值密码前先休眠5秒,最后打印用户名和密码,而getInfo是非同步方法,用来打印用户名和密码。

public class ThreadFirst extends Thread {

    private Task mTask;

    public ThreadFirst(Task task) {
        this.mTask = task;
    }

    @Override
    public void run() {
        super.run();
        mTask.setName("jack","poiuytr");
    }
}

ThreadFirst线程类很简单,就是调用mTask实例的setName方法,设置用户名为jack,密码为poiuytr。

public class Client {

    public static void main(String[] args) {
        Task task=new Task();
        Thread threadFirst=new ThreadFirst(task);
        threadFirst.setName("ThreadFirst");
        threadFirst.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        task.getInfo();
    }

}

创建Task实例和线程实例threadFirst,执行threadFirst线程,休眠2秒后获取相关信息。

打印:

[main]name=jack  password=12345
[ThreadFirst]name=jack  password=poiuytr

通过打印结果就可以看出数据出现了脏读,出现脏读的原因是因为getInfo方法并不是同步的,所以可以在任意时候调用,解决办法就是加上同步synchronized关键字。

关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁,在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。当一个线程执行的代码出现异常时,其所持有的锁会自动释放。

用关键字synchronized声明方法在某些情况下是有弊端的,当某个线程调用同步方法执行一个长时间的任务,那么其他线程就必须等待比较长的时间,在这样的情况下可以使用synchronized同步语句块来解决,synchronized方法是对当前对象进行加锁,而synchronized代码块是对某一个对象进行加锁。在使用同步synchronized代码块时需要注意的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中的所有其他synchronized(this)同步代码块的访问将被阻塞,这说明synchronized使用的“对象监视器”是一个。

多个线程调用同一个对象中的不同名称的synchronized同步方法或synchronized(this)同步代码块时,调用的效果就是按顺序执行的,也就是同步,阻塞的。为此Java提供“任意对象”作为“对象监视器”来实现同步的功能。这个任意对象大多数是实例变量及方法的参数,使用格式为synchronized(非this对象)。


838794-506ddad529df4cd4.webp.jpg

相关文章

  • Android小知识-Java多线程相关(同步方法与同步代码块)

    本平台的文章更新会有延迟,大家可以关注微信公众号-顾林海,如果大家想获取最新教程,请关注微信公众号,谢谢! “非线...

  • Java之同步代码块

    Java多线程的同步代码块 synchronized(对象){ 需要同步的代码 } 同步代码块可以解决安全...

  • Java并发之synchronized

    Java多线程同步关键词是常用的多线程同步手段。它可以修饰静态类方法,实例方法,或代码块。修饰static静态方法...

  • Java多线程同步2——同步方法

    java多线程同步除了上文说到的同步代码块,还可以使用同步方法,还是银行取钱的那个问题,代码如下 public c...

  • 多线程"锁重入"概念

    Java多线程锁重入是指: 在已经获得锁的同步方法或同步代码块内部可以调用锁定对象的其他同步方法, 不需要重新获取...

  • Java多线程是如何解决同步的?

    同步资源 同步资源,是对资源(类、方法、代码块、变量)进行同步控制。在java中的多线程操作(修改)同一个共享变量...

  • 对象级别锁 vs 类级别锁 – Java

    简书 賈小強转载请注明原创出处,谢谢! 同步针对的是多线程。同步的方法或代码块同时只能由一个线程执行。 Java支...

  • 释放锁的三种情况

    1.执行完同步方法或同步代码块 2.在同步方法或同步代码块中调用wait方法 3.在同步方法或同步代码块中发生异常...

  • JUC学习笔记三

    JUC学习笔记三 用于解决多线程同步问题的方式 隐式锁(synchronized) 同步代码块 同步方法 显式锁(...

  • 5月份第一周学习安排

    学习内容: java多线程及线程同步的方法(使用) java多线程各种同步方法的原理和优缺点 java多线程设计模...

网友评论

    本文标题:Android小知识-Java多线程相关(同步方法与同步代码块)

    本文链接:https://www.haomeiwen.com/subject/dvbvfqtx.html