Java 并发编程 一 ( synchronized 、ReentrantLock )


 

目录

synchronized

1.线程八锁

情况1:12 或 21

情况2:1s 后12,或 2, 1s 后 1

情况3:3 1s 12 或 23 1s 1 或 32 1s 1

情况4:2, 1s 后 1

情况5:2, 1s 后 1

情况6:1s 后12, 或 2 1s后 1

情况7:2 ,1s 后 1

情况8:1s 后12, 或 2 1s后 1

2.模拟买票

3.转账问题

ReentrantLock

 1、Lock

 2、使用​编辑

小结


synchronized

synchronized 是 Java 中的关键字,是一种同步锁

它修饰的对象有以下几种:

  • 1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{} 括起来的代码,作用的对象是调用这个代码块的对象;
  • 2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用 的对象是调用这个方法的对象;
  • 3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的 所有对象;
  • 4. 修改一个类,其作用的范围是 synchronized 后面括号括起来的部分,作用主 的对象是这个类的所有对象

1.线程八锁

所谓的“线程八锁”,其实就是考察 synchronized 锁住的是哪个对象

情况1:12 或 21

锁住的为同一对象,2个线程都有可能执行

@Slf4j
class Number{
public synchronized void a() {
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
情况2:1s 后12,或 2, 1s 后 1

锁住的为同一对象,2个线程都有可能执行

@Slf4j
class Number{
public synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
情况3:3 1s 12 或 23 1s 1 或 32 1s 1

锁住的为同一对象,3个线程都有可能执行

注意了 c 没加锁

class Number{
public synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
public void c() {
log.debug("3");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
new Thread(()->{ n1.c(); }).start();
}
情况4:2, 1s 后 1

锁住的不为同一对象,不存在锁竞争,第二个线程先执行

@Slf4j
class Number{
public synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}
情况5:2, 1s 后 1

锁住的不为同一对象,不存在锁竞争,第二个线程先执行,第一个锁的是类(Number.class),第二个是对象(this)

@Slf4j
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
情况6:1s 后12, 或 2 1s后 1

锁住的为同一对象,2个线程都有可能执行

@Slf4j
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public static synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
情况7:2 ,1s 后 1

锁住的不为同一对象,不存在锁竞争,第二个线程先执行

@Slf4j
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}
情况8:1s 后12, 或 2 1s后 1

锁住的为同一对象,2个线程都有可能执行

class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public static synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}

 线程八锁的关键:

1、非静态方法的锁默认为this,静态方法的锁为对应的Class实例

2、在某一个时刻内,只能有一个线程持有锁,无论几个线程

2.模拟买票

出现线程安全问题的是在卖票的时候可能多个线程在卖票,同时取出了共享变量count,最后的count数量就是最后那个线程处理的值。而不是共同处理的值,因为他们的执行序列发生了错误,导致count没有等待处理完就被另外一个线程先读取进来了。

import java.util.List;
import java.util.Random;
import java.util.Vector;


@Slf4j
public class Ticket {
    private Integer count;

    public Ticket(Integer count) {
        this.count = count;
    }

    public Integer getCount() {
        return count;
    }

    public  Integer sell(Integer amount) {
        if (this.count >= amount) {
            this.count -= amount;
            return amount;
        } else {
            return 0;
        }
    }

    static Random random =new Random();
    public  static  Integer randomAmount(){
        return  random.nextInt(5) + 1;
    }

  
}
  public static void main(String[] args) {

//        模拟多人买票
        Ticket ticket = new Ticket(9000);
        List<Integer> list = new Vector<>();
        List<Thread> threadList = new Vector<>();
        for (int i = 0; i < 2000; i++) {
            Thread thread = new Thread(() -> {
                Integer sells = ticket.sell(randomAmount());
                list.add(sells);
            });
            threadList.add(thread);
            thread.start();
        }
        for (Thread thread : threadList) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        log.debug(" 余票{}",ticket.getCount());
        log.debug(" 卖出的票数{}",list.stream().mapToInt(i ->i).sum());
    }

加锁之后

import java.util.List;
import java.util.Random;
import java.util.Vector;


@Slf4j
public class Ticket {
    private Integer count;

    public Ticket(Integer count) {
        this.count = count;
    }

    public Integer getCount() {
        return count;
    }

    public  synchronize Integer sell(Integer amount) {
        if (this.count >= amount) {
            this.count -= amount;
            return amount;
        } else {
            return 0;
        }
    }

    static Random random =new Random();
    public  static  Integer randomAmount(){
        return  random.nextInt(5) + 1;
    }

  
}

 ——————————————————————————————————————————

3.转账问题

@Slf4j
public class ExerciseTransfer {
    public static void main(String[] args) throws InterruptedException {
        Account a = new Account(1000);
        Account b = new Account(1000);
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                a.transfer(b, randomAmount());
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                b.transfer(a, randomAmount());
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        // 查看转账2000次后的总金额
        log.debug("total:{}", (a.getMoney() + b.getMoney()));
    }

    // Random 为线程安全
    static Random random = new Random();

    // 随机 1~100
    public static int randomAmount() {
        return random.nextInt(100) + 1;
    }
}

// 账户
class Account {
    private int money;

    public Account(int money) {
        this.money = money;
    }

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }

    // 转账
    public void transfer(Account target, int amount) {
        synchronized(Account.class) {
            if (this.money >= amount) {
                this.setMoney(this.getMoney() - amount);
                target.setMoney(target.getMoney() + amount);
            }
        }
    }
}


这里其实会发生线程安全问题。主要就是在a转账的同时b也进去转账,那么b获取到的肯定就是没有转账的a,相对a也是。

那么直接给方法加上synchronize行不行?如果绑定的是本类对象,很明显是不行,因为是两个账户,锁是不同的,进入了不同房间。那么解决方案就是可以使用唯一的Account.class的本类,那么就能够锁住了


ReentrantLock

ReentrantLock 是唯一实现了 Lock 接口的类,并且 ReentrantLock 提供了更 多的方法

 1、Lock

Lock 锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允 许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对 象。Lock 提供了比 synchronized 更多的功能

对比synchronized 

  • Lock 不是 Java 语言内置的,synchronized 是 Java 语言的关键字,因此是内置特性。Lock 是一个类,通过这个类可以实现同步访问;
  • Lock 和 synchronized 有一点非常大的不同,采用 synchronized 不需要用户去手动释放锁,当 synchronized 方法或者 synchronized 代码块执行完之后,系统会自动让线程释放对锁的占用;而 Lock 则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象

 

 2、使用

lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他 线程获取,则进行等待。 采用 Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一 般来说,使用 Lock 必须在 try{}catch{}块中进行,并且将释放锁的操作放在 finally 块中进行,以保证锁一定被被释放,防止死锁的发生

Lock lock = ...;
lock.lock();
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}

小结

Lock 和 synchronized 有以下几点不同:

1. Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现;
2. synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;
3. Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用synchronized 时,等待的线程会一直等待下去,不能够响应中断;
4. 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
5. Lock 可以提高多个线程进行读操作的效率。

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于synchronized

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值