JUC一-synchronized和LOCK锁

JUC

JUC指的是:Java里的一个包
java.util.concurrent 并发
java.util.concurrent.atomic:原子性
java.util.concurrent.locks:lock锁

image-20220925163032504

业务:普通的线程代码Thread

Runnable没有返回值、效率相比Callable 较低!

所以企业中一般更多使用Callable 去执行一些任务

1.概述

进程和线程

进程是操作系统分配资源的最小单元,而线程是cpu调度的最小单元。

程序执行的一次过程,一个进程包含一个或多个线程。进程是资源分配的单位。

可以指程序执行过程中,负责实现某个功能的单位。线程是CPU调度和执行的单位。

一个进程至少包含一个线程

java默认有两个线程:主线程和gc守护线程

java 真的可以开启线程吗?

不能!!!

在Thread.start中调用了 本地方法,底层的C++。Java无法直接操作硬件。

从源码来看, 一个线程如果已经调用过 start 方法,那么再次调用 start 方法的时候会抛出 IllegalThreadStateException 异常, 涉及的技术点为线程的状态切换。

public synchronized void start() {
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

private native void start0();

并发和并行

并发:同一时刻,多个线程交替执行。(一个CPU交替执行线程)

并行:同一时刻,多个线程同时执行。(多个CPU同时执行多个线程)

public class TestProcessors {
    public static void main(String[] args) {
        //获取cpu核数
        //cpu密集型 io密集型
        System.out.println(Runtime.getRuntime().availableProcessors());
    }
}
多核 CPU 内部集成了多个计算核心(Core),每个核心相当于一个简单的 CPU,多核 CPU 的每个核心都可以独立地执行一个任务,而且多个核心之间不会相互干扰。在不同核心上执行的多个任务,是真正地同时运行,这种状态就叫做并行。
    多核心技术是将多个一样的CPU放置于一个封装内(或直接将两个CPU做成一个芯片),而英特尔的HT技术(超线程技术)是在CPU内部仅复制必要的资源、让一个核模拟成两个线程;也就是一个实体核心,两个逻辑线程,在一单位时间内处理两个线程的工作,模拟实体双核心、双线程运作。
    多核CPU,可以并行执行多进程、多线程

所以说 并发编程可以充分利用cpu的资源

wait/sleep的区别

Thread.sleep(long millis),线程休眠。一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。
obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout) timeout时间到自动唤醒。

1、来自不同的类

wait => Object

sleep => Thread

一般情况企业中使用休眠是:

import java.util.concurrent.TimeUnit; 
TimeUnit.DAYS.sleep(1); //休眠1天
TimeUnit.SECONDS.sleep(1); //休眠1s

2、关于锁的释放

wait:会释放锁;

sleep:睡觉了,不会释放锁;

3、使用的范围是不同的

wait 必须在同步代码块(synchronized)中;如果不放在同步代码块或方法中,运行时会报错

sleep 可以在任何地方睡;

Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。

5、是否需要捕获异常

wait不需要捕获异常;(目前也需要了)

sleep必须要捕获异常;

2.传统的synchronized

在开发中,我们在javaThread中的new Thread() 放一个rannable 已经不用了。

我们应该把线程看作一个单独的资源类,没有任何的附属操作!

// 资源类 OOP 属性、方法
class Ticket {
    //属性
  private int number = 30;

  //卖票的方式
  public synchronized void sale() {
    if (number > 0) {
      System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "张票剩余" + number + "张票");
    }
  } 
}
 public static void main(String[] args) {
     //并发 多线程操作同一个资源类
    final Ticket ticket = new Ticket();
	//把资源类丢入线程
    new Thread(()->{
      for (int i = 0; i < 40; i++) {
        ticket.sale();
      }
    },"A").start();
    new Thread(()->{
      for (int i = 0; i < 40; i++) {
        ticket.sale();
      }
    },"B").start();
    new Thread(()->{
      for (int i = 0; i < 40; i++) {
        ticket.sale();
      }
    },"C").start();
  }
}

synchronized 方法控制 “对象” 的访问,每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放,后面被阻塞的线程才能获得这个锁,继续执行。

synchronized的方法加上static变成静态方法 那么锁的就是这个类

缺陷:若将一个大的方法申明为 synchronized 将会影响效率。

3.LOCK锁

image-20220926084240585

image-20220926082243785

new ReentrantLock

无参构造是非公平锁,传入参数,如果是true 是公平锁。

源码:


    /**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

公平锁是指多个线程按照申请锁的顺序来获取锁
优点:等待锁的线程不会饿死。
缺点:整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。
作用:严格按照线程启动的顺序来执行的,不允许其他线程插队执行

非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。非公平锁是按照cup调度的
优点:可以减少唤起线程的开销,吞吐量比公平锁大。,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。
缺点:处于等待队列中的线程可能会饿死,或者等很久才会获得锁。
作用:线程启动允许插队的。

默认是非公平锁,是为了公平,比如一个线程要3s,另一个线程要3h,难道一定要让3h的锁先来就先执行吗

对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。

使用方法:

  • new
  • 加锁
  • 业务代码
  • 解锁
{
    private int number = 30;


    Lock lock = new ReentrantLock();


    //卖票的方式
    public void sale() {
        //加锁
        lock.lock();
        try {
            //业务代码
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "张票剩余" + number + "张票");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            //解锁
            lock.unlock();
        }

    }
}

Synchronized 与Lock 的区别

手动挡和自动挡的区别

1、Synchronized 内置的Java关键字,Lock是一个Java类

2、Synchronized 无法判断获取锁的状态,Lock可以判断

3、Synchronized 会自动释放锁,lock必须要手动加锁和手动释放锁!可能会遇到死锁

4、Synchronized 线程1(获得锁->阻塞)、线程2(等待);lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,不会造成长久的等待。

5、Synchronized 是可重入锁,不可以中断的,非公平的;Lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁;

6、Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码;

什么是可重入锁

就是一个线程不用释放,可以重复的获取一个锁n次,只是在释放的时候,也需要相应的释放n次。(简单来说:A线程在某上下文中或得了某锁,当A线程想要在次获取该锁时,不会应为锁已经被自己占用,而需要先等到锁的释放)假使A线程即获得了锁,又在等待锁的释放,就会造成死锁。
指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。
synchronized 和 ReentrantLock 都是可重入锁。
案例:Synchronzied 版本生产者和消费者的问题
/*
线程之间的通信问题:生产者和消费者问题
线程交替执行 A B操作同一个变量 num=0 A让num+1,B让num-1;
 **/
public class ConsumeAndProduct {
  public static void main(String[] args) {
    Data data = new Data();

    new Thread(() -> {
      for (int i = 0; i < 10; i++) {
        try {
          data.increment();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }, "A").start();
    new Thread(() -> {
      for (int i = 0; i < 10; i++) {
        try {
          data.decrement();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }, "B").start();
  }
}

class Data {
  private int num = 0;

  // +1
  public synchronized void increment() throws InterruptedException {
    // 判断等待
    if (num != 0) {//while
      this.wait();
    }
    num++;
    System.out.println(Thread.currentThread().getName() + "=>" + num);
    // 通知其他线程 +1 执行完毕
    this.notifyAll();
  }

  // -1
  public synchronized void decrement() throws InterruptedException {
    // 判断等待
    if (num == 0) { //while
      this.wait();
    }
    num--;
    System.out.println(Thread.currentThread().getName() + "=>" + num);
    // 通知其他线程 -1 执行完毕
    this.notifyAll();
  }
}


存在的问题(虚假唤醒)

如果上面的代码是4个线程,if判断时 可能进去2个,其他线程通知时,两个又同时唤醒,可能原本是0,+1后 通知了两-1,最后变成了负一。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CoNSrrWe-1664277114135)(…/…/tools/Typora/upload/image-20220926092234091.png)]

public synchronized void increment() throws InterruptedException {
  // 判断等待
  while (num != 0) {//while
    this.wait();

  }
  num++;
  System.out.println(Thread.currentThread().getName() + "=>" + num);
  // 通知其他线程 +1 执行完毕
  this.notifyAll();
}

// -1
public synchronized void decrement() throws InterruptedException {
  // 判断等待
  while (num == 0) { //while
    this.wait();
    //把if改成while后 被唤醒不会马上去num--; 会再进行一次判断,防止了一下子唤醒了多个 多个线程的-1或者+1
  }
  num--;
  System.out.println(Thread.currentThread().getName() + "=>" + num);
  // 通知其他线程 -1 执行完毕
  this.notifyAll();
}
案例:LOCK版本生产者和消费者的问题

image-20220926094334561

public class LockCAP {
  public static void main(String[] args) {
    Data2 data = new Data2();

    new Thread(() -> {
      for (int i = 0; i < 10; i++) {

        try {
          data.increment();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }

      }
    }, "A").start();
    new Thread(() -> {
      for (int i = 0; i < 10; i++) {
        try {
          data.decrement();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }, "B").start();
    new Thread(() -> {
      for (int i = 0; i < 10; i++) {
        try {
          data.increment();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }, "C").start();
    new Thread(() -> {
      for (int i = 0; i < 10; i++) {
        try {
          data.decrement();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }, "D").start();
  }
}

class Data2 {
  private int num = 0;
  Lock lock = new ReentrantLock();
  Condition condition = lock.newCondition();
  // +1
  public  void increment() throws InterruptedException {
    lock.lock();
    try {
      // 判断等待
      while (num != 0) {
        condition.await();
      }
      num++;
      System.out.println(Thread.currentThread().getName() + "=>" + num);
      // 通知其他线程 +1 执行完毕
      condition.signalAll();
    }finally {
      lock.unlock();
    }

  }

  // -1
  public  void decrement() throws InterruptedException {
    lock.lock();
    try {
      // 判断等待
      while (num == 0) {
        condition.await();
      }
      num--;
      System.out.println(Thread.currentThread().getName() + "=>" + num);
      // 通知其他线程 +1 执行完毕
      condition.signalAll();
    }finally {
      lock.unlock();
    }

  }
}

精准的通知和唤醒的线程

任何一个新的技术,绝对不是仅仅只是覆盖了原来的技术,优势和补充

这个例子不太好,这个是通过状态控制的,不知道为啥要用三个condition 一个也是可以的,因为这里while条件已经限制了顺序

public class C {
  public static void main(String[] args) {
    Data3 data3 = new Data3();

    new Thread(()  -> {
      for (int i = 0; i < 10; i++) {
        data3.printA();
      }
    },"A").start();
    new Thread(() -> {
      for (int i = 0; i < 10; i++) {
        data3.printB();
      }
    },"B").start();
    new Thread(() -> {
      for (int i = 0; i < 10; i++) {
        data3.printC();
      }
    },"C").start();
  }

}
class Data3 {
  private Lock lock = new ReentrantLock();
  private Condition condition1 = lock.newCondition();
  private Condition condition2 = lock.newCondition();
  private Condition condition3 = lock.newCondition();
  private int num = 1; // 1A 2B 3C

  public void printA() {
    lock.lock();
    try {
      // 业务代码 判断 -> 执行 -> 通知
      while (num != 1) {
        condition1.await();
      }
      System.out.println(Thread.currentThread().getName() + "==> AAAA" );
      num = 2;
      condition2.signal();
    }catch (Exception e) {
      e.printStackTrace();
    }finally {
      lock.unlock();
    }
  }
  public void printB() {
    lock.lock();
    try {
      // 业务代码 判断 -> 执行 -> 通知
      while (num != 2) {
        condition2.await();
      }
      System.out.println(Thread.currentThread().getName() + "==> BBBB" );
      num = 3;
      condition3.signal();
    }catch (Exception e) {
      e.printStackTrace();
    }finally {
      lock.unlock();
    }
  }
  public void printC() {
    lock.lock();
    try {
      // 业务代码 判断 -> 执行 -> 通知
      while (num != 3) {
        condition3.await();
      }
      System.out.println(Thread.currentThread().getName() + "==> CCCC" );
      num = 1;
      condition1.signal();
    }catch (Exception e) {
      e.printStackTrace();
    }finally {
      lock.unlock();
    }
  }
}
/*
A==> AAAA
B==> BBBB
C==> CCCC
A==> AAAA
B==> BBBB
C==> CCCC
...
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值