前序
无论是互联网系统还是企业系统,在追求稳定计算的同时也在追求系统更高的吞吐量,这也对我们开发人员提出了更高的要求,如何开发高效率的程序是每个程序员必须掌握的技能,并发或者并行的的程序并不意味着满足越多的Thread,线程的多少对系统的性能来说是一个抛物线,同时多线程的引入也带来了共享数据的安全隐患!
数据同步
接下来我们用代码说明数据不一致的问题:
public class WinRunnable implements Runnable {
private int index = 1;
private final static int MAX = 500;
@Override
public void run() {
while (index <= MAX) {
System.out.println(Thread.currentThread()+"的号码是:"+(index++));
}
}
public static void main(String[] args) {
final WinRunnable wr = new WinRunnable();
Thread wr1 = new Thread(wr,"1号窗口");
Thread wr2 = new Thread(wr,"2号窗口");
Thread wr3 = new Thread(wr,"3号窗口");
Thread wr4 = new Thread(wr,"4号窗口");
wr1.start();
wr2.start();
wr3.start();
wr4.start();
}
}
运行以上代码我们发现
1. 某个号码出现多次
2. 号码有时候超过了最大值500
问题分析:
线程的执行是由CPU时间片轮询调度
号码出现多次 线程1在执行index = 88,做了++操作,但是没有写操作,此时CPU中断了线程1的执行权,线程2看到的依然是88此时往后操作线程1和2都输出了89
号码超多最大值 同样在while判断时,线程1进入后,操作了++没写操作,此时线程2看到的依然是499,所以是成立的,当线程1操作完之后已经是500了,线程2继续执行从500++就成了501
synchronized关键字
synchronized提供了一种排他机制,也就是同一时间只能一个线程执行某些操作.
synchronized可以实现一个简单的策略来防止线程干扰和内存一致性错误,如果一个对象对多个线程是可见的,那么对该对象的所有读和写都将通过同步的方式进行,具体表现:
synchronized关键字提供了一种锁的机制(每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权),能够确保变量间的互斥访问,从而防止数据不一致问题出现
synchronized关键字包括monitor enter 和 monitor exit两个JVM指令,它能确保任何线程在执行monitor enter之前都将从主内存中读取数据,而不是从缓存,在monitor exit之后,共享变量的之必须刷到主内存中
synchronized的指令严格遵守java happens-before规则,既monitor exit之前必定有一个monitor enter指令
synchronized用法
synchronized可以修饰代码块和方法,不能修饰class类和变量
public class Mutex{
private final static Object MUTEX = new Object();
public void accessResource(){
synchronized (MUTEX) {
try{
System.out.println(Thread.currentThread()+"睡眠");
TimeUnit.SECONDS.sleep(100);
} catch(InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
final Mutex mutex = new Mutex();
for(int i = 1;i <= 5;i++){
new Thread(){public void run(){
mutex.accessResource();
}}.start();
}
}
}
运行代码可以看到
只有线程0被执行,其余的全部进入阻塞状态
补充 monitor(对象监视器)
每个对象都与一个monitor相关联,一个monitor的lock锁只能被一个线程同一时间获取,在一个线程尝试获取锁时会有一下几件事情出路:
1.如果monitor计数器为0,就意味着该monitor的lock还没有被获取,某个线程获取之后会立即+1操作,从此该线程就是这个monitor的所有者
2.如果一个线程持有monitor所有权后重入,monitor计数器会继续累加
3.如果monitor已经被一个线程获得,其他线程尝试获取monitor所有权时,线程将进入阻塞状态,知道monitor计数器为0后才再次尝试获取
使用synchronized的注意事项
1.与monitor关联的对象不能为空!
2.synchronized作用域太大
同理人无完人,金无足赤啊,保证了数据安全的前提往往是浪费了一部分性能.在synchronized代码块中所有的线程都是串行执行的,一个线程在执行,其他线程就得阻塞,所以在写代码时一定要缩小synchronized域
3.不同的monitor企图锁同样方法
在同样的synchronized中只能存在一个monitor,不然没有意义
4.多个锁的交叉导致死锁
不要使用synchronized嵌套!