Java基础 浅解线程

2024-06-18 21:38
文章标签 java 基础 线程 浅解

本文主要是介绍Java基础 浅解线程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

线程:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

进程是程序由开始到结束,按照设计好的程序步骤执行,而线程却不然可以并发执行,比如现在的cup都是多核多线程的,方便我们可以处理多个事情,而是单一的事情。

这个时候就会有两个概念,并发性和并行性。

并发:当有多个线程在操作时,如果系统只有一个CPU(而此cp是单核),则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。这种方式我们称之为并发。

并行:当系统有一个以上CPU(如果单核需要多个cpu,如果是多核的化,一个cpu也可以)时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行。

多核CPU可以同时执行多个进程。

而并发和并行两个看似一样,其实区别很大,并发是指在处理两个以上进程,我们在使用的时候感觉是在同时进行,其实不是而是cpu在通过时间间隔,相互切换而让使用者察觉不到。而并行就不是了因为现在很多cpu都是多核,可以同时运行两个以上的进行(当然不能超过多核的数量)。

所以微观上说,多核CPU可以同时执行多个进程,进程数与CPU核数相当。但宏观上说,由于CPU会分时间片执行多个进程,所以实际执行进程个数会远多于CPU核数。

顺便提一下,还有个协程,而协程一般出现再爬虫中,暂时不说,这一篇文章主要是写java 的线程。

线程一般有5个状态:创建线程,等待(也有说六种,其中又添加超时等待),运行线程,线程阻塞,线程结束

java 中一般线程的创建有三种方式,继承Thread类,实现Runnable 接口和通过callable来创建线程。

一般基础的使用的前两种,而第三种一般时候不会讲解,就算讲解也是简单提一下,不过工作中会用。

继承Thread

public class Test extends Thread{@Overridepublic void run() {// TODO Auto-generated method stub}
g
}

在使用的时候,一般我们都会重写run方法,而线程的调用,不是通过对象调用run方法,而是通过start方法如下:

public class Test extends Thread{public static void main(String[] args) {Test test=new Test();test.start();}@Overridepublic void run() {System.out.println("线程一个");}}
//输出
线程一个

线程类和普通的类一样可以有自己的构造方法,以及属性。当然是 有需要的时候可以添加。

实现Runable接口

class sonThread implements Runnable{@Overridepublic void run() {// TODO Auto-generated method stub}
}

实现runnable接口如上,但是其调用又与继承thread类有些不同

public class Test {public static void main(String[] args) {MyThread myThread=new MyThread();new Thread(myThread).start();}
}class MyThread implements Runnable{@Overridepublic void run() {System.out.println("实现runnable接口的线程");	}}
//输出
实现runnable接口的线程

有时候新jdk中调用会用lambda方式进行调用,如果不太了解,可以看我以前的文章 java基础 浅解1.8新增lambda表达式

Callable

class MyThread implements Callable<String>{//callable 可以返回值,所以泛型根据自己来定义,目前用String@Overridepublic String call() throws Exception {// TODO Auto-generated method stubreturn null;//因为上面泛型是String,所以这个返回String}
}

Callable调用线程又有不同,调用如下:

public class Test {public static void main(String[] args) {try {FutureTask<String> task=new FutureTask<>(new MyThread());Thread my=new Thread(task, "test");my.start();			} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
// 上面的每次需要新建一个线程,//下面这个是常用的一种方式,直接用线程池,进行的一个调用,一般格式如下:
public class Test {public static void main(String[] args) {try {
//		 可以新建一个同时启动10个线程,ExecutorService  service=Executors.newFixedThreadPool(10);
//		 提交执行任务Future<String> task=service.submit(new MyThread());
//			得到返回值String str=task.get();
//			节约资源,关闭服务service.shutdown();		} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}

在调用Callable的线程时,用的java类其实用的时java中的juc中的类。如果有必要可以单独再写一篇文章进行浅解。

线程给予了我们最大可能利用电脑的性能之外,还造成了一个新的问题,就是数据的不安全性。比如银行卡提钱的功能,同时两人对一个卡的钱进行提款,当然在两个不同的银行。

class MyThread implements Runnable{int moneyCount=500;@Overridepublic void run() {if (moneyCount-300>0) {try {Thread.sleep(10);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}moneyCount=moneyCount-300;System.out.println(Thread.currentThread().getName()+"拿了300,账户里面还有"+moneyCount);}}}public class Test {public static void main(String[] args) {Runnable mythread=new MyThread();new Thread(mythread,"丈夫").start();new Thread(mythread,"老婆").start();}
}
//输出
老婆拿了300,账户里面还有-100
丈夫拿了300,账户里面还有-100

看到这个结果,惊不惊喜意不意外,当然如果没有sleep的话,可能不会输出两行,但是程序游行方法的运行,应该会超过10毫秒的,所以这个只是塑造了一个假想,不要过度思考即可。

这个时候,应该有疑问了,为什么不用thread类而使用Runnable这个接口。这个改成thread是否可行。先不解释,还是继续上代码;

class MyThread extends Thread{int moneyCount=500;@Overridepublic void run() {if (moneyCount-300>0) {try {Thread.sleep(10);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}moneyCount=moneyCount-300;System.out.println(Thread.currentThread().getName()+"拿了300,账户里面还有"+moneyCount);}}}public class Test {public static void main(String[] args) {MyThread mythread=new MyThread();mythread.start();mythread.start();}
}
//输出
Exception in thread "main" java.lang.IllegalThreadStateExceptionat java.lang.Thread.start(Unknown Source)at test.Test.main(Test.java:14)
Thread-0拿了300,账户里面还有200

编译无问题,但是运行就报错了,为什么因为继承thread类的线程,只能运行一次start。

runable接口和thread类的一个区别,就是runable创建的线程,可以操作同一个对象,而thread创建的线程,却无法共同操作一个对象。

这个说明一点,如果类和对象有区别的,同一个类,只有通过new创建一次就是一个新对象。不要把对象类和对象搞混乱了。多嘴说一下 thread类其实也是继承了runable。所以用一个非官方的方式来调用也是可以的

class MyThread extends Thread{int moneyCount=500;@Overridepublic void run() {if (moneyCount-300>0) {try {Thread.sleep(10);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}moneyCount=moneyCount-300;System.out.println(Thread.currentThread().getName()+"拿了300,账户里面还有"+moneyCount);}}}public class Test {public static void main(String[] args) {MyThread mythread=new MyThread();new Thread(mythread,"丈夫").start();new Thread(mythread,"老婆").start();}
}//输出
老婆拿了300,账户里面还有-100
丈夫拿了300,账户里面还有-100

而这个调用方式可行,但是一般继承了thread类,然后进行调用多线程的时候,不会这样调用,因为在jdk帮助文档中,继承thread类中的调用时没有这样使用的。所以知道即可,暂不深究,所以按照传统的继承thread类的方式进行多线程调用即可,所以理解为,继承thread类不可操作同一对象。

而在一般使用多线程的时候,常用的是runnable接口实现因为有以下有点或者说与继承thread类实现多线程的区别。

  • Runnable的优点
    • 使用runable接口实现线程,可以继承多个接口,而thread类只能继承一个.
    • runnable可以共享一个对象,而thread却无法相互独立无法共享资源

上面说了这样多,可以多线程的弊端就是其无法保证数据的安全,java未来保证其数据的安全,在多线程中有synchronized关键字和lock类,保证数据的安全.

简单提一些几个个线程中常用的两个方法,一个是sleep.也就是让本线程暂时休眠一下需要写时间.

还有一个就是礼让yield,不过这个礼让是看心情,就像在公交车上碰见一个健壮的老人,可以让也可以不让.

还有一个重要的方式,就是强制插队,也就是类似的vip,我来了就需要我先办理事情,其他人后面等着.也就是join方法

class MyThread implements Runnable{public  void run() {for (int i=0;i<10;i++) {try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(Thread.currentThread().getName()+"的线程循环"+i);}}
}public class Test {public static void main(String[] args) {Runnable mythread=new MyThread();Thread myt=new Thread(mythread,"runnable");myt.start();for (int i=0;i<10;i++) {try {myt.join();} catch (InterruptedException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}try {Thread.sleep(100);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("main"+i);}}}//输出
runnable的线程循环0
runnable的线程循环1
runnable的线程循环2
runnable的线程循环3
runnable的线程循环4
runnable的线程循环5
runnable的线程循环6
runnable的线程循环7
runnable的线程循环8
runnable的线程循环9
main0
main1
main2
main3
main4
main5
main6
main7
main8
main9

虽然线程中运行的sleep时间, 比main中要长,单是输出的结果就是main需要等线程运行完,才会继续运行主线程,如果有疑问可以将join()注释掉看结果.

既然有强制插队,所以线程应该也有自己的权重,也就是自己优先级,

public class Test {public static void main(String[] args) {System.out.println(Thread.currentThread().getPriority());Runnable runnale=()->{System.out.println(Thread.currentThread().getPriority());};new Thread(runnale).start();}
}
//输出
5
5public class Test {public static void main(String[] args) {System.out.println(Thread.currentThread().getPriority());Runnable runnale=()->{System.out.println(Thread.currentThread().getPriority());};Thread mythread=new Thread(runnale);mythread.setPriority(Thread.MAX_PRIORITY);mythread.start();Thread mythread1=new Thread(runnale);mythread1.setPriority(Thread.MIN_PRIORITY);mythread1.start();}
}
//输出
5
10
1    

可以看出其优先级默认都是5,优先级的默认范围是1–10;

现在好奇了优先级有什么用呢?

public class Test {public static void main(String[] args) {System.out.println(Thread.currentThread().getPriority());Runnable runnable=new MyThread();Thread mythread=new Thread(runnable,"a");mythread.start();Thread mythread1=new Thread(runnable,"b");mythread1.setPriority(8);mythread1.start();}
}class MyThread implements Runnable{public  void run() {for (int i=0;i<1000;i++) {System.out.println(Thread.currentThread().getName()+"的线程循环"+i);}}

​ 其中mythread1.setPriority(8),这个如果不存在的话,运行a,b两个线程结束的话都有先后,如果添加了mythread1.setPriority(8)那么b线程的结束概率要高于a.

  • 注意优先级
    • 第一,优先级是java虚拟机对线程资源的一个优先权,但不是绝对一定是先.
    • 第二:因为cpu运行的相互交替,而执行优先级也会执行优先级低的.

synchronized关键字

synchronized是为一个同步的方法或方法块进行加锁,在其处理完事务之后再自动释放.但是两者都是锁定的是当前对象.

  • 注意,synchronized可以修饰方法,修饰代码块,但是不能修饰构造器、成员变量等。

所以在使用中请注意.

同步方法

同步方法,格式就是在其多线程下操作会出错的方法中加入synchronized关键字即可

class MyThread implements Runnable{int moneyCount=500;//synchronized 同步方法public synchronized void run() {System.out.println(Thread.currentThread().getName()+"来银行要取钱");if (moneyCount-300>0) {try {Thread.sleep(10);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}moneyCount=moneyCount-300;System.out.println(Thread.currentThread().getName()+"拿了300,账户里面还有"+moneyCount);}}}public class Test {public static void main(String[] args) {Runnable mythread=new MyThread();new Thread(mythread,"丈夫").start();new Thread(mythread,"老婆").start();}
}
//输出
//丈夫来银行要取钱
//丈夫拿了300,账户里面还有200
//老婆来银行要取钱

如果对于结果有疑问,可以调换老婆和丈夫的顺序,然后看输出结果,都会只有在前面的调用才会输出拿到钱.可以多此运行,防止有偶然情况.

同步代码块

格式如下:

synchronized(obj)
{//需要被同步的代码块
}

同步代码块一定要注意的是obj这个对象,而在此方法中操作的具体对象是谁,这个需要很明白,不然就会在同步代码块中出现错误.

class MyThread implements Runnable{int moneyCount=500;// 同步方法public  void run() {synchronized(this){System.out.println(Thread.currentThread().getName()+"来银行要取钱");if (moneyCount-300>0) {try {Thread.sleep(10);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}moneyCount=moneyCount-300;System.out.println(Thread.currentThread().getName()+"拿了300,账户里面还有"+moneyCount);}}
}
}public class Test {public static void main(String[] args) {Runnable mythread=new MyThread();new Thread(mythread,"丈夫").start();new Thread(mythread,"老婆").start();}
}
//输出
//丈夫来银行要取钱
//丈夫拿了300,账户里面还有200
//老婆来银行要取钱

当然这个中的操作对象就是其本身,所以我们用this代替即可,如果怀疑输出结果的偶然性也可以调整顺序多次尝试.

记住无论什么对象调用锁,一定要注意synchronized只会为自己对象调用加锁,其他对象调用毫无作用.

class MyThread implements Runnable{int moneyCount=500;// 同步方法public  void run() {synchronized(this){System.out.println(Thread.currentThread().getName()+"来银行要取钱");if (moneyCount-300>0) {try {Thread.sleep(10);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}moneyCount=moneyCount-300;System.out.println(Thread.currentThread().getName()+"拿了300,账户里面还有"+moneyCount);}}
}
}public class Test {public static void main(String[] args) {Runnable mythread=new MyThread();new Thread(mythread,"张三").start();new Thread(mythread,"张三老婆").start();Runnable mythread2=new MyThread();new Thread(mythread2,"李四").start();new Thread(mythread2,"李四老婆").start();}
}//输出
张三来银行要取钱 //a1
李四来银行要取钱 //b1
张三拿了300,账户里面还有200 //a2
李四拿了300,账户里面还有200 //b2
张三老婆来银行要取钱 //a3
李四老婆来银行要取钱 //b3

可以看出两个对象,直接的同步代码块直接毫无影响,换程同步方法结果也是一样的(说a三条的循环,以b三条的循环,其中ab之间的前后可能会有差异),所以不要单独的看代码觉得调用了同一个类,应该相互制约,但是synchronized只会为调用的对象进行个安全加锁.

  • 死锁

当然在使用synchronized同步的时候,会有死锁现象,就是两个条件相互制约,但是同时需要对方打开.比如两个人吃饭,但是只有一套碗筷,然后习惯不同,一人喜欢先拿碗,一个喜欢先拿筷,然后都在等对方使用完.


class Bowl {}
class Chopsticks {}class EatFood implements Runnable{static Bowl bowl =new Bowl() ;//静态属性,也就是类属性,如有以为可以去看我写的关于对象的文章static Chopsticks chopsticks  =new Chopsticks() ;int peopleType;String peopleName;public EatFood( int peopleType,String peopleName) {this.peopleType=peopleType;this.peopleName=peopleName;}@Overridepublic void run() {try {this.eat();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}private  void eat() throws InterruptedException {if (this.peopleType==0) {//因为前提是筷子和碗都只有一个,所以两个都需要加一个锁synchronized(bowl) {System.out.println(this.peopleName+"拿到了碗,在等筷子");Thread.sleep(1000);synchronized(chopsticks) {System.out.println(this.peopleName+"拿到了筷子,吃饭了");}}}else {synchronized(chopsticks) {System.out.println(this.peopleName+"拿到了筷,在等子碗");Thread.sleep(1000);synchronized(bowl) {System.out.println(this.peopleName+"拿到了子碗,吃饭了");}}}}
}public class Test {public static void main(String[] args) {Runnable eatFood0=new EatFood(0, "阿黄");new Thread(eatFood0).start();Runnable eatFood1=new EatFood(1, "阿红");new Thread(eatFood1).start();}
}
//输出
阿黄拿到了碗,在等筷子
阿红拿到了筷,在等子碗

因为都在等彼此的资源,然而都没有释放,所以进入死锁状态.

上面我们一直提出,synchronized同步代码块和同步方法,都说是加锁,那线程有没有一个lock的类或者关键字呢?的确有而且一般会出现一对那就是lock’和unlock.

当然lock本身就是一个接口,所以一般的时候我们使用实现这个接口的ReentrantLock类.

一般使用lock格式

lock.lock();//lock是实现lock接口的类new成的对象名
try{} finally {
lock.unlock();}

具体实例如下:

class MyThread implements Runnable{int moneyCount=500;Lock lock=new ReentrantLock();public  void run() {lock.lock();try {System.out.println(Thread.currentThread().getName()+"来银行要取钱");if (moneyCount-300>0) {try {Thread.sleep(10);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}moneyCount=moneyCount-300;System.out.println(Thread.currentThread().getName()+"拿了300,账户里面还有"+moneyCount);}} finally {lock.unlock();}}}public class Test {public static void main(String[] args) {Runnable mythread=new MyThread();new Thread(mythread,"张三").start();new Thread(mythread,"张三老婆").start();}
}//输出
张三来银行要取钱 
张三拿了300,账户里面还有200 
张三老婆来银行要取钱 

这个时候肯定有人想到了同步代码块中的京东案例,生产者和消费者的案例,而如果用synchronized关键字肯定不行的,所以这个就了一个wait、notify、notifyAll 三个方法.

因为synchronized可以阻止并发更新同一个共享资源,实现了同步,但是无法实现不同线程之间的信息消息传递(通信).

wait()或wait(long time) 让当前线程等待.

notify()唤醒当前对象的等待线程,如果在当前线程中有多个等待的就随机唤醒一个.

notifuAl()唤醒当前对象线程的所有等待的线程.当然优先级的线程会优先调度.

注意:上面四个方法都是object的方法,都只能在同步方法或者同步代码块中使用,否则就会抛出异常.>

那下面我们就开始实现生产者和消费者的案例,通过synchronized关键字进行实现.

一般消费者和生产者这个案例一般会分成三个部分:

生产者:生产者就是生产资源的类

消费者:消费者就是消耗资源的类

缓存区:无论如何生产者和消费者必须有一个一个部分,因为生产者和消费的交互或者说其有关系的部分.同样synchronized同步必须同同一个对象,因此这个是其必须拥有的部分.

具体看代码,以下代码黏贴可以用

class SysContain{Chicken[] chickens=new Chicken[10];int count=0;public synchronized void push(Chicken chicken) {
//		盘对事发后容器满了,西游等待消费者消费if (count==chickens.length) {try {this.wait();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}chickens[count]=chicken;count++;this.notify();}public synchronized Chicken pop() {if(count==0) {try {this.wait();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}count--;Chicken chicken=chickens[count];this.notify();return chicken;}}class Customer extends Thread{SysContain contain;public Customer(SysContain contain) {this.contain=contain;}public void run() {for (int i=0;i<100;i++) {System.out.println("========消费了id为"+contain.pop().id+"的鸡");
//			contain。push}}}class Producter extends Thread{SysContain contain;public Producter(SysContain contain) {this.contain=contain;}public void run() {for (int i=0;i<100;i++) {System.out.println("生产了id为"+i+"只鸡");contain.push(new Chicken(i));}}}
class Chicken{int id;public Chicken(int id) {this.id=id;}
}
public class Test {public static void main(String[] args) {SysContain contain=new SysContain();new Customer(contain).start();new Producter(contain).start();}
}

既然关键字synchronized和lock都可以实现同步效果,那么两者有什么区别呢?

  • Synchronized是关键字,而lock是一个接口。
  • Synchronized不需要用户手动释放同步锁,无论是Synchronized方法还是Synchronized代码块,指向完毕之后,系统会自动让线程释放对锁的占用(哪怕发生异常也是自动释放)。而lock侧必须需要用户去手动释放锁。如果不lock不手动释放错就会出现死锁的现象,所以一般unlock放在finally块中。
  • 通过lock可以指定有没有成功获取锁,以及在等待锁的线程响应中断,而synchronized却不行,其不但无法时钟释放成功上锁,以及中途中断(如果你要说重启电脑和服务器器的话不就中断了吗,这样想的话就当我没说)。
  • lock可以提高多个线程进行读操作的效率。 在性能上来说,如果竞争资源不激烈的话,两者性功是差别不大,但是当竞争激烈的时候,也就是大量线程同时竞争的话,lock的性能就要远远优于synchronized。

这篇关于Java基础 浅解线程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1073126

相关文章

Java五子棋之坐标校正

上篇针对了Java项目中的解构思维,在这篇内容中我们不妨从整体项目中拆解拿出一个非常重要的五子棋逻辑实现:坐标校正,我们如何使漫无目的鼠标点击变得有序化和可控化呢? 目录 一、从鼠标监听到获取坐标 1.MouseListener和MouseAdapter 2.mousePressed方法 二、坐标校正的具体实现方法 1.关于fillOval方法 2.坐标获取 3.坐标转换 4.坐

Spring Cloud:构建分布式系统的利器

引言 在当今的云计算和微服务架构时代,构建高效、可靠的分布式系统成为软件开发的重要任务。Spring Cloud 提供了一套完整的解决方案,帮助开发者快速构建分布式系统中的一些常见模式(例如配置管理、服务发现、断路器等)。本文将探讨 Spring Cloud 的定义、核心组件、应用场景以及未来的发展趋势。 什么是 Spring Cloud Spring Cloud 是一个基于 Spring

RedHat运维-Linux文本操作基础-AWK进阶

你不用整理,跟着敲一遍,有个印象,然后把它保存到本地,以后要用再去看,如果有了新东西,你自个再添加。这是我参考牛客上的shell编程专项题,只不过换成了问答的方式而已。不用背,就算是我自己亲自敲,我现在好多也记不住。 1. 输出nowcoder.txt文件第5行的内容 2. 输出nowcoder.txt文件第6行的内容 3. 输出nowcoder.txt文件第7行的内容 4. 输出nowcode

Javascript高级程序设计(第四版)--学习记录之变量、内存

原始值与引用值 原始值:简单的数据即基础数据类型,按值访问。 引用值:由多个值构成的对象即复杂数据类型,按引用访问。 动态属性 对于引用值而言,可以随时添加、修改和删除其属性和方法。 let person = new Object();person.name = 'Jason';person.age = 42;console.log(person.name,person.age);//'J

java8的新特性之一(Java Lambda表达式)

1:Java8的新特性 Lambda 表达式: 允许以更简洁的方式表示匿名函数(或称为闭包)。可以将Lambda表达式作为参数传递给方法或赋值给函数式接口类型的变量。 Stream API: 提供了一种处理集合数据的流式处理方式,支持函数式编程风格。 允许以声明性方式处理数据集合(如List、Set等)。提供了一系列操作,如map、filter、reduce等,以支持复杂的查询和转

Vim使用基础篇

本文内容大部分来自 vimtutor,自带的教程的总结。在终端输入vimtutor 即可进入教程。 先总结一下,然后再分别介绍正常模式,插入模式,和可视模式三种模式下的命令。 目录 看完以后的汇总 1.正常模式(Normal模式) 1.移动光标 2.删除 3.【:】输入符 4.撤销 5.替换 6.重复命令【. ; ,】 7.复制粘贴 8.缩进 2.插入模式 INSERT

Java面试八股之怎么通过Java程序判断JVM是32位还是64位

怎么通过Java程序判断JVM是32位还是64位 可以通过Java程序内部检查系统属性来判断当前运行的JVM是32位还是64位。以下是一个简单的方法: public class JvmBitCheck {public static void main(String[] args) {String arch = System.getProperty("os.arch");String dataM

详细分析Springmvc中的@ModelAttribute基本知识(附Demo)

目录 前言1. 注解用法1.1 方法参数1.2 方法1.3 类 2. 注解场景2.1 表单参数2.2 AJAX请求2.3 文件上传 3. 实战4. 总结 前言 将请求参数绑定到模型对象上,或者在请求处理之前添加模型属性 可以在方法参数、方法或者类上使用 一般适用这几种场景: 表单处理:通过 @ModelAttribute 将表单数据绑定到模型对象上预处理逻辑:在请求处理之前

eclipse运行springboot项目,找不到主类

解决办法尝试了很多种,下载sts压缩包行不通。最后解决办法如图: help--->Eclipse Marketplace--->Popular--->找到Spring Tools 3---->Installed。

JAVA读取MongoDB中的二进制图片并显示在页面上

1:Jsp页面: <td><img src="${ctx}/mongoImg/show"></td> 2:xml配置: <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001