前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >初学者第63节之线程安全详解(五)

初学者第63节之线程安全详解(五)

作者头像
用户5224393
发布2019-08-20 15:38:59
2350
发布2019-08-20 15:38:59
举报
文章被收录于专栏:Java研发军团Java研发军团

一、方法内部变量线程安全

方法内部的变量一般不会有“非线程安全”的问题。示例。

代码语言:javascript
复制
public class TheadTest5 {
    public static void main(String[] args) throws InterruptedException {
        //定义一个共享实体对象
        ThreadEntity entity = new ThreadEntity();
        //2个线程共享了一个数据
        Runnable myRunnable1 = new MyRunnable1(entity,1);
        Runnable myRunnable2 = new MyRunnable1(entity,2);
        Thread thread1 = new Thread(myRunnable1);
        thread1.start();

        Thread thread2 = new Thread(myRunnable2);
        thread2.start();
        Thread.sleep(2000);
        System.out.println("程序结束!!!");
    }
}

class MyRunnable1 implements Runnable {
    private ThreadEntity threadEntity;
    private int number;
    public MyRunnable1(ThreadEntity threadEntity,int number) {
        this.threadEntity = threadEntity;
        this.number = number;
    }

    @Override
    public void run() {
        //在线程里调用add方法,测试是否为线程安全
        try {
            this.threadEntity.add(this.number);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class ThreadEntity {
    public void add(int number) throws InterruptedException {
        //这里定义了一个举报变量,线程是安全的
        final int num;
        if (number == 1) {
            num = 100;
            //这里为了演示效果好一点所以加了一个延时1秒
            Thread.sleep(1000);
        } else {
            num = 200;
        }
        System.out.println("输入的number为:" + number + ",num="+num);
    }
}

以上代码重点就是定义了一个局部变量,然后在number=1的情况下,休眠了一秒钟。看看结果:

代码语言:javascript
复制
输入的number为:2,num=200
输入的number为:1,num=100
程序结束!!!

二、成员变量非线程安全

还是使用上面的代码测试,只需要将ThreadEntity的add方法中的num变量改成类的成员变量这样就会成为"线程不安全了".测试一下

代码语言:javascript
复制
class ThreadEntity {
    /**
     * 修改为成员变量
     */
    private int num;
    public void add(int number) throws InterruptedException {
       /* 这里定义了一个局部变量,线程是安全的
        final int num;*/
        if (number == 1) {
            num = 100;
            //这里为了演示效果好一点所以加了一个延时1秒
            Thread.sleep(1000);
        } else {
            num = 200;
        }
        System.out.println("输入的number为:" + number + ",num="+num);
    }
}

看看结果:

代码语言:javascript
复制
输入的number为:2,num=200
输入的number为:1,num=200
程序结束!!!

以上2个都输出num=200了,因为这个就是线程不安全造成的,当1线程还未执行到最后一行打印代码,2线程这个时候把num值修改为200了,然后打印了第一条信息,然后1线程就跟着打印第二行信息,这个时候num值已经是200了。

注意:主要是因为2个线程同时操作的同一个对象实例的变量所产生的非线程安全问题的。

三、synchronized

这个关键字前面已经简单描述过了。这里在重新说下。

Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。它是一个对象锁。但java严格上说不是对象锁。由于大家都是这么叫的这里也这叫吧。

Synchronized的作用主要有三个:

(1)确保线程互斥的访问同步代码

(2)保证共享变量的修改能够及时可见

(3)有效解决重排序问题。

注意:线程锁碰到异常了就会自动释放锁的。

上一段代码是非线程安全的,现在要变成线程安全的只需要加上这个关键字就好了,测试一下。

代码语言:javascript
复制
/**
 * 加上synchronized关键字老同步这个方法就达到线程安全了
 * @param number
 * @throws InterruptedException
 */
public synchronized void add(int number) throws InterruptedException {
   /* 这里定义了一个局部变量,线程是安全的
    final int num;*/
    if (number == 1) {
        num = 100;
        //这里为了演示效果好一点所以加了一个延时1秒
        Thread.sleep(1000);
    } else {
        num = 200;
    }
    System.out.println("输入的number为:" + number + ",num="+num);
}

在ThreadEntity类的add方法上加上了synchronized关键字就可以将这个add方法变成在线程中同步了。

结果:

代码语言:javascript
复制
输入的number为:1,num=100
输入的number为:2,num=200
程序结束!!!

同步也是排队运行的,主要是用于有共享数据才会用到同步操作,如果不是没有共享数据就不需要使用同步了。

四、线程的脏读现象

原理和前面的类似,看代码先。

代码语言:javascript
复制
public class TheadTest5 {
    public static void main(String[] args) throws InterruptedException {
        //定义一个共享实体对象
        ThreadEntity entity = new ThreadEntity();
        //2个线程共享了一个数据
        Runnable myRunnable1 = new MyRunnable1(entity);
        Thread thread1 = new Thread(myRunnable1);
        thread1.setName("A");
        thread1.start();
        //为了造成脏读所以在这里需要休眠500毫秒
        Thread.sleep(500);
        entity.getValue();
        System.out.println("程序结束!!!");
    }
}

class MyRunnable1 implements Runnable {
    private ThreadEntity threadEntity;
    public MyRunnable1(ThreadEntity threadEntity) {
        this.threadEntity = threadEntity;
    }

    @Override
    public void run() {
        try {
            this.threadEntity.setValue("我是临时变量num1","我是临时变量num2");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class ThreadEntity {
    /**
     * 成员变量
     */
    private String num1 = "我是成员变量num1";
    private String num2 = "我是成员变量num2";
    /**
     * 加上synchronized关键字老同步这个方法就达到线程安全了
     * @param num1
     * @param num2
     * @throws InterruptedException
     */
    public synchronized void setValue(String num1,String num2) throws InterruptedException {
        this.num1 = num1;
        //为了造成脏读所以在这里需要休眠1秒钟
        Thread.sleep(1000);
        this.num2 = num2;
        System.out.println("setValue:" + Thread.currentThread().getName() + ",num1=" + num1);
        System.out.println("setValue:" + Thread.currentThread().getName() + ",num2=" + num2);
    }

    /**
     * 取值
     */
    public void getValue(){
        System.out.println("getValue:" + Thread.currentThread().getName() + ",this.num1=" + this.num1);
        System.out.println("getValue:" + Thread.currentThread().getName() + ",this.num2=" + this.num2);
    }
}

解释一下上面的代码,为了制造脏读所以在setvalue方法中为num1和num2中间增加了一个休眠,另外在主方法调用getValue方法的前也休眠了200毫秒,为了就是给num1赋值完成之后休眠,然后主线程getValue就直接运行了,现在看下结果吧。

代码语言:javascript
复制
getValue:main,this.num1=我是临时变量num1
getValue:main,this.num2=我是成员变量num2
程序结束!!!
setValue:A,num1=我是临时变量num1
setValue:A,num2=我是临时变量num2

看出来什么问题了吧,第一行打印的信息和第二行打印的信息。

这样的话解决办法是把getValue方法也同步就可以了。

代码语言:javascript
复制
/**
 * 取值
 */
public synchronized void getValue(){
    System.out.println("getValue:" + Thread.currentThread().getName() + ",this.num1=" + this.num1);
    System.out.println("getValue:" + Thread.currentThread().getName() + ",this.num2=" + this.num2);
}

再次打印结果:

代码语言:javascript
复制
setValue:A,num1=我是临时变量num1
setValue:A,num2=我是临时变量num2
getValue:main,this.num1=我是临时变量num1
getValue:main,this.num2=我是临时变量num2
程序结束!!!

现在就全部一致了!!!

五、synchronized语句块

这个同步语句块比较简单,直接看代码吧!!!

代码语言:javascript
复制
public class TestThread5 {

    public static void main(String[] args) {
        ThreadEntity threadEntity = new ThreadEntity();
        Runnable myRunnable1 = new MyRunnable(threadEntity);
        Thread thread1 = new Thread(myRunnable1);
        thread1.setName("A");
        Runnable myRunnable2 = new MyRunnable(threadEntity);
        Thread thread2 = new Thread(myRunnable2);
        thread2.setName("B");
        thread1.start();
        thread2.start();
    }
}

class MyRunnable implements Runnable {
    private ThreadEntity threadEntity;

    public MyRunnable(ThreadEntity threadEntity) {
        this.threadEntity = threadEntity;
    }

    @Override
    public void run() {
        this.threadEntity.show();
    }
}

class ThreadEntity {
    public void show()  {
        try {
            /**
             * 同步代码块
             */
            synchronized (this) {
                System.out.println(Thread.currentThread().getName()+",开始时间"+System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName()+",结束时间"+System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果

代码语言:javascript
复制
A,开始时间1524235595295
A,结束时间1524235597423
B,开始时间1524235597427
B,结束时间1524235599427

注意:使用synchronized(this)的时候当一个线程访问到某对象中的synchronized(this)或者synchronized代码块时,其它线程对同一个对象的所有synchronized(this)或者synchronized代码块将被阻塞。

再看一个例子。

代码语言:javascript
复制
public class TestThread5 {

    public static void main(String[] args) {
        ThreadEntity threadEntity = new ThreadEntity();
        Runnable myRunnable1 = new MyRunnable(threadEntity,true);
        Thread thread1 = new Thread(myRunnable1);
        thread1.setName("A");
        thread1.start();

        Runnable myRunnable2 = new MyRunnable(threadEntity,false);
        Thread thread2 = new Thread(myRunnable2);
        thread2.setName("B");
        thread2.start();
    }
}

class MyRunnable implements Runnable {
    private ThreadEntity threadEntity;
    private boolean flag;

    public MyRunnable(ThreadEntity threadEntity,boolean flag) {
        this.threadEntity = threadEntity;
        this.flag = flag;
    }

    @Override
    public void run() {
        //flag:true执行show1方法
        if (flag) {
            try {
                this.threadEntity.show1();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("====================");
        }
        //flag:true执行show2方法
        else {
            this.threadEntity.show2();
        }
    }
}

class ThreadEntity {
    public void show1() throws InterruptedException {
        //同步锁代码块
        synchronized (this) {
            System.out.println("show1开始打印"+Thread.currentThread().getName());
            //为了显示效果这里休眠2秒
            Thread.sleep(2000);
            System.out.println("show1结束打印"+Thread.currentThread().getName());
        }
    }
    public void show2()  {
        //同步锁代码块
        synchronized (this) {
            System.out.println("show2开始打印"+Thread.currentThread().getName());
            System.out.println("show2结束打印"+Thread.currentThread().getName());
        }
    }
}

结果:

代码语言:javascript
复制
show1开始打印A
show1结束打印A
====================
show2开始打印B
show2结束打印B

以上可以看出,只要线程执行到了show1方法的synchronized(this)同步代码块之后,show2方法的synchronized(this)同步代码块将被阻塞了。

总结

Java的锁分为对象锁和类锁。

  1. 当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内针对该对象的操作只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

 2. 然而,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。

 3. 尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对该object中所有其它synchronized(this)同步代码块的访问将被阻塞。

 4. 同步加锁的是对象,而不是代码。因此,如果你的类中有一个同步方法,这个方法可以被两个不同的线程同时执行,只要每个线程自己创建一个的该类的实例即可。

 5. 不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法。

 6. synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法。

 7.对一个全局对象或者类加锁时,对该类的所有对象都起作用。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-04-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java研发军团 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、方法内部变量线程安全
  • 二、成员变量非线程安全
  • 三、synchronized
  • 四、线程的脏读现象
  • 五、synchronized语句块
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档