java:多线程基础(史上最全版本!!!)

20 篇文章 0 订阅

多线程

1.1引入:

学习多线程之前,我们要学习进程,因为线程是基于进程存在的
1.什么是进程?

  • 通过任务管理器我们可以看到进程的存在

  • 我们发现,只有在运行中的程序,才会出现在任务管理器中

  • 进程: 运行中的程序

  • 进程是系统进行资源分配和调度的单位,每一个进程都有自己的内存空间和系统资源

    2.什么是多进程?

  • 最早的计算机,都是单核单进程的,也就是说,它一次只能做一件事

  • 很显然,我们现在计算机可以同时做很多事,比如:一边聊微信,一边聊qq

  • 我们现在的计算机,可以在一个时间段内,同时执行多个任务

    3.有什么好处:

    它可以提高cpu的使用率

    4.我一边聊微信,一边聊qq,请问它是同时进行的吗?

  • 不是的,单cpu同一时间点,只能做一件事情

  •      而是cpu高速在程序间切换,让我们觉得它是同时运行的。
    
1.2概念:

在同一个进程内,又可以执行多个任务(我在聊微信的时候,我可以一边发消息,一边传文件)

1.2.1线程:

它实际是程序执行的基本单元,也就是执行路径,是程序使用cpu的最基本单位

1.2.2单线程:

程序只有一条执行的路径(我们之前学习的都是单线程)

1.2.3多线程:

程序有多条执行路径

1.2.4多线程意义?

1.线程的执行是抢占式的,它要去抢占cpu资源

2.一个多线程程序在执行时,如果一些线程必须等待的时候,cpu资源就可以交给其他线程去使用,提高程序的使用率,提高工作效率。

3.抢占资源的时候,是具有随机性的,我们不敢保证哪一个线程在哪一个时间可以抢到cpu资源。

1.2.5并行与并发:

并行:多个处理器或多核处理器在某一个时间段内同时运行多个程序

并发:一个处理器在某一个时间段内同时运行多个程序

1.2.6举例:

并发相当于一个人同时吃三个馒头

并行就相当于三个人同时吃三个馒头,一人吃一个

1.3多线程的实现(Thread):
1.3.1引入:

由于线程是依赖于进程存在的,所以我们首先要创建一个进程,而进程是由系统来创建的,所以我们需要调用系统的某些功能来创建,但是java是不能调用系统功能的。

但是它可以调用C/C++写好的程序来创建进程,进而实现多线程

java将C/C++写好的代码封装到一个类中,然后通过这个类就可以调用创建进程、或者线程的功能,然后间接的实现多线程

1.3.2创建方法:

①将类声明为Thread的子类,该子类重写Thread类的run方法

public class MyThread extends Thread {

    @Override
    public void run() {
        //线程一般执行比较耗时的工作
        for(int i=0;i<5;i++){
            System.out.println(getName()+":"+i);
        }
    }
}
class MyThreadDemo{
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        MyThread myThread2 = new MyThread();
        //myThread.run();调用普通方法
        //myThread2.run();

        myThread.start();//启动线程
        myThread2.start();
    }
}

注意:调用run()方法只是普通方法的调用,交给main处理,start()才是jvm启动线程

1.3.3线程重命名:

①利用Setname()改名字

myThread.setName("tom");
myThread2.setName("jack");

②有参构造:在子类中声明有参构造,并显示的指定访问父类的有参

public MyThread(String name){
    super(name);
}
MyThread myThread = new MyThread("tom");
MyThread myThread2 = new MyThread("jack");

③获取当前执行线程的名字

System.out.println(Thread.currentThread().getName());

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cPFsah3v-1607342467562)(C:\Users\24582\AppData\Roaming\Typora\typora-user-images\image-20201206115430149.png)]

1.4线程优先级的设置:
1.4.1两种线程调度模型:

1.分时调度:所有线程轮流使用cpu的使用权,平均分配每个线程占用的cpu时间片

2.抢占式调度:该调度模型会优先让优先级高的线程使用cpu,如果优先级相同,则随机选择。优先级高的线程只代表它几率大。

1.4.2获取优先级的方法及设置:
public class MyThread2  extends Thread{
    public MyThread2(String name) {
        super(name);
    }

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

class MyThread2Demo{
    public static void main(String[] args) {
        MyThread2 myThread = new MyThread2("线程1");
        MyThread2 myThread2 = new MyThread2("线程2");
        
        //优先级设置:(1--10)
        myThread.setPriority(1);
        myThread2.setPriority(10);
        //显示优先级
        System.out.println(myThread.getPriority());
        System.out.println(myThread2.getPriority());

        myThread.start();
        myThread2.start();
    }
}

默认优先级:5

优先级范围:1-10

1.5线程控制–线程睡眠(sleep):
1.5.1格式:
public static void sleep(long millis)
1.5.2代码:
/**
 * 线程睡眠:
 */

public class MyThread3 extends  Thread{
    public MyThread3(String name) {
        super(name);
    }

    @Override
    public void run() {
        for(int i=0;i<10;i++){
            try {
                Thread.sleep(1000);//单位毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName()+":"+i);
        }
    }
}
class MyThread3Demo{
    public static void main(String[] args) {
        MyThread3 myThread = new MyThread3("线程1:");
        myThread.start();
    }
}
1.5.3注意:

sleep在哪个线程中调用,阻塞哪个方法

1.6线程控制–加入线程(join):
1.6.1格式:
public final void join(long millis)
    //该方法是调用该方法的线程执行完毕后,再去调用该方法
    //不设置毫秒值,则一直等待该线程执行完毕,其他线程才开始执行
1.6.2代码:
public class MyThread4 extends Thread {

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

    @Override
    public void run() {
        for(int i=0;i<10;i++){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName()+":"+i);
        }
    }
}
class MyThread4Demo{
    public static void main(String[] args) {
        MyThread4 myThread = new MyThread4("线程1:");
        MyThread4 myThread2 = new MyThread4("线程1:");
        MyThread4 myThread3= new MyThread4("线程1:");
        myThread.start();
        try {
            myThread.join(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        myThread2.start();
        myThread3.start();
    }
}
1.7线程控制—线程礼让(yield):
1.7.1格式:
public static void yield()
    //线程礼让可以让线程间的抢占趋近和平,就是你一下,我一下,但仅仅只是让抢占不激烈,但并不完成是你一下,我一下
1.7.2代码:
/**
 * 线程礼让
 */
public class MyThread5 extends Thread {
    public MyThread5(String name) {
        super(name);
    }

    @Override
    public void run() {
        for(int i=0;i<50;i++){
            System.out.println(getName()+":"+i);
            Thread.yield();
        }
    }
}
class MyThread5Demo{
    public static void main(String[] args) {
        MyThread5 myThread = new MyThread5("线程1:");
        MyThread5 myThread2 = new MyThread5("线程2:");
        MyThread5 myThread3 = new MyThread5("线程3:");


        myThread.start();
        myThread2.start();
        myThread3.start();
    }
}
1.8线程控制–线程守护SetDaemon
1.8.1格式:

将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。

public final void setDaemon(boolean on)
1.8.2代码:
/**
 * 守护线程
 */

public class MyThread6 extends Thread{
    public MyThread6(String name) {
        super(name);
    }

    @Override
    public void run() {
        for(int i=0;i<50;i++){
            System.out.println(getName()+":"+i);
        }
    }
}
class MyThread6Demo{
    public static void main(String[] args) {
        MyThread6 myThread = new MyThread6("亚索");
        MyThread6 myThread2 = new MyThread6("永恩");

        myThread.setDaemon(true);
        myThread2.setDaemon(true);
        myThread.start();
        myThread2.start();

        Thread.currentThread().setName("主水晶");
        for(int i=0;i<3;i++){
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }

    }
}
1.9线程控制—终止线程(stop):
1.9.1格式:
public final void stop()
public void interrupt()
1.9.2代码:
/**
 * 终止线程
 */

public class MyThread7 extends Thread{
    public MyThread7(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println("线程开始:" + new Date());

        try {
            for (int i = 0; i < 20; i++) {
                Thread.sleep(5000);
                System.out.println(getName() + ":" + i);
            }
        } catch (InterruptedException e) {
            //e.printStackTrace();
            System.out.println("线程被终止了!");
        }
        System.out.println("线程结束:" + new Date());
    }
}

class MyThread7Demo{
    public static void main(String[] args) {
        MyThread7 myThread = new MyThread7("线程1:");
        myThread.start();

        try {
            Thread.sleep(3000);
            myThread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
1.9.3注意:

interrupt和stop不同,前者走的是异常处理机制,如果你的线程终止还有一些代码必须执行的话,你可以把这些代码写在finally或try-catch结构外。

1.10线程的声明周期:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oxxemx9r-1607342467564)(C:\Users\24582\AppData\Roaming\Typora\typora-user-images\image-20201206155531002.png)]

1.11创建多线程的第二种方法:
1.11.1Runnable接口:
/**
 * 第二种创建方式:
 */

public class RunnableDemo implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
class RunnableTest{
    public static void main(String[] args) {
        RunnableDemo runnableDemo = new RunnableDemo();
        Thread t = new Thread(runnableDemo);
        Thread t2 = new Thread(runnableDemo);
        t.setName("线程1:");
        t2.setName("线程2:");
        t.start();
        t2.start();
    }
}
1.11.2问题:

为什么还会有第二种方法?

答:因为解决了单继承的局限性

例如:子类实现父类,但子类想实现多线程,只能用第二种。

1.12综合练习:
/**
 * 使用多线程模拟电影院售票
 * 需求:电影院上映了一部电影,该电影院只能容乃100张,并且有3个窗口同时售票:
 */

public class WorkDemo extends Thread {
    public   static int ticket =100;

    @Override
    public void run() {
        while (true){
            if(ticket>0){
//                try {
//                    Thread.sleep(500);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
                System.out.println(getName()+"正在售票:"+"第"+(ticket--)+"张");
            }
            if(ticket==0){
                Thread.currentThread().interrupt();
                System.out.println("售罄!!!");
            }
        }
    }
}
class WorkDemoTest{
    public static void main(String[] args) {
        WorkDemo workDemo = new WorkDemo();
        WorkDemo workDemo2 = new WorkDemo();
        WorkDemo workDemo3 = new WorkDemo();
        workDemo.setName("窗口1:");
        workDemo2.setName("窗口2:");
        workDemo3.setName("窗口3:");

        workDemo.start();
        workDemo2.start();
        workDemo3.start();


    }
}

1.网络延迟sleep会导致出现一票多卖,为什么?

cpu执行具有原子性

2.出现0或者负票数,为什么?

1.13解决问题:解决多线程安全问题:

解决多线程安全问题:

1.是否是多线程

2.是否共享数据

3.是否有多条语句操作共享数据

思路:

考虑把这多条操作共享数据的语句进行包裹,当某一个程序在执行被包裹的语句时,其他线程不能执行这些语句

java为我们提供了一种机制解决我们的需求:同步机制

格式:

synchronized(任意对象){

}

代码:
public void run() {
    synchronized (object) {
        while (true) {
            if (ticket > 0) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(getName() + "正在售票:" + "第" + (ticket--) + "张");
                Thread.yield();
            }
        }
    }
}
死锁问题:

死锁:就是指两个或两个以上的线程在执行过程中,因为争夺资源而产生的一种相互等待的现象。

1.14生产者消费者模型:
需求:

不同线程操作同一个学生对象,一个线程用来设置学生对象,一个线程用来获取学生对象。

分析:

资源类:Student

设置线程:SetThread—生产者

获取线程:getThread—消费者

测试类

问题:

可能出现生产者未产生数据,而消费者则抢占资源,get就会返回null

再或者生产者连续抢占资源,则新产生的资源会覆盖掉之前的,也没啥用

优化:

Object类中的线程操作方法:

wait():当前线程等待

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

notifyAll():唤醒等待的多个线程

代码:

①学生类:

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();
    }
}

②设置线程类:

public class SetThread implements Runnable {
    private Student student;

    public SetThread(Student student){
        this.student = student;
    }
    @Override
    public void run() {
     student.set("tom",18);
     student.set("jack",20);
    }
}

③获取线程类:

public class GetThread implements  Runnable {
    private  Student student;

    public GetThread (Student student){
        this.student = student;
    }
    @Override
    public void run() {
    student.get();
    student.get();
    }
}

④学生测试类:

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 = new Thread(getThread);
        Thread thread2 = new Thread(setThread);

        thread.start();
        thread2.start();
    }
}
1.15线程组:
概述:

Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。

获取线程组方法:

默认情况下,所有的线程都属于主线程组。
public final ThreadGroup getThreadGroup()

设置线程分组:

我们也可以给线程设置分组
Thread(ThreadGroup group, Runnable target, String name)

好处:

便于统一管理线程组。

代码:
public class ThreadGroupDemo {
    public static void main(String[] args) {
        method1();
        method2();
    }
    public static void method1(){
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable,"tom");
        Thread thread2 = new Thread(myRunnable,"jack");

        ThreadGroup tg1 =thread.getThreadGroup();
        ThreadGroup tg2 =thread2.getThreadGroup();
        System.out.println(tg1.getName());
        System.out.println(tg2.getName());
        System.out.println(Thread.currentThread().getThreadGroup().getName());

//        thread.start();
//        thread2.start();
    }
    public static void method2(){
        ThreadGroup threadGroup = new ThreadGroup("NewThread");
        MyRunnable myRunnable2 = new MyRunnable();
        Thread thread = new Thread(threadGroup,myRunnable2,"tom");
        Thread thread2 = new Thread(threadGroup,myRunnable2,"jack");

        System.out.println(thread.getThreadGroup().getName());
        System.out.println(thread2.getThreadGroup().getName());
        System.out.println(Thread.currentThread().getThreadGroup().getName());
    }
}
1.16线程池:
概述:

程序启动一个新线程成本是比较高的,因为它涉及到与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存周期很短的线程时,更应该考虑使用线程池。

特点:

线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象继续使用。

新特性:

在JDK5以前,我们必须手动实现自己的线程池,从jdk5以后,java内置支持线程池。

创建线程池方法:

JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法

public static ExecutorService newCachedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
执行方法:

这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供如下方法
Future<?> submit(Runnable task)
Future submit(Callable task)

代码:
/**
 * 线程池创建的第一种方法:带缓冲区
 * public static ExecutorService newCachedThreadPool()
 */
public class ExecutorsDemo {
    public static void main(String[] args) {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

        cachedThreadPool.submit(new MyRunnable());
        cachedThreadPool.submit(new MyRunnable());
        cachedThreadPool.submit(new MyRunnable());
//          cachedThreadPool.execute(new MyRunnable());

        cachedThreadPool.shutdown();
    }
}
/**
 * 创建线程池的第二种方法:指定个数
 * public static ExecutorService newFixedThreadPool(int nThreads)
 */

public class ExecutorsDemo2 {
    public static void main(String[] args) {
        //创建线程池对象,并指定个数
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        //创建三个线程
        fixedThreadPool.submit(new MyRunnable());
        fixedThreadPool.submit(new MyRunnable());
        fixedThreadPool.submit(new MyRunnable());

        //停止线程池
        fixedThreadPool.shutdown();
    }
}
/**
 * 创建线程池的第三种方法:默认一个,且执行有序
 * public static ExecutorService newSingleThreadExecutor()
 */

public class ExecutorsDemo3 {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        executorService.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("我是第一个");
            }
        });
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("我是第二个");
            }
        });
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("我是第三个");
            }
        });

        executorService.shutdown();
    }
}
1.17实现线程的第三种方法:
格式1:

实现Callable接口

public class MyCallable implements Callable {
    @Override
    public Object call() throws Exception {
        for(int i=0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
        return null;
    }
}

测试类

public class CallableDemo {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.submit(new MyCallable());
        executorService.submit(new MyCallable());

        executorService.shutdown();
    }
}
格式2:

实现泛型

public class MyCallable2  implements Callable<Integer> {
    private int  num;
    public MyCallable2(int num){
        this.num =num;
    }
    @Override
    public Integer call() throws Exception {
        int result = 0;
        for (int i=0;i<num;i++){
            result += i;
        }
        return result;
    }
}

测试类

public class CallableDemo2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        Future<Integer> result1 = executorService.submit(new MyCallable2(5));
        Future<Integer> result2 = executorService.submit(new MyCallable2(100));

        Integer i1 = result1.get();
        Integer i2 = result2.get();

        System.out.println(i1);
        System.out.println(i2);

        executorService.shutdown();
    }
}
好处:

可以有返回值
可以抛出异常

缺点:

代码比较复杂,所以一般不用

1.18使用匿名内部类实现多线程:
格式:

new Thread(){代码;}.start();
new Thread(new Runnable(){代码;}){}.start();

代码:
public class NonameThreadDemo1 {
    public static void main(String[] args) {
        //继承Thread类
      new Thread(){
         @Override
         public void run() {
             System.out.println(Thread.currentThread().getName());
         }
      }.start();
        //实现Runnable接口
      new Thread(new Runnable() {
          @Override
          public void run() {
              System.out.println(Thread.currentThread().getName());
          }
      }).start();
    }
}
注意:

面试题写法,开发中不用

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("1");
    }
}){
    @Override
    public void run() {
        System.out.println("2");
    }
}.start();

结果会输出2,jvm会走Thread的run方法,而不走Runnable的run方法

1.19定时器:
概述:

定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务,以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能。

Timer:

public Timer()
public void schedule(TimerTask task, long delay)
public void schedule(TimerTask task,long delay,long period)

TimerTask:

抽象类,可同类自定义类继承实现方法

public abstract void run()
public boolean cancel()

代码案例:
public class TimerDemo {
    public static void main(String[] args) {
        //创建对象
        Timer timer = new Timer();
//        //延迟多少时间后执行任务
//        //public void schedule(TimerTask task, long delay)
//        timer.schedule(new MyTask(),500);
//
//        //指定时间后,重复执行
//        //public void schedule(TimerTask task,long delay,long period)
//        timer.schedule(new MyTask(),3000,1000);//三秒后执行并且每隔一秒执行一次

        //停止
        timer.schedule(new MyTask(timer),3000);


    }
}
class MyTask extends TimerTask{
    private Timer t;
    public MyTask(){

    }
    public MyTask(Timer t){
        this.t = t;
    }

    @Override
    public void run() {
        System.out.println("~~~");
        t.cancel();
    }
}
练习:
/**
 * 需求在指定时间删除目录下所有文件和文件夹
 *
 */

public class TimerDemo2 {
    public static void main(String[] args) throws ParseException {
        Timer timer = new Timer();

        String s="2020-12-7 18:43:00";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d = sdf.parse(s);

        timer.schedule(new delFile(),d);
    }
}

class delFile extends TimerTask{

    @Override
    public void run() {
        File file = new File("TEST");
        del(file);
    }
    public void del(File f){
        File [] files = f.listFiles();
        if(files!=null){
            for(File file: files){
                if(file.isDirectory()){
                    del(file);
                }else {
                    file.delete();
                }
            }
            f.delete();
        }
    }
}
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值