java学习笔记10

多线程的引入

  • 1.什么是线程
    • 线程是程序执行的一条路径, 一个进程中可以包含多条线程
    • 多线程并发执行可以提高程序的效率, 可以同时完成多项工作
  • 2.多线程的应用场景
    • 红蜘蛛同时共享屏幕给多个电脑
    • 迅雷开启多条线程一起下载
    • QQ同时和多个人一起视频
    • 服务器同时处理多个客户端请求

多线程并行和并发的区别

  • 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
  • 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
  • 比如我跟两个网友聊天,左手操作一个电脑跟甲聊,同时右手用另一台电脑跟乙聊天,这就叫并行。
  • 如果用一台电脑我先给甲发个消息,然后立刻再给乙发消息,然后再跟甲聊,再跟乙聊。这就叫并发。

Java程序运行原理和JVM的启动是多线程的吗

  • A:Java程序运行原理

    • Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
  • B:JVM的启动是多线程的吗

    • JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
//结果中"我是主线程的执行代码"和"垃圾被清除了"是交替出现的
package com.heima.thread;

public class Demo1_Thread {

    /**
     * 证明jvm是多线程的
     */
    public static void main(String[] args) {
        for(int i = 0; i < 100000; i++) {
            new Demo();
        }
        
        for(int i = 0; i < 10000; i++) {
            System.out.println("我是主线程的执行代码");
        }
    }

}

class Demo {

    @Override
    protected void finalize() {
        System.out.println("垃圾被清除了");
    }
    
}

多线程程序实现的方式1

  • 1.继承Thread
    • 定义类继承Thread
    • 重写run方法
    • 把新线程要做的事写在run方法中
    • 创建线程对象
    • 开启新线程, 内部会自动执行run方法
package com.heima.thread;

public class Demo2_Thread {

    /**
     * @param args
     */
    public static void main(String[] args) {
        Mythread mt = new Mythread();       //4,创建Thread类的子类对象
        mt.start();                         //5,开启线程
        for (int i = 0; i < 1000; i++) {    
            System.out.println("bb");
        }
    }

}

class Mythread extends Thread {             //1,继承Thread
    public void run() {                     //2,重写run方法
        for (int i = 0; i < 1000; i++) {    //3,将要执行的代码写在run方法中
            System.out.println("aaaaaa");
        }
    }
}

多线程程序实现的方式2

  • 2.实现Runnable
    • 定义类实现Runnable接口
    • 实现run方法
    • 把新线程要做的事写在run方法中
    • 创建自定义的Runnable的子类对象
    • 创建Thread对象, 传入Runnable
    • 调用start()开启新线程, 内部会自动调用Runnable的run()方法
package com.heima.thread;

public class Demo3_Thread {

    /**
     * @param args
     */
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();   //4,创建Runnable的子类对象
        //Runnable target = mr;父类引用指向子类对象
        Thread t = new Thread(mr);  //5,将子类对象传递给Thread的构造函数,父类引用指向子类对象
        t.start();                  //6,开启线程
        
        for (int i = 0; i < 1000; i++) {    
            System.out.println("bb");
        }
    }

}

class MyRunnable implements Runnable {      //1,定义一个类实现Runnable

    @Override
    public void run() {                     //2,重写run方法
        for (int i = 0; i < 1000; i++) {    //3,将要执行的代码写在run方法中
            System.out.println("aaaaaa");
        }
    }
    
}

实现Runnable的原理

  • 查看源码
    • 1,看Thread类的构造函数,传递了Runnable接口的引用
    • 2,通过init()方法找到传递的target给成员变量的target赋值
    • 3,查看run方法,发现run方法中有判断,如果target不为null就会调用Runnable接口子类对象的run方法

两种方式的区别

  • 查看源码的区别:

    • a.继承Thread : 由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法(底层虚拟机完成,看不到源码)
    • b.实现Runnable : 构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量Runnable的引用是否为空, 不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法
  • 继承Thread

    • 好处是:可以直接使用Thread类中的方法,代码简单
    • 弊端是:如果已经有了父类,就不能用这种方法
  • 实现Runnable接口

    • 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的
    • 弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂

匿名内部类实现线程的两种方式

package com.heima.thread;

public class Demo4_Thread {

    /**
     * @param args
     */
    public static void main(String[] args) {
        new Thread() {                                  //1,继承Thread类
            public void run() {                         //2,重写run方法
                for (int i = 0; i < 1000; i++) {        //3,将要执行的代码写在run方法中
                    System.out.println("aaaaaaaaa");
                }
            }
        }.start();                                      //4,开启线程
        
        new Thread(new Runnable() {                     //1,将Runnable的子类对象传递给Thread的构造方法
            public void run() {                         //2,重写run方法
                for (int i = 0; i < 1000; i++) {        //3,将要执行的代码写在run方法中
                    System.out.println("bb");
                }
            }
        }).start();                                     //4,开启线程
    }

}

获取名字和设置名字

1.获取名字
* 通过getName()方法获取线程对象的名字

  • 2.设置名字
    • 通过构造函数可以传入String类型的名字
    • 通过setName(String)方法可以设置线程对象的名字
package com.heima.threadmethod;

public class Demo1_Name {

    public static void main(String[] args) {    //通过setName给name(线程名字)赋值
        new Thread("A") {
            public void run() {
                this.setName("张三");
                System.out.println(this.getName() + "....aaa");
            }
        }.start();
        
         Thread t = new Thread("B") {
            public void run() {
                this.setName("李四");
                System.out.println(this.getName() + "....bb");
            }
        };
        //t.setName("李四");
        t.start();
    }

    public static void demo1() {        //通过构造方法给name(线程名字)赋值
        new Thread("A") {
            public void run() {
                System.out.println(this.getName() + "....aaa");
            }
        }.start();
        
        new Thread("B") {
            public void run() {
                System.out.println(this.getName() + "....bbb");
            }
        }.start();
    }

}

获取当前线程的对象

  • Thread.currentThread(), 主线程也可以获取
package com.heima.threadmethod;

public class Demo2_CurrentThread {

    /**
     * @param args
     */
    public static void main(String[] args) {
        new Thread() {
            public void run() {
                System.out.println(getName() + "....aaaaaa");
            }
        }.start();
        
        new Thread(new Runnable() {
            public void run() {
                //Thread.currentThread()获取当前正在执行的线程
                System.out.println(Thread.currentThread().getName() + "...bb");
            }   
        }).start();
        
        Thread.currentThread().setName("我是主线程");
        System.out.println(Thread.currentThread().getName());
    }

}

休眠线程

  • Thread.sleep(毫秒,纳秒), 控制当前线程休眠若干毫秒1秒= 1000毫秒 1秒 = 1000 * 1000 * 1000纳秒 1000000000
package com.heima.threadmethod;

public class Demo3_Sleep {

    /**
     * @param args
     * @throws InterruptedException 
     */
    public static void main(String[] args) throws InterruptedException {
        new Thread() {
            public void run() {
                for(int i = 0; i < 10; i++) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        
                        e.printStackTrace();
                    }
                    System.out.println(getName() + "...aaaaa");
                }
            }
        }.start();
        
        new Thread() {
            public void run() {
                for(int i = 0; i < 10; i++) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        
                        e.printStackTrace();
                    }
                    System.out.println(getName() + "...bb");
                }
            }
        }.start();
    }

    public static void demo1() throws InterruptedException {
        for(int i = 20;i >= 0; i--) {
            Thread.sleep(1000);     //1秒= 1000毫秒
            System.out.println("倒数第" + i + "秒");
        }
    }

}

守护线程

  • setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出
package com.heima.threadmethod;

public class Demo4_Daemon {

    public static void main(String[] args) {
        Thread t1 = new Thread() {
            public void run() {
                for (int i = 0; i < 2; i++) {
                    System.out.println(getName() + "...aaaaa");
                }
            }
        };
        
        Thread t2 = new Thread() {
            public void run() {
                for (int i = 0; i < 50; i++) {
                    System.out.println(getName() + "...bb");
                }
            }
        };
        
        t2.setDaemon(true); //当传入true就是意味着设置为守护线程
        t1.start();
        t2.start();
    }

}

加入线程

  • join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
  • join(int), 可以等待指定的毫秒之后继续
package com.heima.threadmethod;

public class Demo5_Join {

    /**
     * @param args
     */
    public static void main(String[] args) {
        final Thread t1 = new Thread() {
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(getName() + "...aaaaaaa");
                }
            }
        };
        //匿名内部类在使用它所在方法中的局部变量的时候,必须用final修饰
        Thread t2 = new Thread() {
            public void run() {
                for (int i = 0; i < 10; i++) {
                    if(i == 2) {
                        try {
                            //t1.join();
                            t1.join(1); //插队指定的时间,过了指定时间后,两条线程交替执行,这里是1毫秒
                        } catch (InterruptedException e) {
                            
                            e.printStackTrace();
                        }
                    }
                    System.out.println(getName() + "...bb");
                }
            }
        };
        t1.start();
        t2.start();
    }

}

礼让线程

  • yield让出cpu
package com.heima.threadmethod;

public class Demo6_Yield {

    /**
     *
     */
    public static void main(String[] args) {
        new MyThread().start();
        new MyThread().start();
    }

}

class MyThread extends Thread {
    public void run() {
        for(int i = 1;i <= 1000; i++) {
            if(i % 10 == 0) {
                Thread.yield();
            }
            System.out.println(getName() + "..." + i);  //表现出的礼让效果很差
        }
    }
}

设置线程的优先级

package com.heima.threadmethod;

public class Demo7_Priority {

    public static void main(String[] args) {
        Thread t1 = new Thread(){
            public void run() {
                for(int i = 0; i < 1000; i++) {
                    System.out.println(getName() + "...aaaaaaaa");
                }
            }
        };
        
        Thread t2 = new Thread(){
            public void run() {
                for(int i = 0; i < 1000; i++) {
                    System.out.println(getName() + "...bb");
                }
            }
        };
        
        //t1.setPriority(10);       //设置最大优先级
        //t2.setPriority(1);
        
        t1.setPriority(Thread.MIN_PRIORITY);    //设置最小的线程优先级
        t2.setPriority(Thread.MAX_PRIORITY);    //设置最大的线程优先级
        
        t1.start();
        t2.start();
    }

}

同步代码块

  • 1.什么情况下需要同步
    • 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
    • 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
  • 2.同步代码块
    • 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
    • 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的
package com.heima.syn;

public class Demo1_Synchronized {

    /**
     * @param args
     */
    public static void main(String[] args) {
        final Printer p = new Printer();
        
        new Thread() {
            public void run() {
                while(true) {
                    p.print1();
                }
            }
        }.start();
        
        new Thread() {
            public void run() {
                while(true) {
                    p.print2();
                }
            }
        }.start();
    }

}

class Printer {
    String s = "abc";
    public void print1() {      
        synchronized(s) {
            System.out.print("黑");
            System.out.print("马");
            System.out.print("程");
            System.out.print("序");
            System.out.print("员");
            System.out.println();
        }
    }
    
    public void print2() {
        synchronized(s) {               //同步代码块,锁机制,锁对象可以是任意对象
            System.out.print("传");      //锁对象不能用匿名对象,因为匿名对象不是同一个对象
            System.out.print("智");
            System.out.print("播");
            System.out.print("客");
            System.out.println();
        }
    }
}

同步方法

  • 使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
  • 非静态同步函数的锁是:this
  • 静态的同步函数的锁是:字节码对象
package com.heima.syn;

public class Demo1_Synchronized {

    /**
     * @param args
     */
    public static void main(String[] args) {
        final Printer2 p = new Printer2();
        
        new Thread() {
            public void run() {
                while(true) {
                    p.print1();
                }
            }
        }.start();
        
        new Thread() {
            public void run() {
                while(true) {
                    p.print2();
                }
            }
        }.start();
    }

}

class Printer2 {
    String s = "abc";
    //public synchronized void print1() {   //同步方法只需要在方法上加synchronized关键字即可
    public static synchronized void print1() {      
        System.out.print("黑");
        System.out.print("马");
        System.out.print("程");
        System.out.print("序");
        System.out.print("员");
        System.out.println();
    }
    
    public void print2() {
        //synchronized(this) {              //非静态的同步方法的锁对象是this
        synchronized(Printer2.class) {      //静态的同步方法的锁对象是该类的字节码        
            System.out.print("传");          
            System.out.print("智");
            System.out.print("播");
            System.out.print("客");
            System.out.println();
        }
    }
}

线程安全问题

  • 多线程并发操作同一数据时, 就有可能出现线程安全问题
  • 使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作
package com.heima.syn;

public class Demo3_Ticket {

    /**
     * 需求:铁路售票,一共100张,通过四个窗口卖完
     */
    public static void main(String[] args) {
        new Ticket().start();
        new Ticket().start();
        new Ticket().start();
        new Ticket().start();
    }

}

class Ticket extends Thread {
    private static int ticket = 100;
    
    public void run() {
        while(true) {
            synchronized(Ticket.class) {    //如果用引用数据类型成员变量当作锁对象,必须是静态的
                if(ticket == 0) {
                    break;
                }
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    
                    e.printStackTrace();
                }
                System.out.println(getName() + "...这是第" + ticket-- + "号票");
            }
        }
    }
}

火车站卖票的例子用实现Runnable接口

package com.heima.syn;

public class Demo4_Ticket {

    /**
     * @param args
     */
    public static void main(String[] args) {
        MyTicket mt = new MyTicket();
        new Thread(mt).start();
        new Thread(mt).start();
        new Thread(mt).start();
        new Thread(mt).start();
        //多次启动一个线程是非法的
    }

}

class MyTicket implements Runnable {
    private int tickets = 100;
    public void run() {
        while(true) {
            synchronized(Ticket.class) {        //这里可以换成this
                if(tickets == 0) {
                    break;
                }
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "...这是第" + tickets-- + "号票");
            }
        }
    }
}

死锁

  • 多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁
    • 尽量不要嵌套使用
package com.heima.syn;

public class Demo5_DeadLock {

    /**
     * @param args
     */
    private static String s1 = "筷子左";
    private static String s2 = "筷子右";
    public static void main(String[] args) {
        new Thread() {
            public void run() {
                while(true) {
                    synchronized(s1) {
                        System.out.println(getName() + "...获取" + s1 + "等待" + s2);
                        synchronized(s2) {
                            System.out.println(getName() + "...拿到" + s2 + "开吃");
                        }
                    }
                }
            }
        }.start();
        
        new Thread() {
            public void run() {
                while(true) {
                    synchronized(s2) {
                        System.out.println(getName() + "...获取" + s2 + "等待" + s1);
                        synchronized(s1) {
                            System.out.println(getName() + "...拿到" + s1 + "开吃");
                        }
                    }
                }
            }
        }.start();
    }

}

以前的线程安全的类回顾

A:回顾以前说过的线程安全问题
* 看源码:Vector,StringBuffer,Hashtable
Collections.synchroinzed(xxx)返回指定 collection 支持的同步(线程安全的)collection,即可以将线程不安全变成线程安全的
* Vector是线程安全的,ArrayList是线程不安全的
* StringBuffer是线程安全的,StringBuilder是线程不安全的
* Hashtable是线程安全的,HashMap是线程不安全的

单例设计模式

  • 单例设计模式:保证类在内存中只有一个对象。
  • 如何保证类在内存中只有一个对象呢?
    • (1)控制类的创建,不让其他类来创建本类的对象。private
    • (2)在本类中定义一个本类的对象。Singleton s;
    • (3)提供公共的访问方式。 public static Singleton getInstance(){return s}
package com.heima.thread;

public class Demo1_Singleton {

    /**
     * @param args
     */
    public static void main(String[] args) {
        /*Singleton s1 = Singleton.s;
        Singleton s2 = Singleton.s;
        System.out.println(s1 == s2);*/
        
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);
    }

}

/*
 * 饿汉式
 */
class Singleton {
    //1,私有构造方法,其他类不能访问该构造方法了
    private Singleton() {}
    //2,创建本类对象
    private static Singleton s = new Singleton();
    //3,对外提供公共的访问方法
    public static Singleton getInstance() {         
        return s;
    }
}

/*
 * 懒汉式,单例的延迟加载模式
 */
/*class Singleton {
    //1,私有构造方法,其他类不能访问该构造方法了
    private Singleton() {}
    //2,声明一个引用
    private static Singleton s;
    //3,对外提供公共的访问方法
    public static Singleton getInstance() {
        if(s == null) {
            //多线程会引起创建多个对象
            s = new Singleton();
        }
        return s;
    }
}*/

/*
 * 区别
 * 1,饿汉式是空间换时间,懒汉式是时间换空间
 * 2,在多线程访问时,饿汉式不会创建多个对象,而懒汉式有可能会创建多个对象
 */
//其他方式
/*class Singleton {
    //1,私有构造方法,其他类不能访问该构造方法了
    private Singleton() {}
    //2,创建本类对象
    public static final Singleton s = new Singleton();

}*/

Runtime类

  • Runtime类是一个单例类
package com.heima.thread;

import java.io.IOException;

public class Demo2_Runtime {

    /**
     * @param args
     * @throws IOException 
     */
    public static void main(String[] args) throws IOException {
        Runtime r = Runtime.getRuntime();   //获取运行时对象
        //r.exec("shutdown -s -t 300");//300秒后关机
        r.exec("shutdown -a");//取消关机

    }

}

Timer

  • Timer类:计时器
package com.heima.thread;


import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class Demo3_Timer {

    /**
     * @param args
     * @throws InterruptedException 
     */
    public static void main(String[] args) throws InterruptedException {
        Timer t = new Timer();
        //在指定时间安排指定任务
        //第一个参数,是安排的任务,第二个参数是执行的时间,第三个参数是执行的重复周期
        t.schedule(new MyTimerTask(), new Date(118,10,27,9,52,30),3000);
        
        while(true) {
            Thread.sleep(1000);
            System.out.println(new Date());
        }
    }

}

class MyTimerTask extends TimerTask {

    @Override
    public void run() {
        System.out.println("起床被英语单词");
    }
    
}

两个线程间的通信

1.什么时候需要通信
* 多个线程并发执行时, 在默认情况下CPU是随机切换线程的
* 如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印

  • 2.怎么通信
    • 如果希望线程等待, 就调用wait()
    • 如果希望唤醒等待的线程, 就调用notify();
    • 这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用
package com.heima.thread2;

public class Demo1_Notify {

    /**
     * @param args
     */
    public static void main(String[] args) {
        final Printer p = new Printer();
        new Thread() {
            public void run() {
                while(true) {
                    try {
                        p.print1();
                    } catch (InterruptedException e) {
                        
                        e.printStackTrace();
                    }
                }
            }
        }.start();
        
        new Thread() {
            public void run() {
                while(true) {
                    try {
                        p.print2();
                    } catch (InterruptedException e) {
                        
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }

}
//等待唤醒机制
class Printer {
    private int flag = 1;
    public void print1() throws InterruptedException {
        synchronized(this) {
            if(flag != 1) {
                this.wait();        //当前线程等待
            }
            System.out.print("黑");
            System.out.print("马");
            System.out.print("程");
            System.out.print("序");
            System.out.print("员");
            System.out.print("\r\n");
            flag = 2;
            this.notify();          //随机唤醒单个等待的线程
        }
    }
    
    public void print2() throws InterruptedException {
        synchronized(this) {
            if(flag != 2) {
                this.wait();
            }
            System.out.print("传");
            System.out.print("智");
            System.out.print("播");
            System.out.print("客");
            System.out.print("\r\n");
            flag = 1;
            this.notify();          
        }
    }
}

三个或三个以上间的线程通信

  • 多个线程通信的问题
    • notify()方法是随机唤醒一个线程
    • notifyAll()方法是唤醒所有线程
    • JDK5之前无法唤醒指定的一个线程
    • 如果多个线程之间通信, 需要使用notifyAll()通知所有线程, 用while来反复判断条件
package com.heima.thread2;

public class Demo2_NotifyAll {

    /**
     * 1,在同步代码块中,用哪个对象锁,就用哪个对象调用wait方法
     * 2,为什么wait方法和notify方法定义在Object这类中?
     *  因为锁对象可以是任意对象,Object是所有类的基类,所以wait方法和notify方法需要定义在Object这个类中
     * 3,sleep方法和wait方法的区别?
     * a,sleep方法必须传入参数,参数就是时间,时间到了自动醒来
     * wait方法可以传入参数也可以不传入参数,传入参数就是在参数的时间结束后等待,不传入参数就是直接等待
     * b,sleep方法在同步函数或同步代码块中,不释放锁,睡着了也抱着锁睡
     * wait方法在同步函数或同步代码块中,释放锁
     */
    public static void main(String[] args) {
        final Printer2 p = new Printer2();
        new Thread() {
            public void run() {
                while(true) {
                    try {
                        p.print1();
                    } catch (InterruptedException e) {
                        
                        e.printStackTrace();
                    }
                }
            }
        }.start();
        new Thread() {
            public void run() {
                while(true) {
                    try {
                        p.print2();
                    } catch (InterruptedException e) {
                        
                        e.printStackTrace();
                    }
                }
            }
        }.start();
        new Thread() {
            public void run() {
                while(true) {
                    try {
                        p.print3();
                    } catch (InterruptedException e) {
                        
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }

}

class Printer2 {
    private int flag = 1;
    public void print1() throws InterruptedException {
        synchronized(this) {
            while(flag != 1) {
                this.wait();        //当前线程等待,if语句是在哪里等待,就在哪里起来
            }                       //while循环是循环判断,每次都会判断标记
            System.out.print("黑");
            System.out.print("马");
            System.out.print("程");
            System.out.print("序");
            System.out.print("员");
            System.out.print("\r\n");
            flag = 2;
            this.notify();          //随机唤醒单个等待的线程
            this.notifyAll();           //随机唤醒单个等待的线程
        }
    }
    
    public void print2() throws InterruptedException {
        synchronized(this) {
            while(flag != 2) {
                this.wait();
            }
            System.out.print("传");
            System.out.print("智");
            System.out.print("播");
            System.out.print("客");
            System.out.print("\r\n");
            flag = 3;
            this.notify();          
            this.notifyAll();           
        }
    }
    
    public void print3() throws InterruptedException {
        synchronized(this) {
            while(flag != 3) {
                this.wait();
            }
            System.out.print("i");
            System.out.print("t");
            System.out.print("g");
            System.out.print("z");
            System.out.print("\r\n");
            flag = 1;
            this.notify();          
            this.notifyAll();           
        }
    }
}

JDK1.5的新特性互斥锁

  • 1.同步
    • 使用ReentrantLock类的lock()和unlock()方法进行同步
  • 2.通信
    • 使用ReentrantLock类的newCondition()方法可以获取Condition对象
    • 需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法
    • 不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了
package com.heima.thread2;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Demo3_ReentrantLock {

    /**
     * @param args
     */
    public static void main(String[] args) {
        final Printer3 p = new Printer3();
        
        new Thread() {
            public void run() {
                while(true) {
                    try {
                        p.print1();
                    } catch (InterruptedException e) {
                        
                        e.printStackTrace();
                    }
                }
            }
        }.start();
        
        new Thread() {
            public void run() {
                while(true) {
                    try {
                        p.print2();
                    } catch (InterruptedException e) {
                        
                        e.printStackTrace();
                    }
                }
            }
        }.start();
        
        new Thread() {
            public void run() {
                while(true) {
                    try {
                        p.print3();
                    } catch (InterruptedException e) {
                        
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }

}

class Printer3 {
    private ReentrantLock r = new ReentrantLock();
    private Condition c1 = r.newCondition();
    private Condition c2 = r.newCondition();
    private Condition c3 = r.newCondition();
    
    private int flag = 1;
    public void print1() throws InterruptedException {
        r.lock();
            if(flag != 1) {
                c1.await();     
            }                       
            System.out.print("黑");
            System.out.print("马");
            System.out.print("程");
            System.out.print("序");
            System.out.print("员");
            System.out.print("\r\n");
            flag = 2;
            c2.signal();
        r.unlock();
    }
    
    public void print2() throws InterruptedException {
        r.lock();
            if(flag != 2) {
                c2.await();
            }
            System.out.print("传");
            System.out.print("智");
            System.out.print("播");
            System.out.print("客");
            System.out.print("\r\n");
            flag = 3;
            c3.signal();            
        r.unlock();
    }
    
    public void print3() throws InterruptedException {
        r.lock();
            if(flag != 3) {
                c3.await();
            }
            System.out.print("i");
            System.out.print("t");
            System.out.print("g");
            System.out.print("z");
            System.out.print("\r\n");
            flag = 1;
            c1.signal();            
        r.unlock();
    }
}

线程组的概述和使用

  • A:线程组概述
    • Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
    • 默认情况下,所有的线程都属于主线程组。
      • public final ThreadGroup getThreadGroup()//通过线程对象获取他所属于的组
      • public final String getName()//通过线程组对象获取他组的名字
    • 我们也可以给线程设置分组
      • 1,ThreadGroup(String name) 创建线程组对象并给其赋值名字
      • 2,创建线程对象
      • 3,Thread(ThreadGroup?group, Runnable?target, String?name)
      • 4,设置整组的优先级或者守护线程
package com.heima.thread2;

public class Demo4_ThreadGroup {

    /**
     * @param args
     */
    public static void main(String[] args) {
        ThreadGroup tg = new ThreadGroup("我是一个新的线程组");      //创建新的线程组
        MyRunnable mr = new MyRunnable();               //创建Runnable的子类对象
        
        Thread t1 = new Thread(tg, mr, "张三");           //将线程t1放在组中
        Thread t2 = new Thread(tg, mr, "李四");           //将线程t2放在组中
        
        System.out.println(t1.getThreadGroup().getName());  //获取组名
        System.out.println(t2.getThreadGroup().getName());
        
        tg.setDaemon(true);         //将整个组设置为守护线程
    }

    public static void demo1() {
        MyRunnable mr = new MyRunnable();
        Thread t1 = new Thread(mr, "张三");
        Thread t2 = new Thread(mr, "李四");

        ThreadGroup tg1 = t1.getThreadGroup();
        ThreadGroup tg2 = t2.getThreadGroup();

        System.out.println(tg1.getName());      //默认的是主线程
        System.out.println(tg2.getName());
    }

}

class MyRunnable implements Runnable {

    public void run() {
        for(int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName() + "..." + i);
        }
    }
    
}

线程的五种状态

线程状态图.png

线程池的概述和使用

  • A:线程池概述
    • 程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
  • B:内置线程池的使用概述
    • JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
      • public static ExecutorService newFixedThreadPool(int nThreads)
      • public static ExecutorService newSingleThreadExecutor()
      • 这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法
      • Future<?> submit(Runnable task)
      • <T> Future<T> submit(Callable<T> task)
    • 使用步骤:
      • 创建线程池对象
      • 创建Runnable实例
      • 提交Runnable实例
      • 关闭线程池
package com.heima.thread2;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo5_Executors {

    /**
     * @param args
     */
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(2);//创建线程池
        pool.submit(new MyRunnable());                  //将线程放进池子里并执行
        pool.submit(new MyRunnable());
        
        pool.shutdown();                            //关闭线程池
    }

}

多线程程序实现的方式3

  • 提交的是Callable
package com.heima.thread2;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Demo6_Callable {

    /**
     * @param args
     * @throws ExecutionException 
     * @throws InterruptedException 
     */
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService pool = Executors.newFixedThreadPool(2);//创建线程池
        Future<Integer> f1 = pool.submit(new MyCallable(100));                  //将线程放进池子里并执行
        Future<Integer> f2 = pool.submit(new MyCallable(50));
        
        System.out.println(f1.get());
        System.out.println(f2.get());
        pool.shutdown();                            //关闭线程池
    }

}

class MyCallable implements Callable<Integer> {
    private int num;
    public MyCallable(int num) {
        this.num = num;
    }
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= num; i++) {
            sum += i;
        }
        
        return sum;
    }
    
}

简单工厂模式概述和使用

  • A:简单工厂模式概述
    • 又叫静态工厂方法模式,它定义一个具体的工厂类负责创建一些类的实例
  • B:优点
    • 客户端不需要在负责对象的创建,从而明确了各个类的职责
  • C:缺点
    • 这个静态工厂类负责所有对象的创建,如果有新的对象增加,或者某些对象的创建方式不同,就需要不断的修改工厂类,不利于后期的维护
package com.heima.factory;

public abstract class Animal {
    public abstract void eat();
}
package com.heima.factory;

public class Cat extends Animal {

    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }

}
package com.heima.factory;

public class Dog extends Animal {

    @Override
    public void eat() {
        System.out.println("狗吃肉");
    }

}
package com.heima.factory;

public class AnimalFactory {
    public static Animal createAnimal(String name) {
        if("dog".equals(name)) {
            return new Dog();
        }else if("cat".equals(name)) {
            return new Cat();
        }else {
            return null;
        }
    }
}
package com.heima.factory;

public class Test {

    public static void main(String[] args) {
        Dog d = (Dog) AnimalFactory.createAnimal("dog");
        d.eat();
        Cat c = (Cat) AnimalFactory.createAnimal("cat");
        c.eat();
    }

}

工厂方法模式的概述和使用

  • A:工厂方法模式概述
    • 工厂方法模式中抽象工厂类负责定义创建对象的接口,具体对象的创建工作由继承抽象工厂的具体类实现。
  • B:优点
    • 客户端不需要在负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,不影响已有的代码,后期维护容易,增强了系统的扩展性
  • C:缺点
    • 需要额外的编写代码,增加了工作量
package com.heima.factorymethod;

public abstract class Animal {
    public abstract void eat();
}
package com.heima.factorymethod;

public class Cat extends Animal {

    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }

}
package com.heima.factorymethod;

public class CatFactory implements Factory {

    @Override
    public Animal createAnimal() {

        return new Cat();
    }


}
package com.heima.factorymethod;

public class Dog extends Animal {

    @Override
    public void eat() {
        System.out.println("狗吃肉");
    }

}
package com.heima.factorymethod;

public class DogFactory implements Factory {

    @Override
    public Animal createAnimal() {

        return new Dog();
    }

}
package com.heima.factorymethod;

public interface Factory {
    public Animal createAnimal();
}
package com.heima.factorymethod;

public class Test {

    public static void main(String[] args) {
        DogFactory df = new DogFactory();
        Dog d = (Dog) df.createAnimal();
        d.eat();
    }

}

图形界面

  • Graphical User Interface(图形用户接口)
  • FlowLayout(流式布局管理器)
    • 从左到右的顺序排列。
    • Panel默认的布局管理器。
  • BorderLayout(边界布局管理器)
    • 东,南,西,北,中
    • Frame默认的布局管理器。
  • GridLayout(网格布局管理器)
    • 规则的矩阵
  • CardLayout(卡片布局管理器)
    • 选项卡
  • GridBagLayout(网格包布局管理器)
    • 非规则的矩阵
package com.heima.gui;

import java.awt.Button;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;

public class Demo1_Frame {

    /**
     * @param args
     */
    public static void main(String[] args) {
        Frame f = new Frame("我的第一个窗口");
        f.setSize(400, 600);                //设置窗体大小
        f.setLocation(500, 50);             //设置窗体位置
        f.setIconImage(Toolkit.getDefaultToolkit().createImage("qq.png"));
        Button b1 = new Button("按钮一");
        Button b2 = new Button("按钮二");
        f.add(b1);
        f.add(b2);
        f.setLayout(new FlowLayout());      //设置布局管理器
        //f.addWindowListener(new MyWindowAdapter());
        f.addWindowListener(new MyWindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        
        b1.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {    //单击
                System.exit(0);
            }
            /*public void mouseReleased(MouseEvent e) { //释放
                System.exit(0);
            }*/
        });
        
        b1.addKeyListener(new KeyAdapter() {
            public void keyReleased(KeyEvent e) {
                if(e.getKeyCode() == KeyEvent.VK_SPACE) {
                    System.exit(0);
                }
            }
        });
        
        b2.addActionListener(new ActionListener() { //添加动作监听
            
            @Override
            public void actionPerformed(ActionEvent e) {
                System.exit(0);
            }
        });
        f.setVisible(true);                 //设置窗体可见
    }

}

/*class MyWindowListener implements WindowListener {

    @Override
    public void windowOpened(WindowEvent e) {
    }

    @Override
    public void windowClosing(WindowEvent e) {
        System.exit(0);
    }

    @Override
    public void windowClosed(WindowEvent e) {
        System.out.println("Closed");
    }

    @Override
    public void windowIconified(WindowEvent e) {
    }

    @Override
    public void windowDeiconified(WindowEvent e) {
    }

    @Override
    public void windowActivated(WindowEvent e) {
    }

    @Override
    public void windowDeactivated(WindowEvent e) {
    }
    
}*/

class MyWindowAdapter extends WindowAdapter {
    public void windowClosing(WindowEvent e) {
        System.exit(0);
    }
}

适配器设计模式

  • a.什么是适配器
    • 在使用监听器的时候, 需要定义一个类事件监听器接口.
    • 通常接口中有多个方法, 而程序中不一定所有的都用到, 但又必须重写, 这很繁琐.
    • 适配器简化了这些操作, 我们定义监听器时只要继承适配器, 然后重写需要的方法即可.
  • b.适配器原理
    • 适配器就是一个类, 实现了监听器接口, 所有抽象方法都重写了, 但是方法全是空的.
    • 适配器类需要定义成抽象的,因为创建该类对象,调用空方法是没有意义的
    • 目的就是为了简化程序员的操作, 定义监听器时继承适配器, 只重写需要的方法就可以了.

GUI(需要知道的)

  • 事件处理
    • 事件: 用户的一个操作
    • 事件源: 被操作的组件
    • 监听器: 一个自定义类的对象, 实现了监听器接口, 包含事件处理方法,把监听器添加在事件源上, 当事件发生的时候虚拟机就会自动调用监听器中的事件处理方法

网络编程概述

  • A:计算机网络
    • 是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。
  • B:网络编程
    • 就是用来实现网络互连的不同计算机上运行的程序间可以进行数据交换。

网络编程三要素之IP概述

  • 每个设备在网络中的唯一标识
  • 每台网络终端在网络中都有一个独立的地址,我们在网络中传输数据就是使用这个地址。
  • ipconfig:查看本机IP192.168.12.42
  • ping:测试连接192.168.40.62
  • 本地回路地址:127.0.0.1 255.255.255.255是广播地址
  • IPv4:4个字节组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿。2011年初已经用尽。
  • IPv6:8组,每组4个16进制数。
  • 1a2b:0000:aaaa:0000:0000:0000:aabb:1f2f
  • 1a2b::aaaa:0000:0000:0000:aabb:1f2f
  • 1a2b:0000:aaaa::aabb:1f2f
  • 1a2b:0000:aaaa::0000:aabb:1f2f
  • 1a2b:0000:aaaa:0000::aabb:1f2f

网络编程三要素之端口号概述

  • 每个程序在设备上的唯一标识
  • 每个网络程序都需要绑定一个端口号,传输数据的时候除了确定发到哪台机器上,还要明确发到哪个程序。
  • 端口号范围从0-65535
  • 编写网络应用就需要绑定一个端口号,尽量使用1024以上的,1024以下的基本上都被系统程序占用了。
  • 常用端口
    • mysql: 3306
    • oracle: 1521
    • web: 80
    • tomcat: 8080
    • QQ: 4000
    • feiQ: 2425

网络编程三要素协议

  • 为计算机网络中进行数据交换而建立的规则、标准或约定的集合。
  • UDP
    • 面向无连接,数据不安全,速度快。不区分客户端与服务端。
  • TCP
      * 面向连接(三次握手),数据安全,速度略低。分为客户端和服务端。
    • 三次握手: 客户端先向服务端发起请求, 服务端响应请求, 传输数据

Socket通信原理图解

  • A:Socket套接字概述:
    • 网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。
    • 通信的两端都有Socket。
    • 网络通信其实就是Socket间的通信。
    • 数据在两个Socket间通过IO流传输。
    • Socket在应用程序中创建,通过一种绑定机制与驱动程序建立关系,告诉自己所对应的IP和port。

UDP传输

  • 1.发送Send
    • 创建DatagramSocket, 随机端口号
    • 创建DatagramPacket, 指定数据, 长度, 地址, 端口
    • 使用DatagramSocket发送DatagramPacket
    • 关闭DatagramSocket
  • 2.接收Receive
    • 创建DatagramSocket, 指定端口号
    • 创建DatagramPacket, 指定数组, 长度
    • 使用DatagramSocket接收DatagramPacket
    • 关闭DatagramSocket
    • 从DatagramPacket中获取数据
  • 3.接收方获取ip和端口号
    • String ip = packet.getAddress().getHostAddress();
    • int port = packet.getPort();
package com.heima.socket;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;

public class Demo1_Send {

    /**
     * @param args
     * @throws Exception 
     */
    public static void main(String[] args) throws Exception {
        String str = "what are you doing";
        DatagramSocket socket = new DatagramSocket();   //创建Socket,码头
        DatagramPacket packet =                         //创建Packet,集装箱
                new DatagramPacket(str.getBytes(), str.getBytes().length, InetAddress.getByName("127.0.0.1"), 6666);
        socket.send(packet);                            //将数据发出去,发货
        socket.close();                                 //关闭Socket,码头
    }

}
package com.heima.socket;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class Demo1_Receive {

    /**
     * @param args
     * @throws Exception 
     */
    public static void main(String[] args) throws Exception {
        DatagramSocket socket = new DatagramSocket(6666);   //创建Socket相当于创建码头
        DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);   //创建Packet相当于创建集装箱
        socket.receive(packet);             //接货,接收数据
        
        byte[] arr = packet.getData();      //获取数据
        int len = packet.getLength();       //获取有效字节个数
        System.out.println(new String(arr, 0, len));
        socket.close();
    }

}

UDP传输优化

package com.heima.socket;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Scanner;

public class Demo2_Send {

    public static void main(String[] args) throws Exception {
        Scanner sc = new Scanner(System.in);
        DatagramSocket socket = new DatagramSocket();   //创建Socket,码头
        while(true) {
            String line = sc.nextLine();
            if("quit".equals(line)) {
                break;
            }
            DatagramPacket packet =                         //创建Packet,集装箱
                    new DatagramPacket(line.getBytes(), line.getBytes().length, InetAddress.getByName("127.0.0.1"), 6666);
            socket.send(packet);                            //将数据发出去,发货
        }
        socket.close();                                 //关闭Socket,码头
    }

}
package com.heima.socket;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class Demo2_Receive {

    /**
     * @param args
     * @throws Exception 
     */
    public static void main(String[] args) throws Exception {
        DatagramSocket socket = new DatagramSocket(6666);   //创建Socket相当于创建码头
        DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);   //创建Packet相当于创建集装箱
        while(true) {
            socket.receive(packet);             //接货,接收数据
            
            byte[] arr = packet.getData();      //获取数据
            int len = packet.getLength();       //获取有效字节个数
            String ip = packet.getAddress().getHostAddress();//获取ip地址
            int port = packet.getPort();    //获取端口号
            System.out.println(ip + ":" + port + ":" + new String(arr, 0, len));
        }
    }

}

UDP传输多线程

  • A发送和接收在一个窗口完成
package com.heima.socket;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Scanner;

public class Demo3_MoreThread {

    public static void main(String[] args) {
        new Receive().start();
        new Send().start();
    }

}

class Receive extends Thread {
    public void run() {
        try {
            DatagramSocket socket = new DatagramSocket(6666);   //创建Socket相当于创建码头
            DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);   //创建Packet相当于创建集装箱
            while(true) {
                socket.receive(packet);             //接货,接收数据
                
                byte[] arr = packet.getData();      //获取数据
                int len = packet.getLength();       //获取有效字节个数
                String ip = packet.getAddress().getHostAddress();//获取ip地址
                int port = packet.getPort();    //获取端口号
                System.out.println(ip + ":" + port + ":" + new String(arr, 0, len));
            }
        }  catch (IOException e) {      
            e.printStackTrace();
        }
    }
}

class Send extends Thread {
    public void run() {
        try {
            Scanner sc = new Scanner(System.in);
            DatagramSocket socket = new DatagramSocket();   //创建Socket,码头
            while(true) {
                String line = sc.nextLine();
                if("quit".equals(line)) {
                    break;
                }
                DatagramPacket packet =                         //创建Packet,集装箱
                        new DatagramPacket(line.getBytes(), line.getBytes().length, InetAddress.getByName("127.0.0.1"), 6666);
                socket.send(packet);                            //将数据发出去,发货
            }
            socket.close();
        } catch (IOException e) {
            
            e.printStackTrace();
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容