• 多线程补充


    1、线程的状态

    线程对象在不同的运行时期有不同的状态,状态信息就存在于State枚举类中

    Thread.State :

    NEW :至今尚未启动的线程

    RUNNABLE:正在java虚拟机中执行的线程

    BLOCKED:受阻塞于并等待某个监视器锁的线程

    WAITING:无限期的等待另一个线程来执行某一个特定操作的线程

    TIMED_WAITING:等待另一个线程来执行某一特定操作的线程

    TERMINATED:已退出的线程

     

    package com.threadTest.thread.add.state.test01;
    
    /**
     * Created by sky on 2017/3/23.
     */
    public class Run {
        public static void main(String[] args) {
            try {
                MyThread thread01 = new MyThread();
                System.out.println("main 方法中的状态1: " + thread01.getState());
                Thread.sleep(1000);
                thread01.start();
                Thread.sleep(1000);
                System.out.println("main 方法中的状态2: " + thread01.getState());
                MyThread thread02 = new MyThread();
                System.out.println("main 方法中的状态1: " + thread02.getState());
                Thread.sleep(1000);
                thread02.start();
                Thread.sleep(1000);
                System.out.println("main 方法中的状态2: " + thread02.getState());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    输出:
    构造方法中的状态1: RUNNABLE
    构造方法中的状态2: RUNNABLE
    main 方法中的状态1: NEW
    run 构造方法中的状态1: RUNNABLE
    main 方法中的状态2: TIMED_WAITING
    构造方法中的状态1: RUNNABLE
    构造方法中的状态2: RUNNABLE
    main 方法中的状态1: NEW
    run 构造方法中的状态1: RUNNABLE
    main 方法中的状态2: TIMED_WAITING
    package com.threadTest.thread.add.state.test01;
    
    /**
     * Created by sky on 2017/3/23.
     */
    public class Lock {
        public static final Byte lock = new Byte("0");
    }
    package com.threadTest.thread.add.state.test01;
    
    
    /**
     * Created by sky on 2017/3/23.
     */
    public class MyThread extends Thread {
        public MyThread() {
            try {
                System.out.println("构造方法中的状态1: " + Thread.currentThread().getState());
                Thread.sleep(1000);
                System.out.println("构造方法中的状态2: " + Thread.currentThread().getState());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public void run() {
            try {
                System.out.println("run 构造方法中的状态1: " + Thread.currentThread().getState());
                Thread.sleep(1000);
                try {
                    synchronized (Lock.lock) {
                        Thread.sleep(1000);
                        Lock.lock.wait();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("run 构造方法中的状态2: " + Thread.currentThread().getState());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    2、线程组

    线程组作用:可以批量的管理线程或线程组对象,有效地对线程或线程组对象进行组织。

    1)线程对象关联线程组:1级关联

    1级关联:父对象中有子对象,但并不创建子孙对象。为了有效地对这些线程进行组织管理,通常情况下是创建一个线程组,然后将部分线程归属到该组中,对零散的线程进行有限的组织与规划。

    package com.threadTest.thread.add.state.group;
    
    /**
     * Created by sky on 2017/3/23.
     */
    public class ThreadA extends Thread{
        @Override
        public void run() {
            try
            {
                while (!Thread.currentThread().isInterrupted())
                {
                    System.out.println("ThreadName = " + Thread.currentThread().getName());
                    Thread.sleep(3000);
                }
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }
    package com.threadTest.thread.add.state.group;
    
    /**
     * Created by sky on 2017/3/23.
     */
    public class ThreadB extends Thread{
        @Override
        public void run() {
            try
            {
                while (!Thread.currentThread().isInterrupted())
                {
                    System.out.println("ThreadName = " + Thread.currentThread().getName());
                    Thread.sleep(3000);
                }
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }
    package com.threadTest.thread.add.state.group;
    
    /**
     * Created by sky on 2017/3/23.
     */
    public class Run {
        public static void main(String[] args) {
            ThreadA threadA = new ThreadA();
            ThreadB threadB = new ThreadB();
            ThreadGroup group = new ThreadGroup("线程组");
            Thread athread = new Thread(group, threadA);
            Thread bthread = new Thread(group, threadB);
            athread.start();
            bthread.start();
            System.out.println("interrupt前活动的线程组: " + group.activeCount());
            System.out.println("线程组的名称为: " + group.getName());
            //线程中断
            group.interrupt();
            System.out.println("interrupt后活动的线程组: " + group.activeCount());
            group.list();
        }
    }
    输出:
    interrupt前活动的线程组: 2
    线程组的名称为: 线程组
    interrupt后活动的线程组: 2
    java.lang.ThreadGroup[name=线程组,maxpri=10]
        Thread[Thread-2,5,线程组]
        Thread[Thread-3,5,线程组]

    2)线程对象关联线程组:多级关联

    多级关联:父对象中有子对象,子对象中再创建子对象

    package com.threadTest.thread.add.state.group.test02;
    
    /**
     * Created by sky on 2017/3/23.
     */
    public class Run {
        public static void main(String[] args) {
            //在main组中添加一个线程组A,然后在这个A中添加线程对象Z
            //方法acctiveGroupCount()个acctiveCount()的值不是固定的
            //是系统环境的一个快照
            ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
            ThreadGroup group = new ThreadGroup(mainGroup, "A");
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println("runMethod!");
                    try {
                        Thread.sleep(10000); //线程必须在运行状态下才可以受组管理
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            Thread newThread = new Thread(group, runnable);
            newThread.setName("Z");
            newThread.start();//线程必须启动后才归到A组中
            ThreadGroup[] listGroup = new ThreadGroup[Thread.currentThread().getThreadGroup().activeGroupCount()];
            Thread.currentThread().getThreadGroup().enumerate(listGroup);
            System.out.println("main 线程中有多少个子线程组: " + listGroup.length + " 名字为: " + listGroup[0].getName());;
            Thread[] listThread = new Thread[listGroup[0].activeCount()];
            //将此listGroup[0]线程组中的每个活动线程复制到指定的数组中listThread
            listGroup[0].enumerate(listThread);
            System.out.println(listThread[0].getName());
        }
    }

    3)线程组自动归属特性

    自动归属:自动归到当前线程组中

    package com.threadTest.thread.add.state.group.test03;
    
    
    /**
     * Created by sky on 2017/3/23.
     */
    public class Run {
        public static void main(String[] args) {
            //方法activeGroupCount()取得当前线程组对象中的子线程数量
            //方法acctiveGroupCount()的作用是将线程中的子线程组以复制的形式拷贝到ThreadGroup[]数组对象中
            System.out.println("A处线程:" + Thread.currentThread().getName()
                    + "所属的线程组名为:"
                    + Thread.currentThread().getThreadGroup().getName() + " "
                    + "中有线程组数量: "
                    + Thread.currentThread().getThreadGroup().activeGroupCount());
            ThreadGroup group = new ThreadGroup("新的组");//自动加到main组中
            System.out.println("B处线程:" + Thread.currentThread().getName()
                    + " 所属的线程组名为: "
                    + Thread.currentThread().getThreadGroup().getName()
                    + " 中有线程组数量"
                    + Thread.currentThread().getThreadGroup().activeGroupCount());
            ThreadGroup[] threadGroups = new ThreadGroup[Thread.currentThread().getThreadGroup().activeGroupCount()];
            Thread.currentThread().getThreadGroup().enumerate(threadGroups);
            for (int i = 0; i < threadGroups.length; i++) {
                System.out.println("第一个线程组名称为: " + threadGroups[i].getName());
            }
        }
    }

    输出:
    A处线程:main所属的线程组名为:main 中有线程组数量: 0
    B处线程:main 所属的线程组名为: main 中有线程组数量1
    第一个线程组名称为: 新的组

     4)获取根线程组

    package com.threadTest.thread.add.state.group.test04;
    
    /**
     * Created by sky on 2017/3/23.
     */
    public class Run {
        public static void main(String[] args) {
            System.out.println("线程:" + Thread.currentThread().getName()
            + " 所在的线程组名为: "
            + Thread.currentThread().getThreadGroup().getName());
            System.out.println("main线程所在的线程组的父线程组名称是:"
            +Thread.currentThread().getThreadGroup().getParent()
                    .getName());
            System.out.println("main线程所在的线程组的父线程组的父线程组的名称是:"
            +Thread.currentThread().getThreadGroup().getParent()
            .getParent().getName());
        }
    }

    输出:
    Exception in thread "main" java.lang.NullPointerException
        at com.threadTest.thread.add.state.group.test04.Run.main(Run.java:16)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    线程:main 所在的线程组名为: main
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    main线程所在的线程组的父线程组名称是:system
        at java.lang.reflect.Method.invoke(Method.java:498)
        at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

    5)线程组里加线程组

    package com.threadTest.thread.add.state.group.test05;
    
    /**
     * Created by sky on 2017/3/23.
     */
    public class Run {
        public static void main(String[] args) {
            System.out.println("线程组名称:"
                    + Thread.currentThread().getThreadGroup().getName());
            System.out.println("线程组中活动的线程数量:"
                    + Thread.currentThread().getThreadGroup().activeCount());
            System.out.println("线程组中线程组的数量-加之前:"
                    + Thread.currentThread().getThreadGroup().activeGroupCount());
            new ThreadGroup(Thread.currentThread().getThreadGroup(), "newGroup");
            System.out.println("线程组中线程组数量-加之后:"
                    + Thread.currentThread().getThreadGroup().activeGroupCount());
            System.out.println("父线程组名称:"
                    + Thread.currentThread().getThreadGroup().getParent().getName());
        }
    }

    输出:
    线程组名称:main
    线程组中活动的线程数量:2
    线程组中线程组的数量-加之前:0
    线程组中线程组数量-加之后:1
    父线程组名称:system

    6)组内的线程的批量停止

    package com.threadTest.thread.add.state.group.test06;
    
    /**
     * Created by sky on 2017/3/23.
     */
    public class MyThread extends Thread{
        public MyThread(ThreadGroup group, String name){
            super(group, name);
        }
        @Override
        public void run() {
            System.out.println("ThreadName=" + Thread.currentThread()
                    .getName() + "准备开始死循环了:)");
            while (!this.isInterrupted()) {
            }
            System.out.println("ThreadName=" + Thread.currentThread().getName()
            +"结束了:)");
        }
    }
    package com.threadTest.thread.add.state.group.test06;
    
    /**
     * Created by sky on 2017/3/23.
     */
    public class Run {
        public static void main(String[] args) {
            try {
                ThreadGroup group = new ThreadGroup("我的线程组");
                for (int i = 0; i < 5; i++) {
                    MyThread thread = new MyThread(group, "线程" + (i + 1));
                    thread.start();
                }
                Thread.sleep(5000);
                group.interrupt();
                System.out.println("调用了interrupt()方法");
            } catch (InterruptedException e) {
                System.out.println("停了!");
                e.printStackTrace();
            }
        }
    }
    输出:
    ThreadName=线程1准备开始死循环了:)
    ThreadName=线程2准备开始死循环了:)
    ThreadName=线程3准备开始死循环了:)
    ThreadName=线程4准备开始死循环了:)
    ThreadName=线程5准备开始死循环了:)
    调用了interrupt()方法
    ThreadName=线程1结束了:)
    ThreadName=线程5结束了:)
    ThreadName=线程4结束了:)
    ThreadName=线程3结束了:)
    ThreadName=线程2结束了:)

    7)递归与非递归取得组内对象

    package com.threadTest.thread.add.state.group.test07;
    
    import com.threadTest.thread.add.state.group.test06.MyThread;
    
    /**
     * Created by sky on 2017/3/23.
     */
    public class Run {
        public static void main(String[] args) {
            ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
            ThreadGroup groupA = new ThreadGroup(mainGroup, "A");
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("runMethod!");
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            ThreadGroup groupB = new ThreadGroup(groupA, "B");
            //分配空间,但不一定全部用完
            ThreadGroup[] listGroup1 = new ThreadGroup[Thread.currentThread().getThreadGroup().activeGroupCount()];
            //传入true是递归取得子组及子孙组
            Thread.currentThread().getThreadGroup().enumerate(listGroup1,true);
            for (int i = 0; i < listGroup1.length; i++) {
                if (null != listGroup1[i]) {
                    System.out.println(listGroup1[i].getName());
                }
            }
            ThreadGroup[] listGroup2 = new ThreadGroup[Thread.currentThread().getThreadGroup().activeGroupCount()];
            //传入true是递归取得子组及子孙组
            Thread.currentThread().getThreadGroup().enumerate(listGroup2,false);
            for (int i = 0; i < listGroup2.length; i++) {
                if (null != listGroup2[i]) {
                    System.out.println(listGroup2[i].getName());
                }
            }
        }
    }

    输出:
    A
    B
    A

     3、使线程具有有序性

    package com.threadTest.thread.add.state.order;
    
    /**
     * Created by sky on 2017/3/24.
     */
    public class MyThread extends Thread{
        private Object lock;
        private String showChar;
        private int showNumPosition;
        private int printCount  = 0;//统计打印了几个字母
        volatile private static int addNumber = 1;
    
        public MyThread(Object lock, String showChar, int showNumPosition) {
            super();
            this.lock = lock;
            this.showChar = showChar;
            this.showNumPosition = showNumPosition;
        }
    
        @Override
        public void run() {
            try {
                synchronized (lock) {
                    while (true) {
                        if (showNumPosition == addNumber % 3) {
                            System.out.println("ThreadName="
                                    + Thread.currentThread().getName()
                                    + " runCount=" + addNumber + " " + showChar);
                            lock.notifyAll();
                            addNumber++;
                            printCount++;
                            if (3 == printCount) {
                                break;
                            }
                        } else {
                            lock.wait();
                        }
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    输出:
    ThreadName=Thread-0 runCount=1 A
    ThreadName=Thread-1 runCount=2 B
    ThreadName=Thread-2 runCount=3 C
    ThreadName=Thread-0 runCount=4 A
    ThreadName=Thread-1 runCount=5 B
    ThreadName=Thread-2 runCount=6 C
    ThreadName=Thread-0 runCount=7 A
    ThreadName=Thread-1 runCount=8 B
    ThreadName=Thread-2 runCount=9 C

    4、SimpleDateFormat非线程安全

    1)出现异常

    package com.threadTest.thread.add.state.simpleDateFormat.test01;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * Created by sky on 2017/3/24.
     */
    public class MyThread extends Thread{
        private SimpleDateFormat sdf;
        private String dateString;
    
        public MyThread(SimpleDateFormat sdf, String dateString) {
            super();
            this.sdf = sdf;
            this.dateString = dateString;
        }
    
        @Override
        public void run() {
            try {
                Date dateRef = sdf.parse(dateString);
                String newDateString = sdf.format(dateRef).toString();
                if (!newDateString.equals(dateString)) {
                    System.out.println("ThreadName= " + this.getName()
                    + "报错了 日期字符串: " + dateString + " 转换成的日期为: " + newDateString);
                }
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }
    package com.threadTest.thread.add.state.simpleDateFormat.test01;
    
    import java.text.SimpleDateFormat;
    
    /**
     * Created by sky on 2017/3/24.
     */
    public class Test {
        public static void main(String[] args) {
            //共享变量SimpleDateFormat 导致 java.lang.NumberFormatException: multiple points
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            String[] dateStringArray = new String[]{"2000-01-01", "2001-02-02", "2003-03-03", "2004-04-04"};
            MyThread[] threadArray = new MyThread[4];
            for (int i = 0; i < dateStringArray.length; i++) {
                threadArray[i] = new MyThread(sdf, dateStringArray[i]);
            }
            for (int i = 0; i < dateStringArray.length; i++) {
                threadArray[i].start();
            }
        }
    }
    
    输出:
    Exception in thread "Thread-1" java.lang.NumberFormatException: For input string: ""
    ThreadName= Thread-0报错了 日期字符串: 2000-01-01 转换成的日期为: 2001-01-01
        at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    ThreadName= Thread-3报错了 日期字符串: 2004-04-04 转换成的日期为: 2003-03-03

    2)解决方法一

    package com.threadTest.thread.add.state.simpleDateFormat.test02;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * Created by sky on 2017/3/24.
     * 实际创建了多个SimpleDateFormat
     */
    public class DateTools {
        public static Date parse(String formatPattern, String dateString) throws ParseException {
            return new SimpleDateFormat(formatPattern).parse(dateString);
        }
        public static String format(String formatPattern, Date date) {
            return new SimpleDateFormat(formatPattern).format(date).toString();
        }
    }
    package com.threadTest.thread.add.state.simpleDateFormat.test02;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * Created by sky on 2017/3/24.
     */
    public class MyThread extends Thread{
        private SimpleDateFormat sdf;
        private String dateString;
    
        public MyThread(SimpleDateFormat sdf, String dateString) {
            super();
            this.sdf = sdf;
            this.dateString = dateString;
        }
    
        @Override
        public void run() {
            try {
                Date dateRef = DateTools.parse("yyyy-MM-dd", dateString);
                String newDateString = DateTools.format("yyyy-MM-dd", dateRef);
                if (!newDateString.equals(dateString)) {
                    System.out.println("ThreadName= " + this.getName()
                    + "报错了 日期字符串: " + dateString + " 转换成的日期为: " + newDateString);
                }
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }
    package com.threadTest.thread.add.state.simpleDateFormat.test02;
    
    import java.text.SimpleDateFormat;
    
    /**
     * Created by sky on 2017/3/24.
     */
    public class Test {
        public static void main(String[] args) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            String[] dateStringArray = new String[]{"2000-01-01", "2001-02-02", "2003-03-03", "2004-04-04"};
            MyThread[] threadArray = new MyThread[4];
            for (int i = 0; i < dateStringArray.length; i++) {
                threadArray[i] = new MyThread(sdf, dateStringArray[i]);
            }
            for (int i = 0; i < dateStringArray.length; i++) {
                threadArray[i].start();
            }
        }
    }

    3)解决方法二

    package com.threadTest.thread.add.state.simpleDateFormat.test03;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * Created by sky on 2017/3/24.
     */
    public class DateTools {
        public static ThreadLocal<SimpleDateFormat> t1 = new ThreadLocal<SimpleDateFormat>();
        public static SimpleDateFormat getSimpleDateFormat(String datePattern) {
            SimpleDateFormat sdf = null;
            sdf = t1.get();
            if (null == sdf) {
                sdf = new SimpleDateFormat(datePattern);
                t1.set(sdf);
            }
            return sdf;
        }
    }
    package com.threadTest.thread.add.state.simpleDateFormat.test03;
    
    import com.threadTest.thread.add.state.simpleDateFormat.test02.*;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * Created by sky on 2017/3/24.
     */
    public class MyThread extends Thread{
        private SimpleDateFormat sdf;
        private String dateString;
    
        public MyThread(SimpleDateFormat sdf, String dateString) {
            super();
            this.sdf = sdf;
            this.dateString = dateString;
        }
    
        @Override
        public void run() {
            try {
                Date dateRef = DateTools.getSimpleDateFormat("yyyy-MM-dd").parse(dateString);
                String newDateString = DateTools.getSimpleDateFormat("yyyy-MM-dd").format(dateRef).toString();
                if (!newDateString.equals(dateString)) {
                    System.out.println("ThreadName= " + this.getName()
                    + "报错了 日期字符串: " + dateString + " 转换成的日期为: " + newDateString);
                }
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }

    5、线程中出现异常的处理

    package com.threadTest.thread.add.state.exception;
    
    /**
     * Created by sky on 2017/3/24.
     */
    public class MyThread extends Thread{
        @Override
        public void run() {
            String username = null;
            //NullPointerException
            System.out.println(username.hashCode());
        }
    }
    package com.threadTest.thread.add.state.exception;
    
    /**
     * Created by sky on 2017/3/24.
     */
    public class Main01 {
        public static void main(String[] args) {
            MyThread myThread1 = new MyThread();
            myThread1.setName("线程t1");
            //setUncaughtExceptionHandler 对指定的线程对象设置默认的异常处理器
            myThread1.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                @Override
                public void uncaughtException(Thread t, Throwable e) {
                    System.out.println("线程: " + t.getName() + " 出现异常: ");
                    e.printStackTrace();
                }
            });
            myThread1.start();
            MyThread myThread2 = new MyThread();
            myThread2.setName("线程t2");
            myThread2.start();
        }
    }
    输出:
    线程: 线程t1 出现异常: 
    Exception in thread "线程t2" java.lang.NullPointerException
        at com.threadTest.thread.add.state.exception.MyThread.run(MyThread.java:11)
    java.lang.NullPointerException
        at com.threadTest.thread.add.state.exception.MyThread.run(MyThread.java:11)
    package com.threadTest.thread.add.state.exception;
    
    /**
     * Created by sky on 2017/3/24.
     */
    public class Main02 {
        public static void main(String[] args) {
            //setDefaultUncaughtExceptionHandler 为指定线程类的所有线程对象设置默认的异常处理器
            MyThread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                @Override
                public void uncaughtException(Thread t, Throwable e) {
                    System.out.println("线程: " + t.getName() + " 出现异常: ");
                    e.printStackTrace();
                }
            });
            MyThread myThread1 = new MyThread();
            myThread1.setName("线程t1");
            myThread1.start();
            MyThread myThread2 = new MyThread();
            myThread2.setName("线程t2");
            myThread2.start();
        }
    }
    输出:
    线程: 线程t1 出现异常: 
    java.lang.NullPointerException
        at com.threadTest.thread.add.state.exception.MyThread.run(MyThread.java:11)
    java.lang.NullPointerException
    线程: 线程t2 出现异常: 
        at com.threadTest.thread.add.state.exception.MyThread.run(MyThread.java:11)
  • 相关阅读:
    【Android UI设计与开发】第03期:引导界面(三)仿微信引导界面以及动画效果
    【Android UI设计与开发】第02期:引导界面(二)使用ViewPager实现欢迎引导页面
    【Android UI设计与开发】第01期:引导界面(一)ViewPager介绍和使用详解
    【Android UI设计与开发】第15期:顶部标题栏(六)实现悬浮式顶部和底部标题栏效果
    Android 百度地图开发(一)--- 申请API Key和在项目中显示百度地图
    Android 百度地图开发(二)--- 定位功能之MyLocationOverlay,PopupOverlay的使用
    jsp生命周期,和知识点添加
    jsp 脚本,声明,用户端不可见注释
    初学jsp,1、配置myeclipse
    呃呃,才意识到
  • 原文地址:https://www.cnblogs.com/beaconSky/p/6606425.html
Copyright © 2020-2023  润新知