什么是线程安全问题?
当多个线程共享同一个全局变量,修改的时候,可能会受到其他线程干扰,导致数据有问题,这就现象叫做线程安全问题。读的时候,不会产生线程安全问题
多个线程共享一个局部变量,对变量修改时不会对发生线程安全问题
示例:车站卖票经典案例
public class ThreadDemo implements Runnable { //一共有一百张票 private int count = 100; @Override public void run() { while (count > 0) { try { Thread.sleep(100); sale(); } catch (InterruptedException e) { e.printStackTrace(); } } } private void sale() { if (count > 0) { System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "张票"); count--; } } public static void main(String[] args) { ThreadDemo threadDemo = new ThreadDemo(); Thread t1 = new Thread(threadDemo, "窗口1"); Thread t2 = new Thread(threadDemo, "窗口2"); t1.start(); t2.start(); } }
观察输出发现很多重复票出售。
如何解决线程安全问题?
synchroizcd --手动锁
lock --jdk1.5并发包 --手动
使用synchroizcd解决线程安全问题
private synchronized void sale() { if (count > 0) { System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "张票"); count--; } }
原理:
1、对一段代码块加锁了之后,当第一个线程执行到这段加锁的代码块后就会先拿到一个锁,这时其他线程就不能执行这段加锁的代码块了,会在外面等待。因为锁已经被另一个线程拿到了,只有拿到锁的线程才能执行这段加锁的代码块,当第一个拿到锁的线程把锁释放后,这时其他线程就可以去抢这把锁了,当有一个线程抢到锁后,其他线程就只能继续等待锁释放。
2、锁是什么时候释放?代码执行完毕或者是程序抛出异常锁都会被释放
3、锁已经被释放的话,其他线程开始抢这把锁,执行同步代码块
什么地方需要考虑加锁?
1.有两个线程以上,需要发生同步
2.多个线程想同步,必须要用同意把锁。
3.保证同一时刻只有一个线程进行执行。
好处:解决了多线程的安全问题
弊端:多个线程需要判断锁,较为消耗资源、抢锁的资源。
什么是同步代码块?
就是将可能会发生线程安全问题的代码,给包括起来
synchronized(同一个对象){ //可能会发生线程冲突问题 }
对象如同锁,持有锁的线程可以在同步中执行
同步函数:就是在方法上加上 synchronized
同步函数 使用什么锁? this锁。
一个线程使用同步代码块(this明锁),另一个线程使用同步函数。如果两个线程抢票不能实现同步,那么会出现数据错误。
静态同步函数
方法上加上static关键字,使用synchronized 关键字修饰 或者使用类.class文件。
静态的同步函数使用的锁是 该函数所属字节码文件对象
可以用 getClass方法获取,也可以用当前 类名.class 表示。
多线程死锁问题:
产生场景:初学者喜欢每个地方都加入synchronized,于是synchronized中嵌套synchronized,容易产生死锁
产生原因:A线程拿到了锁2,现在需要拿锁1;B线程拿了锁1,现在需要拿锁2;A线程拿不到锁1就不会释放锁2;B线程拿不到锁2就不会释放锁1
Java内存模型
多线程有三大特性
原子性、可见性、有序性
什么是原子性
即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断。要么就都不执行。
有序性
程序执行的顺序按照代码的先后顺序执行