0
点赞
收藏
分享

微信扫一扫

Java学习笔记(十五)

第九章 多线程

9.1 多线程

这里只是讲一下多线程基础,后面Java高级会讲juc、多线程高级等


1、什么是多线程?

同一个程序同时做多个事情。


程序:为了完成某个任务,功能,而选择一种编程语言(例如:Java)编写的一组指令的集合。

进程:当程序启动时,操作系统会给这个程序分配一块独立的内存空间,以及相关的资源,

    每一个程序启动后有一个独立的进程对它进行管理。每一个进程都有自己唯一编号。

    操作系统是以进程为单位来管理资源。

线程:线程是进程中的其中一条执行路径。同一个进程中可以存在1个或多个的线程。

   例如:qq进程,它里面有多个线程

   (1)收消息线程

   (2)发消息线程

   (3)传输文件的线程

   ...


   最早的计算机是单任务单进程==>计算机同一时刻只能运行一个程序。

   后面出现了多任务多进程的系统==>计算机能同时运行多个程序。

   此时是单个CPU阶段的话,多个进程需要来回“切换”,在进程之间切换时,

   需要给进程做拍照处理,记录当前进程进行到那一步了,

   回头切换回来时,从这个位置继续执行。

   因为进程的内存等资源是不共享的,所以切换时等操作成本比较高。


   当我们多个任务具有相关性时,采用多线程,切换的成本更低一点,

   因为同一个进程的多个线程之间可以共享“堆、方法区”内存。


   如果多个CPU的话,多个进程可以同时进行,不需要切换,前提是进程数量<=CPU的数量。

   如果多个CPU的话,多个线程可以同时进行,不需要切换,前提是线程数量<=CPU的数量。


   线程是CPU调度的最小单位。

   一个进程至少有一个线程的。


并行:

   多个任务同时在多个CPU上运行。不管是微观角度还是宏观角度,都是同时运行的。

并发:

   多个任务“同时”在运行,其实是CPU在多个任务之间快速的“切换”,

   给“人”的感觉是同时的。

9.2 编写多线程程序

9.2.1 继承Thread类

2、Java程序:

(1)main线程:主线程

(2)后台还有一些其他服务线程:GC线程、类加载线程等等

(3)还可以手动再启动一些其他线程


3、如何手动启动其他的线程

(1)继承Thread

(2)实现Runnable

(3)实现Callable(高级)

(4)线程池(高级)


4、继承Thread类的方式步骤

(1)编写线程类,继承Thread

(2)必须重写public void run()方法

这个线程要干什么,就在run()里面写什么,称为线程体

(3)创建自定义线程类的对象

(4)启动线程

注意启动线程,调用start()方法,不要手动调用run()。

如果手动调用run()就不是多线程程序,成了单线程程序。


演示:

   单独启动一个线程,输出1-100之间的偶数

public class EvenThread extends Thread {
    @Override
    public void run() {
        //输出1-100之间的偶数
        for(int i=2; i<=100; i+=2){
            System.out.println("偶数i = " + i);
        }
    }
}

public class TestThread {
    /*public static void main(String[] args) {
        EvenThread evenThread = new EvenThread();
        evenThread.run();//当成普通对象调用方法,而不是多线程在工作

        //串行,先运行完上面的run()方法的所有代码,再运行下面的代码

        //在main线程中输出1-100的奇数
        for(int i=1; i<=100; i+=2){
            System.out.println("奇数i = " + i);
        }
    }*/

    public static void main(String[] args) {
        EvenThread evenThread = new EvenThread();
        evenThread.start();//从父类Thread类继承的

        //并行或并发,上面的线程的run方法和下面的代码是“同时”进行

        //在main线程中输出1-100的奇数
        for(int i=1; i<=100; i+=2){
            System.out.println("奇数i = " + i);
        }
    }
}

Java学习笔记(十五)_thread

Java学习笔记(十五)_Runnable_02

9.2.2 实现Runnable接口

5、实现Runnable接口的方式

(1)编写线程类,实参Runnable接口

(2)必须重写public void run()方法

这个线程要干什么,就在run()里面写什么,称为线程体

(3)创建线程类对象

(4)启动线程,需要借助Thread类的对象,才能调用start方法启动线程



6、两种方式的区别

(1)实现接口没有单继承限制

(2)其他的区别后面再说

public class EvenRunnable implements Runnable {
    @Override
    public void run() {
        //输出1-100之间的偶数
        for(int i=2; i<=100; i+=2){
            System.out.println("偶数i = " + i);
        }
    }
}

public class TestRunnable {
    public static void main(String[] args) {
        EvenRunnable evenRunnable = new EvenRunnable();
//        evenRunnable.start();//错误,因为EvenRunnable没有start方法,它的父类是Object
                            //父接口Runnable也没有start方法
        Thread thread = new Thread(evenRunnable);
        thread.start();

        //在main线程中输出1-100的奇数
        for(int i=1; i<=100; i+=2){
            System.out.println("奇数i = " + i);
        }
    }
}

9.2.3 匿名内部类

public class TestThreadRunnable {
    public static void main(String[] args) {
        //使用匿名内部类分别继承Thread和实现Runnable接口
        //一个输出1-100的偶数,一个输出1-100的奇数
        new Thread(){//继承Thread类的方式
            @Override
            public void run() {
                //输出1-100之间的偶数
                for(int i=2; i<=100; i+=2){
                    System.out.println("偶数i = " + i);
                }
            }
        }.start();

        /*Runnable runnable = new Runnable(){
            @Override
            public void run() {
                //输出1-100之间的奇数
                for(int i=1; i<=100; i+=2){
                    System.out.println("奇数i = " + i);
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();*/

        new Thread(new Runnable(){
            @Override
            public void run() {
                //输出1-100之间的奇数
                for(int i=1; i<=100; i+=2){
                    System.out.println("奇数i = " + i);
                }
            }
        }).start();
    }
}

9.3 Thread类的方法

系列一、

7、Thread类的API方法

(1)String getName():获取线程的名字

(2)void setName(String name):设置线程名字

(3)public static Thread currentThread():获取当前线程对象,执行这句代码的线程对象

(4)public static void sleep(long time):线程休眠,运行这句代码的线程会休眠xx时间,单位是毫秒

(5)public void join():线程加塞,线程阻塞,

                   加塞是 a.join(),代表a线程会把当前线程(运行这句代码的线程)给阻塞了,

                   即当前线程被加塞了,直到a线程完事。

    public void join(long time)

                   加塞是 a.join(时间),代表a线程会把当前线程(运行这句代码的线程)给阻塞了,

                    即当前线程被加塞了,直到时间到了,当前线程恢复。

public class TestMethod {
    public static void main(String[] args) {
        ThreadDemo threadDemo1 = new ThreadDemo();
        ThreadDemo threadDemo2 = new ThreadDemo();
        ThreadDemo threadDemo3 = new ThreadDemo("线程3");
        ThreadDemo threadDemo4 = new ThreadDemo();

        threadDemo4.setName("线程4");

        threadDemo1.start();
        threadDemo2.start();
        threadDemo3.start();
        threadDemo4.start();

//        System.out.println(getName());//错误,(1)TestMethod没有继承Thread类(2)main方法是静态方法,不能直接调用非静态方法
        Thread currentThread = Thread.currentThread();
        System.out.println("主线程的名字: " + currentThread.getName());

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("其他代码");

    }
}
class ThreadDemo extends Thread{
    public ThreadDemo() {
    }

    public ThreadDemo(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(getName());//getName()从父类Thread类继承的
    }
}

public class TestJoin {
    public static void main(String[] args) {
        PrintNum p = new PrintNum();
        p.start();

        PrintChar printChar = new PrintChar();
        printChar.start();

        try {
//            p.join();
            p.join(3000);//阻塞当前线程,main线程,因为这句代码由main线程调用
                            //和printChar线程无关。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("main");
    }
}
class PrintNum extends Thread{
    @Override
    public void run() {
        for(int i=1; i<=10; i++){
            System.out.println("i = " + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class PrintChar extends Thread{
    @Override
    public void run() {
        for(char c = 'a'; c<='z'; c++){
            System.out.println("c = " + c);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

系列二、

1、Thread类API
部分构造器:
- public Thread() :分配一个新的线程对象。
- public Thread(String name) :分配一个指定名字的新的线程对象。
- public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
- public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。

方法们:
- public void run() :此线程要执行的任务在此处定义代码。
- public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
- public String getName() :获取当前线程名称。
- public void setName(String name):设置当前线程的名称。
如果不设置线程名称,有默认名字,Thread-编号。
- public static Thread currentThread() :返回当前正在执行的线程对象的引用。
- public final int getPriority() :返回线程优先级
- public final void setPriority(int newPriority) :改变线程的优先级
    说明:当线程的优先级比较高时,可以获得更多的CPU调度的机会。
        但是不代表线程优先级低的完全没有机会。
    优先级的值范围是:【1,10】
- public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
- public static void yield():yield只是让当前线程暂停一下,让系统的线程调度器重新调度一次,
    希望优先级与当前线程相同或更高的其他线程能够获得执行机会,但是这个不能保证,完全有可能的情况是,
    当某个线程调用了yield方法暂停之后,线程调度器又将其调度出来重新执行。
- public void join() :等待该线程终止。
  public void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。如果millis时间到,将不再等待。
  public void join(long millis, int nanos) :等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。

public class TestMethod {
    public static void main(String[] args) {
        Thread currentThread = Thread.currentThread();
        //因为是main线程在执行上面这句代码,所以得到的是main线程对象
        System.out.println(currentThread);

        MyRunnable my = new MyRunnable();
        Thread t = new Thread(my);
        t.setName("t线程");
        t.start();

        Thread t2 = new Thread(my);
        t2.setName("t2线程");
        t2.start();
    }
}

class MyRunnable implements Runnable{
    @Override
    public void run() {
        Thread currentThread = Thread.currentThread();
        //因为这句代码是在MyRunnable的run方法中,那么这里获取的是t线程对象或t2线程对象
        System.out.println("当前线程名称:" + currentThread.getName());
    }
}

public class TestPriority {
    public static void main(String[] args) {
        /*
        一个线程打印1-100奇数,一个线程打印1-100的偶数,
        把打印奇数的线程的优先级设置为最高,
        把打印偶数的线程的优先级设置为最低,
         */
        Thread even = new Thread(){
            @Override
            public void run() {
                for(int i=2; i<=100; i+=2){
                    System.out.println("偶数:" + i);
                }
            }
        };
        Thread odd = new Thread(){
            @Override
            public void run() {
                for(int i=1; i<=100; i+=2){
                    System.out.println("奇数:" + i);
                }
            }
        };

       // even.setPriority(100);//java.lang.IllegalArgumentException非法参数异常
        /*
        查找某个类的源码的快捷键:
        (1)Ctrl + n:打开搜索框,输入类名
        (2)Ctrl + F12:打开某个类的成员列表
        (3)如果类名和方法名已使用,直接按Ctrl键+单击类名或方法名,也可以定位源码

        public final static int MIN_PRIORITY = 1;  最低优先级
        public final static int NORM_PRIORITY = 5;  普通优先级
        public final static int MAX_PRIORITY = 10;  最高优先级
         */
        even.setPriority(10);
        odd.setPriority(1);
        even.start();
        odd.start();
    }
}

public class TestYield {
    public static void main(String[] args) {
        /*
        在main线程中输出1-100的偶数,
        在另一个线程中输出1-100的奇数
         */

        Thread even = new Thread(){
            @Override
            public void run() {
                for(int i=2; i<=100; i+=2){
                    System.out.println("偶数:" + i);
                }
            }
        };
        even.setPriority(10);
        Thread.currentThread().setPriority(1);
        even.start();

        for(int i=1; i<=100; i+=2){
            System.out.println("奇数:" + i);
            if(i==5){
                Thread.yield();//当前线程暂停,让出CPU
            }
        }
    }
}

系列三、

  @Deprecated

 public final void stop()已过时。

 如何解决让某个线程提前停止的操作?

public class TestStop {
    static boolean flag = true;
    public static void main(String[] args) {
        /*
        两个线程:
        1、一个线程输出1-100的偶数
        2、另一个线程输出1-100的奇数
        3、偶数线程是连续打印数字
        4、奇数线程是每隔1毫秒打印一个数字
        5、当偶数线程打印完了,可以奇数线程提前停下来,就算奇数线程没有打印完所有的奇数

        思路之一:标记法
         */
        Thread even = new Thread(){
            @Override
            public void run() {
                for(int i=2; i<=100; i+=2){
                    System.out.println("偶数:" + i);
                }
                flag = false;
            }
        };
        Thread odd = new Thread(){
            @Override
            public void run() {
                for(int i=1; i<=100 && flag; i+=2){
                    System.out.println("奇数:" + i);
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        even.start();
        odd.start();
    }
}

系列四、

- public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。

       当正在运行的线程都是守护线程时,Java 虚拟机退出。


 守护线程是一种特殊的线程,特殊在它不能独立存在。当JVM中,所有非守护线程都结束了,那么守护线程会自动结束。

public class TestDaemon {
    public static void main(String[] args) {
        //主线程先启动PrintNumber线程,并把PrintNumber线程对象设置为守护线程
        PrintNumber p = new PrintNumber();
        p.setDaemon(true);
        p.start();


    }

/*    @Test
    public void test(){
        PrintNumber p = new PrintNumber();
        p.start();
    }*/
}

class PrintNumber extends Thread{
    @Override
    public void run() {
        int num = 1;
        while(true){
            System.out.println("num =  " + num);
            num++;
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

系列五、

-  public void interrupt() 中断线程

public class Sporter extends Thread {
    private long timePerMeter;
    private long restTime;
    private long time;
    private static volatile boolean flag = true;//这里是static,所有运动员共享
    //volatile表示每一个线程不缓存该变量的值,直接从主存里面读取该变量的值,
    //如果缓存了,也及时和主存中的值同步,保证一致性
    private int distance;//记录运动员跑了多少米

    public Sporter(String name, long timePerMeter, long restTime) {
        super(name);
        this.timePerMeter = timePerMeter;
        this.restTime = restTime;
    }

    @Override
    public void run() {
        long start = System.currentTimeMillis();

        for(int i=1; i<=30 && flag; i++){
            try {
                Thread.sleep(timePerMeter);//模拟跑1米的时间
            } catch (InterruptedException e) {
//                e.printStackTrace();
                System.err.println(getName()+"线程被中断了,异常信息:" + e);
                continue;
            }
            System.out.println(getName() + "跑了" +i + "米");
            distance++;
            if(i==10 || i==20){//i%10==0,会包含30米
                System.out.println(getName() + "开始休息....");
                try {
                    Thread.sleep(restTime);//模拟休息时间
                } catch (InterruptedException e) {
//                    e.printStackTrace();
                    System.err.println(getName()+"线程被中断了,异常信息:" + e);
                    continue;
                }
            }
        }

        if(distance == 30){
            System.out.println(getName() +"已经到达终点");
            flag = false;
        }

        long end = System.currentTimeMillis();
        time = end - start;
    }

    public long getTime() {
        return time;
    }

    public int getDistance() {
        return distance;
    }

    public static boolean isFlag() {
        return flag;
    }
}

public class Exercise5 {
    public static void main(String[] args) {
        Sporter rabbit = new Sporter("兔子",100,10000);
        Sporter tortoise = new Sporter("乌龟",1000,1000);

        rabbit.start();
        tortoise.start();

        while(true){
//            if(Sporter.isFlag()==false){
            if(!Sporter.isFlag()){
                rabbit.interrupt();
                tortoise.interrupt();
                break;
            }
        }

        System.out.println("兔子跑了:" + rabbit.getDistance()+",耗时:" + rabbit.getTime());
        System.out.println("乌龟跑了:" + tortoise.getDistance()+",耗时:" + tortoise.getTime());

        if(rabbit.getDistance() > tortoise.getDistance()){
            System.out.println("兔子赢");
        }else if(rabbit.getDistance() < tortoise.getDistance()){
            System.out.println("乌龟赢");
        }else {
            if (rabbit.getTime() < tortoise.getTime()) {
                System.out.println("兔子赢");
            } else if (rabbit.getTime() > tortoise.getTime()) {
                System.out.println("乌龟赢");
            } else {
                System.out.println("平局");
            }
        }
    }
}


举报

相关推荐

0 条评论