Java中的线程
- 线程类创建的两种方式:让一个类称为线程类的方式有两种,一个是实现java.lang.Runnable接口,另一个是继承自java.lang.Thread类。
- 解释Runnable接口与Thread类的区别:
- 线程继承自Thread则不能继承自其他类,而Runnable接口可以。
- 线程类继承自Thread相对于Runnable来说。使用线程的方法更方便一些。
- 实现Runnable接口的线程类的多线程,可以更方便的访问同一变量,而Thread类则需要内部类来进行替代。
- synchronized关键字代表要为某一段代码加上一个同步锁,这样的锁是绑定在某一个对象上边的。如果是同步代码块,需要为该synchronized关键字提供一个对象的引用;如果是同步方法,只需要加一个synchronized关键字的修饰。synchronizedz为某一段代码加上锁以后,某个线程进入该段代码之前,首先需要检查该锁是否被占用,如果没有被占用则继续执行;如果已经被占用,则需要等到该锁被释放以后才能继续执行。其中,该线程执行完该段代码就是释放锁的标志。
- 使用Java的线程池,往线程池添加任务时:
- 如果此时线程池中的数量小于corePoolSize,即使缓冲队列workQueue未满,那么任务被放入缓冲队列。
- 如果此时线程池中的数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列。
- 如果此时线程池中的数量大于corePoolSize,但是缓冲队列workQueue已满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
- 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue已满,并且线程池中的数量等于maximumPoolSize,那么通过handler所指定的策略来出来此任务
常用的线程池
- Executors.newCacheThreadPool():可缓存线程池,先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务
- Executors.newFixedThreadPool(int n):创建一个可重用固定个数的线程池,以共享的无界队列方式来运行这些线程。
- Executors.newScheduledThreadPool(int n):创建一个定长线程池,支持定时及周期性任务执行
- Executors.newSingleThreadExecutor():创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
package com.cy.thread;
import java.io.Serializable;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolTest {
private static int produceTaskSleepTime = 2000;
public static void main(String[] args){
/**
* 构造一个线程池
* @corePoolSize:核心线程
* @maximumPoolSize:最大线程
* @keepAliveTime:存活时间
* @timeUnit:时间单位
* @workQueue:工作队列
* @handler:采取策略
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 0,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), new ThreadPoolExecutor.DiscardOldestPolicy());
//每隔produceTaskSleepTime的时间向线程池派送一个任务
int i = 1;
while(true){
try {
Thread.sleep(produceTaskSleepTime); //休息一定的时间
String task = "task@" + i; //设置任务名字
System.out.println("put " + task);
//用execute方法启动一个线程
threadPoolExecutor.execute(new ThreadPoolTask(task));
i++;
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
class ThreadPoolTask implements Runnable, Serializable{ //线程类
private static final long serialVersionUID = 0;
private static int consumeTaskSleepTime = 2000; //时间间歇,毫秒
private String threadPoolTaskData; //存储任务名的变量
ThreadPoolTask(String tasks){
threadPoolTaskData = tasks;
}
//每个任务的执行过程,现在是什么都没做,处理print和sleep
@Override
public void run() {
System.out.println("start.."+threadPoolTaskData);
try{
Thread.sleep(consumeTaskSleepTime);
}catch (InterruptedException e){
e.printStackTrace();
}
threadPoolTaskData = null;
}
}
Java中的反射
反射是为了能够动态地加载一个类,动态地调用一个方法,动态地访问一个属性等动态要求而设计的。它出发点就在于JVM会为每一个类创建一个java.lang.Class类的实例,通过该对象可以获取这个类的信息,然后通过使用java.lang.reflect包下的API以达到到各种动态需求。
Class类的含义和作用
每一个Class类的对象就代表了一种被加载进入JVM的类,它代表了该类的一种信息映射。开发者可以通过一下3种途径获取到Class对象。
- Class类的forName()方法的返回值。
- 访问所有类都会拥有的静态的Class属性。类名.class
- 调用所有对象都会有的getClass()方法。实例.getClass()
在Class类中,定义许多关于类信息的方法。例如,getName()、getMethod()、getConstructor()和newInstance()等可以用于反射开发,还有isInstance()和isInterface()等一些关于类的功能方法。
反射创建实例以及常用方法
- 创建实例即获取构造方法,由构造方法创建实例
//第一种:通过实例直接调用newInstance()方法创建实例
Class<String> clazz = (Class<String>) Class.forName("java.lang.String");
Object obj = clazz.newInstance();
String strObj = (String) obj;
//第二种:通过实例获取构造函数,然后用构造函数创建实例
Constructor<?> constructor = clazz.getConstructor(String.class);
Object ins = constructor.newInstance("张三");
String two = (String) ins;
//第三种:可以通过getConstructors获取到所有的构造方法
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor<?> constructor1 : constructors) {
System.out.println(constructor1);
}
- 根据反射对象获取方法,并进行调用
//获取方法并进行调用
Method split = clazz.getDeclaredMethod("split", String.class);
String[] strs = (String[]) split.invoke("1,2,3",",");
for (String str : strs) {
System.out.println(str);
}
//根据反射对象获取所有可见的对象
Method[] declaredMethods = clazz.getDeclaredMethods();
- 根据反射对象获取属性
//已知属性名,通过属性名获取反射对象该属性的值
Field serialVersionUID = clazz.getDeclaredField("hash");
//获取所有的可见属性
Field[] declaredFields = clazz.getDeclaredFields();
反射机制来访问一个类的私有成员
在使用反射机制访问私有成员的时候,它们的可访问性是为false的。需要调用setAccessible(true)方法,把原本不可访问的私有成员变为可以访问以后,才能进行成功的访问或调用。
Java的网络编程
TCP/IP协议的理解
TCP/IP定义了电子设备(如计算机)连入因特网标准,以及数据如何在它们之间传输的标准。它既是互联网中的基本通信语言或协议,也是局域网的通信协议。
TCP/IP是一组包括TCP协议、IP协议,UDP协议、ICMP协议和其他一些协议的协议组。需要进行网络通信的计算需要提供符合这些协议标准的程序以后,才能进行网络通信。
TCP协议的通信特点
TCP协议主要拥有如下的通信特点:
- 面向连接的传输。
- 端到端的通信。
- 可靠性,确保传输数据的正确性,不出现丢失或乱序。
- 采用字节流方式,即以字节为单位传输字节序列。
Java的TCP编程模型
编写Java的TCP网络应用程序需要分为服务器端和客户端两个部分:
服务器端:
- 创建一个服务器端的Socket,指定一个端口号。
- 开始监听来自客户端的请求要求。
- 获得输出流或输入流
- 调用输入流/输出流的read()或write()方法,进行数据的传输。
- 释放资源,关闭输出流/输入流、Socket和ServerSocket对象
ServerSocket ss = new ServerSocket(8880);//用端口号创建一个serverSoket对象
Socket s = ss.accept();//开始监听来自客户端的请求
OutputStream os = s.getOutputStream(); //获得输出流
PrintWriter pw = new PrintWriter(os); //创建PrintWriter对象
pw.write("now time = " + new Date()); //向输出流中写出当前的时间
pw.flush(); //清空缓存
pw.close(); //关闭流资源
s.close();
ss.close();
客户端:
- 创建Socket对象,建立与服务器的连接。
- 获得输出流或输入流
- 调用输入流/输出流的read()或write()方法,进行数据的传输。
- 释放资源,关闭输出流/输入流、socket对象
s = new Socket("localhost", 8880); //用IP地址和端口创建Socket对象
InputStream is = s.getInputStream(); //获得输入流
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr); //创建BufferReader对象
String str = br.readLine(); //读取一行
System.out.println(str); //打印从服务器端来的结果
br.close(); //关闭流资源
isr.close();
is.close();
s.close();
UDP协议的通信特点
- UDP是一个无连接协议,传输数据之前源端和终端不建立连接,当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。
- 不需要维护连接状态,包括收发状态等。
- 字节开销很小。
- 吞吐量主要受应用软件生成数据的速率、传输宽带、源端和终端主机性能等因素的限制。
Java访问Web站点
- 用URL类创建一个资源定位的对象。
- 调用URL的openConnection()方法得到HttpURLConnection对象。
- 调用HttpURLConnection的open()方法打开连接。
- 用getHeaderFields()方法得到响应结果的头部信息。
- 用getInputStream()方法得到输入流对象,得到响应内容。
//创建URL对象
URL url = new URL("https://music.91q.com/player");
//用URL创建HttpURLConnection对象
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.connect(); //打开连接
Map<String, List<String>> header = conn.getHeaderFields();
for (String key : header.keySet()){
System.out.println(key + ":" +header.get(key));
}
//打印响应内容
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
String str = null;
while ((str = br.readLine()) != null){
System.out.println(str);
}
conn.disconnect();
Java对数据库操作
JDBC操作数据库步骤
- 注册驱动程序
- 获取数据库连接
- 创建会话
- 执行SQL语句
- 处理结果集
- 关闭连接
Connection connection = null;
try {
Class.forName("com.mysql.jdbc.Driver"); //加载驱动
connection = DriverManager //获取连接
.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
Statement statement = connection.createStatement(); //创建会话
connection.setAutoCommit(false); //将自动提交设置为false
boolean flag = //创建表
statement.execute("create table student( id int primary key auto_increment, name varchar(30),age int,gender varchar(10))");
//向表中插入数据
statement.execute("insert into student(name,age,gender) values ('liubei','30','M')");
//执行查询语句
ResultSet resultSet = statement.executeQuery("select * from student");
//获取结果
while (resultSet.next()){
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
String gender = resultSet.getString("gender");
System.out.println("姓名:"+name+";年龄:"+age+";性别:"+gender);
}
conn.commit(); //提交事务
} catch (Exception e) {
e.printStackTrace();
conn.rollback(); //事务回滚
}finally {
try {
if(statement != null){statement.close();}
if (connection != null){ connection.close(); }
} catch (SQLException e) { e.printStackTrace(); }
}
使用JDBC事务
事务的四大特性ACID
- 原子性:Atomicity
- 一致性:Consistency
- 隔离性:Isolation
- 持久性:Durability
JDBC的事务主要是在代码中控制的,关键点在于:关闭自动提交事务(connection.setAutoCommit(false);)、调用commit()方法提交事务和调用rollback()方法回滚事务。一般来说,JDBC中使用事务服务大致有以下步骤。 - 关闭自动提交事务,设置连接的自动提交事务属性为false.
- 捕获(try catch)执行代码。如果执行过程顺利,提交事务,一旦发生异常,回滚(rollback)事务。
- 关闭连接。
JavaEE相关知识
简单的web目录结构
- projectName:项目名称
- src :放java代码
- WebContent
- META-INF
- WEB-INF
- lib : 存放jar包
- web.xml :项目配置项
- classes :存放编译后的代码
- pages :放静态页面
servlet的概念及配置
Servlet在Java Web服务器中就充当了信息资源的最小表示单位,代表了一个用户可以通过浏览器获取的资源。Servlet可以进行无限的扩展,它可以使用Java的所有类库资源,为用户返回文本、图片、音频、视频等各类信息资源。
从编程角度开看,Servlet是一个Java类,这个类需要实现Servlet接口,提供一个公开的无参数的构造方法。由Web容器来控制它的创建、初始化、提供服务、销毁等。它的各种行为方式通过在web.xml文件中的配置类决定。
web.xml需要配置下面信息
<!-- 首先定义一个名为MyTestServlet的Servlet,指定好它的完整类名 -->
<servlet>
<!-- servlet的名字 -->
<servlet-name>MyTestServlet</servlet-name>
<!-- Servlet的完整类名 -->
<servlet-class>MyTestServlet</servlet-class>
<!-- 初始化参数定义 可选 -->
<init-param>
<!-- 初始化参数的名字 -->
<param-name>myparam</param-name>
<!-- 初始化参数的值 -->
<param-value>100</param-value>
</init-param>
</servlet>
<!-- 再指定该Servlet的URL -->
<servlet-mapping>
<!-- Servlet的名字,须同上面一样 -->
<servlet-name>MyTestServlet</servlet-name>
<!-- URL,往往以“/”开头 -->
<url-pattern>/MyTestServlet</url-pattern>
</servlet-mapping>
Servlet的生命周期
servlet的生命周期分为4个阶段:加载、初始化、提供服务和销毁,这些过程都是由Web容器来掌控。开发者关注最多的是初始化和提供服务两个阶段,在init()方法中,开发者可以获取配置在web.xml中的初始化参数;service()方法中的代码,会在Servlet的请求来到时被调用。
JavaEE中提供的Servlet接口实现类
在JavaEE的SDK中,一共提供了一下3个Servlet接口的实现类
- javax.faces.webapp.FacesServlet;
- javax.servlet.GenericServlet;
- javax.servlet.http.HttpServlet;
Servlet中获取请求参数的值
在Servlet中,任何负责做出响应的方法(例如,service()、doPost()、和doGet())都会包含一个ServletRequest对象参数,不管是post还是get的请求方式,Servlet都可以通过ServletRequest接口的getParameter()或getParameterValues()方法获取到。前者适用于只有一个值的参数,后者多用于有多值的参数,列如,复选框(checkbox)
Servlet中Forward和Redirect的区别
Forward和Redirec代表了两种请求转发方式:直接请求转发和间接请求转发。对应到代码里,分别是RequestDispatcher类的froward方法和HttpServletResponse类的sendRedirect()方法。
对于间接转发方式,服务器端在响应第一次请求的时候,让浏览器再向另外一个URL发出请求,从而达到转发的目的。它本质上是两次HTTP请求,对应两个request对象。
对于直接转发方式,客户端浏览器只发出一次请求,Servlet把请求转发给Servlet、html、jsp或其他信息资源,由第二个信息资源响应该请求,两个信息资源共享同一个request对象。
过滤器的作用和工作原理
对于Web应用程序来说,过滤器是处于Web容器内的一个组件,它会过滤特定请求资源请求信息和响应信息。一个请求来到的时候,Web容器会判断是否有过滤器与该信息资源关联,如果有,则交给过滤器一一处理,然后再交给目标资源,响应的时候则以相反的顺序交给过滤器处理,最后再返回给用户浏览器。过滤器对应Filter接口,开发者一般需要实现doFiler()方法,并在web.xml文件夹中提供相应的配置。
监听器的作用和工作原理
对于Web应用程序来说,监听器是处于Web容器内的一个组件,它会对Web容器中的3种范围对象进行监听:request、session和application。当这些范围对象在创建或销毁的时候,Web容器会主动的调用它们的初始化或销毁的回调方法,从而达到时间响应的效果。根据范围不同,Java EE为开发者提供如下一些监听器接口。
- Request事件监听器接口ServletRequestListener;
- Session事件监听器接口HttpSessionListener;
- Application事件监听器接口ServletContextListener;
JSP动态语言
Jsp运行机制
当客户端发出一次对某个JSP的请求,Web容器处理该请求的过程如下:
- Web容器会检验JSP的语法是否正确。
- 将JSP文件转换成Servlet的源码文件。
- 编译该源码文件成为Class文件。
- 创建一个该Servlet类的对象实例,以Servlet的方式为请求提供服务。
JSP的内置对象及其用途
JSP包含9个内置对象,分别是application、session、request、response、out、page、pageContext、exception、config.
pageContext代表的是JSP页面上下文,也就是一个运行环境。为开发者提供了访问其他内置对象的同一入口。
pageContext.getRequest(); //获取请求对象
pageContext.getSession(); //获取会话对象
pageContext.getServletContext(); //获取Servlet上下文对象
pageContext.getResponse(); //获取响应对象
pageContext.getOut(); //获取输出流对象
JSP使用JavaBean
JSP使用JavaBean有两种方式:JSP的脚本中使用纯粹的Java代码和一些JavaBean的动作标签。其中使用jsp:useBean等动作标签会更加简洁一些,使用得也更多。用动作标签来使用JavaBean的时候,用jsp:useBean标签在特定范围内声明或创建一个JavaBean,用jsp:setProperty标签来设置一个JavaBean的属性的值,用jsp:getProperty标签来获取一个JavaBean的属性的值。
使用迭代标签<c:forEach>循环显示数据
<c:forEach>标签的使用方法如下:
- 把需要循环的内容放在<c:forEach>与</c:forEach>之间。
- 为<c:forEach>提供items属性,它指定的是集合对象。
- 为<c:forEach>提供var属性,它指定的是每次循环得到的集合元素。
- 如果需要记录循环状态,则指定varStatus属性。
- 在循环体中,使用表达式语言,访问var属性指定的变量的各种属性。
<c:forEach begin="20" end="50" step="2" var="i">
偶数:<c:out value="${i}"/><br/>
</c:forEach>
<%
List<User> users = new ArrayList<>();
for (int i = 0; i < 5;i++){
User user = new User("wang" + i, 25 + i, i % 2 == 0 ? "male" : "female");
users.add(user);
}
request.setAttribute("users",users);
%>
<table>
<tr>
<th>编号</th>
<th>用户名</th>
<th>年龄</th>
<th>性别</th>
</tr>
<c:forEach items="${users}" var="user" varStatus="status">
<tr>
<td>${status.count}</td>
<td>${user.name}</td>
<td>${user.age}</td>
<td>${user.gender}</td>
</tr>
</c:forEach>
</table>
JSTL逻辑判断标签
JSTL主要提供了两种标签来处理条件表达式,它们是:<c:if>或<c:choose>。
- 标签的条件表达式一般放在它的test属性中,如果返回为true则打印标签体的内容。
- 一般需要结合<c:when>和<c:otherwise>来使用,条件表达式写在<c:when>标签的test属性中,它类似于Java的switch语句。
<ul>
<c:if test="${ 3 > 5}">
<li><a href="www.baidu.com">百度</a></li>
</c:if>
<c:if test="${3 < 5}">
<li><a href="www.jd.com">京东</a></li>
</c:if>
</ul>
<br/><br/><br/>
<c:choose>
<c:when test="${5 > 3}">
男
</c:when>
<c:when test="${5 < 3}">
女
</c:when>
<c:otherwise>
未知性别
</c:otherwise>
</c:choose>