Java多线程初级(3):线程同步

一、引入

我们直接看简单又亲切的的代码解释一下为什么要使用线程同步:

我们用多个线程同时执行这段代码:

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适合大量锁的代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值