【线程】获取线程相关信息的方法、线程的两种实现方式、线程的优先级 与 slepp阻塞 等

目录

~使用线程内容做一个聊天室项目~

客户端代码:

服务端代码:

TCP的三次握手和四次挥手

客户端与服务端建立TCP链接时需要进行三次握手

双方断开链接时要进行四次挥手

多客户端链接

1、多线程

2、创建线程有两种方式

        1、方式一:继承Thread,并重写run方法

        2、方式二:实现Runnable接口单独定义线程任务

        3、匿名内部类形式的线程创建

 拓展知识:

        java中的代码都是靠线程运行的,执行main方法的线程称为"主线程"。

3、使用多线程实现多客户端连接服务端

4、线程API

        获取线程相关信息的方法:

5、线程优先级

6、sleep阻塞


~使用线程内容做一个聊天室项目~

  • 客户端代码:

package socket;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

/**
 * 聊天室客户端
 */
public class Client {
    /*
        java.net.Socket 套接字
        Socket封装了TCP协议的通讯细节,我们通过它可以与远端计算机建立链接,
        并通过它获取两个流(一个输入,一个输出),然后对两个流的数据读写完成
        与远端计算机的数据交互工作。
        我们可以把Socket想象成是一个电话,电话有一个听筒(输入流),一个麦克
        风(输出流),通过它们就可以与对方交流了。
     */
    private Socket socket;

    /**
     * 构造方法,用来初始化客户端
     */
    public Client(){
        try {
            System.out.println("正在链接服务端...");
            /*
                实例化Socket时要传入两个参数
                参数1:服务端的地址信息
                     可以是IP地址,如果链接本机可以写"localhost"
                参数2:服务端开启的服务端口
                我们通过IP找到网络上的服务端计算机,通过端口链接运行在该机器上
                的服务端应用程序。
                实例化的过程就是链接的过程,如果链接失败会抛出异常:
                java.net.ConnectException: Connection refused: connect
             */
            socket = new Socket("localhost",8088);
            System.out.println("与服务端建立链接!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 客户端开始工作的方法
     */
    public void start(){
        try {
            /*
                Socket提供了一个方法:
                OutputStream getOutputStream()
                该方法获取的字节输出流写出的字节会通过网络发送给对方计算机。
             */
            //低级流,将字节通过网络发送给对方
            OutputStream out = socket.getOutputStream();
            //高级流,负责衔接字节流与字符流,并将写出的字符按指定字符集转字节
            OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
            //高级流,负责块写文本数据加速
            BufferedWriter bw = new BufferedWriter(osw);
            //高级流,负责按行写出字符串,自动行刷新
            PrintWriter pw = new PrintWriter(bw,true);

            Scanner scanner = new Scanner(System.in);
            while(true) {
                String line = scanner.nextLine();
                if("exit".equalsIgnoreCase(line)){
                    break;
                }
                pw.println(line);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                /*
                    通讯完毕后调用socket的close方法。
                    该方法会给对方发送断开信号。
                 */
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Client client = new Client();
        client.start();
    }
}
  • 服务端代码:

package socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 聊天室服务端
 */
public class Server {
    /**
     * 运行在服务端的ServerSocket主要完成两个工作:
     * 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接
     * 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket
     *   就可以和该客户端交互了
     *
     * 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个
     * 电话使得服务端与你沟通。
     */
    private ServerSocket serverSocket;

    /**
     * 服务端构造方法,用来初始化
     */
    public Server(){
        try {
            System.out.println("正在启动服务端...");
            /*
                实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他
                应用程序占用的端口相同,否则会抛出异常:
                java.net.BindException:address already in use

                端口是一个数字,取值范围:0-65535之间。
                6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
             */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服务端开始工作的方法
     */
    public void start(){
        try {
            System.out.println("等待客户端链接...");
            /*
                ServerSocket提供了接受客户端链接的方法:
                Socket accept()
                这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端
                的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例
                通过这个Socket就可以与客户端进行交互了。

                可以理解为此操作是接电话,电话没响时就一直等。
             */
            Socket socket = serverSocket.accept();
            System.out.println("一个客户端链接了!");

            /*
                Socket提供的方法:
                InputStream getInputStream()
                获取的字节输入流读取的是对方计算机发送过来的字节
             */
            InputStream in = socket.getInputStream();
            InputStreamReader isr = new InputStreamReader(in,"UTF-8");
            BufferedReader br = new BufferedReader(isr);

            String message = null;
            while((message = br.readLine())!=null) {
                System.out.println("客户端说:" + message);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }
}
  • 需要注意的几个点:

    • 1:
              当客户端不再与服务端通讯时,需要调用socket.close()断开链接,此时会发送断开链接的信号给服务端。这时服务端的br.readLine()方法会返回null,表示客户端断开了链接。

    • 2:
              当客户端链接后不输入信息发送给服务端时,服务端的br.readLine()方法是出于阻塞状态的,直到读取了一行来自客户端发送的字符串。

TCP的三次握手和四次挥手

  • 客户端与服务端建立TCP链接时需要进行三次握手

  • 双方断开链接时要进行四次挥手

  • 多客户端链接

  • 之前只有第一个连接的客户端可以与服务端说话。
  • 原因:
    • 服务端只调用过一次accept方法,因此只有第一个客户端链接时服务端接受了链接并返回了Socket,此时可以与其交互。
    • 而第二个客户端建立链接时,由于服务端没有再次调用accept,因此无法与其交互。

package socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 聊天室服务端
 */
public class Server {
    /**
     * 运行在服务端的ServerSocket主要完成两个工作:
     * 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接
     * 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket
     *   就可以和该客户端交互了
     *
     * 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个
     * 电话使得服务端与你沟通。
     */
    private ServerSocket serverSocket;

    /**
     * 服务端构造方法,用来初始化
     */
    public Server(){
        try {
            System.out.println("正在启动服务端...");
            /*
                实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他
                应用程序占用的端口相同,否则会抛出异常:
                java.net.BindException:address already in use

                端口是一个数字,取值范围:0-65535之间。
                6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
             */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服务端开始工作的方法
     */
    public void start(){
        try {
            while(true) {
                System.out.println("等待客户端链接...");
                /*
                    ServerSocket提供了接受客户端链接的方法:
                    Socket accept()
                    这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端
                    的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例
                    通过这个Socket就可以与客户端进行交互了。

                    可以理解为此操作是接电话,电话没响时就一直等。
                 */
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端链接了!");
                /*
                    Socket提供的方法:
                    InputStream getInputStream()
                    获取的字节输入流读取的是对方计算机发送过来的字节
                 */
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, "UTF-8");
                BufferedReader br = new BufferedReader(isr);

                String message = null;
                while ((message = br.readLine()) != null) {
                    System.out.println("客户端说:" + message);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }
}
  • 添加循环操作后,发现依然无法实现。
  • 原因在于:
    • 外层的while循环里面嵌套了一个内层循环(循环读取客户端发送消息),而循环执行机制决定了里层循环不结束,外层循环则无法进入第二次操作。

1、多线程

  • 线程
            单一的顺序执行流程就是一个线程。
    • 顺序执行:代码一句一句的先后执行
  • 多线程
            多个线程并发执行。
            线程之间的代码是快速被CPU切换执行的,造成一种感官上"同时"执行的效果。
    • 并发:
              多个线程实际运行是走走停停的。线程调度程序会将CPU运行时间划分为若干个时间片段并尽可能均匀的分配给每个线程,拿到时间片的线程被CPU执行这段时间。当超时后线程调度程序会再次分配一个时间片段给一个线程使得CPU执行它。如此反复。由于CPU执行时间在纳秒级别,我们感觉不到切换线程运行的过程。所以微观上走走停停,宏观上感觉一起运行的现象成为并发运行!
  • 用途
    • 当出现多个代码片段执行顺序有冲突时,希望它们各干各的时就应当放在不同线程上"同时"运行

    • 一个线程可以运行,但是多个线程可以更快时,可以使用多线程运行

线程的生命周期图:

2、创建线程有两种方式

        1、方式一:继承Thread,并重写run方法

  • 第一种创建线程的方式:继承Thread,并重写run方法
  • 定义一个线程类,重写run方法,在其中定义线程要执行的任务(希望和其他线程并发执行的任务)。
  • 优点:

    • 结构简单,便于匿名内部类创建

  • 缺点:

    • 继承冲突:由于java单继承,会导致不能在继承其他类去复用方法,
                        这在实际开发中是非常不便的。

    • 耦合问题:定义线程的同时重写了run方法,会导致线程与线程任务绑定在了一起,
                        不利于线程的重用。

  • :启动该线程要调用该线程的start方法,而不是run方法!!!
package thread;

/**
 * 此种线程创建方式的
 * 优点: 结构简单,利于匿名内部类形式创建
 * 缺点:
 *      1、存在继承冲突问题
 *          java 是单继承的,这意味着继承了 Thread 就无法在再继承其他类了,
 *          这在实际开发中存在着诸多不便( 实际上继承最主要的目的是为了复用方法 )
 *          PS:( 如果只有一个子类需要实现线程,而其他子类不需要实现线程,单独往超类里放一个线程,
 *                那意味着所有子类都有线程的共性,可实际上只有一个子类使用,这违背了 面对对象设计的编程思想的)
 *
 *              ( 这里我们想到了 超类 ,但 超类的意义是: 为了提供多个类中的 共性 存在的 )
 *              ( 不是共性的东西 我们是不能往超类上放的 )
 *
 *      2、继承Thread的同时重写run方法,将线程任务定义在run方法中,
 *         这会导致线程与线程要执行的任务存在了必然的耦合关系,不利于线程的重(chong)用
 */
public class ThreadDemo1 {
    public static void main(String[] args) {
        //实例化两个线程
        /*
           此处实例化采用Thread去赋值
           是多态的写法,向上造型,
           毕竟MyThread1和2都是继承自Thread,所以将他俩当成线程看也是可以的
           这是一种编程思想
         */
        Thread t1 = new MyThread1();
        Thread t2 = new MyThread2();

        //调用线程的start将线程启动。
        //注意:启动线程不能直接调用run方法!!!
        t1.start();
        t2.start();
    }
}

class MyThread1 extends Thread{
    public void run(){
        for (int i=0;i<1000;i++){
            System.out.println("你是谁啊?");
        }
    }
}

class MyThread2 extends Thread{
    public void run(){
        for (int i=0;i<1000;i++){
            System.out.println("11111111111111111111");
        }
    }
}

        2、方式二:实现Runnable接口单独定义线程任务

  • 第二种创建线程的方式 (常用)
  • 实现 Runnable 接口单独定义线程任务
  • 将线程和任务分开干,这样 线程线程要执行的任务 就不是必然的 耦合关系
    ------ (解除了耦合思想)
package thread;

/**
 * 第二种创建线程的方式      (常用)
 * 实现Runnable接口单独定义线程任务
 *
 * 将线程和任务分开干,这样 线程 与 线程要执行的任务 就不是必然的 耦合关系  (解除了耦合思想)
 */
public class ThreadDemo2 {
    public static void main(String[] args) {
        //实例化线程要执行的任务
        Runnable r1 = new MyRunnable1();
        Runnable r2 = new MyRunnable2();

        //创建两条线程
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);

        //启动线程任务
        t1.start();
        t2.start();
    }
}

class MyRunnable1 implements Runnable {
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println("123");
        }
    }
}

class MyRunnable2 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("11111111111111");
        }
    }
}

        3、匿名内部类形式的线程创建

  • 第一种方式:(无法使用lambda表达式)
    • 继承Thread重写run方法
      Thread是一个类,无法使用lambda表达式实现
  • 第二种方式:(可以使用lambda表达式)
    • 实现Runnable接口重写run方法
      可以用lambda表达式创建Runnable(因为Runnable是个接口)
    • 例:
      • Thread t2 =new Thread(() - > {
                 for (int i=0;i<1000;i++){
                        System.out.println("Runnable匿名内部类");
                 }
        });
package thread;

/**
 * 使用匿名内部类完成线程的两种创建
 */
public class ThreadDemo3 {
    public static void main(String[] args) {
        //第一种方式:继承Thread重写run方法
        //Thread是一个类,无法使用lambda表达式实现
        Thread t1 = new Thread(){
            public void run() {
                for (int i=0;i<1000;i++){
                    System.out.println("Thread匿名内部类");
                }
            }
        };

        //第二种方式:实现Runnable接口重写run方法
/*        Runnable r2 = new Runnable() {
            public void run() {
                for (int i=0;i<1000;i++){
                    System.out.println("Runnable匿名内部类");
                }
            }
        };
*/
/*
        Runnable r2 = ()->{
                for (int i=0;i<1000;i++){
                    System.out.println("Runnable匿名内部类");
                }
        };
 */
        //可以用lambda表达式创建Runnable(因为Runnable是个接口)
        Thread t2 =new Thread(()->{
            for (int i=0;i<1000;i++){
                System.out.println("Runnable匿名内部类");
            }
        });

        t1.start();
        t2.start();
    }
}

 拓展知识:

        java中的代码都是靠线程运行的,执行main方法的线程称为"主线程"。

  • java中所有的代码都是靠线程执行的,main方法也不例外。
  • JVM启动后会创建一条线程来执行main方法,并取名为“main",所以通常称它为:“主线程”

  • 我们自己定义的线程在不指定名字的情况下系统会分配一个名字,
            格式为“thread-X”(X是一个数)

  • Thread有一个静态方法:
    • Thread currentThread()
      获取运行该方法的线程
  • 例:

  • Thread main = Thread.currentThread();
    System.out.println("主线程:"+main);

    //主线程:Thread [     main    ,           5            ,   main    ]
    //                 
    线程   [  线程名字 ,  线程的优先级 ,   线程组  ]

  • 后期会学习到一个很重要的API:ThreadLocal,它可以使得我们在一个线程上跨越多个方法时共享数据使用,其内部要用到currentThread方法来辨别线程。如spring的事物控制就是靠ThreadLocal实现的。
package thread;

/**
 * java中所有的代码都是靠线程执行的,main方法也不例外。
 * JVM启动后会创建一条线程来执行main方法,并取名为“main",所以通常称它为:”主线程“
 *
 * 我们自己定义的线程在不指定名字的情况下系统会分配一个名字,
 * 格式为“thread-X”(X是一个数)
 *
 * Thread有一个静态方法:
 *             Thread currentThread()
 *             获取运行该方法的线程
 */
public class CurrentThreadDemo {
    public static void main(String[] args) {
        /*
            后期会学习到一个很重要的API:ThreadLocal,它可以使得我们在一个线程上跨越多个
            方法时共享数据使用,其内部要用到currentThread方法来辨别线程。
            如spring的事物控制就是靠ThreadLocal实现的。
         */
        /*
            Thread有一个静态方法:
            Thread currentThread()
            获取运行该方法的线程
         */
        //主线程执行main方法进来后会执行下面这句代码,此方法会的就是主线程
        Thread main = Thread.currentThread();
        System.out.println("主线程:"+main);
        //主线程:Thread[  main  ,      5      , main  ]
        //       线程  [ 线程名字 ,  线程的优先级 , 线程组 ]

        dosome();
        System.out.println("主线程执行完main方法后就结束了");
    }

    public static void dosome(){
        //获取调用dosome方法的线程
        Thread t = Thread.currentThread();
        System.out.println("调用dosome方法的线程是:"+t);
    }
}

3、使用多线程实现多客户端连接服务端

流程图:

 服务端代码改造:

package socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 聊天室服务端
 */
public class Server {
    /**
     * 运行在服务端的ServerSocket主要完成两个工作:
     * 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接
     * 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket
     *   就可以和该客户端交互了
     *
     * 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个
     * 电话使得服务端与你沟通。
     */
    private ServerSocket serverSocket;

    /**
     * 服务端构造方法,用来初始化
     */
    public Server(){
        try {
            System.out.println("正在启动服务端...");
            /*
                实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他
                应用程序占用的端口相同,否则会抛出异常:
                java.net.BindException:address already in use

                端口是一个数字,取值范围:0-65535之间。
                6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
             */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服务端开始工作的方法
     */
    public void start(){
        try {
            while(true) {
                System.out.println("等待客户端链接...");
                /*
                    ServerSocket提供了接受客户端链接的方法:
                    Socket accept()
                    这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端
                    的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例
                    通过这个Socket就可以与客户端进行交互了。

                    可以理解为此操作是接电话,电话没响时就一直等。
                 */
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端链接了!");
                //启动一个线程与该客户端交互
                ClientHandler clientHandler = new ClientHandler(socket);
                Thread t = new Thread(clientHandler);
                t.start();

            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }

    /**
     * 定义线程任务
     * 目的是让一个线程完成与特定客户端的交互工作
     */
    private class ClientHandler implements Runnable{
        private Socket socket;
        public ClientHandler(Socket socket){
            this.socket = socket;
        }
        public void run(){
            try{
                 /*
                    Socket提供的方法:
                    InputStream getInputStream()
                    获取的字节输入流读取的是对方计算机发送过来的字节
                 */
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, "UTF-8");
                BufferedReader br = new BufferedReader(isr);

                String message = null;
                while ((message = br.readLine()) != null) {
                    System.out.println("客户端说:" + message);
                }
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }


}

4、线程API

        获取线程相关信息的方法:

  • 获取线程信息的一组方法:
  • 获取主线程
    •         Thread main = Thread .currentThread () ;
  • 获取线程的名字
    •         String name = main .getName () ;
  • 获取该线程的唯一标识
    •         long id = main .getId () ;                     //不一定是long值
  • 获取线程的优先级(1-10)
    •         int priority = main .getPriority () ;
  • 线程是否还活着
    •         boolean isAlive  = main .isAlive () ;
  • 线程是否为守护线程
    •         boolean isDaemon = main .isDaemon () ;
  • 线程是否被中断了
    •         boolean isInterrupted = main .isInterrupted () ;
  • PS:
    •             ID:唯一标识   使用该值可以唯一标识一个个体。
    •             可以做为唯一表示的值 必须同时具备两个条件:
    •                 1:非空    2:唯一
  • 扩展:行规:        
            一般is开头的基本都是Boolean类型,get的变型
    • 例:
              isAlive  isDaemon  isInterrupted
package thread;

/**
 * 获取线程信息的一组方法
 *
 * 扩展:行规:
 *      一般is开头的基本都是Boolean类型,get的变型
 *      例:isAlive  isDaemon  isInterrupted
 */
public class ThreadInfoDemo {
    public static void main(String[] args) {
        //获取主线程
        Thread main = Thread.currentThread();


        //获取线程的名字
        String name = main.getName();
        System.out.println("线程的名字:"+name);
        /*
            ID:唯一标识   使用该值可以唯一标识一个个体。
            可以做为唯一表示的值 必须同时具备两个条件:
                1:非空    2:唯一
         */
        long id = main.getId();//不一定是long值
        System.out.println("ID:"+id);


        //获取线程的优先级(1-10)
        int priority = main.getPriority();
        System.out.println("线程的优先级:"+priority);


        //线程是否还活着
        boolean isAlive  = main.isAlive();
        //线程是否为守护线程
        boolean isDaemon = main.isDaemon();
        //线程是否被中断了
        boolean isInterrupted = main.isInterrupted();

        System.out.println("线程是否还活着:"+isAlive);
        System.out.println("线程是否为守护线程:"+isDaemon);
        System.out.println("线程是否被中断了:"+isInterrupted);
    }
}

5、线程优先级

  • 线程 start 后会纳入到线程调度器中统一管理,
    线程只能被动的被分配时间片并发运行,
    而无法主动索取时间片。
    • 线程有10个优先级,使用整数1-10表示
    • 1为最小优先级,10为最高优先级.5为默认值

  • 线程调度器尽可能均匀的将时间片分配给每个线程。

  • 调整线程的优先级可以最大程度的干涉获取时间片的几率,
    优先级越高的线程获取时间片的次数越多,反之则越少

package thread;

/**
 * 线程的优先级
 *
 * 线程有10个优先级,分别对应整数1-10
 * 其中1为最低,10为最高,5位默认值。
 *
 * 线程调用start方法后就纳入到线程调度器中被统一管理,
 * 此时线程只能被动的被分配时间片, 并发运行,而无法主动索取时间片
 *
 * 调度器会尽可能均匀的将时间片分配给每个线程。
 * 调整线程的优先级可以最大程度额改善获取时间片的概率,
 * 线程优先级越高的线程获取时间片的概率越高。
 */
public class PriorityDemo {
    public static void main(String[] args) {
        Thread norm = new Thread() {
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    System.out.println("nor");
                }
            }
        };
        Thread max = new Thread() {
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    System.out.println("max");
                }
            }
        };
        Thread min = new Thread() {
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    System.out.println("min");
                }
            }
        };
        //优先级只能对应整数1-10.传入其他数字会抛出异常,Priority规定
        min.setPriority(Thread.MIN_PRIORITY);//最小优先级
//        min.setPriority(1);//这是最小优先级,上面是为了优雅Priority自己设定的名字
        max.setPriority(Thread.MAX_PRIORITY);//最大优先级
        min.start();
        max.start();
        norm.start();
    }
}

6、sleep阻塞

线程提供了一个静态方法:

  • static void sleep(long ms)

  • 使运行该方法的线程进入阻塞状态指定的毫秒,超时后线程会自动回到RUNNABLE状态等待再次获取时间片并发运行.

package thread;

import java.util.Scanner;

/**
 * 线程提供了一个静态的方法:
 * static void sleep(long ms)
 * 该方法可以让运行这个方法的线程出于阻塞状态指定毫秒,超时后线程会自动回到RUNNABLE状态。
 * 在此等待获取时间片并发执行。
 *
 * 此处扩展一个面试题:
 *      Thread.sleep(1000);(1000ms=1s)
 *      是不是就说明就阻塞了1秒钟,然后可以把它精确到1秒钟,作为一个间隔时间去循环它,是不是真的就是卡1秒?
 *   答:
 *      不是,是存在误差的
 *      当Running调用了Sleep 形成阻塞,这个阻塞是准确的,
 *      但是阻塞后回到了Runnable状态,这个时候要等调度器给你分配时间片,
 *      这个在等待时间片的过程就是误差,所以做不到精确阻塞的
 *      要想解决 得从硬件下手
 */
public class SleepDemo {
    public static void main(String[] args) {
        System.out.println("start");
/*
 *  简易的倒计时程序
 *  要求:
 *      程序启动后输入一个整数,从该数字开始每秒递减,
 *      到0时输出时间到,程序结束
 */
        Scanner scanner = new Scanner(System.in);
//我的方法
//        int i= scanner.nextInt();
//        while (true) {
//            i--;
//            try {
//                Thread.sleep(1000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            System.out.println(i);
//            if (i==0){
//                break;
//            }
//        }

//老师的方法
        for (int i = scanner.nextInt();i>0;i--){
            System.out.println(i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("时间到");
        System.out.println("end");
    }
}

7、sleep方法处理异常

  • sleep方法处理异常:
    • InterruptedException.
  • 当一个线程调用sleep方法处于睡眠阻塞的过程中,该线程的interrupt()方法被调用时,sleep方法会抛出该异常从而打断睡眠阻塞。
package thread;

/** interrupt()方法使用
 *      当一个线程调用sleep方法出于睡眠阻塞的过程中,
 *      如果该线程的interrupt()方法被调用,
 *      那么该线程会立即中断睡眠阻塞,
 *      并在sleep方法这里抛出中断异常
 */
public class SleepInterruptDemo2 {
    public static void main(String[] args) {
        Thread lin = new Thread(){
            public void run(){
                System.out.println("林:刚美容完,休息一下吧~");
                try {
                    /*
                        lin线程调用sleep方法后会进入睡眠阻塞,
                        期间如果lin线程的interrupt方法被调用的话,
                        就会在sleep方法这里立即抛出中断异常,
                        并结束sleep阻塞,并跳到catch块
                     */
                    Thread.sleep(30000000);
                } catch (InterruptedException e) {
                    System.out.println("此处因其他因数导致sleep中断,就会报异常");
                    System.out.println("林:干嘛呢!干嘛呢!干嘛呢!都破了相了!");
                }
                System.out.println("正常情况下,会在sleep结束后执行这里");
                System.out.println("林:醒了");
            }
        };

        Thread huang = new Thread(){
            public void run(){
                System.out.println("黄:大锤80,小锤40,开始砸墙!");
                for (int i=0;i<5;i++){
                    System.out.println("黄:80!");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        //这里正常sleep,不会出现异常,所以此处可以不写
                        //不调用interrupt,就不会出现异常,
                        // 这里是java中的语法,人家不知道你是否会不会调用interrupt,所以这里强行捕获异常
                    }
                }
                System.out.println("咣当!");
                System.out.println("黄:大哥!搞定");
                //让huang线程中断lin线程的睡眠阻塞
                lin.interrupt();
            }
        };

        lin.start();
        huang.start();
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Speechless_小赵啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值