zoukankan      html  css  js  c++  java
  • JAVA多线程Thread VS Runnable详解

    要求

      • 必备知识

        本文要求基本了解JAVA编程知识。

      • 开发环境

        windows 7/EditPlus

      • 演示地址

        源文件

     

    进程与线程

    进程是程序在处理机中的一次运行。一个进程既包括其所要执行的指令,也包括了执行指令所需的系统资源,不同进程所占用的系统资源相对独立。所以进程是重量级的任务,它们之间的通信和转换都需要操作系统付出较大的开销。

    线程是进程中的一个实体,是被系统独立调度和分派的基本单位。线程自己基本上不拥有系统资源,但它可以与同属一个进程的其他线程共享进程所拥有的全部资源。所以线程是轻量级的任务,它们之间的通信和转换只需要较小的系统开销。

    Java支持多线程编程,因此用Java编写的应用程序可以同时执行多个任务。Java的多线程机制使用起来非常方便,用户只需关注程序细节的实现,而不用担心后台的多任务系统。

    Java语言里,线程表现为线程类。Thread线程类封装了所有需要的线程操作控制。在设计程序时,必须很清晰地区分开线程对象和运行线程,可以将线程对象看作是运行线程的控制面板。在线程对象里有很多方法来控制一个线程是否运行,睡眠,挂起或停止。线程类是控制线程行为的唯一的手段。一旦一个Java程序启动后,就已经有一个线程在运行。可通过调用Thread.currentThread方法来查看当前运行的是哪一个线程。

    线程创建的两种方法

    JAVA中创建线程可以通过继承Thread类和实现Runnable接口来创建一个线程。Runnable方式可以避免Thread 方式由于JAVA单继承特性带来的缺陷。Runnable的代码可以被多个线程(Thread实例)共享,适合于多个线程处理同一资源的情况。

    • 继承Thread类

    class MyThread extends Thread{
        .....
        @Override
        public void run(){
    
        }
    
    }
    
    MyThread mt=new MyThread();
    mt.start();
    • 实现Runnable接口

    class MyThread implements Runnable{
        ....
        @Override
        public void run(){
    
        }
    }
    
    MyThread mt=new MyThread();
    Thread td=new Thread(mt);
    sd.start();

    Thread&Runnable分别模拟卖火车票

    • Thread方式

    class MyThread extends Thread
    {
        private int ticketsCont=5; //一共有5张火车票
    
        private String name; //窗口, 也即是线程的名字
    
        public MyThread(String name){
            this.name=name;
        }
    
        @Override
        public void run(){
            
            while(ticketsCont>0){
                ticketsCont--; //如果还有票,就卖掉一张票
                System.out.println(name+"卖掉了1张票,剩余票数为:"+ticketsCont);
            }
            
        }
    }
    
    public class TicketsThread
    {
        public static void main(String args[]){
            
            //创建三个线程,模拟三个窗口卖票
            MyThread mt1=new MyThread("窗口1");
            MyThread mt2=new MyThread("窗口2");
            MyThread mt3=new MyThread("窗口3");
    
            //启动三个线程,也即是窗口,开始卖票
            mt1.start();
            mt2.start();
            mt3.start();
    
        }
    }
    • Runnable方式

    class MyThread2 implements Runnable
    {
        private int ticketsCont=1000; //一共有5张火车票
        
        @Override
        public void run(){
            
                
                while(true){
                     synchronized(this){
                        if(ticketsCont<=0){
                            break;
                        }
                        ticketsCont--; //如果还有票,就卖掉一张票
                        
                        System.out.println(Thread.currentThread().getName()+"卖掉了1张票,剩余票数为:"+ticketsCont);
                        
                        /*try{
                            Thread.sleep(50);  //通过阻塞程序来查看效果
                        }
                        catch(Exception e){
                            System.out.println(e);
                        }*/
    
                    }
                }
            
        }
    
    
    
        /*@Override  //不加同步锁
        public void run(){
            while(ticketsCont>0){
                ticketsCont--; //如果还有票,就卖掉一张票
                System.out.println(Thread.currentThread().getName()+"卖掉了1张票,剩余票数为:"+ticketsCont);
            }
        }*/
    }
    
    public class TicketsRunnable
    {
        public static void main(String args[]){
            
            MyThread2 mt=new MyThread2();
            //创建三个线程来模拟三个售票窗口
            Thread th1=new Thread(mt,"窗口1");
            Thread th2=new Thread(mt,"窗口2");
            Thread th3=new Thread(mt,"窗口3");
    
            //启动三个线程,也即是三个窗口,开始卖票
            th1.start();
            th2.start();
            th3.start();
            
    
        }
    }

    线程的生命周期

    2015-03-12_141728

    • 创建:新建一个线程对象,如Thread thd=new Thread()
    • 就绪:创建了线程对象后,调用了线程的start()方法(此时线程知识进入了线程队列,等待获取CPU服务 ,具备了运行的条件,但并不一定已经开始运行了)
    • 运行:处于就绪状态的线程,一旦获取了CPU资源,便进入到运行状态,开始执行run()方法里面的逻辑
    • 终止:线程的run()方法执行完毕,或者线程调用了stop()方法,线程便进入终止状态
    • 阻塞:一个正在执行的线程在某些情况系,由于某种原因而暂时让出了CPU资源,暂停了自己的执行,便进入了阻塞状态,如调用了sleep()方法

    线程的分类

    • 用户线程:运行在前台,执行具有的任务程序的主线程,连接网络的子线程等都是用户线程
    • 守护线程:运行在后头,为其他前台线程服务。一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作。可以通过调用Thread类的setDaemon(true)方法来设置当前的线程为守护线程,该方法必须在start()方法之前调用,否则会抛出 IllegalThreadStateException异常。在守护线程中产生的新线程也是守护线程。不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑。

    守护线程测试案例

    2015-03-12_145001

    场景:一个主线程,一个守护线程,守护线程会在很长一段时间内向本地文件中写入数据,主线程进入阻塞状态等待用户的输入,一旦确认了用户的输入阻塞就会解除掉,主线程继续运行直到结束,守护线程也会随虚拟机一同结束。

    import java.io.*;
    import java.util.Scanner;
    
    class Daemon  implements Runnable
    {
        @Override
        public void run(){
            System.out.println("进入守护线程");
            try{
                writeToFile();
            }
            catch(Exception e){
                e.printStackTrace();
            }
    
            System.out.println("退出守护线程");
        }
    
    
        private void writeToFile() throws Exception{
                File filename=new File("F:/慕课网(imooc)/细说多线程之Thread VS Runnable/daemon.txt");
                OutputStream os=new FileOutputStream(filename,true);
                int count=0;
                while(count<999){
                    os.write(("
    word"+count).getBytes());
                    System.out.println("守护线程"+Thread.currentThread().getName()+"向文件中写入word"+count++);
                    Thread.sleep(1000);
                }
        }
    }
    
    public class DaemonThreadDemo
    {
        public static void main(String args[]){
            System.out.println("进入主线程"+Thread.currentThread().getName());
            Daemon daemonThread=new Daemon();
            Thread thread =new Thread(daemonThread);
            thread.setDaemon(true);
            thread.start();
    
            Scanner sc=new Scanner(System.in);
            sc.next();
    
            System.out.println("退出主线程"+Thread.currentThread().getName());
    
            
        }
    }

    测试结果

    2015-03-12_145755

    使用jstack生成线程快照

    jstack工具到jdk安装目录bin文件夹下。jstack能生成JVM当前时刻线程的快照(threaddump, 即当前进程中所有线程的信息)。帮助定位程序问题出现的原因,如长时间停顿、CPU占用率过高等。

    使用方法

    jstack [-l] <pid> : [-l]可有可无,表示关于锁的二位信息;<pid>表示进程ID。

    2015-03-12_151225

    总结

    建议使用Runnable这种方式创建线程。 程序中的同一资源指的是同一个Runnable对象。安全的卖票程序中需要加入同步synchronized。

    如以上文章或链接对你有帮助的话,别忘了在文章结尾处轻轻点击一下 “还不错”按钮或到页面右下角点击 “赞一个” 按钮哦。你也可以点击页面右边“分享”悬浮按钮哦,让更多的人阅读这篇文章。

    作者:Li-Cheng
    由于本人水平有限,文章在表述和代码方面如有不妥之处,欢迎批评指正。留下你的脚印,欢迎评论哦。你也可以关注我,一起学习哦!
  • 相关阅读:
    【Spring Cloud】异常记录
    目录
    java8 语言特性
    Java 8 并发编程
    Spring Boot系列(四) Spring Cloud 之 Config Client
    找到字符串中最长的非重复子串
    Spring Boot系列(四) Spring Boot 之验证
    链表两数相加(add two numbers)
    找到数组中和为给定值的两个数
    Spring Boot系列(三) Spring Boot 之 JDBC
  • 原文地址:https://www.cnblogs.com/Li-Cheng/p/4332179.html
Copyright © 2011-2022 走看看