能够保证在同一时刻最多有一个线程执行该段代码,以保证并发的安全性。当第一个线程去执行该段代码的时候就拿到锁, 并独占这把锁,当方法执行结束或者一定条件后它才释放这把锁,在没释放锁之前,所有的线程处于等待状态。
不控制高并发的后果:定义一个全部变量count,开启一千个线程对count进行相加,为了等所有线程都执行完后再输出count,这里使用了 CountDownLatch来完成,CountDownLatch我们后续会细说,CountDownLatch countDownLatch = new CountDownLatch(1000);
传入的构造函数为1000,每次执行countDownLatch.countDown();
计数器都会-1,直到计数器为0,才会执行countDownLatch.await();
后的代码,countDownLatch.await();
就起到阻塞的作用。
public class Test {
private static Integer count = 0;
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10000);
Test test = new Test();
for (int i = 0; i < 10000; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
test.add();
countDownLatch.countDown();
}
});
thread.start();
}
countDownLatch.await();
System.out.println(count);
}
public void add() {
count++;
}
}
预期值本来是1000,但是没有控制并发,所以结果为986
count++,看上去只是一个操作,实际上包含了三个动作
1,从内存中读取count
2,将count加1
3,将count的值写入到内存中
出现结果与预期不一样的原因是当执行完第一个线程的时候,可能第二个线程就进入执行,就被打断了,结果还没有写入到内存中, (如当count为5,第一个线程进入方法,将count+1 ,结果count=6,但是还没有写入内存,第二个线程就进去,它读到的值不是6,而是5, 因为第一个线程还没有将count写入内存),这样是线程不安全的,下面我们使用synchronized来完成线程的同步,保证操作的原子性和可见性。使用synchronized有三种方式
1.代码块锁:将关键字包裹方法体
public void add() {
synchronized (this) {
count++;
}
}
2.普通方法锁:synchronized加在方法上
public synchronized void add() {
count++;
}
3.类锁:静态方法锁 静态方法可以直接被类使用,所以实际上对静态方法的同步就是对类的同步,不过值得注意的是,使用类锁, 类锁只能在同一时刻被一个对象拥有,而方法锁可以被多个对象拥有,他们之间互不影响,可以各自运行, 也可以同时运行,而类锁只能是一个对象单独运行,类锁有两种写法,一种是直接将synchronized关键字加在静态 方法上面,另一种则是使用类.class形式
1.静态方法形式
public static synchronized void add() {
count++;
}
2.类.class形式
public void add() {
synchronized (Test.class){
count++;
}
}
被synchronized 关键字修饰的方法、代码块,就是 monitor机制的临界区,进入锁和释放锁是基于monitor来实现的 同步方法和同步代码块, monitor有两个指令,monitorenter会插入到同步代码块的位置, monitorexit会插入到方法块结束和退出的时候, 可能有多个monitorexit对应一个monitorenter,因为退出可以是方法结束或者抛出异常
javac
命令将java文件编辑为.class文件javac SynchronizedTest.java
javap
命令反编译.class文件javap -verbose SynchronizedTest.class
实际上一个对象和一个monitor相关联,一个monitorde lock锁只能被一个线程在同一时间获得 monditorenter的三种情况
1.当monitor计数器为0,代表没有被获得,然后线程立马获得该monitor,并把计数器加1,当别的线程想进来,但是看到计数器为1,就代表已经被其他线程占有,就只有等待
2.如果monitor已经拿到了锁的所有权,又重入了锁,则monitor会累加
3.如果monitor被其他线程占用了,当我想去获取的时候,不能获取到,只能处于阻塞状态,当计数器为0才能再次尝试去获取锁
monditorexit的作用是释放锁,前提是要先拥有此锁,原理是将计数器减1,减1如果monitor为0,则对象不再拥有对锁的所有权,就是解锁,如果不是1,则证明是重入进来的,继续持有锁,