一、引入
我们直接看简单又亲切的的代码解释一下为什么要使用线程同步:
我们用多个线程同时执行这段代码:
num++;
我们未使用线程同步代码如下:
public class demo {
public static void main(String[] args) {
AddSum addSum = new AddSum(); //创建业务对象
for (int i = 0; i < 10000; i++) {
new Thread(()->{
addSum.add(); //执行num++;
}).start();
}
TimeUnit.SECONDS.sleep(2);//等待其他线程执行完毕输出打印num
System.out.println(addSum.getNum());
}
}
class AddSum{ //创建所需要的业务类。
private int num = 0;
public int getNum() {//为了方便之后在主线程中打印num的值
return num;
}
public void add(){
num++;
}
}
执行结果: 9968(结果不唯一,小于10000)
-
为什么会产生这样的结果呢?
因为num++并不具有**原子性!**我们查看JVM执行指令会发现!
0: iconst_0
1: istore_1 #num++在执行时候会先保存num的值
2: iinc 1, 1 #然后再执行加一操作
5: return
所以,多个线程同时对num进行+1操作时,可能同时保存了一个相同的num,再执行+1操作对原数据进行覆盖!所产生的的问题!
所以得:
多个线程同时操作同一个共享变量时候时(增删改等),将会导致数据覆盖导致不准确,相互之间产生冲突。为了解决这一冲突,我们就有了线程同步这一说法。
二、线程同步
**并发:**多个线程访问同一个对象。
**形成条件:**队列+锁
我们会用以下的线程同步方法解决上述问题!
1、同步方法:
public synchronized void method(int args){}
- synchronized方法控制对象访问,每个对象对应一把锁
- 每个synchronized方法必须调用改方法对象的锁才能执行,否则线程阻塞
- 方法一旦执行,就独占该锁,直到方法结束才返回锁
解决引言案例:
public void add(){ //原代码行
num++;
}
public synchronized void add(){ //加上synchronized关键字,问题解决
num++;
}
执行结果: 10000(结果唯一)
2、同步块:
synchronized(Obj){}
- 同步代码块锁的是对象,对某个对象的增删改查,只用锁对象
- Obj称之为同步监视器,可以是任何对象
- 第一个线程访问,锁定同步监视器,执行其中的代码
- 第二个线程访问,发现同步监视器被锁定,无法访问(相当于钥匙被拿走)
- 第一个线程访问完毕,解除同步监视器(相当于还钥匙)
- 第三个线程访问,发现没有锁,可以访问
解决引言案例:(仅修改了业务类代码)
class AddSum{ //创建所需要的业务类。
private int num = 0;
public int getNum() {//为了方便之后在主线程中打印num的值
return num;
}
public void add(){
synchronized (this){ //对this增加监听器!
num++;
}
}
}
补充
死锁
- 多个线程各自占有一些共享资源,并且互相等待对方资源。导致双方都在等待对方资源。(多个线程互相抱着对方需要的资源,然后形成僵持。)
if(choice == 0){
synchronized (lipStick){
System.out.println(this.girlName+"获得口红");
Thread.sleep(1000);
synchronized (mirror){
System.out.println(this.girlName+"获得镜子");
}
}
}else{
synchronized (mirror){
System.out.println(this.girlName+"获得镜子");
Thread.sleep(1000);
synchronized (lipStick){
System.out.println(this.girlName+"获得口红");
}
}
}
3.Lock(锁)
——JDK1.5之后有了Lock锁
Lock锁会在之后的Java多线程高级,JUC并发编程中详细说到,这里只说用法!
定义Lock锁:
private final ReentrantLock lock = new ReentrantLock();
在执行程序中加锁解锁:
public void run() {
while(true){
try {
lock.lock();//加锁
try {
}
finally {
lock.unlock();//解锁
}
}
}
解决引言案例:(仅修改了业务类代码)
class AddSum{ //创建所需要的业务类。
private int num = 0;
private final ReentrantLock lock = new ReentrantLock(); //创建锁对象
public int getNum() {//为了方便之后在主线程中打印num的值
return num;
}
public void add(){
lock.lock(); //加锁
try {
num++;
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); //解锁
}
}
}
三、synchronized和Lock区别
-
synchronized是内置的java关键字,Lock是一个java类
-
synchronized无法判断锁的状态,Lock可以判断是否获得到了锁
-
synchronized会自动释放锁,lock必须手动释放锁,如果不释放则死锁
-
synchronized一个线程如果获得锁阻塞,则另外的线程傻傻等待,则lock不一定
-
synchronized可重入锁,不可以中断的,非公平的;Lock可重入锁,可以判断锁,非公平(可以自己设置)
-
synchronized适合锁的少量代码,lock适合大量锁的代码