二、java多线程基础之多线程安全总结以及分析

一、线程安全问题为什么会存在

1.当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。

二、代码例子

1.代码

public class ThreadTrain implements Runnable {
    private int trainCount = 100;
    @Override
    public void run() {
        while (trainCount > 0) {
            try {
                Thread.sleep(500);
            } catch (Exception e) {

            }
            sale();
        }
    }
    public void sale() {
        if (trainCount > 0) {
            System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票");
            trainCount--;
        }
    }
    public static void main(String[] args) {
        ThreadTrain threadTrain = new ThreadTrain();
        Thread t1 = new Thread(threadTrain, "①号");
        Thread t2 = new Thread(threadTrain, "②号");
        t1.start();
        t2.start();
    }
}

2.结果

3.分析

一号窗口和二号窗口同时出售火车票,部分火车票会重复出售结论发现,多个线程共享同一个全局成员变量时,做写的操作可能会发生数据冲突问题。

三、线程安全的解决方法

1.使用多线程之间同步synchronized或使用锁(lock),将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。代码执行完成后释放锁,让后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。

2.内置锁使用synchronized关键字实现,synchronized关键字有两种用法:

2.1.修饰需要进行同步的方法(所有访问状态变量的方法都必须进行同步),此时充当锁的对象为调用同步方法的对象

2.2.同步代码块和直接使用synchronized修饰需要同步的方法是一样的,但是锁的粒度可以更细,并且充当锁的对象不一定是this,也可以是其它对象,所以使用起来更加灵活。

2.3.每一个Java对象都可以用作一个实现同步的锁,称为内置锁,线程进入同步代码块之前自动获取到锁,代码块执行完成正常退出或代码块中抛出异常退出时会释放掉锁,内置锁为互斥锁,即线程A获取到锁后,线程B阻塞直到线程A释放锁,线程B才能获取到同一个锁。

3.代码(同步方法,其他的方法省略)

public class ThreadTrain implements Runnable {
    private int trainCount = 100;
    @Override
    public void run() {
        while (trainCount > 0) {
            try {
                Thread.sleep(500);
            } catch (Exception e) {

            }
            sale();
        }
    }
    public synchronized void sale() {
        if (trainCount > 0) {
            System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票");
            trainCount--;
        }
    }
    public static void main(String[] args) {
        ThreadTrain threadTrain = new ThreadTrain();
        Thread t1 = new Thread(threadTrain, "①号");
        Thread t2 = new Thread(threadTrain, "②号");
        t1.start();
        t2.start();
    }
}

3.1.结果(结果正常)

①号,出售第1张票
②号,出售第2张票
①号,出售第3张票
②号,出售第4张票
①号,出售第5张票
②号,出售第6张票
①号,出售第7张票

四、ThreadLocal的用法

1.ThreadLocal提高一个线程的局部变量,访问某个线程拥有自己局部变量(每个线程都有一个)。

2.当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

3.代码

class Res {
    // 生成序列号共享变量
    public static Integer count = 0;
    public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        protected Integer initialValue() {
            return 0;
        };
    };
    public Integer getNum() {
        int count = threadLocal.get() + 1;
        threadLocal.set(count);
        return count;
    }
}
public class ThreadLocaDemo2 extends Thread {
    private Res res;

    public ThreadLocaDemo2(Res res) {
        this.res = res;
    }
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + "i---" + i + "--num:" + res.getNum());
        }
    }
    public static void main(String[] args) {
        Res res = new Res();
        ThreadLocaDemo2 threadLocaDemo1 = new ThreadLocaDemo2(res);
        ThreadLocaDemo2 threadLocaDemo2 = new ThreadLocaDemo2(res);
        ThreadLocaDemo2 threadLocaDemo3 = new ThreadLocaDemo2(res);
        threadLocaDemo1.start();
        threadLocaDemo2.start();
        threadLocaDemo3.start();
    }
}

4.结果

Thread-0---i---0--num:1
Thread-0---i---1--num:2
Thread-0---i---2--num:3
Thread-2---i---0--num:1
Thread-2---i---1--num:2
Thread-2---i---2--num:3
Thread-1---i---0--num:1
Thread-1---i---1--num:2
Thread-1---i---2--num:3

五、结束

1.今天这一个就说到这里吧!!!

发布了122 篇原创文章 · 获赞 64 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/chenmingxu438521/article/details/103763169