线程数据共享和安全ThreadLocal
什么是ThreadLocal
threadLocal快速入门
实现同一线程资源数据共享
基础类Pig,Dog;
public class Pig {
}
public class Dog {
}
T1类(主线程)
public class T1 {
//创建ThreadLocal对象,做成public ,static ;
public static ThreadLocal<Object> threadLocal1 = new ThreadLocal<>();
//T1类中的静态内部类(线程类)
public static class Task implements Runnable{
@Override
public void run() {
//
Dog dog = new Dog();
Pig pig = new Pig();
//给threadLocal1对象放置set dog ,隔山打牛
System.out.println("Task中放入了dog="+dog);
threadLocal1.set(dog);
System.out.println("Task在run方法中线程="+Thread.currentThread().getName());
new T1Service().update();
}
}
public static void main(String[] args) {
//启动线程
new Thread(new Task()).start();//主线程启动一个新的线程;
}
}
T2DAO类(取出同一线程数据)
public class T2DAO {
public void update(){
//取出线程关联的thread Local1对象的数据
Object o = T1.threadLocal1.get();
//获取当前线程名
String name = Thread.currentThread().getName();
System.out.println("在T2DAO的update()线程是="+name+"取出dog="+o);
}
}
T1Service类(取出同一线程类并调用T2DAO中方法)
public class T1Service {
//取出threadLocal1对象关联的对象;
public void update(){
Object o = T1.threadLocal1.get();
//获取当前线程名
String name = Thread.currentThread().getName();
System.out.println("在T1Service中的update() 线程name = "
+name+"dog="+o);
//调用dao-update
new T2DAO().update();
}
}
同一线程数据共享效果图
threadLocal源码分析?
流程图set方法
set(T value)
/*
老韩解读
public void set(T value) {
//1. 获取当前线程, 关联到当前线程!
Thread t = Thread.currentThread();
//2. 通过线程对象, 获取到ThreadLocalMap
// ThreadLocalMap 类型 ThreadLocal.ThreadLocalMap
ThreadLocalMap map = getMap(t);
//3. 如果map不为null, 将数据(dog,pig..) 放入map -key:threadLocal value:存放的数据
// 从这个源码我们已然看出一个threadlocal只能关联一个数据,如果你set, 就会替换
//4. 如果map为null, 就创建一个和当前线程关联的ThreadLocalMap, 并且该数据放入
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
*/
/**
* 1,源代码
* public void set(T value) {
* Thread t = Thread.currentThread();
* ThreadLocalMap map = getMap(t);
* if (map != null) {
* map.set(this, value);
* } else {
* createMap(t, value);
* }
* }
* 2,分析源代码执行过程,带入示例进行贯通
* 3.01,当启动主线程时执行run(),set方法()->Thread t = Thread.currentThread();获取当前线程
* 3.02,getMap(t)->源代码: ThreadLocalMap getMap(Thread t) {
* return t.threadLocals;
*
* }
* t.threadLocals-> ThreadLocal.ThreadLocalMap threadLocals = null;这里表达了
* // getMap(thread t)根据当前线程,返回对应的threadLocal的静态内部类的threadLocalMap
* // static class ThreadLocalMap {
* static class Entry extends WeakReference<ThreadLocal<?>> {
* Object value;
*
* Entry(ThreadLocal<?> k, Object v) {
* super(k);
* value = v;
* }
* }
* }
* //后续执行:
* 3.03,判断当前threadLocals(ThreadLocalMap是否为空),不为空
* * map.set(this, value);this->ThreadLocal;value->Object
* 3.04,不为空就创建,并添加进ThreadLocalMap
*
*
*/
get()
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// ThreadLocal.ThreadLocalMap threadLocals = null;
// ThreadLocalMpa是ThreadLocal的静态内部类;
// getMap(thread t)根据当前线程,返回对应的threadLocal的静态内部类的threadLocalMap
/**
* 老韩解读 get
* public T get() {
* //1. 先得到当前的线程对象
* Thread t = Thread.currentThread();
* //2.通过线程获取到对应的ThrealLocalMap
* ThreadLocalMap map = getMap(t);
* if (map != null) {
* //3. 如果map不为空, 根据当前的 threadlocal对象,得到对应的Entry
* ThreadLocalMap.Entry e = map.getEntry(this);//Entry为ThreadLocalMap的静态内部类
* //4. 如果e 不为null
* if (e != null) {
* @SuppressWarnings("unchecked")
* //返回当前threadlocal关联的数据value
* T result = (T)e.value;
* return result;
* }
* }
* return setInitialValue();
* }
*
*/
根据当前的 threadlocal对象,得到对应的Entry?
内部类/线程(review)
内部类分类
局部内部类
为什么外部类访问局部内部类的成员访问方式:创建对象,再访问?
鱼聪明解惑:
在 Java 中,外部类是无法直接访问局部内部类的成员的。这是因为局部内部类是定义在某个方法或作用域内的内部类,其作用域仅限于所定义的方法或作用域内部。
要访问局部内部类的成员,可以通过创建局部内部类的对象,然后通过该对象来访问其成员。**这是因为局部内部类的对象是在其所在的方法或作用域内创建的,**可以访问该方法或作用域内的变量和成员,包括外部类的成员。
以下是一个示例代码,演示了外部类访问局部内部类的成员的方法:
public class OuterClass {
private int outerData = 10; // 外部类的成员变量
public void accessLocalInnerClass() {
class LocalInnerClass { // 局部内部类
private int innerData = 20; // 局部内部类的成员变量
public void printData() {
System.out.println("Outer data: " + outerData); // 外部类成员的访问
System.out.println("Inner data: " + innerData); // 内部类成员的访问
}
}
LocalInnerClass inner = new LocalInnerClass(); // 创建局部内部类的对象
inner.printData(); // 通过对象访问局部内部类的成员
}
}
在上述代码中,accessLocalInnerClass()
方法创建了一个局部内部类 LocalInnerClass
的对象 inner
,然后可以通过该对象访问局部内部类的成员变量和方法。
需要注意的是,由于局部内部类是在方法内部定义的,所以它的作用域也仅限于该方法内部。因此,在其他方法或作用域中是无法访问局部内部类的。
匿名内部类
匿名内部类示例Code
基于接口的局部内部类
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer04 outer04 = new Outer04();
outer04.method();
}
}
interface IA {
// 接口public
void cry();
}
class Outer04 { //外部类
private int n1 = 10;//属性
public void method() {//方法
//基于接口的局部内部类(带名字的)
// class Outer04$1 implements IA {
// @Override
// public void cry() {
// System.out.println("老虎叫唤...");
// }
// }
//7. jdk 底层在创建匿名内部类 Outer04$1,立即马上就创建了 Outer04$1 实例,并且把地址
// 返回给 tiger
//8. 匿名内部类使用一次,就不能再使用
IA tiger = new IA() {
@Override
public void cry() {
System.out.println("老虎叫唤...");
}
};
//注:匿名内部内也作用域也是只能在定义它的方法或代码块中
/**
* 需要注意的是,由于局部内部类或匿名内部类是在方法内部定义的,
* 所以它的作用域也仅限于该方法内部。
* 因此,在其他方法或作用域中是无法访问局部内部类(匿名内部类)的。
* . 匿名内部类使用一次,就不能再使用!!!
**/
System.out.println("tiger 的运行类型=" + tiger.getClass());
tiger.cry();
}
}
基于类的匿名内部类
class Father{
private String name ;
public Father(String name) {
this.name = name;
System.out.println("接收到 name=" + name);
}
public Father() {
}
public void test() {//方法
}
}
//在其他类的方法中的匿名内部类重写了father的test方法
Father father = new Father("jack"){
@Override
public void test(){
System.out.println("匿名内部类重写了test方法");
}
};
System.out.println("father对象的运行类型="+father.getClass());
father.test();
基于抽象类的匿名内部类
abstract class Animal{
abstract void eat();
}
//
Animal animal = new Animal(){
@Override
void eat() {
System.out.println("小狗吃骨头...");
}
};
System.out.println("animal对象的运行类型="+animal.getClass());
animal.eat();
}
成员内部类
如果成员内部类的成员和外部类的成员重名,会遵守就近原则.可以通过 外部类名.this.属性 来访问外部类的成员;
成员内部类Code
public class MemberInnerClass {
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.gerInnerClass();
inner.say();
//直接访问成员内部类
Outer.Inner inner1 = outer.new Inner();//规则
inner1.say();
}
}
class Outer{
private int n1 = 10;
public String name = "张三";
private void hi() {
System.out.println("hi()方法...");
}
public class Inner{
private double sal = 99.8; private int n1 = 66;
public void say() {
//可以直接访问外部类的所有成员,包含私有的
//如果成员内部类的成员和外部类的成员重名,会遵守就近原则.
//,可以通过 外部类名.this.属性 来访问外部类的成员
System.out.println("n1 = " + n1 + " name = " + name + " 外部类的 n1=" + Outer.this.n1);
hi();
hi();
}
}
public Inner gerInnerClass(){
return new Inner();
}
}
静态内部类
静态成员变量与非静态成员变量之间的互相访问的区别?
www.pind.com解惑:
java中呢
Java静态成员变量和非静态成员变量之间的互相访问区别
在Java中,静态成员变量和非静态成员变量之间的访问也存在一些区别。
首先,静态成员变量是所有类的实例共享的,所以调用的时候只需要通过 类名.属性名
就可以调用(也只能这样调用)。而非静态成员变量的值是各个实例私有的,所以调用的时候必须先有具体的类的实例,然后通过 实例名.属性名
来调用blog.csdn.net。
其次,静态成员变量在类加载完成的时候就已经分配完空间,所以实际存在,可以被静态方法调用。而非静态成员变量只有在创建对象的时候才分配空间,对象被销毁的时候空间被回收blog.csdn.net。
在访问权限上,静态方法只能访问静态成员变量,而不能访问非静态成员变量。这是因为静态方法不需要实例化就可以调用,如果此时调用非静态的成员,不能确定类是否已被实例化,被调用的成员是否存在。非静态成员方法可以访问所有的成员变量,包括静态和非静态zhuanlan.zhihu.com。
如果在静态方法中需要使用非静态成员,可以通过以下两种方式解决:
- 创建类的对象,然后通过对象来访问非静态成员juejin.cn。
- 将非静态成员改为静态成员。如果某个成员不依赖于对象实例,并且在整个类中都是相同的值或行为,那么可以将其声明为静态成员。这样就可以在静态方法中直接访问和调用该成员。但需要注意的是,将非静态成员改为静态成员可能会影响程序的设计和逻辑,需要根据具体情况进行考虑juejin.cn。
例如,在下面的代码中,静态方法 printInfo()
可以访问静态成员变量 name
,但无法直接访问非静态成员变量 age
:
public class Person {
public static String name;
public int age;
public static void printInfo() {
System.out.println("Name: " + name);
System.out.println("Age: " + age); // 错误,无法从静态上下文中引用非静态变量 age
}
}
如果需要在 printInfo()
方法中访问 age
,可以通过创建 Person
类的对象来实现:
public class Person {
public static String name;
public int age;
public static void printInfo() {
Person p = new Person();
System.out.println("Name: " + name);
System.out.println("Age: " + p.age); // 通过对象 p 来访问非静态变量 age
}
}
静态内部类Code
public class StaticInnerClass {
public static void main(String[] args) {
Outer10 outer10 = new Outer10();
//访问静态内部类
//类.
Outer10.Inner10 inner10 = new Outer10.Inner10();
inner10.say();
Outer10.Inner10 inner101 = outer10.getInner10();
inner101.say();
}
}
class Outer10 { //外部类private int n1 = 10;
private static String name = "张三";
private static void cry() {
}
//Inner10 就是静态内部类
//1. 放在外部类的成员位置
//2. 使用 static 修饰
//3. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
//4. 可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
//5. 作用域 :同其他的成员,为整个类体
static class Inner10 {
private static String name = "韩顺平教育";
public void say() {
//如果外部类和静态内部类的成员重名时,静态内部类访问的时,
//默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.成员)
System.out.println(name + " 外部类 name= " + Outer10.name);
cry();
}
}
public void m1() { //外部类---访问------>静态内部类 访问方式:创建对象,再访问
Inner10 inner10 = new Inner10();
inner10.say();
}
public Inner10 getInner10() {
return new Inner10();
}
public static Inner10 getInner10_() {
return new Inner10();
}
}
线程
IO流
web应用常用功能文件上传
文件上传需要导入的包
链接:https://pan.baidu.com/s/1FapXjFcvZ4BzuYKIVODbvg?pwd=cbw9
提取码:cbw9
文件上传原理分析图(老韩)
http请求头(File)
服务器端处理
文件上传入门示例
1,先导包
链接:https://pan.baidu.com/s/1FapXjFcvZ4BzuYKIVODbvg?pwd=cbw9
提取码:cbw9
2,表单->jsp页面
<%--
Created by IntelliJ IDEA.
User: 韩顺平
Version: 1.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 指定了base标签 -->
<base href="<%=request.getContextPath()+"/"%>>">
<style type="text/css">
input[type="submit"] {
outline: none;
border-radius: 5px;
cursor: pointer;
background-color: #31B0D5;
border: none;
width: 70px;
height: 35px;
font-size: 20px;
}
img {
border-radius: 50%;
}
form {
position: relative;
width: 200px;
height: 200px;
}
input[type="file"] {
position: absolute;
left: 0;
top: 0;
height: 200px;
opacity: 0;
cursor: pointer;
}
</style>
<script type="text/javascript">
function prev(event) {
//获取展示图片的区域
var img = document.getElementById("prevView");
//获取文件对象
var file = event.files[0];
//获取文件阅读器: Js的一个类,直接使用即可
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function () {
//给img的src设置图片url
img.setAttribute("src", this.result);
}
}
</script>
</head>
<body>
<!-- 表单的enctype属性要设置为multipart/form-data
enctype="multipart/form-data" 表示提交的数据是多个部分构造,有文件和文本
-->
<form action="fileUploadServlet" method="post" enctype="multipart/form-data">
家居图: <img src="2.jpg" alt="" width="200" height="200" id="prevView">
<%-- 小伙伴愿意完成自己测试--%>
<input type="file" name="pic" id="" value="" οnchange="prev(this)"/>
家居名: <input type="text" name="name"><br/>
<input type="submit" value="上传"/>
</form>
</body>
</html>
enctype
enctype
属性在HTML表单中定义了在将表单数据发送到服务器之前如何对其进行编码。这个属性只有在表单的method
属性设置为post
时才会使用runoob.com。
enctype
属性的默认值是application/x-www-form-urlencoded
。这意味着,如果你没有明确指定enctype
属性,那么表单数据将会以这种方式进行编码。这是一种标准的编码格式,适用于大多数情况,特别是当表单中没有文件上传控件时cnblogs.com。
enctype
属性的其他可选值有:
-
multipart/form-data
:这个值用于上传非文本的内容,比如图片或者mp3等。只有这个值才能完整地传递文件数据cnblogs.com。 -
text/plain
:这个值用于纯文本传输,主要用于发送邮件时设置。如果在发送头文件时没有设置这种编码类型,可能会出现接收时编码混乱的问题cnblogs.com。
这些编码类型都是MIME类型,用于告诉浏览器如何解析接收到的数据。MIME(Multipurpose Internet Mail Extensions)是一种多用途网际邮件扩充协议,最早应用于电子邮件系统,后来也应用到浏览器cnblogs.com。
3,Servlet(配置)
package com.tom.servlet;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
public class FileUploadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if(ServletFileUpload.isMultipartContent(request)){
System.out.println("OK");
}else {
System.out.println("不是表单文件");
}
}
}
4,web.xml配置(*)
<servlet>
<servlet-name>FileUploadServlet</servlet-name>
<servlet-class>com.tom.servlet.FileUploadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FileUploadServlet</servlet-name>
<url-pattern>/fileUploadServlet</url-pattern>
</servlet-mapping>
FileItem文件项中的文件上传到服务器的temp下的文件保存找指定的目录
核心代码(IO流/文件传输流/打印流)
//否则就是一个文件
String name = fileItem.getName();
System.out.println("上传文件名="+name);
//并且将这个上传到服务器的temp下的文件保存找指定的目录
String filePath = "/upload/";
String fileRealPath = request.getServletContext().getRealPath(filePath);
System.out.println("fileRealPath="+fileRealPath);
//创建上传目录,上传文件
File fileRealPathDirectory = new File(fileRealPath);
if(!fileRealPathDirectory.exists()){//如果文件不存在就创建
fileRealPathDirectory.mkdirs();//创建
}
// //将文件拷贝到fileRealPathDirectory目录
// String fileFullPath= fileRealPathDirectory+ name;,错误1
String fileFullPath= fileRealPathDirectory+"/"+ name;
fileItem.write(new File(fileFullPath));
//判断是否拷贝成功
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("上传成功~");
文件上传问题
中文编码
//解决接收到的文件中文乱码问题
servletFileUpload.setHeaderEncoding("utf-8");
分目录存放
编写工具类,获得当前日期作为分级目录
package com.tom.utlis;
import java.time.LocalDateTime;
import java.time.Month;
public class WebUtils {
//获取当前时间用于在上传文件时的分目录存放
public static String GetYearMonthDay(){
LocalDateTime ldt = LocalDateTime.now();
int year = ldt.getYear();
Month month = ldt.getMonth();
int day = ldt.getDayOfMonth();
return year+"/"+month+"/"+day+"/";
}
public static void main(String[] args) {
//测试工具类
String s = WebUtils.GetYearMonthDay();
System.out.println(s);
}
}
使用工具类更改上传的文件路径
//创建上传目录,上传文件
File fileRealPathDirectory = new File(fileRealPath+ WebUtils.GetYearMonthDay());
文件覆盖问题
//添加UUID(随机的字符串形式),保持前缀是唯一的
name = UUID.randomUUID().toString()+"_"+System.currentTimeMillis()+"_"+name;
多文件上传
将//ServletFileUpload对象可以把表单数据提交的数据text / 文件将其封装到FileItem文件项,经过判断后的文件上传方法封装到一个方法中
package com.tom.utlis;
import org.apache.commons.fileupload.FileItem;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
public class UploadUtils {
public static boolean UploadSuccess(HttpServletRequest request, HttpServletResponse response,FileItem fileItem) throws Exception {
String name = fileItem.getName();
System.out.println("上传文件名="+name);
//并且将这个上传到服务器的temp下的文件保存找指定的目录
String filePath = "/upload/";
String fileRealPath = request.getServletContext().getRealPath(filePath);
System.out.println("fileRealPath="+fileRealPath);
//创建上传目录,上传文件
File fileRealPathDirectory = new File(fileRealPath+ WebUtils.GetYearMonthDay());
if(!fileRealPathDirectory.exists()){//如果文件不存在就创建
fileRealPathDirectory.mkdirs();//创建
}
// //将文件拷贝到fileRealPathDirectory目录
// String fileFullPath= fileRealPathDirectory+ name;,错误1
//添加UUID(随机的字符串形式),保持前缀是唯一的
name = UUID.randomUUID().toString()+"_"+System.currentTimeMillis()+"_"+name;
String fileFullPath= fileRealPathDirectory+"/"+ name;
fileItem.write(new File(fileFullPath));
//判断是否拷贝成功
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("上传成功~");
return true ;
}
}
多文件上传?
public static boolean UploadSuccess(HttpServletRequest request, HttpServletResponse response, MultipartFile[] files) throws Exception {
for (MultipartFile file : files) {
String name = file.getOriginalFilename();
System.out.println("上传文件名="+name);
String filePath = "/upload/";
String fileRealPath = request.getServletContext().getRealPath(filePath);
System.out.println("fileRealPath="+fileRealPath);
File fileRealPathDirectory = new File(fileRealPath+ WebUtils.GetYearMonthDay());
if(!fileRealPathDirectory.exists()){
fileRealPathDirectory.mkdirs();
}
name = UUID.randomUUID().toString()+"_"+System.currentTimeMillis()+"_"+name;
String fileFullPath= fileRealPathDirectory+"/"+ name;
file.transferTo(new File(fileFullPath));
}
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("上传成功~");
return true;
}
web应用常用功能文件下载
文件下载功能示意图
web页面(简)
<%--
Created by IntelliJ IDEA.
User: Tom
Date: 2023/11/8
Time: 23:36
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta charset="UTF-8">
<title>文件下载</title>
<base href="<%=request.getContextPath()+"/"%>">
</head>
<body>
<h1>文件下载</h1>
<a href="fileDownLoad?name=1.jpg">点击下载小狗图片</a><br/>
<a href="fileDownLoad?name=韩顺平零基础Java笔记.pdf">点击下载 韩顺平零基础Java笔记.pdf</a><br/>
</body>
</html>
Servlet页面(简)
package com.tom.servlet;
import org.apache.commons.io.IOUtils;
import sun.misc.BASE64Encoder;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
public class FileDownLoadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// System.out.println("FileDownLoadServlet被调用~");
//获取所要下载文件的名字
request.setCharacterEncoding("utf-8");
String downLoadFileName = request.getParameter("name");
// System.out.println("downLoadFileName="+downLoadFileName);
//给http响应,设置响应头Content-Type,就是文件名MIME
// 通过servletContext来获取
ServletContext servletContext = request.getServletContext();
String downLoadPath = "/DownLoad/";
//servletContext.getMimeType()
String downLoadFileFullPath = downLoadPath+downLoadFileName;
String mimeType = servletContext.getMimeType(downLoadFileFullPath);
// System.out.println("mimeType="+mimeType);
response.setContentType(mimeType);
//给http响应,设置响应头Content-Disposition
//不同浏览器写法不一样,考虑编码
//火狐是文件名中文需要base64,而ie/chrome URL编码
if(request.getHeader("User-Agent").contains("Firefox")){
response.setHeader("Content-Disposition","attachment;filename==?UTF-8?B?"+new BASE64Encoder().encode(downLoadFileName.getBytes("UTF-8"))+"?=");
}else {
response.setHeader("Content-Disposition","attachment;filename="+ URLEncoder.encode(downLoadFileName,"UTF-8"));
}
//读取下载的文件数据,返回给客户端/浏览器
//(1)创建一个和要下载的文件,关联的输入流
InputStream resourceAsStream = servletContext.getResourceAsStream(downLoadFileFullPath);
//(2)得到返回数据的输出流
ServletOutputStream outputStream = response.getOutputStream();
//(3)使用工具类,将输入流关联的文件,对拷到输出流,并返回给客户端/浏览器
IOUtils.copy(resourceAsStream,outputStream);
}
}