2018-06-12 第三十七天

简介:

一、线程安全

线程安全的问题,是针对多线程的程序。单线程的情况下,是不存在线程安全问题。


产生线程安全问题的原因:多个线程同时访问同一块代码。但是实际上我们希望该代码块是原子性的,在某一个时间点,只希望一个线程单独访问,不希望多个线程同时访问。


解决方案

1:同步代码块。

synchronized (this) {

//被同步的代码块;

}

synchronized(同步监视器对象) :java 关键字

{}:同步代码块:希望在某一个时间点只有一个线程访问的代码块。


执行过程:

1:线程1 执行到了同步代码块。要对同步监视器进行检测,看是否被其他的线程上锁了。发现没有上锁,那么线程1对同步监视器对象 上锁,并开始访问 同步代码块。

2:线程2 执行到了同步代码块,检测同步监视器,发现监视器已经被 线程1 上锁了。就进入就绪状态,等待cpu 下次调度执行。

3:线程1 执行完毕同步代码块,然后对 同步监视器对象  解锁。并执行后续的代码。

4:当 线程2 被再次调度执行的时候,发现同步监视器对象已经被解锁,那么就对同步监视器对象 加锁 并访问 同步代码块。


类似于上厕所:没人(有人就等着),进去,锁门,方便,然后冲水开门出去。下一个。


2:同步方法:

相当于把整个方法体都同步了。

同步监视器对象是this(实例方法)。

同步方法,会导致在任意时刻,只能有一个线程访问该方法。一个线程执行完毕方法之后,其他的线程才能访问。


同步块和同步方法都会导致程序的效率降低。多了检测监视器对象是否加锁,加锁和解锁的过程。


3:如果同步方法是静态方法,那么同步监视器对象是当前类的大 Class 对象。

 

import java.util.concurrent.ConcurrentHashMap;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;


/**

 * 模拟两个人取钱,说明线程安全的问题。

 * 张三和 张三媳妇,一起去取钱。

 * 一个在ATM

 * 一个在柜台。

 * 一共1500

 * 每个人都想取1000块钱。

 *

 */

public class AccountTest {


public static void main(String[] args) {

PersonRunnable runnable = new PersonRunnable();


Thread zhangSan = new Thread(runnable, "张三");

Thread zhangSanXiFu = new Thread(runnable, "张三媳妇");


zhangSan.start();

zhangSanXiFu.start();

}

}


/**

 * 账户类

 *

 */

class Account{


private int money = 1500;


//第三种线程同步的实现 jdk1.5出现的。

private Lock lock = new ReentrantLock();


/**

 * 取现

 * @param money  取钱的钱数

 * @return 如果取钱成功,返回 true ,否则返回 false。

 */

public /*synchronized*/ boolean withDrawMoney(int money) {

//同步代码块

//synchronized (this) {

//在try 外面将 需要同步的代码锁住,然后将需要同步的代码,放到try 块中。

lock.lock();

try {

if(this.money >= money){

try {

Thread.sleep(10);

catch (InterruptedException e) {

e.printStackTrace();

}

this.money -= money;

System.out.println(Thread.currentThread().getName() + "--> 取钱[成功]。余额为:"+this.money);

return true;

}

}finally{

//必须执行

lock.unlock();

}

// }


System.out.println(Thread.currentThread().getName() + "--> 取钱[失败]。余额为:"+this.money);

return false;

}

}


class PersonRunnable implements Runnable{

//唯一的账户

private Account account = new Account();


public void run() {

account.withDrawMoney(1000);

}

}


二、关于监视器的选择

如果想实现线程间的互斥访问同步代码块,那么监视器对象必须唯一。只有一个实例存在。


1:this:保证只有一个this。这个对象只被实例化了一次。

2:final static Object obj = new Object();

3: 当一个类加载的时候,类加载只有一次,会产生2部分内容。1部分是类的字节码元数据(方法区)。还会生成一个对象(堆区)。对象的类型是  描述类的类型  Class。

这个大Class 类型的对象也是唯一的。可以通过  类名.class  来得到这个唯一的描述当前类的对象。


好处:不可变以及唯一性。

当前类的大Class 对象。


/**

 * 继承线程Thread 类,来模拟火车站售票。

 * 每一个售票窗口都代表了一个线程。

 * 所有的售票员,都访问同一个售票系统。

 *

 */

public class TestTicket {


public static void main(String[] args) {

TicketSystem system = new TicketSystem();

Saler saler1 = new Saler(system);

saler1.setName("售票员小姐姐-1");//设置线程的名字

Saler saler2 = new Saler(system);

saler2.setName("售票员小姐姐-2");//设置线程的名字

Saler saler3 = new Saler(system);

saler3.setName("售票员小姐姐-3");//设置线程的名字

Saler saler4 = new Saler(system);

saler4.setName("售票员小姐姐-4");//设置线程的名字

Saler saler5 = new Saler(system);

saler5.setName("售票员小姐姐-5");//设置线程的名字

saler1.start();

saler2.start();

saler3.start();

saler4.start();

saler5.start();

}

}


//售票系统

class TicketSystem{

//剩余票数

private int count = 100;

// static Object o = new Object();

/**

 * @param count 本次卖票的张数

 * @return 是否售票成功

 */

public /*synchronized*/ boolean sale(int count){

//使用当前类的 大 Class 对象 作为 监视器。具有不可变和唯一的特性。

synchronized (TicketSystem.class) {

if(this.count >= count){

this.count -= count;

System.out.println(Thread.currentThread().getName() + " 卖出了 "+count+" 张票,剩余票数为:"+this.count);

//让当前线程等待一下下 100

try {

Thread.sleep(10);

catch (InterruptedException e) {

e.printStackTrace();

}

return true;

}

}

//

System.out.println(Thread.currentThread().getName() + " 售票失败 ,余票不足 ,剩余票数为:"+this.count);

return false;

}

}


//售票员

class Saler extends Thread{

//售票员需要访问唯一的票务系统

//多个线程对象,访问同一个对象的数据,可以使用同时持有唯一对象引用的方法。

private TicketSystem system;

public Saler(TicketSystem system) {

this.system = system;

}

//线程任务的主体部分。

public void run() {

boolean result = system.sale(1);

while(result){

result = system.sale(1);

}

}

}


三、单例模式最终版

public class SingleTonTest {

static int num = 0;


public static void main(String[] args) {

//在线程1 中 得到唯一的实例。

new Thread(){

@Override

public void run() {

MySingleton singleton1 = MySingleton.getInstance();

System.out.println(singleton1);

}

}.start();

//在线程2中再得到一次

new Thread(){

@Override

public void run() {

MySingleton singleton2 = MySingleton.getInstance();

System.out.println(singleton2);

}

}.start();

//使用Runnable 接口,匿名内部类

new Thread(new Runnable() {

@Override

public void run() {

}

}).start();

//面试题

new Thread(new Runnable() {// 11111

@Override

public void run() {

System.out.println(22222);

}

}){

public void run() {

System.out.println(11111);

};

}.start();

}

}


//懒汉模式单例类

class MySingleton{

private int num = 10;

private static MySingleton singleton;

private MySingleton(){}

//对外的提供的唯一的 方位 唯一实例的方法

public /*synchronized*/ static MySingleton getInstance(){

//为了提高效率,后续的该方法的访问都不会再进行 加锁和解锁的 和监视器是否有锁的判断

if(singleton == null){

synchronized (MySingleton.class) {

//为了保证唯一的实例

if(singleton == null){

try {

Thread.sleep(1000);

catch (InterruptedException e) {

e.printStackTrace();

}

singleton = new MySingleton();

}

}

}

return singleton;

}

public void setNum(int num){

this.num = num;

}

public int getNum(){

return num;

}

}


四、线程死锁

线程死锁:DeadLock。两个进程相互上锁,当两个进程同时被锁住的时候,程序将无法继续向下执行,此时即为死锁。


static Object o1 = new Object()

static Object o2 = new Object();


//线程-1 执行的代码

synchronized(o1){

// thread-1 stop here

synchronized(o2){

}

}


//线程-2 执行 代码

synchronized(o2){

// thread-2 stop here

synchronized(o1){

}

}


出现死锁的条件:

1:同步代码块嵌套。

2:多个线程之间已经锁住的资源为彼此请求锁住的资源。


如果死锁已经产生,那么没有办法解决,只能在编码的时候避免出现死锁。

避免死锁:避免出现嵌套的同步代码块。


public class DeadLockTest {


public static void main(String[] args) {

new DeadLockThread(0).start();

new DeadLockThread(1).start();

}

}


class DeadLockThread extends Thread{

//两个监视器对象

final static Object O1 = new Object();

final static Object O2 = new Object();

private int id;

public DeadLockThread(int id) {

this.id = id;

}

@Override

public void run() {

if(id == 0){

synchronized (O1) {

System.out.println(Thread.currentThread().getName()+"--O1-->O2");

try {

Thread.sleep(100);

catch (InterruptedException e) {

e.printStackTrace();

}

synchronized (O2) {

System.out.println(Thread.currentThread().getName()+"--O1-->O2--------");

}

}

}else{

synchronized (O2) {

System.out.println(Thread.currentThread().getName()+"--O2-->O1");

synchronized (O1) {

System.out.println(Thread.currentThread().getName()+"--O2-->O1--------");

}

}

}

}

}


五、线程间通信-管道流

使用管道流实现多个线程之间的信息的交互。


import java.io.BufferedReader;

import java.io.BufferedWriter;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.OutputStreamWriter;

import java.io.PipedInputStream;

import java.io.PipedOutputStream;


/**

 * 两个线程 独立的 类,进行信息的交互

 * 管道输出流写出的数据,被管道输入流读取到。

 * 在一个线程中用管道输出流写出数据。

 * 在另外一个线程中使用管道输入流读取 管道输出流写出的数据

 *

 */

public class PipedStreamTest {


public static void main(String[] args) throws Exception {

PipedInputStream pis = new PipedInputStream();

PipedOutputStream pos = new PipedOutputStream();

//可以通过构造方法将管道输入输出流建立关联,也可以通过方法。

pis.connect(pos);

PipedOutputStreamThread thread1 = new PipedOutputStreamThread(pos);

PipedInputStreamThread thread2 = new PipedInputStreamThread(pis);

new Thread(){

public void run() {

thread2.start();

};

}.start();

Thread.sleep(5000);

new Thread(){

public void run() {

thread1.start();

};

}.start();

}

}


//管道输出流的线程类

class PipedOutputStreamThread extends Thread{

//持有管道输出流的引用。

private PipedOutputStream pos;

public PipedOutputStreamThread(PipedOutputStream pos) {

this.pos = pos;

}

public void run() {

try {

//使用管道输出流写出数据

String str = "多年不见,你还好么?";

// byte[] bs = str.getBytes();

BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(pos));

bw.write(str);

// pos.write(bs);

bw.newLine();

bw.flush();

bw.close();

catch (Exception e) {

e.printStackTrace();

}finally {

try {

pos.close();

catch (IOException e) {

e.printStackTrace();

}

}

}

}


//用于读取管道输出流写出数据的输入流 

class PipedInputStreamThread extends Thread{

private PipedInputStream pis;

public PipedInputStreamThread(PipedInputStream pis) {

this.pis = pis;

}

@Override

public void run() {

try {

BufferedReader br = new BufferedReader(new InputStreamReader(pis));

String str = br.readLine();

System.out.println("接收到的内容为:"+str);

br.close();

catch (Exception e) {

e.printStackTrace();

}finally{

try {

pis.close();

catch (IOException e) {

e.printStackTrace();

}

}

}

}


六、生产者消费者典型案例



都是Object类的方法。

Object obj = new Object();


obj .wait():让当前线程在当前对象上等待。 让执行这句代码的线程在obj上等待。

obj .notify(): 将在obj 对象上等待的某个线程唤醒。

obj .notifyAll(): 将在obj 对象上等待的所有的线程唤醒。


这三个方法使用的环境:必须让当前线程持有当前对象监视器的锁。

obj .wait()  表示 当前线程必须对 obj 上锁了。


import java.util.Arrays;


/**

 * 自定义容器  模拟栈

 * 底层使用数组来实现。Object

 *

 */

public class MyStack {

//定义栈的初始化容量

public static final int INIT_CAPACITY = 1;

//栈顶指针 所有对元素的操作都是 通过栈顶指针来完成的。

private int index;

//存放元素数组

private Object[] elementData;


public MyStack() {

elementData = new Object[INIT_CAPACITY];

index = 0;

}


/**

 * 将 e 压入栈顶

 * @param e

 */

public void push(E e){

synchronized (this) {

if(isFull()){

//如果栈已满

//让生产者线程在  当前容器上等待。当前线程进入阻塞状态。并对当前对象解锁。

try {

this.wait();

catch (InterruptedException e1) {

e1.printStackTrace();

}

}

try {

Thread.sleep(300);

catch (InterruptedException e1) {

e1.printStackTrace();

}

//生产者顺利的生产了一个面包

elementData[index++] = e;

System.out.println(Thread.currentThread().getName()+"--生产了一个:"+e);

//TODO  通知消费者你可以继续消费了

//生产者线程通知消费者线程可以继续消费了,消费者线程如果处于阻塞状态,那么消费者线程从阻塞状态进入 就绪状态,等待cpu 的下次的调度执行。

this.notify();


}

}


/**

 * 弹栈操作

 * @return

 */

public synchronized void pop() {

if(isEmpty()){

//消费者线程,发现容器空了,那么就在容器上等待。导致消费者线程进入阻塞状态,并释放对 this 的锁

try {

this.wait();

catch (InterruptedException e) {

e.printStackTrace();

}

}

try {

Thread.sleep(300);

catch (InterruptedException e1) {

e1.printStackTrace();

}

//先指针下移

index --;

E e = (E)elementData[index];

elementData[index] = null;

System.out.println(Thread.currentThread().getName() + "--消费了:"+e);

//如果生产者线程此刻处于阻塞状态。那么会让生产者线程从阻塞状态进入就绪状态,等待cpu 的下次调度,如果被调度执行,那么生产者从上次等待的位置继续执行。

//消费者线程通知在 this 上等待的生产者线程,可以继续生产了。

this.notify();

}


/**

 * 获取栈顶元素,但是不删除

 * @return

 */

public E peek()throws Exception{

if(isEmpty()){

Exception e = new Exception("囊中羞涩,别摸了!");

throw e;

}else{

return (E)elementData[index-1];

}

}


/**

 * 容器中是否包含 e

 * @param e

 * @return

 */

public boolean search(E e){

if(isEmpty())

return false;

//如果 e 是 null   判断 elementData 是否有null

if(e == null){

for(int i=0;i

if(elementData[i] == null)

return true;

}

}else{//e 不是null  

for(int i=0;i

if(e.equals(elementData[i]))

return true;

}

}

return false;

}


/**

 * 判断栈是否满了

 * @return

 */

private boolean isFull(){

return index == elementData.length;

}

/**

 * 栈是否为空

 * @return

 */

public boolean isEmpty(){

return index == 0;

}

/**

 * 得到元素的个数

 * @return

 */

public int size(){

return index;

}


/**

 * 容量

 * @return

 */

public int capacity(){

return elementData.length;

}


@Override

public String toString() {

return Arrays.toString(Arrays.copyOf(elementData, index));

}

}


public class PCTest {


public static void main(String[] args) {

MyStack stack = new MyStack<>();

Producer producer = new Producer(stack);

producer.setName("巴莉甜甜");

Consumer consumer = new Consumer(stack);

consumer.setName("YiBao");

producer.start();

consumer.start();

}

}


/**

 * 消费者线程 ,消费面包

 *

 */

public class Consumer extends Thread{

private MyStack stack;


Consumer(MyStack stack) {

super();

this.stack = stack;

}

@Override

public void run() {

for(int i=0;i<10;i++){

stack.pop();

}

}

}


/**

 * 面包类

 *

 */

public class Bread {

private int id;


Bread(int id) {

super();

this.id = id;

}


@Override

public String toString() {

return "面包 [id=" + id + "]";

}

}


/**

 * 生产者线程

 *

 */

public class Producer extends Thread{

private MyStack stack;

Producer(MyStack stack) {

super();

this.stack = stack;

}


//生产十个面包

public void run() {

for(int i=0;i<10;i++){

stack.push(new Bread(i));

}

}

}


七、多个消费者生产者出现的问题

1:消费者1 开始消费,等待了。

2:消费者2 开始消费,等待了。

3:生产者 生产了一个馒头,唤醒了 消费者1. 生产者继续生产,等待了。

4:消费者1 开始消费,结果把消费者2 唤醒了。消费者1 等待了。

5:消费者2 开始消费,容器已经是空的了。 数组下标越界。

解决:

import java.util.Arrays;


/**

 * 自定义容器  模拟栈

 * 底层使用数组来实现。Object

 *

 */

public class MyStack {

//定义栈的初始化容量

public static final int INIT_CAPACITY = 1;

//栈顶指针 所有对元素的操作都是 通过栈顶指针来完成的。

private int index;

//存放元素数组

private Object[] elementData;


public MyStack() {

elementData = new Object[INIT_CAPACITY];

index = 0;

}

//让生产者线程在o1上等待。 在o2上唤醒。

static Object o1 = new Object();

//让消费者线程在o2 上等待。在o1上唤醒。

static Object o2 = new Object();


/**

 * 将 e 压入栈顶

 * @param e

 */

public void push(E e){

synchronized (o1) {

while(isFull()){//如果栈已满

//让生产者线程在  当前容器上等待。当前线程进入阻塞状态。并对当前对象解锁。

try {

o1.wait();


catch (InterruptedException e1) {

e1.printStackTrace();

}

}

// try {

// Thread.sleep(300);

// } catch (InterruptedException e1) {

// e1.printStackTrace();

// }

//生产者顺利的生产了一个面包

elementData[index++] = e;

System.out.println(Thread.currentThread().getName()+"--生产了一个:"+e);

//TODO  通知消费者你可以继续消费了

//生产者线程通知消费者线程可以继续消费了,消费者线程如果处于阻塞状态,那么消费者线程从阻塞状态进入 就绪状态,等待cpu 的下次的调度执行。

// this.notifyAll();

synchronized (o2) {

o2.notify();

}

}

}


/**

 * 弹栈操作

 * @return

 */

public /*synchronized */void pop() {

synchronized (o2) {

while(isEmpty()){

//消费者线程,发现容器空了,那么就在容器上等待。导致消费者线程进入阻塞状态,并释放对 this 的锁

try {

o2.wait();

catch (InterruptedException e) {

e.printStackTrace();

}

}

// try {

// Thread.sleep(300);

// } catch (InterruptedException e1) {

// e1.printStackTrace();

// }

//先指针下移

index --;

E e = (E)elementData[index];

elementData[index] = null;

System.out.println(Thread.currentThread().getName() + "--消费了:"+e);

//如果生产者线程此刻处于阻塞状态。那么会让生产者线程从阻塞状态进入就绪状态,等待cpu 的下次调度,如果被调度执行,那么生产者从上次等待的位置继续执行。

//消费者线程通知在 this 上等待的生产者线程,可以继续生产了。

// this.notifyAll();

synchronized (o1) {

o1.notify();

}

}

}


/**

 * 获取栈顶元素,但是不删除

 * @return

 */

public E peek()throws Exception{

if(isEmpty()){

Exception e = new Exception("囊中羞涩,别摸了!");

throw e;

}else{

return (E)elementData[index-1];

}

}


/**

 * 容器中是否包含 e

 * @param e

 * @return

 */

public boolean search(E e){

if(isEmpty())

return false;

//如果 e 是 null   判断 elementData 是否有null

if(e == null){

for(int i=0;i

if(elementData[i] == null)

return true;

}

}else{//e 不是null  

for(int i=0;i

if(e.equals(elementData[i]))

return true;

}

}

return false;

}


/**

 * 判断栈是否满了

 * @return

 */

private boolean isFull(){

return index == elementData.length;

}

/**

 * 栈是否为空

 * @return

 */

public boolean isEmpty(){

return index == 0;

}

/**

 * 得到元素的个数

 * @return

 */

public int size(){

return index;

}


/**

 * 容量

 * @return

 */

public int capacity(){

return elementData.length;

}


@Override

public String toString() {

return Arrays.toString(Arrays.copyOf(elementData, index));

}

}




八、jdk1.5的针对同步嵌套的解决

import java.util.Arrays;

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;


/**

 * 自定义容器  模拟栈

 * 底层使用数组来实现。Object

 *

 */

public class MyStack {

//定义栈的初始化容量

public static final int INIT_CAPACITY = 2;

//栈顶指针 所有对元素的操作都是 通过栈顶指针来完成的。

private int index;

//存放元素数组

private Object[] elementData;


public MyStack() {

elementData = new Object[INIT_CAPACITY];

index = 0;

}


//使用jdk1.5 的针对线程死锁的解决方案。

private Lock lock = new ReentrantLock();

//需要两个Condition 对象。

//生产者的Condition

private Condition proCon = lock.newCondition();

//消费者的Condition

private Condition conCon = lock.newCondition();


/**

 * 将 e 压入栈顶

 * @param e

 */

public void push(E e){

lock.lock();

try {

while(isFull()){//如果栈已满

//让生产者线程在  当前容器上等待。当前线程进入阻塞状态。并对当前对象解锁。

try {

// o1.wait();

proCon.await();


catch (InterruptedException e1) {

e1.printStackTrace();

}

}

//生产者顺利的生产了一个面包

elementData[index++] = e;

System.out.println(Thread.currentThread().getName()+"--生产了一个:"+e);

//TODO  通知消费者你可以继续消费了

//生产者线程通知消费者线程可以继续消费了,消费者线程如果处于阻塞状态,那么消费者线程从阻塞状态进入 就绪状态,等待cpu 的下次的调度执行。

// this.notifyAll();

// o2.notify();

conCon.signal();


finally {

lock.unlock();

}

}


/**

 * 弹栈操作

 * @return

 */

public /*synchronized */void pop() {

lock.lock();

try {

while(isEmpty()){

//消费者线程,发现容器空了,那么就在容器上等待。导致消费者线程进入阻塞状态,并释放对 this 的锁

try {

// o2.wait();

conCon.await();

catch (InterruptedException e) {

e.printStackTrace();

}

}

//先指针下移

index --;

E e = (E)elementData[index];

elementData[index] = null;

System.out.println(Thread.currentThread().getName() + "--消费了:"+e);

//如果生产者线程此刻处于阻塞状态。那么会让生产者线程从阻塞状态进入就绪状态,等待cpu 的下次调度,如果被调度执行,那么生产者从上次等待的位置继续执行。

//消费者线程通知在 this 上等待的生产者线程,可以继续生产了。

// this.notifyAll();

// o1.notify();

proCon.signal();

finally {

lock.unlock();

}

}


/**

 * 获取栈顶元素,但是不删除

 * @return

 */

public E peek()throws Exception{

if(isEmpty()){

Exception e = new Exception("囊中羞涩,别摸了!");

throw e;

}else{

return (E)elementData[index-1];

}

}


/**

 * 容器中是否包含 e

 * @param e

 * @return

 */

public boolean search(E e){

if(isEmpty())

return false;

//如果 e 是 null   判断 elementData 是否有null

if(e == null){

for(int i=0;i

if(elementData[i] == null)

return true;

}

}else{//e 不是null  

for(int i=0;i

if(e.equals(elementData[i]))

return true;

}

}

return false;

}


/**

 * 判断栈是否满了

 * @return

 */

private boolean isFull(){

return index == elementData.length;

}

/**

 * 栈是否为空

 * @return

 */

public boolean isEmpty(){

return index == 0;

}

/**

 * 得到元素的个数

 * @return

 */

public int size(){

return index;

}


/**

 * 容量

 * @return

 */

public int capacity(){

return elementData.length;

}


@Override

public String toString() {

return Arrays.toString(Arrays.copyOf(elementData, index));

}

}




目录
相关文章
|
21天前
|
NoSQL 关系型数据库 MySQL
2024 RedisAnd Mysql基础与进阶操作系列(17)作者——LJS[你个小黑子这都还学不会嘛?你是真爱粉嘛?真是的 ~;以后请别侮辱我家鸽鸽]
如何通过php和python客户端来操作Redis数据库等保姆式具体操作详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
21天前
|
存储 NoSQL 关系型数据库
2024 RedisAnd Mysql基础与进阶操作系列(13)作者——LJS[你个小黑子这都还学不会嘛?你是真爱粉嘛?真是的 ~;以后请别侮辱我家鸽鸽]
Redis之Nosql数据库分类、存储类型、特点;Redis 作用、与其他库的对比、使用场景以及如何部署、安装等具体详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
22天前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习(8)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
22天前
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构的基本概念;算法的基本概念、特性以及时间复杂度、空间复杂度等举例说明;【含常见的报错问题及其对应的解决方法】
|
22天前
|
存储 人工智能 算法
2024重生之回溯数据结构与算法系列学习(7)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
IKU达人数据结构与算法系列学习之队列的基本概念、如何判断队列已满/已空、队列的链式存储结构[头的出入队]、双端队列、中缀、后缀、前缀表达式、特殊矩阵和一二维数组的压缩储存等具体操作详解步骤;举例说明、注意点及常见报错问题所对应的解决方法 你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
21天前
|
NoSQL 关系型数据库 MySQL
2024 RedisAnd Mysql基础与进阶操作系列(16-3)作者——LJS[你个小黑子这都还学不会嘛?你是真爱粉嘛?真是的 ~;以后请别侮辱我家鸽鸽]
Redis数据类型之List类型语法格式说明表及LPUSH/LPUSHX/LRANGE、LPOP/LLEN、LREM/LSET/LINDEX/LTRIM、LINSERT RPUSH/RPUSHX/RPOP/RPOPLPUSH等具体操作详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
21天前
|
NoSQL 关系型数据库 MySQL
2024 RedisAnd Mysql基础与进阶操作系列(19)作者——LJS[你个小黑子这都还学不会嘛?你是真爱粉嘛?真是的 ~;以后请别侮辱我家鸽鸽]
Redis应用场景之取最新N个数据的操作、精准设定过期时间、计数器应用、Uniq操作,获取某段时间所有数据排重值、Pub/Sub构建实时消息系统等保姆式具体操作详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
21天前
|
存储 NoSQL 关系型数据库
2024 RedisAnd Mysql基础与进阶操作系列(15)作者——LJS[你个小黑子这都还学不会嘛?你是真爱粉嘛?真是的 ~;以后请别侮辱我家鸽鸽]
2024全网最全切最为详细的Redis库配置;你个小黑子这都还学不会嘛?你是真爱粉嘛?真是的 ~;以后请别侮辱我家鸽鸽
|
21天前
|
NoSQL 关系型数据库 MySQL
2024 RedisAnd Mysql基础与进阶操作系列(16-5)作者——LJS[你个小黑子这都还学不会嘛?你是真爱粉嘛?真是的 ~;以后请别侮辱我家鸽鸽]
Redis数据类型之ZSet类型及相关命令如:SADD/SMEMBERS/SCARD/SISMEMBER、ZRANGEBYSCORE/ZREMRANGEBYRANK/ZREMRANGEBYSCORE、ZREVRANGE/ZREVRANGEBYSCORE/ZREVRANK等命令具体操作详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
21天前
|
存储 NoSQL 关系型数据库
2024 RedisAnd Mysql基础与进阶操作系列(16-4)作者——LJS[你个小黑子这都还学不会嘛?你是真爱粉嘛?真是的 ~;以后请别侮辱我家鸽鸽]
Redis数据类型之Set类型及相关命令如:SADD/SMEMBERS/SCARD/SISMEMBER、SPOP/SREM/SRANDMEMBER/SMOVE、SDIFF/SDIFFSTORE/SINTER/SINTERSTORE 等具体操作详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
下一篇
无影云桌面