【并发】共享模型之管程

发布时间:2024年01月14日

共享模型之管程

共享问题

package 并发;

public class Test1 {
    static int a=0;
    public static void main(String[] args) throws InterruptedException {

        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<5000;i++){
                    a++;
                }

            }
        });
        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
               for(int i=0;i<5000;i++){
                   a--;
               }
            }
        });
        t1.start();;
        t2.start();
        t1.join();;
        t2.join();
        System.out.println("a="+a);
    }

}

与预期的结果不同

问题分析

以上的结果可能是正数,负数,0为什么呢? 因为Java中对静态变量的自增,自减并不是原子操作,要彻底理解,必须从字节码进行分析。

例如:对于i++而言,实际会产生如下的JVM字节码指令:

getstatic i //获取静态变量 iconst_1 //准备常量1 iadd //自增 putstatic i //将修改后的值存入静态变量i

而JAVA 的内存模型如下,完成静态变量的自增,自减需要在主存和工作内存中进行数据交换:

如果是单线程,上面的代码是顺序执行(不会交错) 没有问题:

临界区

  • 一个程序运行多个线程本身是没有问题的。
  • 问题出在多个线程访问共享资源。
    • 多个线程读取共享资源其实也没有问题
    • 在多个线程对共享资源读写操作时发生指令交集,就会出现问题。
  • 一般代码块如果存在对共享资源的多线程读写操作。那么这段代码称为临界区。
Thread t1=new Thread(new Runnable() {
    @Override
    public void run() {
     //临界区
       a++;

    }
});
Thread t2=new Thread(new Runnable() {
    @Override
    public void run() {
    //临界区
      a--;
    }
});

解决方案

  • 阻塞式的解决方案:synchronized \ Lock
  • 非阻塞式的解决方案:原子变量

本次课程使用的解决方案式:synchronzied ,来解决上述问题,俗称【对象锁】。

它采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其他想获取这个对象锁就会被阻塞住,这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心上下文的切换。

注意:

虽然java中的互斥和同步都是可以采用synchronized来完成,但还是有区别的。

  • 互斥是保证临界区的竟态条件发生,同一时刻只有一个线程执行临界区的代码。
  • 同步是由于线程执行的先后,顺序不同,需要一个线程等待其他线程运行到这个点,

synchronzied

语法
synchronized(){  
    临界区
}
解决
package 并发;

import java.util.Date;

public class Test1 {
    static Integer a=0;
    static Object flag=new Object();
    public static void main(String[] args) throws InterruptedException {

        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<5000;i++){
                    //加锁
                    synchronized (flag){
                        a++;
                    }
                }

            }
        });
        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<5000;i++){
                      //加锁
                    synchronized (flag){
                        a--;
                    }
                }
            }
        });
        t1.start();;
        t2.start();
        t1.join();;
        t2.join();
        System.out.println("a="+a);
    }

}
向对象思想改进面
package 并发;

import java.util.Date;

 class Test1 {
    static Integer a=0;
    static   Room room=new Room();


    public static void main(String[] args) throws InterruptedException {

        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<5000;i++){

                        room.increase();;

                }

            }
        });
        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<5000;i++){

                        room.decrease();;


                }
            }
        });
        t1.start();;
        t2.start();
        t1.join();;
        t2.join();
        System.out.println("结果是"+room.count);
    }

}





class Room {
    public static int count=0;
    static Object flag=new Object();
    public void increase(){
        synchronized (flag){
            count++;
        }

    }

    public void decrease(){

        synchronized (flag){
            count--;
        }
    }



}

方法上的synchronized

语法

synchronized加在普通方法上

class Room {
    public static int count=0;
    static Object flag=new Object();
    public synchronized void increase(){

            count++;
        

    }
    //等价于 锁住的是自己的对象
      public void increase(){
        synchronized(this){
            count++;
        }

    }

}

synchronized加在静态方法上

class Room {
    public static int count=0;
    static Object flag=new Object();
    public synchronized  static void increase(){

            count++;
        

    }
    //等价于 锁住的是自己的类对象
      public static void increase(){
        synchronized(Room.class){
            count++;
        }

    }

}

不加synchronized方法无法保证原子性

线程安全分析

成员变量和静态变量是否是安全的?

  • 如果他们没有共享,则线程安全
  • 如果他们被共享了,根据他们的线程是否能改变,又分为两种:

????????????????只有读操作,则线程安全。

????????????????如果有读写操作,则这段代码是临界区,需要考虑线程安全。

局部变量是否是线程安全的?

  • 局部变量是线程安全的
  • 但局部变量引用的对象未必。(堆中的变量就可能被共享)
    • 如果该对象没有逃离方法的作用范围,则是线程安全的。
    • 如果该对象逃离方法的作用范围,则需要考虑线程安全
局部变量线程安全分析

public static void test1(){ int i=10; i++; }

每个线程调用test1()方法时局部变量i,会在每个线程的栈帧内存中被创建多份,因此不存在共享

如图:

局部变量的引用稍有不同

先看一个成员变量的例子

class ThreadUnsafe { 
    ArrayList list = new ArrayList<>(); 

public void method1(int loopNumber) { 
    for (int i = 0; i < loopNumber; i++) { 
    // { 临界区, 会产生竞态条件 method2(); method3();



执行其中一种情况是,如果线程2 还未 add,线程1 remove 就会报错:Exception in thread "Thread1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 at java.util.ArrayList.rangeCheck(ArrayList.java:657) at java.util.ArrayList.remove(ArrayList.java:496) at cn.itcast.n6.ThreadUnsafe.method3(TestThreadSafe.java:35) at cn.itcast.n6.ThreadUnsafe.method1(TestThreadSafe.java:26) at cn.itcast.n6.TestThreadSafe.lambda$main$0(TestThreadSafe.java:14) at java.lang.Thread.run(Thread.java:748)分析:无论哪个线程中的 method2 引用的都是同一个对象中的 list 成员变量method3 与 method2 分析相同 // } 临界区 } } private void method2() { list.add("1"); } private void method3() { list.remove(0); }}

执行

static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args) { 
    ThreadUnsafe test = new ThreadUnsafe(); 
    for (int i = 0; i < THREAD_NUMBER; i++) { 
    new Thread(() -> { test.method1(LOOP_NUMBER); }, "Thread" + i).start(); }}

其中一种情况是,如果线程2 还未 add,线程1 remove 就会报错

Exception in thread "Thread1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 at java.util.ArrayList.rangeCheck(ArrayList.java:657) at java.util.ArrayList.remove(ArrayList.java:496) at cn.itcast.n6.ThreadUnsafe.method3(TestThreadSafe.java:35) at cn.itcast.n6.ThreadUnsafe.method1(TestThreadSafe.java:26) at cn.itcast.n6.TestThreadSafe.lambda$main$0(TestThreadSafe.java:14) at java.lang.Thread.run(Thread.java:748)

分析:

无论哪个线程中的 method2 引用的都是同一个对象中的 list 成员变量

method3 与 method2 分析相同

文章来源:https://blog.csdn.net/m0_62645012/article/details/135585170
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。