文章目录

  • 前言
  • 一、synchronized的使用?
    • 1.0.1 实际变量非线程安全及解决
    • 1.0.2 当类中存在同步方法与非同步方法时(synchronized锁的是对象,而非方法)
    • 1.0.3 synchronized锁重入
    • 1.0.4 用同步代码块解决同步方法的弊端(运行效率)
    • 1.0.5 synchronized代码块的同步性
    • 1.0.6 synchronized(非this对象)的使用
    • 1.0.7 synchronized使用的3个结论
  • 二、多线程的死锁?
    • 2.0.1 死锁
  • 三、并发的三大性质
    • 3.0.1 使用volatile解决多线程的死循环(volatile的可见性)
    • 3.0.2 volatile的非原子性
    • 3.0.3 使用Atomic原子类进行i++操作实现原子性
    • 3.0.4 总结

前言

什么是时候会出现线程不安全?

多个线程同时访问同一个对象中一个全局实例变量,会出现"非线程安全",不同环境不同结果,接下来带大家分析各种同步与异步的问题。


提示:以下是本篇文章正文内容,下面案例可供参考

一、synchronized的使用?

1.0.1 实际变量非线程安全及解决

代码如下(示例):

服务层

public class Service {private int num=0;public void addI(String username){try {if (username.equals("a")){num=100;System.out.println("a set over!");Thread.sleep(2000);}else{num=200;System.out.println("b set over!");}System.out.println(username+" num="+num);} catch (InterruptedException e) {e.printStackTrace();}}
}

两个线程

public class ThreadA extends  Thread{private Service numRef;public ThreadA(Service numRef){super();this.numRef=numRef;}@Overridepublic void run() {super.run();numRef.addI("a");}
}
public class ThreadB extends Thread{private Service numRef;public ThreadB(Service numRef){super();this.numRef=numRef;}@Overridepublic void run() {super.run();numRef.addI("b");}
}

测试类

public class Run {public static void main(String[] args) {Service service =new Service();ThreadA a=new ThreadA(service);a.start();ThreadB b=new ThreadB(service);b.start();}
}

运行结果如下:

出现了 “非线程安全” 覆盖值的情况

解决1:

改变服务类,上一把synchronized锁

public class Service {private int num=0;synchronized public void addI(String username){try {if (username.equals("a")){num=100;System.out.println("a set over!");Thread.sleep(2000);}else{num=200;System.out.println("b set over!");}System.out.println(username+" num="+num);} catch (InterruptedException e) {e.printStackTrace();}}
}

运行结果如下:

线程进入时用synchronized声明的方法时就上锁,方法执行完成后自动解锁,那么下一个线程才会进入synchronized声明的方法里,实现同步运行,线程安全。

解决2:

改变服务类,将全局实例变量变成私有的

public class Service {synchronized public void addI(String username){int num=0;try {if (username.equals("a")){num=100;System.out.println("a set over!");Thread.sleep(2000);}else{num=200;System.out.println("b set over!");}System.out.println(username+" num="+num);} catch (InterruptedException e) {e.printStackTrace();}}
}

运行结果如下:

两个线程成异步执行自己改变自己的变量,互不影响

解决3:

不改变服务类,多创建一个对象

public class Run {public static void main(String[] args) {Service service =new Service();Service service1=new Service();ThreadA a=new ThreadA(service);a.start();ThreadB b=new ThreadB(service1);b.start();}
}

运行结果如下:

多个线程访问多个对象的实例变量,线程与对象一对一的关系,实现线程安全。

总结:多个线程对共享资源有写操作时,必须同步,非共享资源时,可以进行异步(保证线程安全)。
同步的单词为synchronized ,异步为asynchronized

1.0.2 当类中存在同步方法与非同步方法时(synchronized锁的是对象,而非方法)

代码如下(示例):

服务层

public class MyObject {synchronized public void methodA(){try {System.out.println("begin methodA threadName="+Thread.currentThread().getName()+"--begin time="+System.currentTimeMillis());Thread.sleep(5000);System.out.println("A end endTime="+System.currentTimeMillis());} catch (InterruptedException e) {e.printStackTrace();}}synchronized  public void methodB(){try {System.out.println("begin methodB threadName="+Thread.currentThread().getName()+"--begin time="+System.currentTimeMillis());Thread.sleep(5000);System.out.println("B end endTime="+System.currentTimeMillis());} catch (InterruptedException e) {e.printStackTrace();}}
}

两个线程

public class ThreadA extends Thread{private MyObject object;public ThreadA(MyObject object){super();this.object=object;}@Overridepublic void run() {super.run();object.methodA();}
}
public class ThreadB extends Thread{private MyObject object;public ThreadB(MyObject object){super();this.object=object;}@Overridepublic void run() {super.run();object.methodB();}
}

运行类

public class Run {public static void main(String[] args) {MyObject object=new MyObject();ThreadA a=new ThreadA(object);a.setName("A");ThreadB b=new ThreadB(object);b.setName("B");a.start();b.start();}
}

运行结果如下:

synchronized锁的是对象,而非方法,所以当A线程执行,对象上锁,B线程等待,当A线程执行完毕,释放锁,B线程才能执行methodB方法,所以是同步执行。

当methodA带synchronized,而methodB不带synchronized的情况。

代码如下(示例):
只改变服务层,其他不变
服务层

public class MyObject {synchronized public void methodA(){try {System.out.println("begin methodA threadName="+Thread.currentThread().getName()+"--begin time="+System.currentTimeMillis());Thread.sleep(5000);System.out.println("A end endTime="+System.currentTimeMillis());} catch (InterruptedException e) {e.printStackTrace();}}public void methodB(){try {System.out.println("begin methodB threadName="+Thread.currentThread().getName()+"--begin time="+System.currentTimeMillis());Thread.sleep(5000);System.out.println("B end endTime="+System.currentTimeMillis());} catch (InterruptedException e) {e.printStackTrace();}}
}

运行结果如下:

A线程持有了对象锁,但是B线程可以以异步的方式访问非synchronized类型的方法。

1.0.3 synchronized锁重入

概念:自己可以再次获取自己内部的锁,支持继承的环境中。
代码如下(示例):

服务层

public class Service {synchronized public void service1(){try {System.out.println("service1");Thread.sleep(10000);service2();} catch (InterruptedException e) {e.printStackTrace();}}synchronized public void service2(){try {System.out.println("service2");Thread.sleep(10000);service3();} catch (InterruptedException e) {e.printStackTrace();}}synchronized public void service3(){try {System.out.println("service3");Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}}synchronized public void service4(){System.out.println("service4");}
}

两个线程类

public class MyThread1 extends Thread{private  Service service;public MyThread1(Service service){this.service=service;}@Overridepublic void run() {super.run();service.service4();}
}
public class MyThread2 extends Thread{private  Service service;public MyThread2(Service service){this.service=service;}@Overridepublic void run() {super.run();service.service1();}
}

测试类

public class test1 {public static void main(String[] args) {Service service=new Service();MyThread2 myThread=new MyThread2(service);myThread.start();MyThread1 myThread1=new MyThread1(service);myThread1.start();}
}

运行结果如下:

在一个 synchronized方法/块的内部调用本类其他的synchronized方法/块时,是永远可以得到锁的。

1.0.4 用同步代码块解决同步方法的弊端(运行效率)

用synchronized声明的方法
代码如下(示例):

服务类

public class Taak {private String getData1;private String getData2;synchronized public  void doLongTimeTack(){try {System.out.println("begin task");Thread.sleep(3000);getData1="长时间处理任务从远程返回值1 threadName="+Thread.currentThread().getName();getData2="长时间处理任务从远程返回值2 threadName="+Thread.currentThread().getName();System.out.println(getData1);System.out.println(getData2);System.out.println("end task");} catch (InterruptedException e) {e.printStackTrace();}}
}
public class CommonUtils {public static long beginTime1;public static long endTime1;public static long beginTime2;public static long endTime2;
}

两个线程类

public class MyThread1 extends Thread{private Taak taak;public MyThread1(Taak taak){this.taak=taak;}@Overridepublic void run() {super.run();CommonUtils.beginTime1=System.currentTimeMillis();taak.doLongTimeTack();CommonUtils.endTime1=System.currentTimeMillis();}
}
public class MyThread2 extends Thread{private Taak taak;public MyThread2(Taak taak){this.taak=taak;}@Overridepublic void run() {super.run();CommonUtils.beginTime2=System.currentTimeMillis();taak.doLongTimeTack();CommonUtils.endTime2=System.currentTimeMillis();}
}

测试类

public class test {public static void main(String[] args) {Taak taak=new Taak();MyThread1 myThread1=new MyThread1(taak);myThread1.start();MyThread2 myThread2=new MyThread2(taak);myThread2.start();try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}long beginTime=CommonUtils.beginTime1;if (CommonUtils.beginTime2<CommonUtils.beginTime1){beginTime=CommonUtils.beginTime2;}long endTime=CommonUtils.endTime1;if (CommonUtils.endTime2>CommonUtils.endTime1){endTime=CommonUtils.endTime2;}System.out.println("耗时:"+((endTime-beginTime)/1000));}
}

运行结果如下:

用synchronized修饰的方法,是同步执行,保证了线程安全,但是效率低下,解决此问题,运用synchronized代码块

解决:

改变服务类,使用同步代码块

public class Taak {private String getData1;private String getData2;public  void doLongTimeTack(){try {System.out.println("begin task");Thread.sleep(3000);String  privateGetDate1="长时间处理任务从远程返回值1 threadName="+Thread.currentThread().getName();String  privateGetDate2="长时间处理任务从远程返回值2 threadName="+Thread.currentThread().getName();synchronized (this){getData1=privateGetDate1;getData2=privateGetDate2;System.out.println(getData1);System.out.println(getData2);System.out.println("end task");}} catch (InterruptedException e) {e.printStackTrace();}}
}

运行结果如下:

当一个线程访问object的一个 synchronized同步代码块时,另一个线程仍然可以访问该object对象中的非synchronized同步代码块,在保证线程安全的同时,同步异步相结合,实现大幅度提高运行效率。

1.0.5 synchronized代码块的同步性

在使用同步synchronized(this)代码块时要注意的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中所有其他synchronized(this)同步代码块的访问被阻塞,说明synchronized(this)使用的是同一个“锁”。

代码如下:

服务类

public class ObjectService {public void  serviceMethodA(){try {synchronized (this){System.out.println("A begin time="+System.currentTimeMillis());Thread.sleep(2000);System.out.println("A end time="+System.currentTimeMillis());}} catch (InterruptedException e) {e.printStackTrace();}}public void  serviceMethodB(){synchronized (this){System.out.println("B begin time="+System.currentTimeMillis());System.out.println("B end time="+System.currentTimeMillis());}}
}

两个线程类

public class ThreadA extends Thread{private ObjectService objectService;public ThreadA(ObjectService objectService){super();this.objectService=objectService;}@Overridepublic void run() {super.run();objectService.serviceMethodA();}
}
public class ThreadB extends Thread{private ObjectService objectService;public ThreadB(ObjectService objectService){super();this.objectService=objectService;}@Overridepublic void run() {super.run();objectService.serviceMethodB();}
}

测试类

public class run {public static void main(String[] args) {ObjectService objectService=new ObjectService();ThreadA threadA=new ThreadA(objectService);threadA.setName("a");threadA.start();ThreadB threadB=new ThreadB(objectService);threadA.setName("b");threadB.start();}
}

运行结果如下:

用synchronized同步代码块同步运行

当然如果将一个 synchronized(this)同步代码块换成用synchronized关键字修饰的方法,结果会变吗,答案是否定的,因为 synchronized(this)同步代码块将当前对象作为锁,使用synchronized关键字修饰的serviceMethodB()同步方法是将当前方法所在类的对象作为锁,都是一把锁,所以是同步运行,改变服务类。
代码如下:

服务类

public class ObjectService {public void  serviceMethodA(){try {synchronized (this){System.out.println("A begin time="+System.currentTimeMillis());Thread.sleep(2000);System.out.println("A end time="+System.currentTimeMillis());}} catch (InterruptedException e) {e.printStackTrace();}}synchronized   public void  serviceMethodB(){System.out.println("B begin time="+System.currentTimeMillis());System.out.println("B end time="+System.currentTimeMillis());}
}

运行结果如下:

1.0.6 synchronized(非this对象)的使用

synchronized(非this对象 x)格式的写法是将x对象本身作为“对象监视器”

当不使用 synchronized(非this对象 x)时
代码如下(示例):

服务类

public class MyOneList {private List list=new ArrayList();synchronized public void  add(String data){list.add(data);};synchronized  public int getSize(){return list.size();}
}public class MyService {public MyOneList addServiceMethod(MyOneList list,String data){try {           if (list.getSize() < 1) {Thread.sleep(2000);list.add(data);}} catch (InterruptedException e) {e.printStackTrace();}return list;}
}

线程类

public class MyThread1 extends Thread{private MyOneList list;public MyThread1(MyOneList list){this.list=list;}@Overridepublic void run() {super.run();MyService myService=new MyService();myService.addServiceMethod(list,"A");}
}
public class MyThread2 extends Thread{private MyOneList list;public MyThread2(MyOneList list){this.list=list;}@Overridepublic void run() {super.run();MyService myService=new MyService();myService.addServiceMethod(list,"B");}
}

测试类

public class test1 {public static void main(String[] args) {Service service=new Service();MyThread2 myThread=new MyThread2(service);myThread.start();MyThread1 myThread1=new MyThread1(service);myThread1.start();}
}

测试结果为listsize=2,因为是异步运行,第一个线程进入时延时了两秒,此时还没有进行add操作,第二个线程在延时过程中进入了该方法,所以就进行了两次add操作。

当使用使用 synchronized(非this对象 x)时。
更改MyService类

public class MyService {public MyOneList addServiceMethod(MyOneList list,String data){try {synchronized (list) {if (list.getSize() < 1) {Thread.sleep(2000);list.add(data);}}} catch (InterruptedException e) {e.printStackTrace();}return list;}
}

测试结果为listsize=1,因为synchronized(非this对象 x)是将x对象本身作为“对象监视器”,第一个线程进入时获得了锁,直到执行完add操作才释放锁,此时另一个线程进入该方法,if判断为false,实现了同步运行。

1.0.7 synchronized使用的3个结论

1.当多个线程同时执行synchronized(非this对象x){}同步代码块时呈同步效果
2.当其他线程执行x对象中synchronized同步方法时呈同步效果,
还可以将synchronized关键字应用在static静态方法上,就是对当前的*.java文件对应的class类的对象进行持锁。
当然在x对象中synchronized关键字应用在static和非static是异步执行的
在x对象中synchronized关键字应用在static静态方法和synchronized(x.class)是同步运行的,因为他们都是*.java文件对应的class类的对象进行持锁。
3.当其他线程执行x对象方法里面synchronized(this)同步代码块时也呈现同步效果
*注意:如果其他线程调用x对象中不加synchronized关键字的方法还是异步调用

二、多线程的死锁?

2.0.1 死锁

java线程死锁是一个经典的多线程问题,因为不同的线程都在等待根本不可能被释放的锁,从而导致所有的任务都无法继续完成。在多线程中,“死锁”是必须避免的,因为这会造成线程的“假死”

代码如下(示例):

服务类:

public class DealThread implements Runnable{public String username;public Object lock1=new Object();public Object lock2=new Object();public void setFlag(String username){this.username=username;}@Overridepublic void run() {if (username.equals("a")){synchronized (lock1){try {System.out.println("username="+username);Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock2){System.out.println("按lock1---lock2代码顺序执行了");}}}if (username.equals("b")){synchronized (lock2){try {System.out.println("username="+username);Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock1){System.out.println("按lock2---lock1代码顺序执行了");}}}}
}

测试类

public class run {public static void main(String[] args) {try {DealThread dealThread=new DealThread();dealThread.setFlag("a");Thread thread1=new Thread(dealThread);thread1.start();Thread.sleep(100);dealThread.setFlag("b");Thread thread2=new Thread(dealThread);thread2.start();} catch (InterruptedException e) {e.printStackTrace();}}
}

运行结果如下:

当setFlag赋值为a是第一个线程进入时获得lock1锁,延时100毫秒,赋值为b,第二个线程进入获取了lock2锁,lock1等待lock2锁,lock2等待lock1锁,自己持有自己的锁,又在等在对方的锁,造成假死,死锁。

三、并发的三大性质

1.原子性:指一组操作在执行时不能被打断,要么全部执行成功,要么全部执行失败,不能进行分割和中断。
2.可见性:A线程更改的数据B线程立马能看到。
3.有序性:如果在本线程内观察,所有的操作都是有序的;如果在一个线程观察另一个线程,所有的操作都是无序的。

3.0.1 使用volatile解决多线程的死循环(volatile的可见性)

代码如下(示例):

服务类

public class RunThread extends Thread{private boolean isRunning=true;public boolean isRunning() {return isRunning;}public void setRunning(boolean running) {isRunning = running;}@Overridepublic void run() {super.run();System.out.println("进入run了");while (isRunning == true){ }System.out.println("线程被停止了!");}
}}
}

测试类

public class run {public static void main(String[] args) {try {RunThread thread=new RunThread();thread.start();thread.sleep(1000);thread.setRunning(false);System.out.println("已经赋值为false");} catch (InterruptedException e) {e.printStackTrace();}}
}

运行结果如下:

为什么会造成死循环呢?因为在启动线程时,变量private boolean isRunning=true;分别存在于公共内存及线程的私有内存中,线程运行后一直在线程的私有变量中获取 isRunning,而代码 thread.setRunning(false);虽然被执行,跟新的却是公共内存中的isRuning变量变为false,操作的是2块内存地址的数据,所以一直是死循环状态

这个问题其实就是私有内存中的值和公共内存中的值不同步造成的,解决这样的问题就要使用volatile关键字了,他主要作用就是当前线程访问isRunning这个变量时,强制性从公共内存中进行取值

更改服务类
代码如下(示例):

服务类

public class RunThread extends Thread{volatile private boolean isRunning=true;public boolean isRunning() {return isRunning;}public void setRunning(boolean running) {isRunning = running;}@Overridepublic void run() {super.run();System.out.println("进入run了");while (isRunning == true){ }System.out.println("线程被停止了!");}
}}
}

运行结果如下:

通过使用volatile关键字,强制从公共内存中读取变量的值,增加了实例变量多个线程之间的可见性。

3.0.2 volatile的非原子性

非原子性是因为i++操作不是原子性的,会拆成三个步骤。
关键字volatile不支持i++运算原子性
代码如下(示例):

线程类

public class MyThread extends Thread{volatile public static  int count;private static void addCount(){for (int i = 0; i <100 ; i++) {count++;}System.out.println("count=" +count+" ----线程名称"+Thread.currentThread().getName());}@Overridepublic void run() {super.run();addCount();}
}

测试类

public class run {public static void main(String[] args) {MyThread[] t1=new MyThread[100];for (int i = 0; i < 100; i++) {t1[i]=new MyThread();}for (int i = 0; i <100 ; i++) {t1[i].start();}}
}

运行结果如下:

运算结果不是10000,说明多线程环境下volatile public static int count++运算操作是非原子性的。


但是synchronized具有原子性和可见性
改变线程类

线程类

public class MyThread extends Thread{public static  int count;synchronized private static void addCount(){for (int i = 0; i <100 ; i++) {count++;}System.out.println("count=" +count+" ----线程名称"+Thread.currentThread().getName());}@Overridepublic void run() {super.run();addCount();}
}

运行结果如下:

3.0.3 使用Atomic原子类进行i++操作实现原子性

线程类

public class AddCountThread extends Thread{private AtomicInteger count=new AtomicInteger(0);@Overridepublic void run() {super.run();for (int i = 0; i <10000 ; i++) {//原子上增加一个当前值System.out.println(count.incrementAndGet());}
}
}

测试类

public class Run {public static void main(String[] args) {AddCountThread countThread=new AddCountThread();Thread t1=new Thread(countThread);t1.start();Thread t2=new Thread(countThread);t2.start();Thread t3=new Thread(countThread);t3.start();Thread t4=new Thread(countThread);t4.start();Thread t5=new Thread(countThread);t5.start();}
}

运行结果如下:

原子操作是不能分割的整体,没有其他线程能够中断或检查正在原子操作中的变量。一个原子(atomic)类型就是一个原子操作的可用的类型,他可以在没有锁的情况下做到线程安全

3.0.4 总结

可见性:synchronized和volatile具有可见性。
原子性:使用synchronized具有原子性,使用Atomic原子类具有原子性
synchronized和volayile都禁止代码重排序

volatile和synchronized的使用场景是
1.当想要实现1个变量的值被更改时,让其他线程能取到最新值时,就要对变量使用volatile
2.如果多个线程对同一个对象中的同一个实例变量进行操作时,为了避免出现非线程安全,就要使用synchronized

对象及变量的并发访问(案例加总结)--学习笔记相关推荐

  1. java 并发变量_二、Java多线程编程 (对象及变量的并发访问)

    非线程安全 多个线程对同一个对象中的实例变量进行并发操作时会出现值被更改.值不同步的情况,进而影响程序的执行流程. 线程安全 线程安全就是获得实例变量的值是经过同步处理的.不会出现被更改不同步的情况. ...

  2. 两个线程同时访问一个变量_百战程序员:Java多线程对象及变量的并发访问

    在开发多线程程序时,如果每个多线程处理的事情都不一样,每个线程都互不相关,这样开发的过程就非常轻松.但是很多时候,多线程程序是需要同时访问同一个对象,或者变量的.这样,一个对象同时被多个线程访问,会出 ...

  3. 对象及变量的并发访问一

    一.多个线程操作一个对象实例  当两个线程同时访问一个没有同步的方法,如果两个线程同时操作业务对象中的实例变量,则有可能会出现"非线程安全问题". 1 package concur ...

  4. JavaWeb-综合案例(用户信息)-学习笔记01【列表查询】

    Java后端 学习路线 笔记汇总表[黑马程序员] JavaWeb-综合案例(用户信息)-学习笔记01[列表查询] JavaWeb-综合案例(用户信息)-学习笔记02[登录功能] JavaWeb-综合案 ...

  5. JavaWeb-综合案例(用户信息)-学习笔记05【分页查询功能】

    Java后端 学习路线 笔记汇总表[黑马程序员] JavaWeb-综合案例(用户信息)-学习笔记01[列表查询] JavaWeb-综合案例(用户信息)-学习笔记02[登录功能] JavaWeb-综合案 ...

  6. JavaWeb-综合案例(用户信息)-学习笔记02【登录功能】

    Java后端 学习路线 笔记汇总表[黑马程序员] JavaWeb-综合案例(用户信息)-学习笔记01[列表查询] JavaWeb-综合案例(用户信息)-学习笔记02[登录功能] JavaWeb-综合案 ...

  7. JavaWeb-综合案例(用户信息)-学习笔记06【复杂条件查询功能】

    Java后端 学习路线 笔记汇总表[黑马程序员] JavaWeb-综合案例(用户信息)-学习笔记01[列表查询] JavaWeb-综合案例(用户信息)-学习笔记02[登录功能] JavaWeb-综合案 ...

  8. JavaWeb-综合案例(用户信息)-学习笔记04【删除选中功能】

    Java后端 学习路线 笔记汇总表[黑马程序员] JavaWeb-综合案例(用户信息)-学习笔记01[列表查询] JavaWeb-综合案例(用户信息)-学习笔记02[登录功能] JavaWeb-综合案 ...

  9. oracle访问控制策略查看,【学习笔记】oracle fga 细粒度访问控制研究笔记

    天萃荷净 oracle研究中心学习笔记:分享一篇关于Oracle数据库细粒度访问控制的学习笔记,详细介绍了Implement fine-grained access control (Fine-Gra ...

最新文章

  1. 20162325 金立清 S2 W8 C17
  2. python实现tcp通信_Python实现简易TCP通信程序
  3. Linux:目录操作
  4. Oracle select 基础查询语句 day02
  5. 从挂科学渣到史上学历最低诺奖得主,他用17年时间重新证明自己
  6. android 离屏渲染 简单书,Android OpenGL ES 8.FrameBuffer离屏渲染
  7. 大数据之-Hadoop环境搭建_安装hadoop---大数据之hadoop工作笔记0018
  8. MATLAB信号处理之离散时间系统的时域分析
  9. Clover 文件结构
  10. Construct2太空射击游戏-Debug
  11. 黑苹果鼠标不动_MacOS系统:解决黑果睡眠唤醒后假死问题(如键盘鼠标无反应等)...
  12. Qt 新建文件夹并在该文件夹下新建文件
  13. 生成sign(签名),以及校验工具类
  14. 抖音,B站,小红书三大平台品牌投放特征与建议
  15. jfinal调用mysql存储过程 封装_jfinal 调用存储过程
  16. 微机原理——8086系统的概述与引脚介绍
  17. 【吐血整理】互联网大厂面试遇到的100道软件测试面试题+答案
  18. ‘SHIT’上最全有限状态机设计研究(一)-状态机介绍
  19. java 滑块验证码 开源,Java AWT生成滑动验证码
  20. 树莓派 Pico RP2040 MicroPython 编程 - 软件安装及设置

热门文章

  1. ubuntu安装解压版mysql数据库
  2. 日期选择器(年、季、月、周)展示
  3. Android技术分享
  4. 自考本科计算机哪个专业好,为什么自考本科选择计算机专业的人少
  5. 基于单片机的电子琴系统设计(#0424)
  6. 【数据结构课程设计报告】电话号码查询系统(Java实现)
  7. 初来乍到,请多关照。。。
  8. java excel 插件_轻量级的原生JavaScript的Excel插件——JExcel
  9. 房价 APP - 房地产数据查询, 房产投资参考,国家统计局房地产数据
  10. mysql服务无法启动问题