《七台河物流》[:Java并发编程(03):多线程并发访问,〖「同步控」制〗

2020-03-30 24 views 0

扫一扫用手机浏览

『诚信在线』

『诚信在线』(www.wankeeshoes.com)现已开放『诚信在线』手机版下载。游戏公平、〖公开〗、公正,‘用实力赢取信誉’。

,

本文源码:GitHub·《点这里》 || GitEE·《点这里》

一、【并发】问题

多线程学习的时候,《要面对的第一个复杂问题就是》,【并发】模式下变量‘的访问’,如果不理清楚内在流程和原因,经常会出现这样一个问题:线程处理后的变量值不是自己想要的,《可能还会一脸懵的说》:这不合逻辑吧?

1、『成员变量』访问

多个线程访问类的成员变量, 可能会带来各种问题[。

public class AccessVar01 {
    public static void main(String[] args) {
        Var01Test var01Test = new Var01Test() ;
        VarThread01A varThread01A = new VarThread01A(var01Test) ;
        varThread01A.start();
        VarThread01B varThread01B = new VarThread01B(var01Test) ;
        varThread01B.start();
    }
}
class VarThread01A extends Thread {
    Var01Test var01Test = new Var01Test() ;
    public VarThread01A (Var01Test var01Test){
        this.var01Test = var01Test ;
    }
    @Override
    public void run() {
        var01Test.addNum(50);
    }
}
class VarThread01B extends Thread {
    Var01Test var01Test = new Var01Test() ;
    public VarThread01B (Var01Test var01Test){
        this.var01Test = var01Test ;
    }
    @Override
    public void run() {
        var01Test.addNum(10);
    }
}
class Var01Test {
    private Integer num = 0 ;
    public void addNum (Integer var){
        try {
            if (var == 50){
                num = num + 50 ;
                Thread.sleep(3000);
            } else {
                num = num + var ;
            }
            System.out.println("var="+var+";num="+num);
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

这里案例的流程就是【并发】下运算一个成员变量,<程序的本意是>:var=50,〖得到〗num=50,可输出的实际结果是:

var=10;num=60
var=50;num=60

VarThread01A线程处理中进入休眠,休眠时num『已经被线程』VarThread01B『进行一次加』10的运算,这就是多线程【并发】‘访问导致的结果’。

2、方法私有变量

修改上述的代码逻辑,「把」num变量置于方法内,作为私有的方法变量。

class Var01Test {
    // private Integer num = 0 ;
    public void addNum (Integer var){
        Integer num = 0 ;
        try {
            if (var == 50){
                num = num + 50 ;
                Thread.sleep(3000);
            } else {
                num = num + var ;
            }
            System.out.println("var="+var+";num="+num);
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

<方法内部的变量是私有的>,《且和当前执行》方法的线程绑定,不会存在线程间干扰问题。

‘(二)’、‘同步控制’

1、Synchronized【『关键字』】

(使用)方式:修饰方法,或者以控制同步块的形式, 保证[多个线程【并发】下,(同一时刻只)有一个线程进入方法中,<或者同步代码块中>,〖从而使线程安全〗‘的访问’和处理变量。 如果修饰的是静态方法[,〖作用的是这个类的所有对象〗。

独占锁属于悲观锁一类,synchronized就是一种独占锁,假设处于最坏的情况,(只有一个线程执行),阻塞其他线程,如果【并发】高,处理耗时长,『会导致多个线程挂起』,(等待持有锁的线程释放)锁。

2、修饰方法

“「这个案例和第一个案例原」理上是一样的”,不过这里虽然在修改值的地方加入的‘同步控制’,但是又挖了一个坑,(在读取的时候)没有限制,这个现象俗称脏读。

public class AccessVar02 {
    public static void main(String[] args) {
        Var02Test var02Test = new Var02Test ();
        VarThread02A varThread02A = new VarThread02A(var02Test) ;
        varThread02A.start();
        VarThread02B varThread02B = new VarThread02B(var02Test) ;
        varThread02B.start();
        var02Test.readValue();
    }
}
class VarThread02A extends Thread {
    Var02Test var02Test = new Var02Test ();
    public VarThread02A (Var02Test var02Test){
        this.var02Test = var02Test ;
    }
    @Override
    public void run() {
        var02Test.change("my","name");
    }
}
class VarThread02B extends Thread {
    Var02Test var02Test = new Var02Test ();
    public VarThread02B (Var02Test var02Test){
        this.var02Test = var02Test ;
    }
    @Override
    public void run() {
        var02Test.change("you","age");
    }
}
class Var02Test {
    public String key = "cicada" ;
    public String value = "smile" ;
    public synchronized void change (String key,String value){
        try {
            this.key = key ;
            Thread.sleep(2000);
            this.value = value ;
            System.out.println("key="+key+";value="+value);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void readValue (){
        System.out.println("读取:key="+key+";value="+value);
    }
}

在线程中,逻辑上已经修改了,只是没执行到,但是在main「线程中读取的」value《毫无意义》,需要在读取方法上也加入同步的线程控制。

3、‘同步控制’逻辑

‘同步控制’实现是基于Object《的监视器》。

  • 线程对Object‘的访问’,首先要先获得Object《的监视器》 ;
  • 如果获取成功,‘则会独占该对象’ ;
  • 其他线程会掉进同步队列,线程状态变为阻塞 ;
  • 等Object的持有线程释放锁,会唤醒队列中等待的线程,尝试重启获取对象监视器;

4、修饰代码块

说明一点,「代码块包含方法中的全部逻」辑,锁定的粒度和修饰方法是一样的,‘就写在方法上吧’。同步代码块一个很核心的目的,减小锁定资源的粒度,‘就’如同表锁和行级锁。

public class AccessVar03 {
    public static void main(String[] args) {
        Var03Test var03Test1 = new Var03Test() ;
        Thread thread1 = new Thread(var03Test1) ;
        thread1.start();
        Thread thread2 = new Thread(var03Test1) ;
        thread2.start();
        Thread thread3 = new Thread(var03Test1) ;
        thread3.start();
    }
}
class Var03Test implements Runnable {
    private Integer count = 0 ;
    public void countAdd() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized(this) {
            count++ ;
            System.out.println("count="+count);
        }
    }
    @Override
    public void run() {
        countAdd() ;
    }
}

《这里就是锁定》count处理这个动作的核心代码逻辑,不允许【并发】处理。

5、修饰静态方法

静态方法属于类层级的方法,对象是不可以直接调用的。但是synchronized〖修饰的静态方法锁定的是这个类的所有对象〗。

public class AccessVar04 {
    public static void main(String[] args) {
        Var04Test var04Test1 = new Var04Test() ;
        Thread thread1 = new Thread(var04Test1) ;
        thread1.start();
        Var04Test var04Test2 = new Var04Test() ;
        Thread thread2 = new Thread(var04Test2) ;
        thread2.start();
    }
}
class Var04Test implements Runnable {
    private static Integer count ;
    public Var04Test (){
        count = 0 ;
    }
    public synchronized static void countAdd() {
        System.out.println(Thread.currentThread().getName()+";count="+(count++));
    }
    @Override
    public void run() {
        countAdd() ;
    }
}

【如果不是(使用)】‘同步控制’,从逻辑和感觉上,输出的结果应该如下:

Thread-0;count=0
Thread-1;count=0

加入‘同步控制’之后,实际测试输出结果:

Thread-0;count=0
Thread-1;count=1

6、<〖注意事项〗>

  • 继承中子类覆盖父类方法,synchronized【『关键字』】特性不能继承传递,必须显式声明;
  • 构造方法上不能(使用)synchronized【『关键字』】,【构造方法中支持同步代】码块;
  • 接口中方法,抽象方法也不支持synchronized【『关键字』】 ;

三、Volatile【『关键字』】

1、(基本描述)

Java内存模型中,为了提升性能,线程会在自己的工作内存中拷贝要访问的变量的副本。这样就会出现同一个变量在某个时刻,在一个线程的环境中的值可能与另外一个线程环境中的值,『出现不一致的情况』。

(使用)volatile修饰成员变量,不能修饰方法,《即标识该线程在访问这个变量时需要从》【共享内存中获取】,〖对该变量的修改〗,也需要同步刷新到共享内存中, 保证[了变量对所有线程的可见性。

2、(使用)案例

class Var05Test {
    private volatile boolean myFlag = true ;
    public void setFlag (boolean myFlag){
        this.myFlag = myFlag ;
    }
    public void method() {
        while (myFlag){
            try {
                System.out.println(Thread.currentThread().getName()+myFlag);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

3、<〖注意事项〗>

  • 〖可见性只能确保〗每次读取的是最新的值,‘但不支持变量操作的’原子性;
  • volatile并不会阻塞线程方法,但是‘同步控制’会阻塞;
  • Java‘同步控制’的根本: 保证[【并发】下资源的原子性和可见性;

‘四’、源代码‘【地址】’

GitHub·‘【地址】’
https://github.com/cicadasmile/java-base-parent
GitEE·‘【地址】’
https://gitee.com/cicadasmile/java-base-parent

Sunbet网站内容转载自互联网,如有侵权,联系Sunbet 删除。

本文链接地址:http://www.shfkgcjxyxgs.com/post/1269.html

相关文章

发表评论