多线程分时系统线程安全问题-synchronize

问题

多线程分时系统是存在线程安全问题的,如下例子:

两个线程分别对同一个变量(初始值 = 0)做循环自增和自减操作各50000次,观察结果,并不等于初始值。

public class 分时系统线程安全问题 {
    static int a = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                a++;
            }
        },"t1");

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                a--;
            }
        },"t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(a);
    }
}

出现以上问题的原因就是,看似单个的Java语句(a++、a--)会被操作系统拆分为多几条指令。自增自减并不是原子操作。在多线程的情况下下面的8条指令是会交错执行的,必然导致结果!=预期

a++:

getstatic       i  //获取静态常量的值

iconst_1         //准备常量1 

iadd             //自增

putstatic       //讲修改后的值写入静态变量i

a--:

getstatic       i  //获取静态常量的值

iconst_1         //准备常量1

isub             //自减

putstatic       //讲修改后的值写入静态变量i

临界区 Critical Section

一个程序运行多个线程本身没有问题,问题出在多个线程访问了共享资源,多个读写共享资源其实也没问题,但是多个线程在访问共享资源是出现了指令交错就会有问题。

一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区

static int counter = 0;
static void increment(){
    //临界区
    counter++;
}

static void decrement(){
    //临界区
    counter--;
}

竞态条件 Race condition

多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件

解决问题

1、应用之互斥

为了避免临界区的竞态条件发生,有多种手段可以达到目的。

阻塞式的解决方案:synchronized,Lock

非阻塞式的解决方案:原子变量 

synchronized

语法

synchronize(对象){
    //临界区
}

static int a = 0;
/**
 * 对象锁
 */
static Object lock = new Object();

/**
 * 线程安全
 */
public static void test2() throws InterruptedException {
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 50000; i++) {
            synchronized (lock) {
                a++;
            }
        }
    }, "t1");

    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 50000; i++) {
            synchronized (lock) {
                a--;
            }
        }
    }, "t2");
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(a);
}

synchronize利用对象锁保证了临界区内的代码块的原子性(不可分割)。

过程分析:

思考:

1、synchronize放在for循环外面能保证结果正确吗?(--原子性)

 

synchronized (lock) {
    for (int i = 0; i < 50000; i++) {
        a++;//a--;
    }
}

答案:可以的synchronize关键字包含的代码块为一个整体不可分割。

2、t1里面synchronize(lock1)、t2里面synchronize(lock2)能保证结果正确吗?(--锁对象)

答案:不能,加锁要是同一个对象锁、保护共享资源,必须保证同一个对象锁。

3、t1加锁 synchronize(lock),t2不加锁能保证结果正确吗?(--锁对象)

答案:不能,同问题2.

 

面向对象的优化:

public class 分时系统线程安全问题2 {


    public static void main(String[] args) throws InterruptedException {
        Room room = new Room();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                room.increment();
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                room.decrement();
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(room.getA());

    }
}

class Room {
    private int a = 0;

    public void increment() {
        synchronized (this) {
            a++;
        }
    }

    public void decrement() {
        synchronized (this) {
            a--;
        }
    }

    public int getA() {
        return a;
    }
}

synchronize的使用位置:

1、加在普通法放上。(所的是this对象)

class Test{
    public synchronized void test() {

    }
}
//等价于
class Test{
    public void test() {
        synchronized(this) {

        }
    }
}

2、加在静态方法上(锁的是class对象,调用者公用一把锁)

class Test{
    public synchronized static void test() {
    }
}
//等价于
class Test{
    public static void test() {
        synchronized(Test.class) {

        }
    }
}

synchronized相关的 8锁问题

posted @   iyandongsheng  阅读(95)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示