1.多线程临界资源问题
最简单常见的卖票例子来一波
public class TicketTest implements Runnable{
private int count=10;
public void run() {
while (count > 0) {
System.out.println(Thread.currentThread().getName()+"成功售票,票号为" + count--);
}
}
@Test
public void testSell1(){
TicketTest run = new TicketTest();
Thread thread1 = new Thread(run,"the first");
Thread thread2 = new Thread(run,"the second");
Thread thread3 = new Thread(run,"the third");
thread1.start();
thread2.start();
thread3.start();
}
}
刷了好几次终于看见了想要的错误
错误产生的原因就是因为count–并不是一个原子操作。每次自增实际上是分为3个步骤:
1.获取count变量的当前值
2.将当前值加1
3.将加1后的值存储到count变量中
当前线程刚获取到当前值还没有进行-1的操作,或者-1了还没有重新复制给count变量,下一个线程就又获取到了count,那么这两个线程就会卖出同一张票。
这里解释一下i++与++i
i++是先取了i的值然后才对i进行操作
++i是先对i进行了+1然后才取出i值
2.synchronized 关键字
synchronized关键字代表这个加锁,当线程运行到它修饰的方法或者代码块的时候都要检查有没有其它线程正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程运行完这个方法后再运行此线程。
//在方法上
public synchronized void add1() {
count++;
}
public void add2() {
//在代码块上
synchronized (this){
count++;
}
}
上面实现的是对象锁,但是如果我们实例化了不同的对象然后对他们进行操作,这个锁是不会生效的。在有多个实例的情况下,我们可以利用static关键字实现类锁。
//在方法上
public static synchronized void add1() {
count++;
}
public static void add2() {
//在代码块上
synchronized (this){
count++;
}
}
还有一种锁是私有锁
private Object aa=new Object();
//在方法上
public void add1() {
synchronized (aa){
count++;
}
}
类锁,对象锁,私有锁:
类锁和对象锁不会产生竞争,二者的加锁方法不会相互影响。
私有锁和对象锁也不会产生竞争,二者的加锁方法不会相互影响。
JDK1.6为了减少获得锁和释放锁的性能消耗引入了偏向锁和轻量级锁,所以使用synchronized也没有那么重量级了
3.原子性、可见性与有序性
Java内存模型是围绕着并发过程中如何处理原子性、可见性、有序性这三个特征来建立的。
3.1.原子性(Atomicity)
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作。只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作。
注:
Java 基础类型中,long 和 double 是 64 位长的。32 位架构 CPU 的算术逻辑单元(ALU)宽度是 32 位的,在处理大于 32 位操作时需要处理两次。
3.2.可见性(Visibility)
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
在Java中,synchronized关键字、Lock对象和volatile关键字都可以实现共享变量的可见性。
3.3.有序性(Ordering)
如果同时存在多个线程,在没有采取同步措施的情况下,我们是无法保证线程的先后执行顺序的,线程可以并行的执行。
在Java中,synchronized关键字、Lock对象以及volatile关键字都可以保证多个线程执行时的有序性。
总结
synchronized关键字、Lock对象同时支持原子性、有序性和可见性
volatile关键字只支持可见性和有序性,并不保证原子性。
4.volatile
4.1.Java内存模型
要了解volatile先要了解Java内存模型
Java内存模型规定了所有的变量都存储在主内存中。每条线程中还有自己的工作内存,线程的工作内存中保存了被该线程所使用到的变量(这些变量是从主内存中拷贝而来)。线程对变量的所有操作(读取,赋值)都必须在工作内存中进行。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。
volatile与synchronized 最经典的案例大概就是单例模式的双重校验了。
4.2.Java内存结构
说到Java内存模型就顺道瞄一眼java内存结构