多线程,内附所有源代码和概念

多线程

​ 学习多线程之前,我们需要了解什么是线程。但是我们在学习线程之前,还得了解什么是进程,因为线程是依赖于进程存在。

1.进程

​ 通过任务管理器我们可以观察到进程的存在,并且我们发现,只有在程序运行的时候,任务管理器中才能查看到相应的进程。

**概述:**正在运行的程序就是进程。进程是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源。

多进程的意义:

​ 单进程的计算机只能做一个工作,而我们现在用的计算机都是多进程的,也就意味着它们可以同时做许多事情。

举个栗子:

​ 一边做笔记(Typora进程),一边直播(钉钉继承)

多进程的意义在于,计算机可以在一个时间段内同时执行多个任务。并且,可以提高CPU的使用率。

思考:

一边做笔记(Typora进程),一边直播(钉钉继承) 这两个任务是同时进行的吗?

​ 对于多核CPU 它有可能是同时的,但是对于单核CPU来说,它在某一个时间点,它只能做一件事情。

但是我们在做笔记的时候,同时在直播,我们感官上,这两个任务是同时进行的。但是实际上不是这样的。其实CPU在运行进程的时候进行了程序间的高速切换,这个切换时间非常的短,短到我们根本感觉不来它在切换,所以我们就感觉两个进程是在同时进行的。

2.线程

在同一个进程中,可以同时执行多个任务。而这每一个任务,就是一个线程。

线程: 是程序的执行单元,也是执行路径。线程是程序使用CPU资源的最基本单位。

**单线程:**只有一个执行单元或只有一条执行路径

**多线程:**有多个执行单元或多个执行路径

多线程的意义:

​ 1.线程的执行是抢占式的。每一个线程都要去抢占CPU资源(CPU执行权)。一个多线程的程序在执行时,如果一些线程必须等待的时候,CPU就会将资源提供给其他线程去使用这些资源。这样的话就提高了CPU的使用率。

​ 2.对于进程来说,如果它是多线程的,在抢占CPU资源时,就有更大的几率抢占到CPU资源。提高该程序使用率。

​ 3.在抢占CPU资源的时候,是具有随机性,我们不能保证某一个线程在某一个时间段内能够抢到资源。所以说线程的执行是有随机性。

3.并行、并发

**并行:**多个处理器或多核处理器同时处理多个不同的任务。

**并发:**一个处理器同时处理多个任务。

4.多线程

java程序运行原理

​ java程序运行时,通过java命令,启动一个虚拟机(jvm)(jvm启动时多线程的,因为此时它不仅启动了一个主线程,还启动了一个垃圾回收线程,所以它至少启动了两个线程),相当于启动了一个应用程序,也就是启动了一个进程,该进程会自动启动一个主线程,然后主线程去调用某个类的main方法,所以main是运行再主线程中的。

​ 由于线程是依赖于进程存在的,所以我们需要先创建一个进程。而进程是由系统来创建的,所以我们需要调用系统的某些功能来创建进程。但是我们的Java无法去调用系统功能。所以我们无法直接实现多线程程序。

​ 但是,C/C++是可以调用系统功能的,我们可以把C/C++调用系统功能的代码进行封装,封装到一个类中,我们通过调用这个类,就可以间接的调用系统功能来创建进程,那封装的这个类是哪个类里呢?

​ 这个类叫做Thread

创建一个线程有两种方式

方式1:

​ 1.自定义类,继承Thread类

​ 2.重写run()方法

​ 3.创建线程对象

​ 4.启动线程

问题1:为什么重写run()方法

因为线程类中的方法不一定都要被执行,可能只执行其中的一部分。所以我们把需要线程执行的代码写到run方法中

问题2:run()和 start()方法的区别

run:

​ run的调用就相当于方法的普通调用

**start:**1.启动线程

​ 2.Jvm调用相应的run方法

**问题3:**线程能不能被多次启动?

不能,因为这相当于线程被调用了两次,而不是线程被启动

example1

package com.thread;

/**
 * 线程的启动
 */
public class MyThread extends Thread{
    //重写run方法
    @Override
    public void run() {
        for (int i=0;i<100;i++){
            System.out.println(i);
        }
    }
}

package com.thread;

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
//        myThread1.run();//运行run但是是单线程的
        //要运行多线程
        MyThread myThread2 = new MyThread();
        //开两个线程,并且让线程进行抢占
        myThread1.start();
        myThread2.start();
    }
}

线程设置名称和获取
public final String getName();获取名称
public final void setName(String name);设置名称
    方法1.直接设置名称
    线程名称.setName("XXX");
    方法2.
           MyThread myThread1 = new MyThread("西门");
        注意:要设置有参无参两种
如何获取main方法所在线程?
            直接再main方法中不能使用getName方法,因为getName是Thread类中的方法
            那么怎么办?
                  有另外一个方法
             System.out.println(Thread.currentThread().getName());
输出位main,这就是jvm帮助我们启动的主线程
System.out.println(getName()+":"+i);
线程的优先级设置和使用
package com.thread;

/**
 * 线程调度的两种方式
 *     1.分支调度模型
 *                 所有线程轮流使用cpu的使用权,平均分给每个线程占用cpu的时间片
 *     2.抢占式调度模型
 *                 让优先级高的线程优先使用cpu,若相同则随机选一个
 *                 优先级高的获取的时间相对多一些
 *     再Java中我们使用的是第二种
 *
 *
 *     优先级默认为5
 *     如何设置优先级?
 *         public final void setPriority(new priority);
 *
 *         通过api我们知道优先级是有条件设置的
 *            最高优先级为10
 *            默认为5
 *            最低为1
 *
 */
public class ThreadDemo2 {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        MyThread mt2 = new MyThread();
        MyThread mt3 = new MyThread();

        mt.setName("西门吹雪");
        mt2.setName("叶孤城");
        mt3.setName("陆小凤");



        mt.setPriority(10);
        mt2.setPriority(1);
        mt3.setPriority(1);
//        System.out.println(mt.getPriority());
//        System.out.println(mt2.getPriority());
//        System.out.println(mt3.getPriority());
        mt.start();
        mt2.start();
        mt3.start();

    }
}

线程控制
1.线程的休眠和加入
package com.thread;

import java.util.Date;

public class MySleep extends Thread{
    @Override
    public void run() {
        for (int i = 0;i<30;i++){
//            System.out.println(getName()+new Date()+"---"+i);
//
//            try {
//                Thread.sleep(1000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
            System.out.println(getName()+":"+1);

        }
    }
}

package com.thread;

/**
 * 线程控制:
 *     线程的睡眠
 *     1.public static void sleep(long millis);线程的休眠,单位毫秒
 *     线程的加入
 *     2.public final void join();
 */
public class ThreadDemo3 {
    public static void main(String[] args){
        MySleep ms = new MySleep();
        MySleep ms2 = new MySleep();
        MySleep ms3 = new MySleep();

        ms.setName("西门吹雪");
        try {
            ms.join();//等ms运行完之后再运行后两个线程
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ms2.setName("叶孤城");
        ms3.setName("朱停");

        ms.start();
        ms2.start();
        ms3.start();
    }
}

2.线程的礼让和后台
package com.thread;

public class MyYield extends Thread {
    @Override
    public void run() {
        for (int i=0;i<50;i++){
            System.out.println(getName()+":"+i);
            Thread.yield();
        }
    }
}

package com.thread;

/**
 *  * 线程礼让:
 *  *      public static void yield();就是当前线程愿意让出正在使用的处理器资源
 *  *      这里仅仅是线程让出处理器的使用,但并不是交替出现的,由谁直营还是要抢占
 */

public class MyYieldDemo {
    public static void main(String[] args) {
      MyYield my = new MyYield();
      MyYield my2 = new MyYield();

      my.setName("马云");
      my2.setName("马化腾");

        my.start();
        my2.start();
    }
}
package com.thread;

public class MyDaemon extends Thread{
    @Override
    public void run() {
        for (int i=0;i<100;i++){
            System.out.println(getName()+":"+i);
            Thread.yield();
        }
    }
}

package com.thread;

/**
 * 后台线程:
 *  public final void setDaemon(boolean on);
 *    将此线程标记为daemon线程或用户线程
 *    当运行的唯一线程都是守护线程时,java虚拟机将推行胡
 *    线程启动前必须调用此方法
 */
public class MyDeamonDemo {
    public static void main(String[] args) {
        MyDaemon md1 = new MyDaemon();
        MyDaemon md2 = new MyDaemon();

        md1.setName("张飞");
        md2.setName("关羽");

        //线程启动前必须调用此方法
        md1.setDaemon(true);
        md2.setDaemon(true);
        //吕布这个线程跑完之后张飞和关羽也就不跑了

        md1.start();
        md2.start();

        Thread.currentThread().setName("吕布");
        for (int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }

}

3.线程的中断
package com.thread;

import java.util.Date;

public class MyStop extends Thread {
    @Override
    public void run() {
        //给线程记时
        System.out.println("线程"+getName()+"开始了"+new Date());
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {//捕获线程是否被中断
            System.out.println("线程被中断");
        }
        System.out.println("线程"+getName()+"结束了"+new Date());
    }
}

package com.thread;

/**
 * 线程中断
 * public final void stop();强制线程停止执行(已过时)
 *
 */
public class MyStopDemo {
    public static void main(String[] args) {
        MyStop ms = new MyStop();

        ms.setName("西门庆");
        ms.start();

        try {
            Thread.sleep(3000);
//            ms.stop();// * 这种方法本质上时不安全的
            ms.interrupt();//线程被中断
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

4.线程的生命周期
  • 创建:创建线程对象
  • 就绪:线程对象已经启动,但是没有获取cpu执行权
  • 运行:获取cpu执行权
    • 阻塞:没有线程的执行权会回到就绪那一步
  • 死亡:代码运行完毕,线程消亡
方式2
  • 自定义一个类去实现runnable接口
  • 重写run()
  • 创建自定义接口对象
  • 创建Thread对象,然后将自定义的对象作为参数传递
多线程的第二种实现方式和名称获取
package com.thread;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<=50;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

package com.thread;

/**
 * Thread的构造方法要求传递一个接口
 *
 * 通过接口实现多线程的好处
 * 1.可以避免由于Java单继承带来的局限性
 * 2.适合多个相同程序的代码去处理同一个资源的情况,
 * 将线程同程序的代码程序有效分离,较好的体现了面向对象的思想
 *
 */
public class MyRunnableDemo {
    public static void main(String[] args) {
        //只需要创建一个接口思想
        MyRunnable mr = new MyRunnable();
        //创建两个Thread接口,缤纷且去启动它
        //方式1.设置名称:
//        Thread t1 = new Thread(mr);
//        Thread t2 = new Thread(mr);
//        t1.setName("西门吹雪");
//        t2.setName("西门一白");
        //方式二设置名称:参数设置
        Thread t1 = new Thread(mr,"西门吹雪");
        Thread t2 = new Thread(mr,"叶孤城");
        t1.start();
        t2.start();

    }
}

text
需求:

卖电影票,有三个售票口,使用两种方式实现

方式1
package com.thread.text;
//用两种方式定义售票窗口(3)
//方式1
public class SaleTickets extends Thread {
//    private int tickets = 30;
//    private Object obj = new Object();
//    @Override
//    public void run() {
//        while(true) {
//synchronized (obj){
//                if (tickets > 0) {
//                    System.out.println(Thread.currentThread().getName() + ":第" + (tickets--) + "张票已被卖出");
//                }else{
//                    break;
//                }
//             }
//            try {
//                Thread.sleep(200);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//        }
//        System.out.println(getName()+"All tickets were sold out");
//    }
//tickets
static int tickets = 50;

    //lock object
    static Object obj = new Object();
    @Override
    public void run(){
        while (true){

            //Synchronized
            synchronized (obj){
                if (tickets > 0){
                    System.out.println(getName()+" : 第"+tickets--+"张票");
                }else{

                    break;
                }
            }

            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

package com.thread.text;
//方式1测试类:使用Thread继承方式
public class SaleTicketsDemo {
    public static void main(String[] args) {
        SaleTickets st1 = new SaleTickets();
        SaleTickets st2 = new SaleTickets();
        SaleTickets st3 = new SaleTickets();

        st1.setName("窗口1");
        st2.setName("窗口2");
        st3.setName("窗口3");

        st1.start();
        st2.start();
        st3.start();
    }
}

方式2
package com.thread.text;

/**
 * //用两种方式定义售票窗口(3)
 * //方式2:根据显示情况,我们的售票系统会有延迟
 *    1.会出现重复售票
 *         短暂的延迟,倒是线程同时进入售票系统,同时买票,然后同时输出
 *    2.出现负数票
 *    同时售出,多减了一次
 *
 *    如何判断是多线程问题
 *    1.是否是多线程环境
 *    2.是否有共享数据
 *    3.是否有多条语句操作共享数据
 *    解决方式:
 *    基本思想:让程序没有安全问题的环境
 *    把多个语句操作数据的代码锁起来,让任意时刻只有一个程序执行
 *    而同步代码快可以解决
 *       synchronized(对象)(需要同步的代码)
 *
 */
public class SaleTickets2 implements Runnable{
    private int tickets = 50;
    private Object obj = new Object();
    @Override
    public void run() {
       while(true) {
           synchronized (obj) {
               if (tickets > 0) {
                   try {
                       Thread.sleep(200);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println(Thread.currentThread().getName() + ":第" + (tickets--) + "张票已被卖出");
               }
           }
       }
    }
}

package com.thread.text;
//方式1测试类:使用runnable接口实现

/**
 * 同步可以解决安全问题的根本原因在于对象上,该对象如同锁的功能
 * 代码在运行时它只会运行它自己的Object对象,所以我们需要将Object共享出来,
 * 如果直接在synchronized()上定义新的Object那只是给每一个对象又锁了一个Object,没有任何意义
 * 共享的obj是每一次执行的时候,只有一个Object在运行,这样线程在运行的时候没有其他抢占
 * 只有在其将obj运行完之后,三个创客再进行抢占,再执行,如此就不会出现冲突
 *
 * 同步代码块的特点
 *    前提:
 *        1.多线程
 *        2.多西安城使用的是同一个锁对象
 *    好处:
 *        解决了多线程安全问题
 *    弊病:
 *        当线程相当多是,每个线程都会判断同步上的锁,这是很消耗资源的,降低了代码的运行效率
 */
public class SaleTicketsDemo2 {
    public static void main(String[] args) {
        SaleTickets2 st = new SaleTickets2();

        Thread st1 = new Thread(st,"窗口1");
        Thread st2 = new Thread(st,"窗口2");
        Thread st3 = new Thread(st,"窗口3");

        st1.start();
        st2.start();
        st3.start();
    }
}

同步方法的三种使用
package com.thread.text;


import junit.framework.Test;

public class SaleTickets2_2 implements Runnable {
    private static int tickets = 50;
    private final Object obj = new Object();
    private final Test t = new Test();
    private int x = 0;

    @Override
    public  void  run() {

        while (true) {
            if (x % 2 == 0) {
//                synchronized(this){
//                synchronized(t){
                synchronized(SaleTickets2_2.class){
                if (tickets > 0) {
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":第" + (tickets--) + "张票");
                }
                }


            } else {
              ticket();
            }
            x++;
        }
    }
    private synchronized static void ticket(){
            if (tickets > 0) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":第" + (tickets--) + "张票");
            }

    }

    class Test {

    }
}

package com.thread.text;

/**
 *1. 同步代码快锁对象到底是谁?
 *   任意对象
 *2. 同步方法的格式及锁对象的问题?
 *   将关键字写在方法声明
 *   同步方法块的锁是this
 *3.静态方法的锁对象
 *  静态方法是随着类的加载而加载
 *  其实这里的锁对象是该类的字节码文件对象(反射知识点)
 */
public class SaleTicketsDemo2_2 {
    public static void main(String[] args) {
        SaleTickets2_2 st = new SaleTickets2_2();

        Thread st1 = new Thread(st,"窗口1");
        Thread st2 = new Thread(st,"窗口2");
        Thread st3 = new Thread(st,"窗口3");

        st1.start();
        st2.start();
        st3.start();
    }
}

锁对象的基本操作

虽然我们可以理解同步代码块和同步方法的锁对象问题,但我们并没有直接看到在哪里加了锁,在哪里释放了锁,为了更清晰的表达苏荷加锁和释放锁,jdk5以后提供了一个新的锁对象

Lock:

概述:

这是一个接口,实现类比synchronized语句和方法实现类更广发的操作

Lock实现类:

构造方法:ReetrantLock:

成员方法:void lock:上锁

​ void lock:解锁

package com.thread.lock;

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

public class SaleTickets implements Runnable{
    private int tickets=50;
    //创建一个锁对象
    private Lock l = new ReentrantLock();
    @Override
    public void run() {
        while(true){
            //上锁
            l.lock();
            if (tickets>0){
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+":第"+(tickets--+"张票"));
            }
            //解锁
            l.unlock();
        }
    }
}

package com.thread.lock;

public class LockDemo {
    public static void main(String[] args) {
        SaleTickets st = new SaleTickets();

        Thread t1 = new Thread(st,"窗口1");
        Thread t2 = new Thread(st,"窗口2");
        Thread t3 = new Thread(st,"窗口3");

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

死锁问题

同步的弊病

  • 效率低
  • 如果出现了同步嵌套,就容易产生死锁问题

死锁问题:是指两个或两个以上的线程在执行的过程中,因争夺资源长生的相互等待的现象

死锁代码

package com.thread.lock;

import com.sun.org.apache.xpath.internal.operations.Bool;

public class DeadLock extends Thread{
    private Boolean flag;
    private static Object lockA = new Object();
    private static Object lockB = new Object();
    //有参构造初始化
    public DeadLock(boolean flag){
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag){
            synchronized (lockA){
                System.out.println("if lockA");
                synchronized (lockB){
                    System.out.println("if lockB");
                }
            }
        }else{
            synchronized (lockB){
                System.out.println("else lockB");
                synchronized (lockA){
                    System.out.println("else lockA");
                }
            }
        }
    }
}

package com.thread.lock;

public class DeadLockDemo {
    public static void main(String[] args) {
        DeadLock d1 = new DeadLock(true);
        DeadLock d2 = new DeadLock(false);
        d1.start();
        d2.start();
    }
}

5.生产者消费者模型线程安全问题

等待唤醒机制

wait();当前线程等待

notify();唤醒等待的单个线程

notifyAll();唤醒等待的所有线程

实现等待唤醒机制
实现1正常实现
package com.unit14.Selltickets;

public class Student {
    private String Name;
    private int age;
    boolean flag;

    public Student() {
    }

    public Student(String name, int age) {
        Name = name;
        this.age = age;
    }

    public String getName() {
        return Name;
    }

    public void setName(String name) {
        this.Name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "Name='" + Name + '\'' +
                ", age=" + age +
                ", flag=" + flag +
                '}';
    }
}

package com.unit14.Selltickets;

public class SetThread implements Runnable {
    private Student student;
    private int x = 0;
    private Object object = new Object();

    public SetThread(Student student) {
        this.student = student;
    }

    @Override
    public void run() {
        while(true){
            synchronized (student){
                if(student.flag){
                    //有数据,不生产,等待
                    try {
                        student.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //没有数据就生产,生产完毕,通知消费者
                if(x%2==0){
                    student.setName("西门吹雪");
                    student.setAge(12);
                }else{
                    student.setName("叶孤城");
                    student.setAge(13);
                }
                x++;
                //修改标记
                student.flag = true;
                student.notify();
            }
        }
//        student.setName("西门吹雪");
//        student.setAge(18);
    }
}

package com.unit14.Selltickets;

public class GetThread implements Runnable {
    private Student student;

    public GetThread(Student student) {
        this.student = student;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (student) {
                //判断是否有数据,没有就等待
                if (!student.flag) {
                    try {
                        student.wait();//线程等待的同时,释放对应锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //有数据,就消费,消费完成就通知生产者生产
                System.out.println(student.getName() + "---" + student.getAge());
                //修改标记
                student.flag = false;
                student.notify();
            }
        }
    }
}

package com.unit14.Selltickets;

public class StudentDemo {

    public static void main(String[] args) {
        Student student = new Student();
        //创建自定义线程类对象
        SetThread setThread = new SetThread(student);
        GetThread getThread = new GetThread(student);

        //创建Thread对象,将自定义像成雷对象作为参数传递
        Thread thread = new Thread(setThread,"设置线程");
        Thread thread2 = new Thread(getThread,"获取线程");

        //启动线程
        thread.start();
        thread2.start();
    }
}

实现2.同步方法实现
package com.thread.communication3;

public class Student {
    private String name;
    private int age;
    private boolean flag;//默认问false//标记有没有数据

    public synchronized void set(String name,int age) {
        //有数据的情况
        if (this.flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //添加数据
        this.name = name;
        this.age = age;
        //修改标记
        this.flag = true;
        this.notify();
    }
    public synchronized void get(){
        if (!this.flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //有数据就使用数据输出
        System.out.println(this.name+"---"+this.age);
        //修改标记
        this.flag = false;
        this.notify();
    }
}

package com.thread.communication3;

public class SetStudent implements Runnable{

    private Student s;
    private int x = 0;
    public SetStudent(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
//        Student s = new Student();
        while(true){

                //生产数据
                if (x % 2 == 0) {
                    s.set("西门吹雪",18);
                } else {
                   s.set("叶孤城",12);
                }
                x++;
        }
    }
}

package com.thread.communication3;

public class GetStudent implements Runnable{
    private Student s;

    public GetStudent(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
//        Student s = new Student();
        while(true)//多次获取
        {
            s.get();
        }

    }
}
package com.thread.communication3;

/*
等待唤醒机制
通过同步方法来实现等待唤醒机制
 */
public class StudentDemo {
    public static void main(String[] args) {
        Student s = new Student();
       //这样两边获取到的s是同一个s
        SetStudent ss = new SetStudent(s);
        GetStudent gs = new GetStudent(s);
        Thread t1 = new Thread(ss);
        Thread t2 = new Thread(gs);
        t2.start();
        t1.start();

    }
}

问题:为什么将等待和唤醒的方法都定义在Object中,而不是Thread中

**答:**为了方便锁的调用

6.线程组

概述:他可以对一批线程进行分类管理,java允许程序直接对线程进行控制

好处:统一管理

package com.unit14.ThreadGroup;

public class MYRunnable implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<20;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

package com.unit14.ThreadGroup;

/**
 * 返回此线程所在的线程组getThreadGroup()
 */
public class ThreadGroupDemo {
    public static void main(String[] args) {
        method1();
        System.out.println("########################");
        method2();
    }

    private static void method2() {
        //设置线程组
        ThreadGroup tg = new ThreadGroup("newThread");
        //定义接口对象
        MYRunnable mr = new MYRunnable();

        Thread t1 = new Thread(tg,mr,"西门吹雪");
        Thread t2 = new Thread(tg,mr,"叶孤城");

        System.out.println(t1.getThreadGroup().getName());
        System.out.println(t2.getThreadGroup().getName());
        System.out.println(Thread.currentThread().getThreadGroup().getName());
    }

    private static void method1() {
        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());
        //通过链式编程,我们获取main方法
        System.out.println(Thread.currentThread().getThreadGroup().getName());
    }
}

7.线程池

1.为什么要学线程池

因为我们在创建线程的时候,成本是比较高的,因为每一次创建线程都需要和操作系统进行交互。线程池会在程序启动时,提前创建一些线程放在线程池中等待使用,这样可以大大的提高执行效率

2.特点

a.线程池中的线程在使用之后不会消亡,而是重新回到线程池中成为空闲状态等待下一个对象使用

b.JDK5之前需要手动配置线程池,之后Java开始内置线程池

Executors工厂类:

通过下面方法获得线程池对象

1.public static ExecutorService newCachedThreadPool()

2.public static ExecutorService newFixedThreadPool(int nThreads)

控制输入几个线程

使用:

1、创建线程池对象

public static ExecutorService newFixedThreadPool(int nThreads)

2,创建Runnable对象

可以执行Runnable对象或者callable对象代表的线程

3,提交Runnable实例

a.Futer submit(Runnable task)

b. Future submit(callable task)

4,关闭线程池

shutdown();

代码实例

package com.unit14.ThreadPool;

public class MyThread implements Runnable{

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

package com.unit14.ThreadPool;

import java.util.Collection;
import java.util.List;
import java.util.concurrent.*;

public class ThreadPoolDemo {
    public static void main(String[] args) {
        //创建线程池对象
        ExecutorService es = Executors.newFixedThreadPool(2);
        //创建自定义线程类对象
        MyThread myThread = new MyThread();
        //提交Runnable实例
        es.submit(myThread);
        es.submit(myThread);
        //关闭线程池
        es.shutdown();


    }
}

3.public static ExecutorService newSingleThreadExector()

​ 这些方法的返回值是ExecutorServicr对象,该对象表示一个线程池,它可以执行Runnable 对象或者Callable对象代表的线程

方式三

7.1多线程的第三种实现:Callable接口

Callable接口类似于Runnable接口,但是,callable有返回值,并且可能抛出异常

普通实现类似Runnable
package com.unit14.Callable;

import java.util.concurrent.Callable;

public class MyCallable implements Callable {


    @Override
    public Object call() throws Exception {
        for (int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
        return null;
    }
}

package com.unit14.Callable;

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

public class MyCallableDemo {
    public static void main(String[] args) {
        //创建线程池对象
        ExecutorService es = Executors.newFixedThreadPool(2);

        //创建自定义线程类对象
        MyCallable myCallable = new MyCallable();

        //提交Callable对象
        es.submit(myCallable);
        es.submit(myCallable);
        es.shutdown();
    }
}

指定泛型实现(进行求和)
package com.unit14.Callable.test;

import java.util.concurrent.Callable;

public 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=0;i<=num;i++){
          sum+=i;
      }
      return sum;
    }
}

package com.unit14.Callable.test;

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

/**
 * 第三种实现线程的方式,
 * 1.可以有返回值
 * 2.抛出异常
 * 弊端:
 *    代码比较复杂,所以一般不用
 */
public class MyCallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建线程池对象
        ExecutorService es = Executors.newFixedThreadPool(2);

        //创建自定义线程类对象
//        MyCallable myCallable = new MyCallable(100);
//        MyCallable myCallable2 = new MyCallable(200);

        //提交自定义线程类对象
        Future<Integer> future = es.submit(new MyCallable(100));
        Future<Integer> future2 = es.submit(new MyCallable(5));
        //get()
        int num =  future.get();
        int num2 = future2.get();

        System.out.println("1-100的和:"+num);
        System.out.println("1-200的和:"+num2);
        es.shutdown();
    }
}

7.2使用匿名内部类实现多线程

因为平时开发中可能会需要线程执行某一项工作,且只执行一次,这样就可以简化工程类结构

package com.unit14.Callable.test;
/**
 * 通过笔名内部类使用多线程
 * 1.继承Thread类
 * 2.实现Runnable接口
 */
public class NoNameThreadDemo {
    public static void main(String[] args) {

        //1.Thread对象
        new Thread(){
            @Override
            public void run() {
                for(int i=0;i<=100;i++){
                    System.out.println(Thread.currentThread().getName()+"---"+i);
                }
            }
        }.start();
        //2.Runnable接口:
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<=10;i++){
                    System.out.println(Thread.currentThread().getName()+"---"+i);
                }
            }
        }){}.start();
       //3,当 Runnable和Thread同时引用,会走Thread方法,也就是会输出world
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        }){
            public void run() {
                System.out.println("world");
            }
        }.start();
    }
}

匿名内部类格式

new Thread(){}.start();

9.定时器

是一个线程工具,可用于调度多个定时任务,通过TImer()和TimerTask()这两个类来实现定义和调度的功能

Timer

构造方法:Timer()

成员方法:

1.schedule(TimerTask task,long delay)

2.schedule(TimerTask task,long dealy,long period)

TimerTask

可以有定时器指定一次或重复的执行任务

构造方法:是一个抽象类

成员方法:

1.public abstract void run()

package com.unit14.Timer;

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

public class MyTaskDemo {
    public static void main(String[] args) {
        //创建定时器对象
        Timer timer = new Timer();
        //计划任务对象
       //三秒后执行run,并且每疫苗执行一次
//        timer.schedule(new MyTask(timer),3000,1000);
 //如何执行一次就停止
//        timer.cancel();不能在这里停止
        timer.schedule(new MyTask(timer),3000);
    }
   static class MyTask extends TimerTask {

        private Timer timer;
     public MyTask(Timer timer){
         this.timer = timer;
     }

        @Override
        public void run() {
//           for (int i=0;i<=100;i++){
//               System.out.println(Thread.currentThread().getName()+"---"+i);
//           }
            System.out.println("唐僧洗发哎飘柔");
            timer.cancel();
        }
    }
}

常用发的定时调度框架:Quartz调度框架

课堂练习:

需求:使用定时器在指定时间点删除指定目录

package com.unit14.Timer;

import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

/**
 * 需求:删除目录下所有文件和文件加
 */
public class TimerDemo2 {
    public static void main(String[] args) throws ParseException {
        //创建定时器对象
        Timer t = new Timer();

        String s = "2021-05-02 23:32:00";
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = sdf.parse(s);
        t.schedule(new delFile(),date);
    }
}

//写我们的任务
class delFile extends TimerTask{
    @Override
    public void run() {
        File f = new File("test");
        deleteFolder(f);
    }
    //使用递归删除
    public void deleteFolder(File f){
        //获取目录下的文件对象
        File[] files = f.listFiles();
        if (files!=null){
            //遍历每一个文件对象
            for (File file:files){
                //文件夹时
                if (file.isDirectory()){
                    deleteFolder(file);
                }else{
                    file.delete();
                }
            }
            //最后删除文件夹
            f.delete();
        }
    }
}

你可能感兴趣的:(基础知识)