## **线程安全**
多个线程同时共享一个全局变量或静态变量时,在进行写入操作时可能会引发的数据冲突。
比如多窗口卖票,结果令人吃惊
~~~
public class ThreadDemo implements Runnable {
private int count= 10;
@Override
public void run() {
while (count> 0) {
if (count> 0) {
...
Thread.sleep(100);
...
System.out.println(Thread.currentThread().getName() + "卖了第" + (10 - count+ 1) + "张票");
count--;
}
}
}
public static void main(String[] args) {
ThreadDemo t = new ThreadDemo();
Thread t1 = new Thread(t, "1号窗口");
Thread t2 = new Thread(t, "2号窗口");
t1.start();
t2.start();
}
}
~~~
## **内置锁(synchronized)**
解决线程安全问题可以使用synchronized内置锁
**方式一**:同步代码块
```
public class ThreadDemo implements Runnable {
private int count = 10;
private static Object obj = new Object();// 锁的对象,可以是任意的对象
@Override
public void run() {
while (count > 0) {
synchronized (obj) {
if (count > 0) {
...
Thread.sleep(1000);
...
System.out.println(Thread.currentThread().getName() + "卖了第" + (10 - count + 1) + "张票");
count--;
}
}
}
}
public static void main(String[] args) {
ThreadDemo t = new ThreadDemo();
Thread t1 = new Thread(t, "1号窗口");
Thread t2 = new Thread(t, "2号窗口");
t1.start();
t2.start();
}
}
```
**方式二**:同步函数
```
public class ThreadDemo implements Runnable {
private int count = 50;
@Override
public void run() {
while (count > 0) {
sale();
}
}
public synchronized void sale() {
if (count > 0) {
...
Thread.sleep(1000);
...
System.out.println(Thread.currentThread().getName() + "卖了第" + (50 - count + 1) + "张票");
count--;
}
}
public static void main(String[] args) {
ThreadDemo t = new ThreadDemo();
Thread t1 = new Thread(t, "1号窗口");
Thread t2 = new Thread(t, "2号窗口");
t1.start();
t2.start();
}
}
```
* 方式一锁的对象是obj,也可以是其它任意的对象
* 方式二锁的对象是当前类的字节码
* 如果将方式一中锁的对象换成this,那么他就和方式二中的锁是同一对象
## **多线程的三大特性**
* 原子性
一个操作的执行过程不会被其它因数打断,要么就不执行。原子性保证了线程数据一致性。
* 可见性
当多个线程访问同一个变量时,一个线程修改了变量的值,其他的线程能立即看到。
jmm模型中,共享变量存在于**主内存**中,**本地私有内存**存放的是共享变量的副本,线程执行过程是先写入本地内存, 再将本地内存中值刷新到主内存中,此时主内存会通知其它线程数据发生了改变,其它线程本地私有缓存就会失效。
* 有序性
程序执行的顺序按照代码的先后顺序执行,一般情况下,处理器由于要提高执行效率,对代码进行重排序,运行的顺序可能和代码先后顺序不同,但是结果一样。单线程下不会出现问题,多线程就会出现问题了。
```
int a = 2 //语句1
int r =4 //语句2
a = a + 1 //语句3
r = a*a //语句4
则因为重排序,他还可能执行顺序为 2-1-3-4,1-3-2-4
但绝不可能 2-1-4-3,因为这打破了依赖关系。
```
## **volatile**
* 保障多线程可见性
volatile修饰的变量可使线程本地缓存中的值及时刷新到主内存中去,从而保障可见性
```
private volatile int count = 50;
```
volatile虽然能保证线程可见性,但是无法保证线程的原子性。
```
自增共享变量i++可拆分以下3步
1.从主内存取值;
2.执行+1;
3.值重新写回主内存
```
假设A线程执行完步骤2,在多线程的情况下可能让出时间片,其它线程将i的值赋成5写入到主内存中去,线程A再继续执行将新的值写如主内存,此时就会出现线程安全问题。
* 禁止指令重排序优化
volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置)