Java多线程——Thread类
Java多线程——Thread类
Java 中线程实现方式有两种:
- 继承Thread类,并重写run方法
- 实现Runnable接口的run方法
Thread类
使用方法:继承Thread类,并重写run方法
public class Demo {
    public static class MyThread extends Thread {
        public void run() {
            System.out.println("my thread");
        }
    }
    public static void main(String[] args) {
        Thread mythread = new MyThread();
        // 调用start()方法后,该线程才算启动
        mythread.start();
    }
}
Runable 接口
使用方法:实现Runnable接口的run方法
public class Demo1 {
    public static class MyThread implements Runnable {
        @Override
        public void run() {
            System.out.println("MyThread");
        }
    }
    public static void main(String[] args) {
        new Demo.MyThread().start();
        // Java 8 函数式编程,可以省略MyThread类
        new Thread(() -> {
            System.out.println("Java 8 匿名内部类");
        }).start();
    }
}
Java线程状态
Thread 类里有一个枚举类型 State,定义了线程的几种状态,分别有:
- NEW:表示当前线程尚未启动,NEW状态表示实例化一个线程之后,但没有开始执行,即 Thread 实例还没调用 start() 方法。
- RUNNABLE:表示当前线程正在运行中,RUNNABLE 状态包括了操作系统线程状态中的 Running 和 Ready,处于此状态的线程可能正在运行,也可能正在等待系统资源。
- BLOCKED:表示当前线程处于阻塞状态,处于 BLOCKED 状态的线程正等待锁的释放以进入同步区。
- WAITING:表示当前线程处于无限期等待状态,处于这种状态的线程不会被分配CPU执行时间,它们要等待显示的被其它线程唤醒。
- TIMED_WAITING:表示当前线程处于超时等待状态,处于这种状态的线程也不会被分配 CPU 执行时间,不过无需等待被其它线程显示的唤醒,在一定时间之后它们会由系统自动的唤醒。
- TERMINATED:表示当前线程处于终止状态,已终止线程的线程状态,线程已经结束执行,即 run() 方法执行完成。
Java8 中 state 枚举类代码:
public enum State {
   /**
    * Thread state for a thread which has not yet started.
    */
   NEW,
   /**
    * Thread state for a runnable thread.  A thread in the runnable
    * state is executing in the Java virtual machine but it may
    * be waiting for other resources from the operating system
    * such as processor.
    */
   RUNNABLE,
   /**
    * Thread state for a thread blocked waiting for a monitor lock.
    * A thread in the blocked state is waiting for a monitor lock
    * to enter a synchronized block/method or
    * reenter a synchronized block/method after calling
    * {@link Object#wait() Object.wait}.
    */
   BLOCKED,
   /**
    * Thread state for a waiting thread.
    * A thread is in the waiting state due to calling one of the
    * following methods:
    * <ul>
    *   <li>{@link Object#wait() Object.wait} with no timeout</li>
    *   <li>{@link #join() Thread.join} with no timeout</li>
    *   <li>{@link LockSupport#park() LockSupport.park}</li>
    * </ul>
    *
    * <p>A thread in the waiting state is waiting for another thread to
    * perform a particular action.
    *
    * For example, a thread that has called <tt>Object.wait()</tt>
    * on an object is waiting for another thread to call
    * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
    * that object. A thread that has called <tt>Thread.join()</tt>
    * is waiting for a specified thread to terminate.
    */
   WAITING,
   /**
    * Thread state for a waiting thread with a specified waiting time.
    * A thread is in the timed waiting state due to calling one of
    * the following methods with a specified positive waiting time:
    * <ul>
    *   <li>{@link #sleep Thread.sleep}</li>
    *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
    *   <li>{@link #join(long) Thread.join} with timeout</li>
    *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
    *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
    * </ul>
    */
   TIMED_WAITING,
   /**
    * Thread state for a terminated thread.
    * The thread has completed execution.
    */
   TERMINATED;
}
查看线程当前状态可以调用 getState() 方法,并进入:
/**
  * Returns the state of this thread.
  * This method is designed for use in monitoring of the system state,
  * not for synchronization control.
  *
  * @return this thread's state.
  * @since 1.5
*/
public State getState() {
    // get current thread state
    return sun.misc.VM.toThreadState(threadStatus);
}
// sun.misc.VM 源码:
public static State toThreadState(int var0) {
    if ((var0 & 4) != 0) {
        return State.RUNNABLE;
    } else if ((var0 & 1024) != 0) {
        return State.BLOCKED;
    } else if ((var0 & 16) != 0) {
        return State.WAITING;
    } else if ((var0 & 32) != 0) {
        return State.TIMED_WAITING;
    } else if ((var0 & 2) != 0) {
        return State.TERMINATED;
    } else {
        return (var0 & 1) == 0 ? State.NEW : State.RUNNABLE;
    }
}
NEW:
实例化一个线程之后,并且这个线程没有开始执行,这个时候的状态就是 NEW,尚未启动指的是还没调用 Thread 实例的 start() 方法。
关于start() 的两个引申问题:
- 反复调用同一个线程的start()方法是否可行?
- 假如一个线程执行完毕(此时处于TERMINATED状态),再次调用这个线程的start()方法是否可行?
查看 start() 方法代码:
/**
 * Causes this thread to begin execution; the Java Virtual Machine
 * calls the <code>run</code> method of this thread.
 * <p>
 * The result is that two threads are running concurrently: the
 * current thread (which returns from the call to the
 * <code>start</code> method) and the other thread (which executes its
 * <code>run</code> method).
 * <p>
 * It is never legal to start a thread more than once.
 * In particular, a thread may not be restarted once it has completed
 * execution.
 *
 * @exception  IllegalThreadStateException  if the thread was already
 *               started.
 * @see        #run()
 * @see        #stop()
 */
public synchronized void start() {
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    group.add(this);
    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}
private native void start0();
在 start() 内部,这里有一个 threadStatus 的变量。如果它不等于0,调用 start() 是会直接抛出异常的。在调用一次start()之后,threadStatus 的值会改变(threadStatus !=0),此时再次调用 start() 方法会抛出 IllegalThreadStateException 异常。
因此,两个问题的答案都是不可行。
RUNNABLE:
表示当前线程正在运行中。处于RUNNABLE状态的线程在Java虚拟机中运行,也有可能在等待其他系统资源(比如I/O)。
RUNNABLE 状态也可以理解为存活着正在尝试征用 CPU 的线程(有可能这个瞬间并没有占用CPU,但是它可能正在发送指令等待系统调度)。由于在真正的系统中,并不是开启一个线程后,CPU就只为这一个线程服务,它必须使用许多调度算法来达到某种平衡,不过这个时候线程依然处于RUNNABLE状态。
BLOCKED:
BLOCKED 称为阻塞状态,原因通常是它在等待一个“锁”,当尝试进入一个 synchronized 语句块/方法时,锁已经被其它线程占有,就会被阻塞,直到另一个线程走完临界区或发生了相应锁对象的 wait() 操作后,它才有机会去争夺进入临界区的权利。
处于 BLOCKED 状态的线程,即使对其调用 thread.interrupt() 也无法改变其阻塞状态,因为 interrupt() 方法只是设置线程的中断状态,即做一个标记,不能唤醒处于阻塞状态的线程。
注意:
ReentrantLock.lock() 操作后进入的是 WAITING 状态,其内部调用的是 LockSupport.park() 方法。
WAITING:
处于这种状态的线程不会被分配CPU执行时间,它们要等待显示的被其它线程唤醒。WAITING 状态通常是指一个线程拥有对象锁后进入到相应的代码区域后,调用相应的“锁对象”的 wait() 方法操作后产生的一种结果。变相的实现还有 LockSupport.park()、Thread.join()等,它们也是在等待另一个事件的发生,也就是描述了等待的意思。
以下方法会让线程陷入无限期等待状态:
- 
没有设置timeout参数的Object.wait() 
- 
没有设置timeout参数的Thread.join() 
- 
LockSupport.park() 
注意:
LockSupport.park(Object blocker) 会挂起当前线程,参数blocker是用于设置当前线程的“volatile Object parkBlocker 成员变量”
parkBlocker 是用于记录线程是被谁阻塞的,可以通过LockSupport.getBlocker()获取到阻塞的对象,用于监控和分析线程用的。
“阻塞”与“等待”的区别:
- 
“阻塞”状态是等待着获取到一个排他锁,进入“阻塞”状态都是被动的,离开“阻塞”状态是因为其它线程释放了锁,不阻塞了; 
- 
“等待”状态是在等待一段时间 或者 唤醒动作的发生,进入“等待”状态是主动的 
如主动调用 Object.wait(),如无法获取到 ReentraantLock,主动调用 LockSupport.park(),如主线程主动调用 subThread.join(),让主线程等待子线程执行完毕再执行。离开“等待”状态是因为其它线程发生了唤醒动作或者到达了等待时间。
TIMED_WAITING:
处于这种状态的线程也不会被分配CPU执行时间,不过无需等待被其它线程显示的唤醒,在一定时间之后它们会由系统自动的唤醒。
以下方法会让线程进入TIMED_WAITING限期等待状态:
- 
Thread.sleep()方法 
- 
设置了timeout参数的Object.wait()方法 
- 
设置了timeout参数的Thread.join()方法 
- 
LockSupport.parkNanos()方法 
- 
LockSupport.parkUntil()方法 
TERMINATED:
已终止线程的线程状态,线程已经结束执行。这个状态仅仅是 Java 语言提供的一个状态,在操作系统内部可能已经注销了相应的线程,或者将它复用给其他需要使用线程的请求,而在Java语言级别只是通过Java代码看到的线程状态而已。
线程状态的转换
线程状态转换图:

BLOCKED状态与RUNNABLE状态的转换
处于BLOCKED状态的线程是因为在等待锁的释放。假如这里有两个线程a和b,a线程提前获得了锁并且暂未释放锁,此时b就处于BLOCKED状态。
同时,run() 方法的执行是需要时间的,并不是启动 start() 方法后就会立即执行,查看以下例子:
public class Demo2 {
    public void blockedTest() {
        Thread a = new Thread(new Runnable() {
            @Override
            public void run() {
                testMethod();
            }
        }, "a");
        Thread b = new Thread(new Runnable() {
            @Override
            public void run() {
                testMethod();
            }
        }, "b");
        a.start();
        b.start();
        System.out.println(a.getName() + ":" + a.getState()); // 输出?
        System.out.println(b.getName() + ":" + b.getState()); // 输出?
    }
    // 同步方法争夺锁
    private synchronized void testMethod() {
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        Demo2 demo2 = new Demo2();
        demo2.blockedTest();
    }
}
输出结果:此时 a 线程已得到锁,进入 run() 方法,但还没有执行 Thread.sleep(),由于 a 线程已得到锁,b 线程等待锁的释放,进入 BLOCKED 状态。
a:RUNNABLE
b:BLOCKED
Process finished with exit code 0
调换语句顺序,发现结果发生变化:
public void blockedTest() throws InterruptedException {
    ... ...
    a.start();
    System.out.println(a.getName() + ":" + a.getState()); // 输出?
    b.start();
    System.out.println(b.getName() + ":" + b.getState()); // 输出?
}
输出结果:原因是测试方法的main线程只保证了a,b两个线程调用start()方法(转化为RUNNABLE状态),还没等两个线程真正开始争夺锁,就已经打印此时两个线程的状态(RUNNABLE)了。
a:RUNNABLE
b:RUNNABLE
Process finished with exit code 0
让 main 线程停一段时间,就可以得到 BLOCKED 状态的结果:
public void blockedTest() throws InterruptedException {
    ... ...
    a.start();
    Thread.sleep(200L);
    System.out.println(a.getName() + ":" + a.getState()); // 输出?
    b.start();
    Thread.sleep(200L);
    System.out.println(b.getName() + ":" + b.getState()); // 输出?
}
输出结果:此时 a 线程已得到锁,正在执行 run() 方法,进入 TIMED_WAITING 状态,b 线程由于得不到锁,进入 BLOCKED 状态
a:TIMED_WAITING
b:BLOCKED
Process finished with exit code 0
WAITING状态与RUNNABLE状态的转换
有3个方法可以使线程从RUNNABLE状态转为WAITING状态。
Thread.join():
public void blockedTest() {
    ······
    a.start();
    a.join();
    b.start();
    System.out.println(a.getName() + ":" + a.getState()); // 输出 TERMINATED
    System.out.println(b.getName() + ":" + b.getState());
}
输出结果:调用join()方法不会释放锁,会一直等待当前线程执行完毕(转换为TERMINATED状态)。b线程的状态,有可能打印RUNNABLE(尚未进入同步方法),也有可能打印TIMED_WAITING(进入了同步方法)。
a:TERMINATED
b:TIMED_WAITING
Process finished with exit code 0
需要注意的是,其他线程调用notify()方法只会唤醒单个等待锁的线程,如有有多个线程都在等待这个锁的话不一定会唤醒到之前调用wait()方法的线程。
Object.wait():导致线程进入等待状态,直到它被其他线程通过notify()或者notifyAll唤醒。该方法只能在同步方法中调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。
使用示例:
public class WaitNotifyTest {
	public static void main(String[] args) {
		Object lock = new Object();
		
		new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程A等待获取lock锁");
                synchronized (lock) {
                    try {
                        System.out.println("线程A获取了lock锁");
                        Thread.sleep(1000);
                        System.out.println("线程A将要运行lock.wait()方法进行等待");
                        lock.wait();
                        System.out.println("线程A等待结束");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
		
		new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程B等待获取lock锁");
                synchronized (lock) {
                    System.out.println("线程B获取了lock锁");
                    try {
                    	Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程B将要运行lock.notify()方法进行通知");
                    lock.notify();
                }
            }
        }).start();
	}
}
输出结果:
线程A等待获取lock锁
线程A获取了lock锁
线程B等待获取lock锁
线程A将要运行lock.wait()方法进行等待
线程B获取了lock锁
线程B将要运行lock.notify()方法进行通知
线程A等待结束
Process finished with exit code 0
LockSupport.park():LockSupport.park() 调用的是 Unsafe 中的 native 代码,与Object类的wait/notify机制相比,park 以 thread 为操作对象更符合阻塞线程的直观定义,操作更精准,可以准确地唤醒某一个线程增加了灵活性。
//LockSupport中
public static void park() {
        UNSAFE.park(false, 0L);
    }
TIMED_WAITING状态与RUNNABLE状态转换
TIMED_WAITING 与 WAITING 状态类似,只是 TIMED_WAITING 状态等待的时间是指定的。
Thread.sleep(long):
使当前线程睡眠指定时间。需要注意这里的“睡眠”只是暂时使线程停止执行,并不会释放锁。时间到后,线程会重新进入RUNNABLE状态。
Sleep 方法代码:
public static native void sleep(long millis) throws InterruptedException;
// 纳秒级别控制
public static void sleep(long millis, int nanos) throws InterruptedException {
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
            "nanosecond timeout value out of range");
    }
    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
        millis++;
    }
    sleep(millis);
}
Object.wait(long):
wait(long) 方法使线程进入 TIMED_WAITING 状态。这里的wait(long)方法与无参方法 wait() 相同的地方是,都可以通过其他线程调用 notify() 或 notifyAll() 方法来唤醒。
不同的地方是,有参方法 wait(long) 就算其他线程不来唤醒它,经过指定时间 long 之后它会自动唤醒,拥有去争夺锁的资格。
Thread.join(long):
join(long) 使当前线程执行指定时间,并且使线程进入 TIMED_WAITING 状态。
join(long) 的使用:调用 a.join(1000L),因为是指定了具体 a 线程执行的时间的,并且执行时间是小于 a 线程 sleep 的时间,所以 a 线程状态输出 TIMED_WAITING,b 线程状态仍然不固定(RUNNABLE 或 BLOCKED)。
public void blockedTest() {
    ······
    a.start();
    a.join(1000L);
    b.start();
    System.out.println(a.getName() + ":" + a.getState()); // 输出 TIEMD_WAITING
    System.out.println(b.getName() + ":" + b.getState());
}
Java线程中断
Thread类里提供的关于线程中断的几个方法:
- Thread.interrupt():中断线程。这里的中断线程并不会立即停止线程,而是设置线程的中断状态为true(默认是flase);
- Thread.interrupted():测试当前线程是否被中断。线程的中断状态受这个方法的影响,意思是调用一次使线程中断状态设置为true,连续调用两次会使得这个线程的中断状态重新转为false;
- Thread.isInterrupted():测试当前线程是否被中断。与上面方法不同的是调用这个方法并不会影响线程的中断状态。
“VisualVM线程监控线程状态”与“Java线程状态”对应关系总结:
通过 dump thread stack,并与 VisualVM 监控信息中的线程名称对应,找到的 VisualVM 每种线程状态的线程堆栈如下:
1、运行:RUNNABLE
"http-bio-8080-Acceptor-0" daemon prio=6 tid=0x000000000d7b4800 nid=0xa264 runnable [0x000000001197e000]
      java.lang.Thread.State: RUNNABLE
            at java.net.DualStackPlainSocketImpl.accept0(Native Method)
            at java.net.DualStackPlainSocketImpl.socketAccept(DualStackPlainSocketImpl.java:131)
            at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:398)
            at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:199)
            - locked <0x00000000c2303850> (a java.net.SocksSocketImpl)
            at java.net.ServerSocket.implAccept(ServerSocket.java:530)
            at java.net.ServerSocket.accept(ServerSocket.java:498)
            at org.apache.tomcat.util.net.DefaultServerSocketFactory.acceptSocket(DefaultServerSocketFactory.java:60)
            at org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:220)
            at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
        - None
2、休眠:sleeping
"Druid-ConnectionPool-Destory-293325558" daemon prio=6 tid=0x000000000d7ad000 nid=0x9c94 waiting on condition [0x000000000bf0f000]
      java.lang.Thread.State: TIMED_WAITING (sleeping)
           at java.lang.Thread.sleep(Native Method)
            at com.alibaba.druid.pool.DruidDataSource$DestroyConnectionThread.run(DruidDataSource.java:1685)
Locked ownable synchronizers:
        - None
3、等待:wait
"Finalizer" daemon prio=8 tid=0x0000000009349000 nid=0xa470 in Object.wait() [0x000000000a82f000]
      java.lang.Thread.State: WAITING (on object monitor)
            at java.lang.Object.wait(Native Method)
            - waiting on <0x00000000c22a0108> (a java.lang.ref.ReferenceQueue$Lock)
            at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
      - locked <0x00000000c22a0108> (a java.lang.ref.ReferenceQueue.Lock)
            at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
            at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
   Locked ownable synchronizers:
        - None
 
"JMX server connection timeout 45" daemon prio=6 tid=0x000000000e846000 nid=0xab10 in Object.wait() [0x00000000137df000]
      java.lang.Thread.State: TIMED_WAITING (on object monitor)
            at java.lang.Object.wait(Native Method)
           - waiting on <0x00000000c55da3f0> (a [I)
            at com.sun.jmx.remote.internal.ServerCommunicatorAdmin$Timeout.run(ServerCommunicatorAdmin.java:168)
            - locked <0x00000000c55da3f0> (a [I)
            at java.lang.Thread.run(Thread.java:745)
   Locked ownable synchronizers:
         - None
4、驻留:park
"http-bio-8080-exec-2" daemon prio=6 tid=0x000000000d7b8000 nid=0x9264 waiting on condition [0x000000000ee4e000]
      java.lang.Thread.State: WAITING (parking)
            at sun.misc.Unsafe.park(Native Method)
           - parking to wait for  <0x00000000c5629bc8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject)
            at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
            at java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
            at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
            at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104)
            at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32)
            at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1068)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
            at java.util.concurrent.ThreadPoolExecutor.Worker.run(ThreadPoolExecutor.java:615)
            at org.apache.tomcat.util.threads.TaskThread.WrappingRunnable.run(TaskThread.java:61)
            at java.lang.Thread.run(Thread.java:745)
   Locked ownable synchronizers:
        - None
"pool-9-thread-1" prio=6 tid=0x000000000d7b2000 nid=0xd5fc waiting on condition [0x000000001187e000]
       java.lang.Thread.State: TIMED_WAITING (parking)
             at sun.misc.Unsafe.park(Native Method)
             - parking to wait for  <0x00000000c563b9e0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject)
             at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:226)
             at java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2082)
             at java.util.concurrent.ScheduledThreadPoolExecutor.DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1090)
             at java.util.concurrent.ScheduledThreadPoolExecutor.DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:807)
             at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1068)
             at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
             at java.util.concurrent.ThreadPoolExecutor.Worker.run(ThreadPoolExecutor.java:615)
             at java.lang.Thread.run(Thread.java:745)
   Locked ownable synchronizers:
       - None
5、监视:monitor
"Thread-1" prio=6 tid=0x000000000a8a1800 nid=0xfdb4 waiting for monitor entry [0x000000000b4de000]
      java.lang.Thread.State: BLOCKED (on object monitor)
            at com.Test2$T.run(Test2.java:58)
           - waiting to lock <0x00000000eab757e0> (a java.lang.Object)
Locked ownable synchronizers:
      - None
汇总如下图所示:

 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号