什么是多线程安全问题?如何解决?

什么是多线程安全问题?如何解决?

1,什么叫多线程安全问题?
当多个线程对同一全局变量进行写操作时,可能会发生数据冲突问题.这就是多线程安全问题. 进行读时不会发生安全问题.

经典案例: 多线程抢购火车票.
比如:现有100涨火车票,开两条线程窗口对其进行售卖.

①,不加锁和添加睡眠时间. 就创建两条线程进行售卖.

public class ThreadDemo implements Runnable {
    //定义一个全局变量. 总火车票100张.
    private int count = 100;

        @Override
        public void run() {
            while (count > 0) {
                save();
                count --;
            }
        }

        public void save() {
            System.out.println(Thread.currentThread().getName() + "窗口售卖第" + (100-count+1) + "张票");
        }

    public static void main(String[] args) {
        ThreadDemo threadTest01 = new ThreadDemo();

        Thread t1 = new Thread(threadTest01,"①");
        Thread t2 = new Thread(threadTest01,"②");
        t1.start();
        t2.start();

    }
}

输入结果:

②窗口售卖第1张票
②窗口售卖第2张票
②窗口售卖第3张票
..............
②窗口售卖第100张票
①窗口售卖第1张票

这时候就会发现,第一张票有卖重复.

②,加睡眠时间. 让他产生一定的线程堆积.

public class ThreadDemo implements Runnable {
    //定义一个全局变量. 总火车票100张.
    private int count = 100;

        @Override
        public void run() {
            while (count > 0) {
                try {
                //添加睡眠时间. 让线程产生一定的堆积.
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
               }
                save();
                count --;
            }
        }

        public void save() {
            System.out.println(Thread.currentThread().getName() + "窗口售卖第" + (100-count+1) + "张票");
        }

    public static void main(String[] args) {
        ThreadDemo threadTest01 = new ThreadDemo();

        Thread t1 = new Thread(threadTest01,"①");
        Thread t2 = new Thread(threadTest01,"②");
        t1.start();
        t2.start();

    }
}

输入结果:

①窗口售卖第1张票
①窗口售卖第2张票
①窗口售卖第3张票
..............
②窗口售卖第99张票
②窗口售卖第100张票
①窗口售卖第101张票

这时候会发现第101张票,卖重复了.

③在执行代码块上加内置锁(synchronized). 并加上一个逻辑判断.保证count>0. 票数没有第"0"张的说法.

public class ThreadDemo implements Runnable {
    //定义一个全局变量. 总火车票100张.
    private int count = 100;

        @Override
        public void run() {
            while (count > 0) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                save();
                count --;
            }
        }

        public synchronized void save() {
            //保证票数count一定时大于0的.如果conut=0.逻辑上就不同,没有所谓的第"0"张票.
            //如果if()判断,只有一个条件.后续没有其他条件,即没有elseif(),花括号{}可以省略.
            if (count > 0)
            System.out.println(Thread.currentThread().getName() + "窗口售卖第" + (100-count+1) + "张票");
        }

    public static void main(String[] args) {
        ThreadDemo threadTest01 = new ThreadDemo();

        Thread t1 = new Thread(threadTest01,"①");
        Thread t2 = new Thread(threadTest01,"②");
        t1.start();
        t2.start();
    }
}

输出结果:

②窗口售卖第1张票
①窗口售卖第2张票
②窗口售卖第3张票
..............
①窗口售卖第99张票
②窗口售卖第100张票

上面这个例子就说明了. 多线程对全局变量做写操作时,会产生数据问题. 这时候要解决写时产生的脏数据问题,就要用锁机制来解决. 当执行代码块加锁后, 必须要拿到锁的使用权,才能进入执行代码块,并且释放锁后,其他线程才能再次获取锁,才能执行.

线程安全解决办法: 核心是,在同一时刻只能有一条线程对全局共享数据做操作,直待结束.

问:如何解决多线程之间线程安全问题
答:使用多线程之间同步synchronized或使用锁(lock)。

问:为什么使用线程同步或使用锁能解决线程安全问题呢?
答:将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。代码执行完成后释放锁,让后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。

问:什么是多线程之间同步
答:当多个线程共享同一个资源,不会受到其他线程的干扰。

问:什么是多线程同步
答:当多个线程共享同一个资源,不会受到其他线程的干扰。

内置的锁(内置锁)和显示锁:

内置锁: java代码自带的锁. synchronized.
显示锁: 人为添加的锁. Lock锁

Java提供了一种内置的锁机制来支持原子性.
每一个Java对象都可以用作一个实现同步的锁,称为内置锁,线程进入同步代码块之前自动获取到锁,代码块执行完成正常退出或代码块中抛出异常退出时会释放掉锁.
内置锁为互斥锁,即线程A获取到锁后,线程B阻塞直到线程A释放锁,线程B才能获取到同一个锁.
内置锁使用synchronized关键字实现,synchronized关键字有两种用法:
1.修饰需要进行同步的方法(所有访问状态变量的方法都必须进行同步),此时充当锁的对象为调用同步方法的对象
2.同步代码块和直接使用synchronized修饰需要同步的方法是一样的,但是锁的粒度可以更细,并且充当锁的对象不一定是this,也可以是其它对象,所以使用起来更加灵活.

同步代码块synchronized

就是将可能会发生线程安全问题的代码,给包括起来。
synchronized(同一个数据){
可能会发生线程冲突问题
}
就是同步代码块
synchronized(对象)//这个对象可以为任意对象
{
需要被同步的代码
}

对象如同锁,持有锁的线程可以在同步中执行
没持有锁的线程即使获取CPU的执行权,也进不去
同步的前提:

1,必须要有两个或者两个以上的线程 
2,必须是多个线程使用同一个锁 

必须保证同步中只能有一个线程在运行
好处:解决了多线程的安全问题
弊端:多个线程需要判断锁,较为消耗资源、抢锁的资源。
代码样例:

public void sale() {
		synchronized (this) {
			if (trainCount > 0) {
				System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票");
				trainCount--;
			}
		}
	}

同步方法

什么是同步方法?
答:在方法上修饰synchronized 称为同步方法
代码样例

public synchronized void sale() {
		if (trainCount > 0) {
			System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票");
			trainCount--;
		}
	}

多线程死锁:
什么是多线程死锁?
答:同步中嵌套同步,导致锁无法释放. 比如:一个进程中有两条线程A,B执行. 在A线程同步时,加了锁OJ. 方法执行体中加了锁this. B线程同步时, 加了锁this. 方法执行体加了锁OJ. 这时候就会导致,A线程执行一次过后, 若执行线程B时, 就会出现 OJ ,this锁.之间还没有释放. 这就叫做同步嵌套同步.

Threadlocal:
ThreadLocal提高一个线程的局部变量,访问某个线程拥有自己局部变量。
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

public class Res {
    //定义一个变量.
    private static  Integer count = 0;

    private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            //将它设定给每一个线程. 作为本地内存副本.
            return 0;
        }
    };
public class ThreadDemo1 implements Runnable {
    //将Res 作为局部属性.
    private Res res;

    public ThreadDemo1(Res res){
        this.res = res;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {

            System.out.println(Thread.currentThread().getName()+":"+res.getNum());
        }
    }

    public static void main(String[] args) {
        Res res = new Res();
        ThreadDemo1 threadDemo1 = new ThreadDemo1(res);

        Thread t1 = new Thread(threadDemo1);
        Thread t2 = new Thread(threadDemo1);
        t1.start();
        t2.start();
    }
}

输出结果:

Thread-0:1
Thread-0:2
Thread-0:3
Thread-1:1
Thread-1:2
Thread-1:3

多线程有三大特性:

1,原子性: 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
一个很经典的例子就是银行账户转账问题:
比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。这2个操作必须要具备原子性才能保证不出现一些意外的问题。
我们操作数据也是如此,比如i = i+1;其中就包括,读取i的值,计算i,写入i。这行代码在Java中是不具备原子性的,则多线程运行肯定会出问题,所以也需要我们使用同步和lock这些东西来确保这个特性了。
原子性其实就是保证数据一致、线程安全一部分.

2,可见性: 当多个线程操作通一个全局变量时, A线程读数据进行了修改操作. 在B线程对其进行修改操作之前,就要让B线程知道,已经被修改的数据. 即, 修改数据后, A B 线程都要知道修改后的结果.

3,有序性: 程序执行的顺序,按照代码执行的先后顺序.
一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。如下:
int a = 10; //语句1
int r = 2; //语句2
a = a + 3; //语句3
r = a*a; //语句4
则因为重排序,他还可能执行顺序为 2-1-3-4,1-3-2-4
但绝不可能 2-1-4-3,因为这打破了依赖关系。
显然重排序对单线程运行是不会有任何问题,而多线程就不一定了,所以我们在多线程编程时就得考虑这个问题了。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值