一、简介

java.util.concurrent.locks.Lock 是一个类似于synchronized 块的线程同步机制。但是 Lock比 synchronized 块更加灵活。Lock是个接口,有个实现类是ReentrantLock。

二、Lock和syncronized的区别

  • synchronized是Java语言的关键字。Lock是一个接口。
  • synchronized不需要用户去手动释放锁,发生异常或者线程结束时自动释放锁;Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
  • lock可以配置公平策略,实现线程按照先后顺序获取锁。
  • 提供了trylock方法 可以试图获取锁,获取到或获取不到时,返回不同的返回值 让程序可以灵活处理。
  • lock()和unlock()可以在不同的方法中执行,可以实现同一个线程在上一个方法中lock()在后续的其他方法中unlock(),比syncronized灵活的多。

三、Lock接口抽象方法

  • void lock():获取锁,如果锁不可用,则出于线程调度的目的,当前线程将被禁用,并且在获取锁之前处于休眠状态。
Lock lock = ...;
lock.lock();
try{//处理任务
}catch(Exception ex){}finally{lock.unlock();   //释放锁
}
  • boolean tryLock():如果锁可用立即返回true,如果锁不可用立即返回false;
  • boolean tryLock(long time, TimeUnit unit) throws InterruptedException:如果锁可用,则此方法立即返回true。 如果该锁不可用,则当前线程将出于线程调度目的而被禁用并处于休眠状态,直到发生以下三种情况之一为止:①当前线程获取到该锁;②当前线程被其他线程中断,并且支持中断获取锁;③经过指定的等待时间如果获得了锁,则返回true,没获取到锁返回false。
Lock lock = ...;
if(lock.tryLock()) {try{//处理任务}catch(Exception ex){}finally{lock.unlock();   //释放锁}
}else {//如果不能获取锁,则直接做其他事情
}
  • void unlock():释放锁。释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。

四、ReentrantLock

重入锁也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。避免死锁问题的,synchronized也可重入。

4.1、synchronized重入测试

public class ReentrantDemo {public synchronized  void method1() {System.out.println("synchronized method1");method2();}public synchronized void method2() {System.out.println("synchronized method2");}public static void main(String[] args) {ReentrantDemo reentrantDemo = new ReentrantDemo();reentrantDemo.method1();}
}

执行结果

4.2、ReentrantLock重入测试

public class ReentrantDemo implements Runnable {Lock lock = new ReentrantLock();@Overridepublic void run() {set();}public void set() {try {lock.lock();System.out.println("set 方法");get();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();// 必须在finally中释放}}public void get() {try {lock.lock();System.out.println("get 方法");} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}public static void main(String[] args) {ReentrantDemo reentrantDemo = new ReentrantDemo();new Thread(reentrantDemo).start();}
}

测试结果:同一个线程,首先在set方法中获取锁,然后调用get方法,get方法中重复获取同一个锁。两个方法都执行成功。

五、ReentrantReadWriteLock(读写锁)

读写锁,可以分别获取读锁或写锁。也就是说将数据的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。读锁使用共享模式;写锁使用独占模式读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。当有读锁时,写锁就不能获得而当有写锁时,除了获得写锁的这个线程可以获得读锁外,其他线程不能获得读锁

  • writeLock():获取写锁。
  • readLock():获取读锁。
    执行三个线程进行读写操作,并设置一个屏障,线程依次准备就绪后未获取锁之前都在等待,当第三个线程执行 cyclicBarrier.await();后屏障解除,三个线程同时执行。
public class WriteAndReadLockTest {private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10,60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());private static CyclicBarrier cyclicBarrier = new CyclicBarrier(3);private static int i = 100;public static void main(String[] args) {threadPoolExecutor.execute(()->{read(Thread.currentThread());});threadPoolExecutor.execute(()->{write(Thread.currentThread());});threadPoolExecutor.execute(()->{read(Thread.currentThread());});threadPoolExecutor.shutdown();}private static void read(Thread thread) {try {cyclicBarrier.await();} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}reentrantReadWriteLock.readLock().lock();try {System.out.println("读线程 "+ thread.getName() + " 开始执行, i=" + i);Thread.sleep(1000);System.out.println(thread.getName() +" is over!");} catch (InterruptedException e) {e.printStackTrace();} finally {reentrantReadWriteLock.readLock().unlock();}}private static void write(Thread thread) {try {cyclicBarrier.await();} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}reentrantReadWriteLock.writeLock().lock();try {i++;System.out.println("写线程 "+ thread.getName() + " is doing, i=" + i);System.out.println(thread.getName() +" is over!");} finally {reentrantReadWriteLock.writeLock().unlock();}}
}

执行结果:线程1先获取到了读锁,因为读锁时可以共享的,所有线程3也可以获取到读锁,线程1、3读操作完成后将读锁释放后,线程2才能获取到写锁并开始执行写操作。

六、公平锁与非公平锁

  • 公平锁:就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己
  • 非公平锁:比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式

6.1、如何实现

  • ReentrantLock:模式是非公平锁。也可通过构造方法创建公平锁;
public ReentrantLock() {sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();
}
  • ReentrantReadWriteLock:默认是非公平锁,也可以通过构造方法创建公平锁;
public ReentrantReadWriteLock() {this(false);
}
public ReentrantReadWriteLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();readerLock = new ReadLock(this);writerLock = new WriteLock(this);
}

6.2优缺点

非公平锁性能高于公平锁性能。首先,在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟。而且,非公平锁能更充分的利用cpu的时间片,尽量的减少cpu空闲的状态时间。

七、Condition的使用

当满足一定条件时,调用Condition的await()方法使当前线程进入休眠状态进行等待。调用Condition的signalAll()方法唤醒因await()进入休眠的线程。

Lock锁实现同步时需要使用者手动控制锁的获取和释放,其灵活性使得可以实现更复杂的多线程同步和更高的性能,但同时,使用者一定要在获取锁后及时捕获代码运行过程中的异常并在finally代码块中释放锁。

使用Lock锁及其同步条件来实现一个生产者-消费者模型:

public class MessageStorageByLock {  private int maxSize;  private List<String> messages;  private final ReentrantLock lock;  private final Condition conditionWrite;//声明两个锁条件  private final Condition conditionRead;  public MessageStorageByLock(int maxSize) {  this.maxSize = maxSize;  messages = new LinkedList<String>();  lock = new ReentrantLock(true);//true修改锁的公平性,为true时,使用lifo队列来顺序获得锁  conditionWrite = lock.newCondition();//调用newCondition()方法,即new ConditionObject();  conditionRead = lock.newCondition();  }  public void set(String message){  //使用锁实现同步,获取所得操作,当锁被其他线程占用时,当前线程将进入休眠  lock.lock();  try{  while(messages.size() == maxSize){  System.out.print("the message buffer is full now,start into wait()\n");  conditionWrite.await();//满足条件时,线程休眠并释放锁。当调用 signalAll()时。线程唤醒并重新获得锁  }  Thread.sleep(100);  messages.add(message);  System.out.print("add message:"+message+" success\n");  conditionRead.signalAll();//唤醒因conditionRead.await()休眠的线程  }catch (InterruptedException e){  e.printStackTrace();  }finally {  lock.unlock();  }  }  public String get(){  String message = null;  lock.lock();  try{  while(messages.size() == 0){  conditionRead.await();  System.out.print("the message buffer is empty now,start into wait()\n");  }  Thread.sleep(100);  message = ((LinkedList<String>)messages).poll();  System.out.print("get message:"+message+" success\n");  conditionWrite.signalAll();  }catch (InterruptedException e){  e.printStackTrace();  }finally {  lock.unlock();  }  return message;  }
}  
Modifier and Type Method and Description
void

lock()

获得锁

void lockInterruptibly()

获取锁定,除非当前线程是 interrupted 。

Condition newCondition()

返回一个新Condition绑定到该实例Lock实例。

boolean tryLock()

只有在调用时才可以获得锁。

boolean tryLock(long time, TimeUnit unit)

如果在给定的等待时间内是空闲的,并且当前的线程尚未得到 interrupted,则获取该锁。

void

unlock();

释放锁

package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;public interface Lock {void lock();void lockInterruptibly() throws InterruptedException;boolean tryLock();boolean tryLock(long time, TimeUnit unit) throws InterruptedException;void unlock();Condition newCondition();
}

package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;
import java.util.Date;public interface Condition {void await() throws InterruptedException;void awaitUninterruptibly();long awaitNanos(long nanosTimeout) throws InterruptedException;boolean await(long time, TimeUnit unit) throws InterruptedException;boolean awaitUntil(Date deadline) throws InterruptedException;void signal();void signalAll();
}

、代码举例

8.1Demo1(先演示一下锁的可重入性)

package com.szh.lock;/*** 演示锁的可重入性*/
public class Test01 {public synchronized void metthod1() {System.out.println("同步方法1");//线程执行 metthod1() 方法,默认 this 作为锁对象,//在 metthod1() 方法中调用了 method2() 方法,注意当前线程还是持有 this 锁对象的//method2() 同步方法默认的锁对象也是 this 对象, 要执行 method2() 必须先获得 this 锁对象,//当前 this 对象被当前线程持有,可以 再次获得 this 对象, 这就是锁的可重入性.//假设锁不可重入的话,可能会造成死锁method2();}public synchronized void method2() {System.out.println("同步方法2");method3();}public synchronized void method3() {System.out.println("同步方法3");}public static void main(String[] args) {Test01 obj=new Test01();new Thread(new Runnable() {@Overridepublic void run() {obj.metthod1();}}).start();}
}

运行结果 

8.2Demo2(ReentrantLock的基本使用)

package com.szh.lock;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** ReentrantLock 的基本使用*/
public class Test02 {//定义一个显示锁static Lock lock=new ReentrantLock();public static void method() {//先获得锁lock.lock();//for循环此时就是同步代码块for (int i = 0; i < 3; i++) {System.out.println(Thread.currentThread().getName() + " ---> " + i);}//释放锁lock.unlock();}public static void main(String[] args) {Runnable r=new Runnable() {@Overridepublic void run() {method();}};//启动三个线程new Thread(r).start();new Thread(r).start();new Thread(r).start();}
}

运行结果

8.3Demo3(使用Lock锁同步不同方法中的代码块)

package com.szh.lock;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** 使用 Lock 锁同步不同方法中的同步代码块*/
public class Test03 {//定义锁对象static Lock lock=new ReentrantLock();public static void method1() {//经常在 try 代码块中获得 Lock 锁, 在 finally 子句中释放锁try {lock.lock(); //获得锁System.out.println(Thread.currentThread().getName() + " ---method1--- " + System.currentTimeMillis());Thread.sleep(1000);System.out.println(Thread.currentThread().getName() + " ---method1--- " + System.currentTimeMillis());} catch (InterruptedException e) {e.printStackTrace();}finally {lock.unlock(); //释放锁}}public static void method2() {//经常在 try 代码块中获得 Lock 锁, 在 finally 子句中释放锁try {lock.lock(); //获得锁System.out.println(Thread.currentThread().getName() + " ---method2--- " + System.currentTimeMillis());Thread.sleep(1000);System.out.println(Thread.currentThread().getName() + " ---method2--- " + System.currentTimeMillis());} catch (InterruptedException e) {e.printStackTrace();}finally {lock.unlock(); //释放锁}}public static void main(String[] args) {Runnable r1=new Runnable() {@Overridepublic void run() {method1();}};Runnable r2=new Runnable() {@Overridepublic void run() {method2();}};new Thread(r1).start();new Thread(r1).start();new Thread(r2).start();new Thread(r2).start();}
}

运行结果

8.4Demo4(ReentrantLock锁的可重入性)

package com.szh.lock;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** ReentrantLock 锁的可重入性*/
public class Test04 {static class SubThread extends Thread {//定义锁对象private static Lock lock=new ReentrantLock();//定义变量private static int num=0;@Overridepublic void run() {for (int i = 0; i < 10000; i++) {try {//可重入锁指可以反复获得该锁lock.lock();lock.lock();num++;}finally {lock.unlock();lock.unlock();}}}}public static void main(String[] args) throws InterruptedException {SubThread t1=new SubThread();SubThread t2=new SubThread();t1.start();t2.start();t1.join();t2.join();System.out.println(SubThread.num);}
}

运行结果

8.5Demo5(ReentrantLock的lockInterruptibly()方法)

package com.szh.lock;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** lockInterruptibly()方法*  如果当前线程未被中断则获得锁,*  如果当前线程被中断则出现异常.*/
public class Test05 {static class Service {private Lock lock=new ReentrantLock(); //定义锁对象public void serviceMethod() {try {//lock.lock();  获得锁, 即使调用了线程的 interrupt() 方法, 也没有真正的中断线程//如果线程被中断了, 不会获得锁, 会产生异常lock.lockInterruptibly();System.out.println(Thread.currentThread().getName() + " --- begin lock");//执行一段耗时的操作for (int i = 0; i < Integer.MAX_VALUE; i++) {new StringBuilder();}System.out.println(Thread.currentThread().getName() + " --- end lock");} catch (InterruptedException e) {e.printStackTrace();} finally {System.out.println(Thread.currentThread().getName() + " === 释放锁");lock.unlock(); //释放锁}}}public static void main(String[] args) throws InterruptedException {Service s=new Service();Runnable r=new Runnable() {@Overridepublic void run() {s.serviceMethod();}};Thread t1=new Thread(r);t1.start();Thread.sleep(50);Thread t2=new Thread(r);t2.start();Thread.sleep(50);t2.interrupt(); //中断 t2 线程}
}

运行结果

8.6Demo6(lockInterruptibly()方法可以避免死锁)

package com.szh.lock;import java.util.concurrent.locks.ReentrantLock;/*** 通过 ReentrantLock 锁的 lockInterruptibly() 方法避免死锁的产生*/
public class Test06 {static class MyLock implements Runnable {//创建两个ReentrantLock等锁对象private static ReentrantLock lock1=new ReentrantLock();private static ReentrantLock lock2=new ReentrantLock();int lockNum; //定义整数变量,决定使用哪个锁,偶数用lock1,奇数用lock2public MyLock(int lockNum) {this.lockNum=lockNum;}@Overridepublic void run() {try {if (lockNum % 2 == 1) { //奇数, 先锁 1, 再锁 2lock1.lockInterruptibly();System.out.println(Thread.currentThread().getName() + "获得锁1,还需要获得锁2");Thread.sleep(1000);lock2.lockInterruptibly();System.out.println(Thread.currentThread().getName() + "同时获得了锁1与锁2");}else { //偶数, 先锁 2, 再锁 1lock2.lockInterruptibly();System.out.println(Thread.currentThread().getName() + "获得了锁2,还需要获得锁1");Thread.sleep(1000);lock1.lockInterruptibly();System.out.println(Thread.currentThread().getName() + "同时获得了锁1与锁2");}}catch (InterruptedException e) {e.printStackTrace();}finally {if (lock1.isHeldByCurrentThread()) { //判断当前线程是否持有该锁lock1.unlock();}if (lock2.isHeldByCurrentThread()) {lock2.unlock();}System.out.println(Thread.currentThread().getName() + "线程退出");}}}public static void main(String[] args) throws InterruptedException {MyLock myLock1=new MyLock(11);MyLock myLock2=new MyLock(22);Thread t1=new Thread(myLock1);Thread t2=new Thread(myLock2);t1.start();t2.start();//在 main 线程, 等待 3000 ms, 如果还有线程没有结束就中断该线程Thread.sleep(1000 * 3);//可以中断任何一个线程来解决死锁, t2 线程会放弃对锁 1 的申请, 同时释放锁 2, t1 线程会完成它的任务if (t2.isAlive()) {t2.interrupt();}}
}

运行结果

8.7Demo7(ReentrantLock的tryLock(long time, TimeUnit unit)方法)

package com.szh.lock;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;/*** tryLock(long time, TimeUnit unit) 的作用在给定等待时长内,* 锁没有被另外的线程持有, 并且当前线程也没有被中断, 则获得该锁.* 通过该方法可以实现锁对象的限时等待.*/
public class Test07 {static class TimeLock implements Runnable {private static ReentrantLock lock=new ReentrantLock(); //定义锁对象@Overridepublic void run() {try {//假设 t1 线程先持有锁, 完成任务需要 4 秒钟,//这个时候 t2 线程尝试获得锁, t2 线程在 3 秒内还没有获得锁的话, 那么它就不再等了,直接放弃if (lock.tryLock(3, TimeUnit.SECONDS)) {System.out.println(Thread.currentThread().getName() + "获得锁,执行耗时任务");Thread.sleep(1000 * 4);/*假设 t1 线程先持有锁, 完成任务需要 2 秒钟这个时候t2 线程尝试获得锁, t2 线程会一直尝试在它约定尝试的 3 秒内可以获得锁对象*///Thread.sleep(1000 * 2);}else {System.out.println(Thread.currentThread().getName() + "没有获得锁");}} catch (InterruptedException e) {e.printStackTrace();} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}}}}public static void main(String[] args) {TimeLock timeLock=new TimeLock();Thread t1=new Thread(timeLock);Thread t2=new Thread(timeLock);t1.setName("t1");t2.setName("t2");t1.start();t2.start();}
}

运行结果 

8.8Demo8(ReentrantLock的tryLock()方法)

package com.szh.lock;import java.util.concurrent.locks.ReentrantLock;/*** tryLock() 当锁对象没有被其他线程持有的情况下, 才会获得该锁定*/
public class Test08 {static class Service {private ReentrantLock lock=new ReentrantLock();public void serviceMethod() {try {if (lock.tryLock()) {System.out.println(Thread.currentThread().getName() + "获得锁定");Thread.sleep(1000 * 3); //模拟执行任务的时长}else {System.out.println(Thread.currentThread().getName() + "没有获得锁定");}}catch (InterruptedException e) {e.printStackTrace();}finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}}}}public static void main(String[] args) throws InterruptedException {Service service=new Service();Runnable r=new Runnable() {@Overridepublic void run() {service.serviceMethod();}};Thread t1=new Thread(r);t1.start();Thread.sleep(100);Thread t2=new Thread(r);t2.start();}
}

运行结果

8.9Demo9(tryLock()方法可以避免死锁)

package com.szh.lock;import java.util.concurrent.locks.ReentrantLock;/*** 使用 tryLock() 可以避免死锁*/
public class Test09 {static class MyLock implements Runnable {private static ReentrantLock lock1=new ReentrantLock();private static ReentrantLock lock2=new ReentrantLock();private int lockNum;public MyLock(int lockNum) {this.lockNum=lockNum;}@Overridepublic void run() {if (lockNum % 2 == 0) { //偶数先锁 1, 再锁 2while (true) {try {if (lock1.tryLock()) {System.out.println(Thread.currentThread().getName() + "获得了锁1,还想获得锁2");Thread.sleep(50);try {if (lock2.tryLock()) {System.out.println(Thread.currentThread().getName() + "同时获得了锁1与锁2,完成任务了");return;}} finally {if (lock2.isHeldByCurrentThread()) {lock2.unlock();}}}} catch (InterruptedException e) {e.printStackTrace();} finally {if (lock1.isHeldByCurrentThread()) {lock1.unlock();}}}}else { //奇数就先锁 2, 再锁 1while (true) {try {if (lock2.tryLock()) {System.out.println(Thread.currentThread().getName() + "获得了锁2,还想获得锁1");Thread.sleep(50);try {if (lock1.tryLock()) {System.out.println(Thread.currentThread().getName() + "同时获得了锁1与锁2,完成任务了");return;}} finally {if (lock1.isHeldByCurrentThread()) {lock1.unlock();}}}} catch (InterruptedException e) {e.printStackTrace();} finally {if (lock2.isHeldByCurrentThread()) {lock2.unlock();}}}}}}public static void main(String[] args) {MyLock lock1=new MyLock(11);MyLock lock2=new MyLock(22);Thread t1=new Thread(lock1);Thread t2=new Thread(lock2);t1.start();t2.start();//运行后, 使用 tryLock() 尝试获得锁, 不会傻傻的等待, 通过循环不停的再次尝试, 如果等待的时间足够长, 线程总是会获得想要的资源}
}

运行结果

Java中的Lock详解相关推荐

  1. Java中JDBC连接数据库详解

    今天动力节点java学院小编分享的是JDBC连接数据库的相关知识,希望通过看过此文,各位小伙伴对DBC连接数据库有所了解,下面就跟随小编一起来看看JDBC连接数据库的知识吧. 一.JDBC连接数据库概 ...

  2. JAVA中的Random详解

    JAVA中的Random详解 首先,在JDK自带的常用的random中有两个,这俩都是产生随机数的,不过一个是util下的random,另外一个是Math下的.我们分别介绍一下 util中的rando ...

  3. java中new关键字详解

    java中new关键字详解 在java中我们可以经常使用new来创建一个对象,但是这对于初学者来说可能只会使用却不能理解new关键字和它的语法 new关键字的语法 注意使用前先导包,一般我们使用ide ...

  4. Java中super关键字详解

    Java中super关键字详解 super有什么用? super什么时候不可以省略呢? super在内存图中是如何存在的呢? super使用时的注意事项 super有什么用? (1)当子类中构造方法第 ...

  5. Java中的byte详解

    Java中的byte详解 介绍 byte,即字节,由8位的二进制组成.在Java中,byte类型的数据是8位带符号的二进制数. 在计算机中,8位带符号二进制数的取值范围是[-128, 127],所以在 ...

  6. Java中Iterator迭代器详解

    目录 一.Java中Iterator迭代器详解 1.为什么需要迭代器 2.迭代器长什么样子 3.如何使用迭代器 使用步骤: 代码演示: 迭代器可以简化为增强型for循环: 4.Iterator与Lis ...

  7. Java 中IO流详解(附实例代码/面试题)

    Java I/O流详解 前言 一.I/O流是什么? 二.IO流分类: 1. 流程图: io流对象 2. io流的优缺点: 3. io 流Java中用途有哪些? 三.一些 io 实例 四.面试题: 前言 ...

  8. 【转载】java中泛型使用详解

    引入 Type接口 Class类 Method类 Field类 ParameterizedType接口 TypeVariable接口 类中定义泛型变量 方法中定义泛型变量 方法中泛型参数和泛型返回值 ...

  9. java 中的vector_详解Java中的Vector

    Vector实现了AbstractList抽象类和List接口,和ArrayList一样是基于Array存储的 Vector 是线程安全的,在大多数方法上存在synchronized关键字 //Vec ...

最新文章

  1. AI大潮来袭,Python将纳入高考?!你怎么看
  2. MySQL优化篇:排序分组优化
  3. C语言各类型变量所占的字节数
  4. Wireshark如何选择多行
  5. UNIX进程的创建,进程链和进程扇
  6. 修改linux bash shell PS1
  7. Jupiter黑客松——IPFS开发者大赛火热报名中!
  8. Object关于属性property的静态方法
  9. Java作业09-异常
  10. 漏洞挖掘、漏洞分析和漏洞利用
  11. 使用Seaborn和Pandas进行数据可视化
  12. Vue中使用节流Lodash throttle
  13. 宏正ATEN推出ALTUSEN全系列IP-Based远程机房管理方案
  14. Android两种存储用户临时数据的方式比较:Preferences Bundle
  15. 三位数除以两位数竖式计算没有余数_北京版二年级数学下册第一单元有余数的除法练习题【都有电子版】...
  16. 在构建好XPE操作系统上增加EWF功能
  17. Neo4j清空数据库
  18. Android音量控制器,音量控制器
  19. html5gps定位经纬度,html5 定位 获得当前位置的经纬度
  20. BI 工具常用图表用法

热门文章

  1. Virtual Judge-4099:队列和栈
  2. linux7查看ftp用户,linux vsftp查看ftp账号信息的方法
  3. 花落长春 宜尚、城市便捷双店开业 为东北旅游服务业注入全新理念
  4. 饕餮族北京之选(六)
  5. java正则表达式匹配路径_正则表达式教程之位置匹配详解
  6. python assert函数解析(最清晰的解释)
  7. 体验百度文心一言AI大模型生成俞敏洪、新东方和华尔街英语简介
  8. FISU全球首家运动鞋通证商家联盟 9月5日隆重首发WBF交易所主板区
  9. 织梦友情链接html,关于织梦DEDE友情链接调用的方法与技巧
  10. heracles压测平台介绍