Thread(线程)详解1

来源:互联网 发布:提高心理承受能力 知乎 编辑:程序博客网 时间:2024/05/18 19:44

首先说说并发这个专业名词。

什么是并发啊?并发呢说起来特别简单,就是“同时”做某些事。举个现实生活中的例子:我在烧水的时候还可以洗菜。这就是并发。你在用QQ聊天的时候同时在听着音乐,这就是并发。为什么是我要在“同时”上打引号呢?因为,对于计算机来讲,并发并不是真的“同时”,只是看似“同时”。这个又是什么意思呢?大家都知道为什么计算机能流行起来就是因为它能处理数据的速度超级快,这个快是超级快,超人的那个超,其实计算机的“同时”只是在变个魔术,它再用超人的快来欺骗的你的感官。不知道大家有没有碰到过这种情况。在有些时候你在计算机上开着好几个大型软件,然后在这些软件之间来回切换的时候,会经常卡住,再等一会就把程序切回来给你了。这个其实就是计算机不经意间在像人们展示并发的实质。计算机在运行多个软件的时候,你没在一直利用的软件会处于假死状态,计算机会把软件当前的数据状态压在栈中,当你切回的时候,再把这个软件的数据和状态从栈中弹出(原路返回)。因为这个压栈出栈的操作计算机在处理的时候会特别快,快到你用肉眼区别不出来,这个时候就形成了看上去的“同时”

回到主题,继续来唠唠线程,提到线程,就必须先来说说进程。
进程这个名词,相信很多人都听说过,我们在电脑上打开任务管理器的时候,就可以看到有“进程”的字样。那么到底是什么是进程呢?可以这么理解,进程就是操作系统下的程序(软件),比如你在windows上运行QQ,运行游戏等,这些程序都是进程。

那么什么是线程呢?线程就是进程下的“进程”,这是什么意思呢?操作系统在启动一个进程的时候,操作系统会给这个进程开辟一部分资源给这个进程,那么在运行这个程序的时候,又需要“同时”的 干某件事(比如:在利用播放器播放视频,需要将画面和声音同时播放),这样是不是操作系统又得给这些同时干的事分配几个“进程”啦?这当然是不可能的,因为操作系统 只有一个,分配这么多还不得累死!这个时候,进程就说话了,“老操啊”(操作系统),这是我的地盘,你就不用管啦,我的地盘我做主!我自己来管理,你就管管我管管其他进程就可以啦。

像这种处理进程内部并发事件的,就叫做线程。

举个极端的例子,我某天自己瞎捉摸,想自己开发一套操作系统。于是啊,我就在自己的笔记本上开始敲代码(假如用java写) ,我笔记本的操作系统就对JVM说“我给你分配一些资源,你自己玩去吧!”可是我写的时候发现一个问题,我写的系统总得并发处理事件吧。总不能我写出来的系统在聊QQ的时候不能听音乐,在听音乐的时候不能看网页。这种操作系统用起来会让人特别的不爽。所以我也得让我自己的操作系统可以“同时”处理多个进程啊。于是我想到用JVM的线程,来当我的操作系统的进程。那么,这个时候问题就来了,在我写的操作系统中“同时”可以处理多个任务,这个时候应该叫什么呢?是线程还是进程呢?其实答案很简单。在我的笔记本上测试操作系统的时候,这个时候应该称呼为线程了。因为是在JVM这个进程下的啊!那么对于我在操作程序上跑的软件来说呢?他们就是进程。

线程的概念说完了,我们在来看看线程的状态,一张图搞定:

线程状态

好了,把这写概念解释完毕后,我们再回到代码中来,从代码中理解线程的真谛!


线程的两种创建形式:

第一种创建形式:继承Thread,重写run方法

/** * 线程 * 可以并发执行多个任务 * 线程有两种创建方式 * 方式一:直接继承Thread并重写run方法,在其中定义当前线程只想的任务 *  * @author Analyze * thread:线 * */public class ThreadDemo1 {    public static void main(String[] args) {        Thread t1=new MyThread1();        Thread t2=new MyThread2();        /*         * 需要注意,启动线程要调用start方法,不要调用run方法,         * run方法是线程要执行的任务,start调用完毕后,线程纳入到线程调度中,线程调度会分配CPU时间片来运行该程序,         * 当线程运行时会自动调用run方法,         * start方法调用完毕后run方法很快的启动起来,但绝对不是start方法运行过程中的run方法被执行         */        t1.start();        t2.start();    }}/* * 第一种创建线程方式有两个不足: * 1.由于要求继承Thread类,那么在将来项目中,就有可能出现继承冲突。 * 很多时候我们需要继承项目中的一个父类来复用方法,但是当前类又需要继承Thread作为一个线程去使用时, * java是单继承的,这里就产生了冲突 * 2.由于继承Thread后需要重写run方法来定义线程要执行的任务,这就导致线程与任务有一个强耦合关系, * 当前线程就只能做run方法中定义的任务,其他事情做不了,这不利于线程的重用 */class MyThread1 extends Thread{    public void run(){        for(int i=0;i<100;i++){            System.out.println("你是谁啊?");        }    }}class MyThread2 extends Thread{    public void run(){        for(int i=0;i>100;i++){            System.out.println("我是查水表的");        }    }}

线程的第二种创建形式:实现Runnable接口,单独定义任务

/** * 第二种创建线程的方式 * 定义一个类并实现Runnable接口来单独定义线程要执行的任务 *  * @author Analyze * */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{    @Override    public void run() {        for(int i=0;i<1000;i++){            System.out.println("你是谁啊?");        }    }}class MyRunnable2 implements Runnable{    public void run(){        for(int i=0;i<1000;i++){            System.out.println("我是查水表的");        }    }}

说了线程的创建,我们再来看看线程都有那些方法。
线程可以获取线程的ID,名字,优先级,是否活着,是否为守护线程,是否中断了

/** * 获取线程的相关API * @author Analyze * */public class ThreadDemo5 {    public static void main(String[] args) {        //查看运行main方法的线程的相关信息        Thread main=Thread.currentThread();        //线程ID:        long id=main.getId();        System.out.println("id:"+id);        //获取名字        String name=main.getName();        System.out.println("name:"+name);        //查看优先级        int priority=main.getPriority();        System.out.println("优先级:"+priority);        //是否活着        boolean isAlive=main.isAlive();        System.out.println("isAlive:"+isAlive);        //是否为守护线程        boolean isDaemon=main.isDaemon();        System.out.println("isDaemon:"+isDaemon);        //是否被中断了        boolean isInterrupt=main.isInterrupted();        System.out.println("isInterrupted:"+isInterrupt);    }}

ID,名字,是否活着(线程状态),是否被中断了(线程状态)这些都能理解,守护线程和优先级是什么东东啊?

先来说道说道线程优先级
线程不能干涉线程调度的工作
即:线程不能决定时间片分配给那个线程,也不能决定分配的具体次数
但是可以通过改变线程的优先级来获取更多的CPU时间片几率
线程优先级分为10个等级,对应的数字为1-10
其中1最低,5默认,10最高
理论上优先级越高的线程,获取CPU实践片的次数多

public class ThreadDemo6 {    public static void main(String[] args) {        Thread max=new Thread(){            public void run(){                for(int i=0;i<1000;i++){                    System.out.println("max");                }            }        };        Thread norm=new Thread(){            public void run(){                for(int i=0;i<1000;i++){                    System.out.println("norm");                }            }        };        Thread min=new Thread(){            public void run(){                for(int i=0;i<1000;i++){                    System.out.println("min");                }            }        };        max.setPriority(Thread.MAX_PRIORITY);        min.setPriority(Thread.MIN_PRIORITY);        min.start();        norm.start();        max.start();    }}

解释结果:执行多次,大多数都是max先执行完。

什么是守护线程
守护线程,有称为后台线程
当一个进程中的所有前台线程都结束时,进程结束,那么该进程中的后台线程若还在运行会被强制中断。

代码实例:

public class ThreadDemo9 {    public static void main(String[] args) {        Thread rose=new Thread(){            public void run(){                for(int i=0;i<5;i++){                    System.out.println("rose:Let me go");                    try {                        Thread.sleep(1000);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }                System.out.println("rose:啊啊啊!AAAAaaa");                System.out.println("音效:扑通");            }        };        /*         * jack扮演后台线程         */        Thread jack=new Thread(){            public void run(){                while(true){                    System.out.println("You jump!I jump");                    try {                        Thread.sleep(1000);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        };        /*         * 设置后台线程,需要在start前调用         */        jack.setDaemon(true);        jack.start();        rose.start();        System.out.println("mian线程结束了!");    }}

结果:当rose线程(前台线程)结束后,jack线程(后台线程)会自动结束。

void join():线程提供了一个方法join,该方法允许一个线程在另一个线程上等待,直到他的工作都结束,才继续当前线程的后续工作。否则一直处于阻塞状态

代码示例:下载和显示用了两个线程,先下载后才能显示

public class ThreadDemo10 {    //表示图片是否下载完毕    public static boolean isFinish;     public static void main(String[] args) {        /*         * 这里必须用finnal修饰,具体原因见show线程的run方法         */        final Thread down=new Thread(){            public void run(){                System.out.println("down:开始下载图片");                for(int i=1;i<=100;i++){                    System.out.println("down:"+i+"%");                    try{                        Thread.sleep(40);                    }catch(InterruptedException e){                        e.printStackTrace();                    }                }                System.out.println("下载完成");                isFinish=true;            }        };        Thread show=new Thread(){            public  void run(){                System.out.println("show:开始显示图片");                //先等待下载线程将图片下载完毕                try {                    /*                     * show线程调用了down的join方法后,                     * show线程进入阻塞状态,直到join线程结束,方可解除阻塞状态                     */                    /*                     * !!这里重点注意,因为show线程本身是main方法的局部匿名内部类,当它想访问                     * 该方法(即main方法)的局部变量时,这个局部变量必须由final修饰                     * 即down线程必须由final修饰                     */                    down.join();                } catch (InterruptedException e) {                    e.printStackTrace();                }                if(!isFinish){                    throw new RuntimeException("图片没有下载完成");                }                System.out.println("show:显示图片完毕");            }        };        down.start();        show.start();    }}

说了线程的实例方法,我们在来说说线程的静态方法(线程的工具)

static Thread curretThread():该方式是用来获取运行这个方法的线程

/** * Thread提供了一个静态方法 * static Thread curretThread() * 该方式是用来获取运行这个方法的线程 *  * java中所有的代码都是由线程运行的,当需要或得某个线程时,只需使它执行上述方法即可 * @author Analyze * */public class ThreadDemo4 {    public static void main(String[] args) {        /*         *在哪个方法中定义下述代码,获取的就是运行该方法的线程。         *         * main方法也是靠一个线程运行的。过程是:         * 当程序起送时,操作系统启动一个进程来运行虚拟机,进程启动后,会创建一个进程来运行main方法         * 进程中至少要有一个线程         */        Thread t=Thread.currentThread();        System.out.println("运行main方法的线程是:"+t);        dosome();        //自定义一个线程        Thread mt=new Thread(){            public void run(){                Thread t=Thread.currentThread();                System.out.println("自定义线程:"+t);                dosome();            }        };        mt.start();    }    public static void dosome(){        Thread t=Thread.currentThread();        System.out.println("运行dosome方法的线程是:"+t);    }}

static void sleep(long ms):该方法会将执行该方法的线程进入阻塞状态执行的毫秒数

public class ThreadDemo7 {    public static void main(String[] args) {         System.out.println("程序开始了");          try {            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }         System.out.println("程序结束了");    }}

利用sleep实现电子表

import java.text.SimpleDateFormat;import java.util.Date;/** * 使用sleep阻塞,实现了电子表功能 * 要求每秒钟输出当前系统时间,格式为:15:27:08 * @author Analyze * */public class ThreadDemo8 {    public static void main(String[] args) {        SimpleDateFormat sdf=new SimpleDateFormat("HH:mm:ss");        while(true){            Date now=new Date();            System.out.println(sdf.format(now));            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}
原创粉丝点击