高并发学习(二) —— JUC

Synchronized

并发编程的目的是为了提高程序的执行效率,提高程序的运行速度。但是并发编程功能还存在着一个致命问题:不同线程访问共享变量的同步问题。

Java的Synchronized关键字用于解决多个 线程之间访问资源的同步性。它保证任意时刻只能有一个线程访问synchronized修饰的方法或代码块。

Java官方从Java6开始在JVM层面对synchronized进行了优化。对锁的实现引入大量的优化:自旋锁、适应性自旋锁、锁消除、锁粗化、轻量级锁等技术来减少锁操作的开销。

synchronized的三种使用方式

  1. synchronized修饰实例方法。给对象实例加锁,进入同步代码前必须获得 当前对象实例的锁。
  2. synchronized修饰静态方法。 给当前类加锁,进入同步代码前要获得当前class的锁。
  3. synchronized修饰代码块。可以 指定加锁的对象(可以是对象也可以 是类)。

synchronized 修饰成员方法

package LockRange;

/**

synchronized是对象锁,锁的是实例。因此如果使用了synchronized,那么其他成员方法也不能同时调用

 */
public class Test1 {

    public static void main(String[] args) throws InterruptedException {
        Person person = new Person();
        new Thread(()->{ person.eat();}).start();
        Thread.sleep(1000);
        new Thread(()->{ person.say();}).start();
    }
}

class Person{
    public synchronized void say(){
        System.out.println("say something");
    }
    public synchronized void eat() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("eating");
    }
}

执行结果:

eating
say something

synchronized是对象锁。对于同一个实例(对象),对应唯一一把锁,每次只能被一个成员方法持有。其他未持有该锁的成员方法必须等待。
同时也要注意,synchronized的位置。如果是锁代码块,锁的是不同对象的话,那么是不同的两把锁,之间是 没有关系的。

synchronized 修饰静态方法、代码块

package LockRange;

public class TestStatic {

    public static void main(String[] args) throws InterruptedException {
        Person2 person = new Person2();
        new Thread(()->{ person.eat();}).start();
        Thread.sleep(1000);
        new Thread(()->{ person.say();}).start();
    }
}

class Person2{
    public static void say(){
    	//修饰静态方法
        synchronized(Person2.class){
            System.out.println("say something");
        }
    }
    //修饰代码块
    public  void eat() {
        synchronized(this){
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("eating");
        }

    }
}

执行结果:

say something
eating

say静态方法锁的是类,而eat方法锁得是实例,是类对象,两者是不同的。所以互不干扰。

synchronized关键字底层实现原理

synchronized同步代码块的实现使用的是:JVM的monitorenter和monitorexit指令,其中monitorenter指令指向同步代码开始的位置,monitorexit指令指向同步代码结束的位置。

在执行monitorenter指令时,线程视图获取锁也就是 对象监视器 monitor的持有权。如果锁的计数器为0则表示锁可以被获取,获取以后将计数器设为1也就是加1。
在执行monitorexit指令后,将锁计数器设为0,表明锁 被释放。如果获取对象锁失败,那当前线程就要阻塞等待,知道锁被另外一个线程释放才能再次尝试获取锁。

synchronized修饰的方法使用的ACC_SYNCHRONIZED 表示,该标识表明该 方法是一个同步方法,从而执行相应的同步调用。

售票例子

package synchronizedDemo;

class Ticket {
    private int tickets = 30;
    public  void sale(){
        synchronized(this){
            if( tickets > 0 ) {
                tickets--;
                System.out.println(Thread.currentThread().getName()+"卖出了1张票,剩余:"+tickets);
            }
        }


    }
}

public class SaleTicket{
    public static void main(String[] args) {
            Ticket ticket = new Ticket();
            new Thread(()->{ for(int i = 0; i < 30; i ++) ticket.sale(); }, "B").start();
            new Thread(()->{ for(int i = 0; i < 30; i ++) ticket.sale(); }, "A").start();
            new Thread(()->{ for(int i = 0; i < 30; i ++) ticket.sale(); }, "C").start();
    }
}

执行结果: A、B、C可能交替买票,是顺序打印的,因为synchronized的粒度比较粗,会打印完才释放掉锁,直至票数为0

B卖出了1张票,剩余:29
B卖出了1张票,剩余:28
B卖出了1张票,剩余:27
B卖出了1张票,剩余:26
B卖出了1张票,剩余:25
B卖出了1张票,剩余:24
B卖出了1张票,剩余:23
B卖出了1张票,剩余:22
B卖出了1张票,剩余:21
B卖出了1张票,剩余:20
B卖出了1张票,剩余:19
B卖出了1张票,剩余:18
B卖出了1张票,剩余:17
B卖出了1张票,剩余:16
B卖出了1张票,剩余:15
B卖出了1张票,剩余:14
B卖出了1张票,剩余:13
B卖出了1张票,剩余:12
B卖出了1张票,剩余:11
B卖出了1张票,剩余:10
B卖出了1张票,剩余:9
B卖出了1张票,剩余:8
B卖出了1张票,剩余:7
B卖出了1张票,剩余:6
B卖出了1张票,剩余:5
B卖出了1张票,剩余:4
B卖出了1张票,剩余:3
B卖出了1张票,剩余:2
B卖出了1张票,剩余:1
B卖出了1张票,剩余:0

Process finished with exit code 0

Java Guide 多线程

JUC

在这里插入图片描述

java.util.concurrent 并发包
java.util.concurrent.atomic 并发原子包
java.util.concurrent.locks 并发锁包,该包下有Lock、ReadWriteLock、Condition。
Condition将Object的监视方法(wait、notify、notifyAll)分解成不同的对象,将他们与Lock的任意实现相结合使用。

Lock 接口

【实现类】
Lock接口实现类有:
ReentrantLock(可重入锁)
ReentrantReadWriteLock.ReadLock(读锁)
ReentrantReadWriteLock.WriteLock(写锁)

【基本方法】

 Lock l = ...;
 l.lock();  	//1、获得锁
 
 // 2、 逻辑业务代码
 try {
   // access the resource protected by this lock
 } finally {
  //3、释放锁
   l.unlock();
 }

【常用方法】
在这里插入图片描述
ReentrantLock的构造方法
在这里插入图片描述

售票例子

package lockDemo;


import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Ticket2 {
    private int tickets = 30;
    public  void sale(){
        Lock lock = new ReentrantLock();
        lock.lock(); //1、获取锁
        //2、逻辑业务代码
        try{

            if( tickets > 0 ) {
                tickets--;
                System.out.println(Thread.currentThread().getName()+"卖出了1张票,剩余:"+tickets);
            }

        }finally {
            lock.unlock(); //3、 释放掉锁
        }


    }
}

public class SaleTicket2{
    public static void main(String[] args) {
            Ticket2 ticket = new Ticket2();
            new Thread(()->{ for(int i = 0; i < 30; i ++) ticket.sale(); }, "B").start();
            new Thread(()->{ for(int i = 0; i < 30; i ++) ticket.sale(); }, "A").start();
            new Thread(()->{ for(int i = 0; i < 30; i ++) ticket.sale(); }, "C").start();
    }
}

打印顺序是错乱的,但是票销售为 0时停止:

B卖出了1张票,剩余:29
C卖出了1张票,剩余:27
B卖出了1张票,剩余:26
C卖出了1张票,剩余:25
A卖出了1张票,剩余:28
C卖出了1张票,剩余:23
B卖出了1张票,剩余:24
C卖出了1张票,剩余:21
A卖出了1张票,剩余:22
C卖出了1张票,剩余:19
B卖出了1张票,剩余:20
C卖出了1张票,剩余:17
A卖出了1张票,剩余:18
C卖出了1张票,剩余:15
B卖出了1张票,剩余:16
C卖出了1张票,剩余:13
A卖出了1张票,剩余:14
C卖出了1张票,剩余:11
B卖出了1张票,剩余:12
C卖出了1张票,剩余:9
A卖出了1张票,剩余:10
C卖出了1张票,剩余:7
B卖出了1张票,剩余:8
C卖出了1张票,剩余:5
A卖出了1张票,剩余:6
C卖出了1张票,剩余:3
B卖出了1张票,剩余:4
C卖出了1张票,剩余:1
A卖出了1张票,剩余:2
B卖出了1张票,剩余:0

synchronize和Lock 的区别

  1. synchronized是Java中内置的的关键字,Lock是接口。
  2. synchronized不可以获得锁的状态,Lock可以通过tryLock判断锁的状态。
  3. synchronized会自动释放掉锁,而Lock需要手动在finally处释放,否则会发生死锁。
  4. synchronized如果获取不到锁会一直等待下去,而Lock可以中断等待。
  5. synchronized是可重入、不可中断的非公平锁,而Lock是可重入锁,可以通过构造方法来决定是公平锁还是非公平锁。
  6. synchronized适合锁少量的代码同步问题,Lock适合锁大量同步代码!

什么是可重入锁?
可重入锁是指自己可以以不同的方式再次访问临界资源而不出现死锁等相关问题。经典之处在于判断了需要使用锁的线程是否为加锁的线程。如果是,则拥有重入的能力。可重入锁在设计上不仅判断锁有没有被锁上,还会判断锁是谁锁上的,当就是自己锁上的时候,那么他依旧可以再次访问临界资源,并把加锁次数加1。只有等待锁计数器降为0是才能释放锁。

什么是公平锁?
所谓公平锁就是 就是遵循先来后到的原则。而非公平锁允许插队。Lock默认构造方法是非公平锁,原因是 为了提高CPU利用率,保证公平。比如有两个线程A、B先后到达,A执行时间是30min,B是2秒,那么CPU根据非公平锁的调度策略会 先执行B的。

线程之间的通信问题

单例模式、排序算法、生产者消费者问题

生产者 消费者问题

在这里插入图片描述

package ThreadCommunication;

public class Communication {
    public static void main(String[] args) {

        Buffer buffer = new Buffer();
        Producer p = new Producer(buffer);
        Consumer c = new Consumer(buffer);


        new Thread(p, "A").start();
        new Thread(c, "B").start();
        new Thread(p, "C").start();
        new Thread(c, "D").start();

    }

}
class Buffer {

    //资源
    private int num = 0;
    //信号量 true表示槽池中为空  false表示槽池不为空 初始状态为空
    private boolean empty = true;

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    public synchronized void consume() throws InterruptedException {

        //1、等待判断
        while ( empty){//只要槽池为空就阻塞等待
            this.wait();
        }
        //2、业务操作
        this.num = this.num - 1;
        System.out.println("线程"+Thread.currentThread().getName()+" - >"+this.getNum());
        //3、信号量置为1 同时通知其他线程
        this.empty = true;
        this.notifyAll();

    }

    //线程同步 记得要加同步锁
    public synchronized void produce() throws InterruptedException {

        //1、等待判断(注意这里是while不能是if)
        while ( !empty){//槽池满了就等待
            this.wait();
        }
        //2、业务操作
        this.num = this.num + 1;
        System.out.println("线程"+Thread.currentThread().getName()+" - >"+this.getNum());

        //3、信号量置为false 同时通知其他线程
        this.empty = false;
        this.notifyAll();
    }
}
class Producer implements Runnable{
    private Buffer buffer;
    public Producer(Buffer buffer){
        this.buffer = buffer;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
                buffer.produce();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

class Consumer implements Runnable{

    private Buffer buffer;
    public Consumer(Buffer buffer){
        this.buffer = buffer;
    }
    @Override
    public void run() {

        while( true ){
            try {
                Thread.sleep(1000);
                buffer.consume();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

注意 consume()和produce的判断等待方法中使用 的是while而不是if,因为if会有虚假唤醒问题。原因如下。

if改成while 循环监听。
假设有四个线程,A、C是Producer,B、D是Consumer。
当number是1时,C抢到了produce的锁。if语句判断后 ,进入wait状态,而wait会放弃掉锁,也就意味着当再次被唤醒时要重新跟其他线程抢锁,如果抢不到,该线程就会阻塞。假设C放弃的同时A抢到了produce的锁,由判断条件和A一起处于wait状态。此时方法外只有B、D了。假设B抢到锁进行消费操作之后number被置为0,同时notifyAll唤醒其他线程。由于系统调度会安排C、A(因为等待时间越长优先级越高)执行。此时C(A)还进行在if语句内,如果 这个时候没有将进行循环判断,则C会对number进行加1,之后的A线程也会进行加1使得number变成2;

在这里插入图片描述

线程如果被唤醒后再次执行时,并不是重新进入方法,而是从上次阻塞的位置从下开始运行,也就是从wait()方法后开始执行。所以判断是否进入某一线程的条件 是用while判断,而不是用If判断判断。

验证如下:

package ThreadCommunication;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class C {

    
    public static void main(String[] args) {
       Data3 d = new Data3();


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

    }
}

//资源类
class Data3{
    private int num = 1;
    Lock lock = new ReentrantLock();
    //1、获得监视器
    Condition conditionA = lock.newCondition();
    Condition conditionB = lock.newCondition();

    public void printB(){

        System.out.println("进入printB方法");
        lock.lock();
        try{
            //3、业务逻辑
            if( num != 2) {
                //B 线程等待
                System.out.println("B线程等待……");
                conditionB.await();
            }
            num = 1;
            System.out.println(Thread.currentThread().getName() + "=>" + num);
            //4、定向通知A线程
            conditionA.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public void printA(){

        //2、获得锁
        lock.lock();
        try{
            //3、业务逻辑
            while( num != 1) conditionA.await();
            num = 2;
            System.out.println(Thread.currentThread().getName() + "=>" + num);
            //4、使用B的监视器定向通知B线程
            conditionB.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

}

执行结果,这里B线程等待……之后获取锁也就A=>2将num置为2之后没有重新进入printB方法。

进入printB方法
B线程等待……
A=>2
B=>1
进入printB方法
A=>2
B=>1

使用JUC

Condition newCondition() 返回Condition用于这种用途实例Lock实例。

官网例子

class BoundedBuffer<E> {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition();   //获取notFull (也就是put)线程监视器condition
   final Condition notEmpty = lock.newCondition();  //获取notEmpty (也就是get)线程监视器condition

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(E x) throws InterruptedException {
     lock.lock(); 	//获取线程锁
     try {
       while (count == items.length)
         notFull.await();
       items[putptr] = x;
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();   //通知notEmpty线程
     } finally {
       lock.unlock();
     }
   }

   public E take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0)
         notEmpty.await();
       E x = (E) items[takeptr];
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   }
 }

使用condition可以精确通知和唤醒线程

package ThreadCommunication;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 使用JUC
 * Lock + condition
 */
public class B {
    public static void main(String[] args) {

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

    }


}

//资源类
class Data{
    private int num = 1;
    Lock lock = new ReentrantLock();
    //1、获得监视器
    Condition conditionA = lock.newCondition();
    Condition conditionB = lock.newCondition();
    Condition conditionC = lock.newCondition();

    public void printA(){
        
        //2、获得锁
        lock.lock();
        try{
            //3、业务逻辑
            while( num != 1) conditionA.await();
            num = 2;
            System.out.println(Thread.currentThread().getName() + "=>" + num);
            //4、使用B的监视器定向通知B线程
            conditionB.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
    public void printB(){
        lock.lock();
        try{
            while( num != 2) conditionB.await();
            num = 3;
            System.out.println(Thread.currentThread().getName() + "=>" + num);
            //通知C线程
            conditionC.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
    public void printC(){
        lock.lock();
        try{
            while( num != 3) conditionC.await();
            num = 1;
            System.out.println(Thread.currentThread().getName() + "=>" + num);
            //通知A线程
            conditionA.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
4S店客户管理小程序-毕业设计,基于微信小程序+SSM+MySql开发,源码+数据库+论文答辩+毕业论文+视频演示 社会的发展和科学技术的进步,互联网技术越来越受欢迎。手机也逐渐受到广大人民群众的喜爱,也逐渐进入了每个用户的使用。手机具有便利性,速度快,效率高,成本低等优点。 因此,构建符合自己要求的操作系统是非常有意义的。 本文从管理员、用户的功能要求出发,4S店客户管理系统中的功能模块主要是实现管理员服务端;首页、个人中心、用户管理、门店管理、车展管理、汽车品牌管理、新闻头条管理、预约试驾管理、我的收藏管理、系统管理,用户客户端:首页、车展、新闻头条、我的。门店客户端:首页、车展、新闻头条、我的经过认真细致的研究,精心准备和规划,最后测试成功,系统可以正常使用。分析功能调整与4S店客户管理系统实现的实际需求相结合,讨论了微信开发者技术与后台结合java语言和MySQL数据库开发4S店客户管理系统的使用。 关键字:4S店客户管理系统小程序 微信开发者 Java技术 MySQL数据库 软件的功能: 1、开发实现4S店客户管理系统的整个系统程序; 2、管理员服务端;首页、个人中心、用户管理、门店管理、车展管理、汽车品牌管理、新闻头条管理、预约试驾管理、我的收藏管理、系统管理等。 3、用户客户端:首页、车展、新闻头条、我的 4、门店客户端:首页、车展、新闻头条、我的等相应操作; 5、基础数据管理:实现系统基本信息的添加、修改及删除等操作,并且根据需求进行交流信息的查看及回复相应操作。
现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本微信小程序医院挂号预约系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息,使用这种软件工具可以帮助管理人员提高事务处理效率,达到事半功倍的效果。此微信小程序医院挂号预约系统利用当下成熟完善的SSM框架,使用跨平台的可开发大型商业网站的Java语言,以及最受欢迎的RDBMS应用软件之一的MySQL数据库进行程序开发。微信小程序医院挂号预约系统有管理员,用户两个角色。管理员功能有个人中心,用户管理,医生信息管理,医院信息管理,科室信息管理,预约信息管理,预约取消管理,留言板,系统管理。微信小程序用户可以注册登录,查看医院信息,查看医生信息,查看公告资讯,在科室信息里面进行预约,也可以取消预约。微信小程序医院挂号预约系统的开发根据操作人员需要设计的界面简洁美观,在功能模块布局上跟同类型网站保持一致,程序在实现基本要求功能时,也为数据信息面临的安全问题提供了一些实用的解决方案。可以说该程序在帮助管理者高效率地处理工作事务的同时,也实现了数据信息的整体化,规范化与自动化。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值