多线程理解学习:
多线程
百度百科:多线程(multithreading),是指从软件或硬件上实现多个线程并发执行的请求具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫做“线程”(Thread),利用它编程的概念就叫做“多线程处理”。
进程与线程区别:
进程:一个程序就是一个进程例如我正在运行的微信,网易云等都属于一个进程。
线程:线程是运行在计算机操作系统上运算调度最小的单位,它包含在我们的进程中,在统一进程中线程拥有该进程的全部系统资源。
并行与并发:
举个例子:
例1:你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。 (不一定是同时的)
你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。
例2:你在打游戏,女朋友突然打来了视频电话,如果你无视女朋友的电话,一直到游戏结束才回她电话,这就说明你不支持并发,也不支持并行。
你在打游戏中,女票来了电话,你赶紧接了电话,放下了手头的游戏,等你女票电话结束你才继续玩你的游戏,这说明你支持并发。(不一定是同时的)
你玩游戏玩到一半,女票来了电话,你一边接电话一边打你的游戏说明你支持并行。
并发的关键是你有处理多个任务的能力,不一定要同时;
并行的关键是你有同时处理多个任务的能力。
所以我认为它们最关键的点就是:是否是『同时』。并发是轮流处理多个任务,并行是同时处理多个任务
线程池:
线程的创建方式:Java就是一个天生的多线程语言。在我们运行main方法的时候,其实就是创建并启动了一个main线程。
线程的状态(五种):
1.新建新创建一个线程。
2.就绪:线程对象创建后,其他线程(比如main线程)调用了该对象的start( )方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权。
3.运行:获取了CPU的使用权。
4.阻塞:线程可能被挂起,或者被中断,让出CPU的使用权。
5.死亡:线程run()、main()方法执行结束,或者因异常退出了run()方法。则该线程结束生命周期。死亡的线程不可再次复生。
在Java中创建线程有三种方式:
1.通过集成Thread类,重写run()方法创建线程
public class Test extends Thread{
@Override
public void run(){
System.out .println("创建线程");
System.out.println(getName());
}
public static void main(){
Test test = new Test();
test.setName("线程A");
test.start();
System.out.println(Thread.currentThread().getName());
}
}
输出结果:main
创建线程
线程A
2.通过实现Runnable接口创建线程
public class Test implements Runnable{
@Override
public void run(){
System.out.println("创建线程");
}
public static void main(){
Test test = new Test();
Thread thread = new Thread(test);
thread.start();
}
}创建线程
3.通过实现Callable接口和Future创建线程,该方式需要通过FutureTask帮助获取返回值。
public class Test implements Callable{
@Override
public Stirng call()throws Exception{
System.out .println("创建线程");
return"返回结果";
}
public static void main(String args[]){
Test test = new Test();
FutureTask task = new FutureTask(test);
Thread thread = new Thread(task);
thread.start();
try{
System.out.println(task.get());
}catch(InterruptedException e){
e.printStackTrace
}catch(ExecutionException e){
e.printStackTrace();
}
}
}创建线程
返回结果
为什么需要线城池?
线程的创建和销毁都会消耗系统的资源,将线程放在一个缓存池,需要使用时直接从缓存池中获取线程,通过重用使用已创建的线程来降低系统所消耗的能源。
线程池的使用:
在Executor类中有4种线程池的创建方法。
1.newCachedThreadPool():创建一个缓存线程池,创建线程数量不限制,线程长时间未使用会被回收,如果有大量线程同时运行可能会导致系统瘫痪。SynchronousQueue(同步队列):内部只能包含一个元素的队列。
public static ExecutorService newCachedThreadPool(){
return new ThreadPoolExecutor(0,Integer.MAX_VALUE,
60L,TimeUnit.SECONDS,new SynchronousQueue());
}
2.newFixedThreadPool():创建固定线程的线程池,LinkedBlockingQueue队列中大小是不限的,所有可能会出现内存溢出的情况。
pulic static ExecutorService newFixedThreadPool(int nThreads){
return new ThreadPoolExecutor(nThreads,nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue());
}
3.newSIngleTreadExecutor()创建一个只有一个线程的线程池,俗称单例线程池同样也是采用的LinkedBlockingQueue.
public static ExecutorService newSingleThreadExecutor(){
return new FinalizableDelegatedExcutorService(1,1,0L,
TimeUnit.MILLISECONDS,new (LinkedBlockingQueue()));
}
4.newScheduledThreadPool():创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize){
return new ScheduledThreadPoolExecutor(corePoolSize);
}
线程池使用注意事项:
阿里开发规范(原文):
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这
样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2) CachedThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
ThreadPoolExecutor:
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,
long KeepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory){
this(corePoolSize,maximumPoolSize,long keepAliveTime,unit.workQueue,threadFactory,defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximunmPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFacyory,
RejectedExecutionHandler handler){
if(corePoolSize < 0 ||
maximumPoolSize<=0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if(workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(KeepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}参数说明:
corePoolSize:核心线程数
maximumPoolSize:最大线程数
keepAliveTime:线程保持活跃时间
unit:线程活跃时间的单位
workQueue:任务队列
threadFactory:线程工厂,线程创建的方式
handler:拒绝策略
TimeUnit类,配合keepAliveTime使用指定时间格式纳秒 NANOSECONDS
微秒 MICROSECONDS
毫秒 MILLISECONDS
秒 SECONDS
分 MINUTES
时 HOURS
天 DAYS
拒绝策略RejectedExecutionHandler:ThreadPoolExecutor提供了四种拒绝策略
1.AbortPolicy(默认使用):抛出异常
public stastic class AbortPolicy implements RejectedExecutionHabdler{
public AbortPolicy(){
public void rejectedExecution(Runnable r,ThreadPoolExecutor e){
throw new RejectedExecutionException("Task"+r.toString()+"rejected from"+e.toString())
}
}
]
2.CallerRunsPolicy:在当前线程运行该任务
public static class CallerRunPolicy implements RejectedExecutor{
public CallerRunPolicy(){}
public void rejectedExecution(Runnable r,ThreadPoolExecutor e){
if(le.isShutdown()){
r.run();
}
}
}
3.DiscardPolicy:丢弃任务
public static class DiscardPolicy implements RejectedExecutionHandler{
public DiscardPolicy(){ }
public void rejectedExecution(Runnable r,ThreadPoolExecutor e){
}
}
4.DiscardOldestPolicy:丢弃最早的任务
public static class DiscardOldestPolicy{
public DiscardOldestPolicy(){ }
public void rejectedExecution(Runnable r,ThreadPoolExecutor e){
if(!e.isShutdown()){
e.getQueue().poll();
e.executor(r);
}
}
}
**线程池的实现原理:**当有新任务时,会创建线程来执行任务,当线程数达到corePoolSize时,就会将任务放在阻塞队列,当阻塞队列满了,并且线程数达到了maximumPoolSize时,会触发拒绝策略拒绝任务。
锁:
**什么时候需要用到锁:**多线程的环境下肯定会出现线程安全的问题,通过锁可以解决线程的安全问题,保证数据的一致性。
锁升级:
锁状态:偏向锁,轻量级锁,重量级锁(级别由低到高)
1. 偏向锁:
大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。
2. 轻量级锁:
线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced MarkWord。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
3. 轻量级解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态。
volatile:
Java中的关键字,基于jvm实现。可以保证被他修饰的方法或是代码块在任意一个时刻只能有一个线程执行。
synchronized实现原理
public class Test(){
public static void main(String[] args){
synchronized(Object.class){
system.out.println("synchronized");
}
}
}
synchronized使用方式Java中所有对象都可以被当做synchronized的锁。
1.synchronized使用在普通方法中,锁是当前对象,进入被synchronized修饰的普通方法时要获取当前对象的锁。
public static void main(String [] args){
Test test = new Test();
new Thread(test::methodA).start();
new Thread(test::methodB).start();
}
public synchronized void methodA(){
try{
Thread.sleep(2000);
}catch(InterrupedException e){
e.printStackTrace();
}
System.out.println("methodA");
}
public synchronized void methodB(){
System.out.println("methodB");
}methodA
methodB
2.synchronized使用在静态方法中,锁是当前class对象,进入被synchronized修饰的静态方法时要获取当前的class对象锁。
public static void main(String [] args){
Test test = new Test();
new Thread(test::methodA).start();
new Thread(test::methodB).start();
}
public static synchronized void methodA(){
try{
Thread.sleep(2000);
}catch(InterrupedException e){
e.printStackTrace();
}
System.out.println("methodA");
}
public static synchronized void methodB(){
System.out.println("methodB");
}methodA
methodB
3.synchronized使用在代码块中,锁是代码块指定对象的锁,进入被synchronized修饰的代码块时需要获取到括号中对象的锁。
public Objectnlock = new Object();
public static void main(String[] args){
Test test = new Test();
new Thread(test::methodA).start();
new Thread(test::methodB).start();
}
public void method(){
synchronized(lock){
try{
Thread.sleep(2000);
}catch(InterrptedExcption e){
e.printStackTrace();
}
System.out.println("methodA");
}
}
public void methodB(){
synchronied(lock){
System.out println("methodB")
}
}methodA
methodB
Lock锁
在Lock接口出现之前,只能靠synchronized关键字实现锁功能的,在JDK1.5后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功能,并且让我们可以比synchronized为灵活的运行性锁。自己管理锁则需要手动获取锁和释放锁,使用不当就会造成系统瘫痪,比如死锁。
//获取锁
void lock();
//当前线程未被中断,则获取锁
void lockInterruptibly() throws InterruptedException;
//尝试获取
boolean tryLock();
//限定时间内获取,超时则抛异常
boolean tryLock(Long time,TimeUnit) throws InterruptedException;
//释放锁
void unlock();
CAS(Compare and Swap)
Compare and Swap:比较并替换,CAS是区别于synchronize的一种乐观锁。CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。在sun.misc包下有个被final关键字修饰的Unsafe的类,该类不对外提供。该类中的方法都被native关键字修饰,表示该方法由本地实现,Unsafe底层均是采用的C++语言实现的,保证在cpu上运行的原子性。Unsafe类只提供了三种原子操作方法,其他类型都是转成这三种类型再使用对应的方法去原子更新的。
AQS(AbstractQueuedSynchronizer)
AQS全称:AbstractQueuedSynchronizer是一个同步队列(它是一个抽象类),AQS底层使用了模板方法模式实现了对同步状态的管理,对阻塞线程进行排队,等待通知等等。AQS的核心也包括了这些方面:同步队列,独占锁的获取和释放,共享锁的获取和释放以及可中断锁,超时等待锁获取这些特性的实现。AbstractQueuedSynchronizer是一个FIFO(First Input First Output)即先进先出的队列
static final class Node {
// 标记共享锁
static final Node SHARED = new Node();
// 标记排它锁
static final Node EXCLUSIVE = null;
// 节点从同步队列取消
static final int CANCELLED = 1;
//后继节点的线程处于等待状态,如果当前节点释放同步状态会通知后继节点,使得后继节点的线程能够运行;
static final int SIGNAL = -1;
// 当前节点进入等待队列中
static final int CONDITION = -2;
// 表示下一次共享式同步状态获取将会无条件传播下去
static final int PROPAGATE = -3;
// 线程状态
volatile int waitStatus;
// 前节点
volatile Node prev;
// 后节点
volatile Node next;
volatile Thread thread;
Node nextWaiter;
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* 返回前节点,是空的就抛异常
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
独占锁
// 独占式获取同步状态,如果获取失败则插入同步队列进行等待;
public final void acquire(int arg) {
// 获取成功直接返回,失败时会点将独占式锁添加至阻塞队列,并循环获取同步状态
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 中断当前线程
selfInterrupt();
}
// 添加至阻塞队列。
private Node addWaiter(Node mode) {
// 将当前线程封装成一个Node节点
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
// 尾节点不为空
Node pred = tail;
if (pred != null) {
// 将当前节点设置成尾节点
node.prev = pred;
// 通过cas操作将当前节点设置成尾节点
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 尾节点为空
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// 尾节点为空则初始化链表
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 不为空,则证明已被初始化,再次将当前节点设置成尾节点,cas自旋尝试,直至成功。
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
// 获取锁
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 当前节点有前节点,并且为头节点则尝试获取锁,tryAcquire是AQS提供的模板方法。
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
// 将当前节点与链表断绝关系
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 获取失败则进入阻塞状态。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
// 取消正在进行的尝试获取,正常情况下不会进入该方法
cancelAcquire(node);
}
}
// 设置头节点
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
// 设置当前线程状态为阻塞状态。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 设置当前线程为阻塞状态为阻塞状态
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
// 阻塞当前线程
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
独占释放锁
public final boolean release(int arg) {
// tryRelease模板方法,尝试释放,释放失败返回false,成功就去唤醒阻塞的线程
if (tryRelease(arg)) {
Node h = head;
// 当头节点不为null并且状态不为0时会执行unparkSuccessor
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// 如果有后继节点,则唤醒后继节点。每一次锁释放后就会唤醒队列中该节点的后继节点所引用的线程,从而进一步可以佐证获得锁的过程是一个FIFO(先进先出)的过程。
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 唤醒后继节点的线程
LockSupport.unpark(s.thread);
}```
**共享锁**
```java
// 尝试获取锁tryAcquireShared是AQS提供的模板方法。
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
// 入队设置为共享式节点。
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// 当前节点的前节点是头节点
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
// 出队操作,设置头节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}```
**共享释放锁**
```java
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
// 去释放锁
doReleaseShared();
return true;
}
return false;
}
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
// 头节点不为空,尾节点不是头节点,并且头节点处于被唤醒状态,则使用cas设置线程状态。
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
// 设置成功后唤醒线程。
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}```
**ReentrantLock(可重入锁)**
可重入锁:当一个线程获取到锁之后,可以继续获取该锁而不会造成死锁。
**公平锁和非公平锁:**
公平锁和非公平锁都继承了一个内部类:Sync类,继承了AbstractQueueSynchronized同步器实现了相关操作。
```java
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
abstract void lock();
// 默认实现的非公平锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
// 设置当前线程为锁的拥有者
setExclusiveOwnerThread(current);
return true;
}
}
// 如果当前线程是专属线程,证明该方法只有当前线程可以进入,直接设置状态
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
// 释放锁
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
// 设置锁的拥有者为null
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
}
公平锁
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
// 公平锁直接交给AQS同步器管理。
acquire(1);
}
// 尝试获取锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 与非公平锁的区别:hasQueuedPredecessors检查是否有前节点在排队,如果有直接阻塞
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
ReentrantLock相关操作
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
lock.lock();
try {
操作
} finally {
lock.unlock();
}
}
公平锁与非公平锁字面上意思就是
公平锁与非公平锁字面上意思就是一个是公平的竞争,一个是非公平的竞争。ReentrantLock默认使用的是非公平锁,因为非公平锁可以减少线程间的切换,可以避免资源的浪费,也可能会导致线程一直处于阻塞状态。Doug Lea大神选择了非公平锁这个也很符合人类的生活,这个世界是不公平的,因为有了种种的不公平,我们的生活才可以如此的绚丽多彩,想象一下,如果这个世界是公平的,人人平等,那该会有多枯燥。
最新Java教程全套
关注 小编 “ 学习”
第1阶段 :Java基础
1.认知基础课程2. java入门阶段3. 面向对象编程4. 飞机小项目5. 面向对象和数组6. 常用类7. 异常机制8. 容器和数据结构9. IO流技术10. 多线程11. 网络编程12. 手写服务器13. 注解和反射14. GOF23种设计模式15. 正则表达式16. JDBC数据库操作17. 手写SORM框架18. JAVA10新特性19.数据结构和算法20. JVM虚拟机讲解21. XML技术解析
第2阶段:数据库开发全套课程
1.Oracle和SQL语言2.Mysql快速使用3.PowerDesigner使用4.JDBC数据库5.Mysql优化6.oracle深度讲解
第3阶段:网页开发和设计
1.HTML基础2.CSS基础3.JavaScript编程4.jQuery5.easyUI
第4阶段:Servlet和JSP实战深入课程
1.Servlet入门和Tomcat2.request和response对象3.转发和重定向_Cookie4.session_Context对象5.JSP6.用户管理系统7.Ajax技术8.EL和JSTL标签库9.过滤器10.监听器
第5阶段:高级框架阶段
1.Mybatis2.Spring3.Spring MVC4.SSM框架整合5.RBAC权限控制项目6.Hibernate37.Hibernate48.jFinal9.Shiro安全框架10.Solr搜索框架11.Struts212.Nginx服务器13.Redis缓存技术14.JVM虚拟机优化15.Zookeeper
第6阶段:微服务架构阶段
1.Spring Boot2.Spring Data3.Spring Cloud
第7阶段:互联网架构阶段
1.Linux系统2.Maven技术3.Git4.SVN5.高并发编程6.系统和虚拟机调优7.JAVA编程规范8.高级网络编程9.Netty框架10.ActiveMQ消息中间件11.单点登录SSO12.数据库和SQL优化13.数据库集群和高并发14.Dubbo15.Redis16.VSFTPD+NGINX
第8阶段:分布式亿级高并发电商项目
1.基于SOA架构介绍2.VSFTPD和Nginx和商品新增3.商品规格参数管理4.Jsonp5.CMS模块6.广告位数据缓存7.SolrJ和SolrCloud8.商品搜索9.商品详情10.单点登录11.购物车12.订单系统13.分库和分表14.分布式部署
第9阶段:毕设项目第1季
1. 电子政务网2. 企业合同管理系统3. 健康管理系统4. 商品供应管理系统5. 土地档案管理系统6. 聊天室设计和实现7. 码头配套和货柜管理系统8. 百货中心供应链系统9. 病历管理系统10. 超市积分管理系统11. 动漫论坛12. 俄罗斯方块13. 个人博客系统14. 固定资产管理系统15. 影视创作论坛16. 屏幕截图工具17. 超级玛丽游戏18. 飞机大战游戏19. 雷电
第10阶段:毕设项目第2季
1. 微博系统2. 写字板3. 坦克大战4. 推箱子5. 电脑彩票系统6. 记账管理系统7. 新闻发布系统8. 医院挂号系统9. 仓库管理系统10. 停车场管理系统11. 网络爬虫12. 酒店管理系统13. 企业财务管理系统14. 车辆管理系统15. 员工信息管理系统16. 旅游网站17. 搜索引擎18. 进销存管理系统19. 在线考试系统20. 物流信息网21. 住院管理系统22. 银行柜员业务绩效系统
关注 小编 “ 资料 ”!