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

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

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

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

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对象)。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值