5多线程
多线程
1 程序,进程,线程
程序(program)
- 是完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码。
进程(process)
- 是程序一次执行过程,或者正在运行的一个程序,是一个动态的过程,有自身的产生,存在,和消亡的过程。
- 如运行中的qq,程序是静态的,进程是动态的。
- 进程作为资源分配的单位,系统在运行时,会为每个进程分配不同的内存区域。
线程(thread)
- 进程可以进一步细化为线程,是一个程序内部的一条执行路径。
- 若一个进程同一时间并行执行多个线程,就是支持多线程的。
- 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小。
- 一个进程中的多个线程共享相同的内存单元/内存地址空间->它们从同一堆中分配对象,可以访问相同的变量和对象,这就使得线程间通信更简便,高效。但多个线程操作共享的系统资源可能会带来安全的隐患。
2 多线程
并行与并发
- 并行:多个CPU同时执行多个任务,比如:多个人同时做不同的事
- 并发:一个CPU(采用时间片)同时执行多个任务
多线程的优点:
- 提高应用程序的相应,对图形化界面更有意义,可增强用户体验
- 提高计算机系统CPU利用率
- 改善程序结构
何时需要多线程
- 程序需要同时执行两个或多个任务
- 程序需要一些需要等待的任务时,如用户输入,文件读写操作,网络操作,搜索等
- 需要一些后台运行的程序时
3 多线程的创建,方式一:继承于Thread类
创建步骤:
- 创建一个继承于Thread类的子类
- 重写Thread类的run()方法--将此线程执行的操作声明在run()中
- 创建Thread类子类对象
- 通过此对象调用start() a启动当前线程b调用当前线程的run()
注意:
- 不能通过直接调用run()的方式启动线程
- 再启动一个线程 不可以再让已经start()线程继续执行 需要再新建一个
//例子 遍历100以内所有偶数
//1
class myThread extends Thread{
//2
@Override
public void run() {
for (int i=0;i<100;i++){
if(i%2==0){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
}
public class G2ThreadTest {
public static void main(String[] args) {
//3
myThread myThread = new myThread();//alr+enter自动生成
//4
myThread.start();
//t1.run这样就不是多线程了
//如下操作还是在main线程中执行的
System.out.println("main");
}
}
//最终结果
//main
//Thread-0 0
//...
简写:创建Thread类的匿名子类的方式
public class G3ThreadJianXie {
public static void main(String[] args) {
//简写
//创建Thread类的匿名子类的方式
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
}.start();
}
}
常用方法
- start():启动当前线程,调用当前线程的run()
- run():通常需要重写thread类中的此方法,将创建线程要执行的操作声明在此方法中
- currentThread():静态方法,返回执行当前代码的线程
- getName():获取当前线程的名字
- setName():设置当前线程的方法
- yield():释放当前cpu的执行权 有可能下一刻又分配到当前线程
- join():在线程a中调用b的join方法,此时线程a进入阻塞状态,直到线程b完全执行完以后,a才结束阻塞状态
- stop():已过时强制结束当前线程
- sleep(long milltime):让当前进程睡眠milltime毫秒数,睡眠时间内当前线程是阻塞状态
- isAlive():检查线程是否存活
线程优先级:
- MAX_PRIORITY: 10 MIN_PRIORITY: 1 NORM_PRIORITY: 5
- 获取和设置当前线程优先级, getPriority(), setPriority();
- 说明:高优先级要抢占低优先级cpu的执行权,但只是从概率上来讲 并不意味着低优先级一定比高优先级晚执行
class HelloThread extends Thread{
@Override
public void run() {
for (int i=0;i<100;i++){
if (i%2==0){
try {
sleep(10);//单位毫秒 因为父类没有抛异常所以这边不能抛
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+Thread.currentThread().getPriority()+" "+i);
}
// if (i%20==0){
// this.yield();
// }
}
}
public HelloThread(String name){
super(name);
}
}
public class G4ThreadFangFa {
public static void main(String[] args) throws InterruptedException {
HelloThread h1=new HelloThread("线程11 ");
h1.setName("线程1 ");//设置线程名字 注意加空格
h1.setPriority(Thread.MAX_PRIORITY);//设置线程优先级
h1.start();
Thread.currentThread().setName("主线程");//设置主线程名字
for (int i=0;i<100;i++){
if (i%2==0){
System.out.println(Thread.currentThread().getName()+Thread.currentThread().getPriority()+" "+i);
}
if(i==20){
h1.join();//当主线程中i到20时停顿 知道线程1中所有任务完成再开始
}
}
System.out.println(h1.isAlive());//false
}
}
//主线程 5 0
//线程1 10 0
//...详细结果看G4
卖票例子
/*
创建三个窗口卖票 总票数100张
存在线程安全问题 待解决
*/
class Window extends Thread{
private static int ticket=100;//必须静态
@Override
public void run() {
while (true){
if (ticket>0){
System.out.println(getName()+": 卖票,票号为:"+ticket);
ticket--;
}else {
break;
}
}
}
}
public class G5ThreadLiZi {
public static void main(String[] args) {
Window t1=new Window();
Window t2=new Window();
Window t3=new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t2.start();
t3.start();
t1.start();
}
}
4 多线程的创建,方式二:实现Runnable接口
创建步骤
- 创建了一个实现Runnable接口的类
- 实现类去实现Runnable中的抽象方法:run()
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 通过Thread类的对象调用start() a启动线程 b调用当前线程的run()方法-调用Runnable类型的target的run
比较创建线程的两种方式
- 开发中,优先选择:实现Runnable接口的方式
- 原因1:实现方式没有类的单继承性的局限性
- 原因2:实现方式更适合处理多个线程共享数据的情况
- 联系1:Thread类本身也是实现了Runnable
- 联系2:两种方法都需要重写run(),将执行逻辑都写在run()中
//1
class MThread implements Runnable{
//2
//@Override
public void run() {
for (int i=0;i<100;i++){
if (i%2==0){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
}
public class G6ThreadTest2 {
public static void main(String[] args) {
//3
MThread mThread=new MThread();
//4
Thread t1=new Thread(mThread);
//5
t1.start();
//6再启动一个线程
Thread t2=new Thread(mThread);
t2.start();
}
}
卖票例子
/*
用方法二实现卖票
还是存在线程问题
*/
class Window1 implements Runnable{
private int ticket=100;//这边不用static
@Override
public void run() {
while (true){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+": 卖票,票号为:"+ticket);
ticket--;
}else {
break;
}
}
}
}
public class G7ThreadLiZi2 {
public static void main(String[] args) {
Window1 w=new Window1();
Thread t1=new Thread(w);
Thread t2=new Thread(w);
Thread t3=new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
5 线程安全
问题
- 卖票过程中出现了重票错票--出现了线程安全问题
问题出现原因
- 当某个线程操作车票的过程中,尚未操作完成时,其他线程也来操作
如何解决
- 当一个线程a在操作共享数据时,其他线程不能参与进来,直到a操作完才行,即使a阻塞也不能改变
- 在Java中我们通过同步机制来解决安全问题
方式一:同步代码块
-
synchronized(同步监视器){
需要同步的代码
}
-
说明:操作共享数据的代码,即为需要被同步的代码
-
共享数据:多个线程共同操作的变量
-
同步监视器:俗称锁,任何一个类的对象,都可以充当锁
-
要求:多个线程必须要共有同一把锁
-
在实现runnable接口创建多线程方式中,可以考虑用this充当监视器
-
在继承Thread类创建多线程方式中,慎用this 通常这个不是唯一 可以考虑当前类
方式二:同步方法
- 如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步的
- 同步方法仍然设到同步监视器,只是不需要我们显示的声明
- 非静态的同步方法,同步监视器:this
- 静态的同步方法,同步监视器:当前类本身
同步的好坏
- 好处:解决了线程的安全问题
- 坏处:操作同步代码时,只能有一个线程参与,其他线程等待,相当于单线程过程
- 因此不要多包也不要少包
//实现runnable接口+同步代码块
class Window1 implements Runnable{
private int ticket=100;//这里可以不用静态
Object obj=new Object();
@Override
public void run() {
//private int ticket=100;放在这就不安全了 多个线程共用一个锁
while (true){
//synchronized (this) this是最方便的 相当于唯一的window1的对象
//synchronized(window1.class)
synchronized (obj) {
if (ticket > 0) {
//被阻塞容易发生线程安全问题(模拟用)
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(currentThread().getName() + ": 卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class G8XianChenAnQuan {
public static void main(String[] args) {
Window1 w=new Window1();
Thread t1=new Thread(w);
Thread t2=new Thread(w);
Thread t3=new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
//继承Thread类+同步代码块
class Window extends Thread{
private static int ticket=100;//必须静态
private static Object obj=new Object();
@Override
public void run() {
while (true){
synchronized (obj){//synchronized(Window.class)这里最方便
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+": 卖票,票号为:"+ticket);
ticket--;
}else {
break;
}
}
}
}
}
public class G9XianChenAnQuan2 {
public static void main(String[] args) {
Window t1=new Window();
Window t2=new Window();
Window t3=new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t2.start();
t3.start();
t1.start();
}
}
//实现runnable接口+同步方法
class Window3 implements Runnable{
private int ticket=100;
@Override
public void run() {
while (true){
show();
}
}
public synchronized void show(){//同步监视器:this 隐含了不用我们显示声明
if (ticket > 0) {
//被阻塞容易发生线程安全问题
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(currentThread().getName() + ": 卖票,票号为:" + ticket);
ticket--;
}
}
}
public class G10XianChenAnQuan3 {
public static void main(String[] args) {
Window3 w = new Window3();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
//继承Thread类+同步方法
class Window4 extends Thread {
private static int ticket = 100;//必须静态
@Override
public void run() {
while (true) {
show();
}
}
//这里默认是this 还是不安全的 要写成静态的
//变成静态的此时就是当前类
public static synchronized void show(){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 卖票,票号为:" + ticket);
ticket--;
}
}
}
public class G11XianChenAnQuan4 {
public static void main(String[] args) {
Window4 t1=new Window4();
Window4 t2=new Window4();
Window4 t3=new Window4();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t2.start();
t3.start();
t1.start();
}
}
方式三:Lock锁(5.0新增)
具体使用
- java.util.concurrent.locks.Lock接口控制多个线程对共享数据资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
- ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁,释放锁
synchromized与lock异同
相同
- 二者都可以解决线程安全问题不同
不同
- synchronized机制在执行完相应的同步代码以后,自动释放同步监视器; Lock需要手动的启动同步(lock)同时手动结束同步(unlock)
- synchronized有代码锁和方法锁,lock只有代码块锁
- 使用lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多子类)
- 优先使用顺序:Lock -> 同步代码块 -> 同步方法
//实现runnable接口+Lock
class Window implements Runnable{
private int ticket=100;
//1实例化ReentrantLock
private ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while (true){
try {
//2调用锁定方法lock()
lock.lock();
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+ticket);
ticket--;
}else {
break;
}
}finally {
//3调用解锁方法unlock()
lock.unlock();
}
}
}
}
public class G14XianChenAnQuannew {
public static void main(String[] args) {
Window w=new Window();
Thread t1=new Thread(w);
Thread t2=new Thread(w);
Thread t3=new Thread(w);
t1.setName("线程1");
t2.setName("线程2");
t3.setName("线程3");
t1.start();
t2.start();
t3.start();
}
}
6 线程的分类和线程的生命周期
线程的分类
- Java中的线程分为两类:一种是守护线程,一种是用户线程
- 它们在几乎每个方面都是相同的,唯一区别是判断JVM何时离开
- 守护线程是用来服务用户线程的,通过在start()方法前调用,thread.setDaemon(true)可以把一个用户线程变成一个守护线程
- Java垃圾回收就是一个典型的守护线程
- 若JVM中都是守护线程,当前JVM将推出
线程的生命周期
- jdk中用Thread.State类定义了线程的几种状态
- 要想实现多线程,必须在主线程中创建新的线程对象。java语言使用thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态
- 新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
- 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
- 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
- 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

7 死锁
死锁:不同线程分别占用对方需要同步资源不放弃,等待对方放弃自己需要的同步资源,形成了死锁,我们使用同步时要避免出现死锁。
class A {
public synchronized void foo(B b) {//同步监视器:A类的对象:a
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 进入了A实例的foo方法"); // ①
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 企图调用B实例的last方法"); // ③
b.last();
}
public synchronized void last() {
System.out.println("进入了A类的last方法内部");
}
}
class B {
public synchronized void bar(A a) {//同步监视器:B类的对象 b
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 进入了B实例的bar方法"); // ②
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 企图调用A实例的last方法"); // ④
a.last();
}
public synchronized void last() {
System.out.println("进入了B类的last方法内部");
}
}
public class G13SiSuo implements Runnable{
A a = new A();
B b = new B();
public void init(){
Thread.currentThread().setName("主线程");
// 调用a对象的foo方法
a.foo(b);
System.out.println("进入了主线程之后");
}
public void run() {
Thread.currentThread().setName("副线程");
// 调用b对象的bar方法
b.bar(a);
System.out.println("进入了副线程之后");
}
public static void main(String[] args) {
G13SiSuo dl = new G13SiSuo();
new Thread(dl).start();
dl.init();
}
}
8 线程的通信(交互)
常用方法
- wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
- notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程则唤醒优先级高的那个
- notifyall():一旦执行,就会唤醒所有被wait的线程
说明
- wait notify notifyall这三个方法必须使用在同步代码块或同步方法中
- 三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则或出现异常
- 三个方法是定义在java.lang.Object中,不是Thread,方便任何对象都可以调
sleep和wait的异同
相同
- 一旦执行方法,都可以让当前线程进入阻塞状态
不同:
- 两个方法声明的位置不同:Thread类中声明sleep(),Object中声明wait()
- 调用的要求不同:sleep可以在任何需要场景下调用;wait必须使用在同步代码块和同步方法中
- 关于是否释放同步监视器,如果两个方法都使用在同步代码块或同步方法中,sleep不会释放锁,wait或释放锁(同步监视器)
例题:使用两个线程打印1-100,线程1线程2交替打印
class Number implements Runnable{
private int number =1;
@Override
public void run() {
while (true){
synchronized (this){
//唤醒 两个用notify 三个及三个以上同notifyall
//省略了this. 应该是notifuAll(this)
//但如果上面的锁自己定义过的obj 再加this就不对了 需要变成obj.
notifyAll();
if (number<=100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+number);
number++;
try {
//使得调用如下wait方法的进程进入阻塞状态wait一执行会释放锁
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
public class G16XianChenTongXin {
public static void main(String[] args) {
Number number=new Number();
Thread t1=new Thread(number);
Thread t2=new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
经典例题:生产者/消费者问题。
- 生产者Producer将产品交给店员Clerk,而消费者Customer从店员处取走产品,店员一次只能持有固定 数量的产品(比如20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者取走产品
/*
生产者消费者问题
是否是多线程问题:是,生产者线程,消费者线程
是否有共享数据:是,店员(或产品)
如何解决线程的安全问题?同步机制
是否涉及到线程的通信?是
*/
class Clerk{
private int productCount=0;
//生产产品
public synchronized void produceProduct() {
if (productCount<20){
productCount++;
System.out.println(Thread.currentThread().getName()+" 开始生产第"+productCount+"个产品");
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消费产品
public synchronized void consumeProduct() {
if (productCount>0){
System.out.println(Thread.currentThread().getName()+" 开始消费第"+productCount+"个产品");
productCount--;
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//生产者
class Producer extends Thread{
private Clerk clerk;
//构造器
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName()+":开始生产产品......");
while (true){
try {
Thread.sleep(10);//为了便于观察结果 无实际作用
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
//消费者
class Consumer extends Thread{
private Clerk clerk;
//构造器
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName()+":开始消费产品......");
while (true){
try {
Thread.sleep(10);//如果时间一样基本就持平 如果时间不一样效果更好
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
public class G17XianChenTestZuiHou {
public static void main(String[] args) {
Clerk clerk=new Clerk();
Producer p1=new Producer(clerk);
p1.setName("生产者1");
Consumer c1=new Consumer(clerk);
c1.setName("消费者1");
p1.start();
c1.start();
}
}
9 JDK5.0新增线程创建方式一:实现Callable接口
如何理解实现callable比实现runnable要强大
- 相比run()方法,call()方法可以有返回值
- call()方法可以抛出异常,被外面操作捕获,获取异常的信息
- callable支持泛型
- 需要借助FutureTask类,比如获取返回结果
//1创建一个实现Callable的实现类
class NumThread implements Callable{
//2实现call方法 将此线程需要执行的操作声明其中
@Override
public Object call() throws Exception {
int sum=0;
for (int i=1;i<=100;i++){
if (i%2==0){
System.out.println(i);
sum+=i;
}
}
return sum;//自动装箱
}
}
public class G18ThreadNew {
public static void main(String[] args) {
//3创建Callable接口实现类的对象
NumThread numThread=new NumThread();
//4将此callable接口实现类的对象传递到futuretask构造器中,创建futuretask对象
FutureTask futureTask = new FutureTask(numThread);
//5将futuretask对象作为参数传递到thread类构造器中,创建thread对象,并调用start
Thread thread= new Thread(futureTask);
thread.start();
//对get感兴趣才有下一步
//6获取callable中的call方法返回值(可选),如果对return值不感兴趣可以不调
try {
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
Object sum=futureTask.get();
System.out.println("总和为:"+sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
10 JDK5.0新增线程创建方式二:线程池
简介
- jdk5.0 起提供了线程池相关API:ExecutorService和Executors
ExecutorService
- 真正的线程池接口。常见子类ThreadPoolExecutor
- void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task):执行任务,有返回值,一般用来执行Callable - void shutdown(): 关闭连接池
Executors
- 工具类,线程池的工厂类,用于创建并返回不同类型的线程池
- Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
- Executors.newFixedThreadPool():创建一个可重用固定线程数的线程池
- Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池
- Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行
线程池的好处
- 提高响应速度
- 降低资源消耗
- 便于线程管理
//线程1
class NumberThread1 implements Runnable{
@Override
public void run() {
for (int i=0;i<=100;i++){
if (i%2==0){
System.out.println(i);
}
}
}
}
//线程2
class NumberThread2 implements Runnable{
@Override
public void run() {
for (int i=0;i<=100;i++){
if (i%2!=0){
System.out.println(i);
}
}
}
}
public class G19ThreadNew2 {
public static void main(String[] args) {
//1提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//ThreadPoolExecutor service1=(ThreadPoolExecutor)service;
//设置线程池的属性
System.out.println(service.getClass());
//class java.util.concurrent.ThreadPoolExecutor
//2执行指定线程的操作,需要提供实现runnable或callable接口实现的类
service.execute(new NumberThread1());//适合Runnable
service.execute(new NumberThread2());
//service.submit();//适合Callable
//3关闭线程池
service.shutdown();
}
}

浙公网安备 33010602011771号