Java进阶八—多线程

Java进阶八—多线程

一. 问题的提出

1.一台计算机为何能够执行多个程序?它们是怎么执行多个程序的?

电脑可以同时做很多事情,一边聊天,一边听歌,一边上网查资料等,原因是电脑有多个核心(脑子),一个核心可以做一件事情,多个核心就可以做多件事情。而在很早的时候计算机CPU只有一个核心只能处理一件事情,所以科学家想出了一个办法,假如有两个应用,一个应用不使用时可将这个应用暂停(挂起),使用时打开将另一个应用暂停(挂起)。造成一种假象表面上CPU可以同时运行两个程序,实际上是进程切换的快,第一个进程打开,第二个进程挂起,给你一种错觉。得出:多核CPU可以同时执行多个进程,单核CPU就可以“同时”执行多个进程。Java进阶八—多线程_第1张图片

微观上说,多核CPU可以同时执行多个进程,进程数与CPU核数相当。但宏观上说,由于CPU会分时间片执行多个进程,所以实际执行进程个数会远多于CPU核数。

 2.那么核心与进程和线程有什么关系?

粗略理解:核心 > 一个进程或多个进程(同时) > 一个线程或多个线程Java进阶八—多线程_第2张图片

具体理解:腾讯面试题04.进程和线程的区别?_面试题线程和进程-CSDN博客

3.什么是并发和并行与区别? 

并发:当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。.这种方式我们称之为并发(Concurrent)。

并行:当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。

区别:并发和并行是即相似又有区别的两个概念,并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。在多道程序环境下,并发性是指在一段时间内宏观上有多个程序在同时运行,但在单处理机系统中,每一时刻却仅能有一道程序执行,故微观上这些程序只能是分时地交替执行。倘若在计算机系统中有多个处理机,则这些可以并发执行的程序便可被分配到多个处理机上,实现并行执行,即利用每个处理机来处理一个可并发执行的程序,这样,多个程序便可以同时执行。

二.两种方法实现多线程 

1.通过继承Thread类 

package com.thread;

// 继承多线程Thread类
public class DemoTest extends Thread{

   // 重新run方法
   @Override
    public void run() {

       while (true) {
           System.out.println("one");
       }

    }
}
package com.thread;

public class Demo {
    public static void main(String[] args) {
        
        DemoTest demoTest = new DemoTest();
        // start()方法多开启一个线程,然后自动调用run()
        demoTest.start();
        
        while (true) {
            System.out.println("two");
        }
        
    }
}

Java进阶八—多线程_第3张图片

2.通过实现接口Runnable 

package com.thread;

public class DemoTest implements Runnable{
   // 重新run方法
   @Override
    public void run() {
       while (true) {
           System.out.println("one");
       }
    }
}
package com.thread;

public class Demo {
    public static void main(String[] args) {

        DemoTest demoTest = new DemoTest();

        // 使用接口Runnable,需要创建一个Thread类
        Thread thread = new Thread(demoTest);

        // start()方法多开启一个线程,然后自动调用run()
        thread.start();

        while (true) {
            System.out.println("two");
        }

    }
}

Java进阶八—多线程_第4张图片

写法二:简化操作以及线程名 

package com.thread;

public class DemoTest implements Runnable{
   // 重新run方法
   @Override
    public void run() {
       while (true) {
           // getName()设置线程名
           System.out.println("one" +Thread.currentThread().getName());
       }
    }
}
package com.thread;

import com.microsoft.bean.Test;

public class Demo {
    public static void main(String[] args) {

        DemoTest demoTest = new DemoTest();

        new Thread(demoTest," Fuck_one").start();

        while (true) {
            System.out.println("two");
        }

    }
}

Java进阶八—多线程_第5张图片

补充:匿名内部类创建多线程——你们老师喜欢的   

package com.thread;

public class DemoTwo {
    public static void main(String[] args) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("one");
                }
            }
        }).start();


        while (true) {
            System.out.println("two");
        }
      
    }
}

三.多线程的执行过程 

  • 一般的程序是从main出发,直线向下进行,只有一条主线
  • 多线程在main主线程序遇到线程程序时会转到线程程序,并返回到主线程序中,这样main程序和线程程序同时执行

 Java进阶八—多线程_第6张图片

四.案例,抢鞋

抢鞋的逻辑代码涵盖在线程当中,假设有10双鞋,有三个人来抢,一个线程就是一个用户,所以这就有三个名称不一样的线程名。(三个人抢占共同资源10双鞋)

package com.thread;

public class DemoTest implements Runnable{

    public int nike = 10;

   @Override
    public void run() {
       while (true) {
           if (nike >0 ) {
               System.out.println(Thread.currentThread().getName() +"还剩"+(--nike)+"双鞋");
           }
       }
    }

}
package com.thread;

public class Demo {
    public static void main(String[] args) {

        DemoTest demoTest = new DemoTest();

        new Thread(demoTest,"one").start();
        new Thread(demoTest,"two").start();
        new Thread(demoTest,"three").start();

    }
}

Java进阶八—多线程_第7张图片

五.后台、守护进程的提出 

 Java进阶八—多线程_第8张图片

图中可知线程分为前台线程(应用)与后台进程(后台进程)。

  • 是前台进程,Background processes是后台进程也叫守护进程,这些进程在电脑开机时就被启动,这样电脑才能正常且安全的运作起来,在程序中也是同理
  • 与进程同理,前台线程为用户提供服务,也有后台线程为前台线程提供的服务进行保护或者守护

 后台线程的创建过程

package com.thread;

public class DaemoTest implements Runnable{
    @Override
    public void run() {
        System.out.println("守护线程开启");
    }
}
package com.thread;

public class Demo {
    public static void main(String[] args) {

        DemoTest demoTest = new DemoTest();
        DaemoTest daemoTest = new DaemoTest();
        Thread dThread = new Thread(daemoTest);

        // 后台线程(守护线程)--维护前台线程
        // 开启
        dThread.setDaemon(true);
        dThread.start();
        // 判断是否开启
        System.out.println(dThread.isDaemon());


        // 前台线程--应用
        new Thread(demoTest,"one").start();
        new Thread(demoTest,"two").start();
        new Thread(demoTest,"three").start();

    }
}

六.发现问题,提出synchronized的概念和用途

但在现实情况,人抢鞋是有延迟的,你不可能像程序一样一秒钟不到就抢完了,所以有使用sleep()方法。

package com.thread;

public class DemoTest implements Runnable{

    public int nike = 10;
   @Override
    public void run() {
       while (true) {
           try {
               // 设置延迟为500毫秒,等于半秒种
               Thread.sleep(500);
           } catch (InterruptedException e) {
               throw new RuntimeException(e);
           }
           if (nike >0 ) {
               System.out.println(Thread.currentThread().getName() +"还剩"+(--nike)+"双鞋");
           }
       }
    }
}
package com.thread;

public class Demo {
    public static void main(String[] args) {

        DemoTest demoTest = new DemoTest();


        new Thread(demoTest,"one").start();
        new Thread(demoTest,"two").start();
        new Thread(demoTest,"three").start();

    }
}

 Java进阶八—多线程_第9张图片

可以从运行结果看出,one和two都抢到第六双鞋,three抢到第零双鞋。

出现这样的原因,是因为线程不同步,线程不安全。可以使用synchronized锁对象,同步数据。

package com.thread;

public class DemoTest implements Runnable{

    public int nike = 10;

    // 创建synchronized锁对象
    Object lock = new Object();

   @Override
    public void run() {
       while (true) {
           // 同步数据,同步代码块
           synchronized (lock) {
               try {
                   // 设置延迟为500毫秒,等于半秒种
                   Thread.sleep(500);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
               if (nike > 0) {
                   System.out.println(Thread.currentThread().getName() + "还剩" + (--nike) + "双鞋");
               }
           }
       }
    }
}

synchronized锁的原理:

如何理解锁呢?当用户一抢到第一双鞋时,锁住第一双鞋,其它用户就无法抢了Java进阶八—多线程_第10张图片上面的synchronized锁的使用方法为同步代码块,还有一种为同步方法(推荐使用)。

package com.thread;

public class DemoTest implements Runnable{

    public int nike = 10;

    // 创建synchronized锁对象
    Object lock = new Object();

   @Override
    public void run() {
       while (true) {
           nikeCatch();
       }
    }
    
    // 同步方法,推荐使用,清晰可观
    public synchronized void nikeCatch() {
       
            try {
                // 设置延迟为500毫秒,等于半秒种
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }      
            if (nike > 0) {
                System.out.println(Thread.currentThread().getName() + "还剩" + (--nike) + "双鞋");
            }
    }
    
}

Java进阶八—多线程_第11张图片

七.Lock、ReentrantLock同步锁 

相比synchronized锁,其实还有更好的锁,名为同步锁。

package com.thread;

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

public class DemoTest implements Runnable{
    public int nike = 10;

    // 创建同步锁,Lock是一个接口,所以要使用它的实现类
   Lock reentrantLock = new ReentrantLock();

   @Override
    public void run() {
       while (true) {
           // 上锁
           reentrantLock.lock();

               try {
                   if (nike > 0) {
                       // 设置延迟为500毫秒,等于半秒种
                       Thread.sleep(500);
                       System.out.println(Thread.currentThread().getName() + "还剩" + (--nike) + "双鞋");
                   }
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               } finally {
                   // 解锁
                   reentrantLock.unlock();
               }
           }
   }
}

 Java进阶八—多线程_第12张图片Java进阶八—多线程_第13张图片Java进阶八—多线程_第14张图片

比较synchronized与reentrantLock区别

  • JDK1.5中,synchroized是重量级操作,性能低效,Lock性能高,更稳定
  • JDK1.6中,synchroized加入很多优化,更加稳定了

锁的释放

  • synchronized以获取锁的线程执行完同步代码,如果线程执行发生异常,jvm会让线程释放锁
  • Lock在finally中必须释放锁,不然容易造成线程死锁

死锁产生

  • synchronized在发生异常时候会自动释放占有的锁,不会出现死锁
  • Lock发生异常时候,不会主动释放,必须手动unlock来释放锁,可能引起死锁的发生

用法

  • synchronized在需要同步的对象中加入,可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象
  • Lock一般使用ReentrantLock类做为锁,通过lock()加锁和unlock()解锁指出,在finally中写unlock()防止死锁
     

八.Thread API说明

 Thread (Java Platform SE 7 ) (oracle.com)

九.CPU线程调度、Priority线程优先级、优先级常量、剩余小问题 

CPU线程调度

  • 每一个线程的优先使用权都是系统随机分配的,人人平等,谁先分配到谁先用
  • 可以设置优先级赋予某一个线程拥有至高适用权,最高为10,最低为1,默认为5,Java可以抢占CPUJava进阶八—多线程_第15张图片

线程1-10中,main()主线程的value = 5,创建 MaxPriorityThread 类和MinPriorityThread来查看线程执行顺序 

package com.thread;

public class MaxPriorityThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName());
        }
    }
}
package com.thread;

public class MinPriorityThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName());
        }
    }
}
package com.thread;

public class Mani {
    public static void main(String[] args) {

        Thread maxThread = new Thread(new MaxPriorityThread(),"max");
        Thread minThread = new Thread(new MinPriorityThread(),"min");

        // 使用setPriority()方法即可越权
        maxThread.setPriority(Thread.MAX_PRIORITY);
        minThread.setPriority(Thread.MIN_PRIORITY);
        
        maxThread.start();
        minThread.start();
    }
}

Java进阶八—多线程_第16张图片

 Java进阶八—多线程_第17张图片

我上面的结果是对的,但是有些系统的结果跟我不一样。原因是你的CPU调度程序非常快,程序还没有反应过来程序就结束了。

十. join线程插队 

上面的CPU调度,如果你把main加进去,你会发现无论main在哪里都会优先调度。 

package com.thread;

public class Mani {
    public static void main(String[] args) {

        Thread maxThread = new Thread(new MaxPriorityThread(),"max");
        Thread minThread = new Thread(new MinPriorityThread(),"min");

        // 使用setPriority()方法即可越权
        maxThread.setPriority(Thread.MAX_PRIORITY);
        minThread.setPriority(Thread.MIN_PRIORITY);

        maxThread.start();
        minThread.start();

        for (int i = 1; i < 8; i++) {
            System.out.println(Thread.currentThread().getName());
        }
    }
}

Java进阶八—多线程_第18张图片

但是我们可以使用join()方法,进行插队。

package com.thread;

public class Mani {
    public static void main(String[] args) throws InterruptedException{

        Thread maxThread = new Thread(new MaxPriorityThread(),"max");
        Thread minThread = new Thread(new MinPriorityThread(),"min");

        // 使用setPriority()方法即可越权
        maxThread.setPriority(Thread.MAX_PRIORITY);
        minThread.setPriority(Thread.MIN_PRIORITY);

        maxThread.start();
        minThread.start();

        for (int i = 1; i < 8; i++) {
            System.out.println(Thread.currentThread().getName());
            if (i == 3) {
                // 使用时,需要抛出异常InterruptedException
                maxThread.join();
            }
        }
    }
}

Java进阶八—多线程_第19张图片


 

十一.sleep线程休眠

 在上面抢鞋的案例中,说到过。

package com.thread;

public class Mani {
    public static void main(String[] args) throws InterruptedException {

        Thread maxThread = new Thread(new MaxPriorityThread(),"max");
        Thread minThread = new Thread(new MinPriorityThread(),"min");

        // 使用setPriority()方法即可越权
        maxThread.setPriority(Thread.MAX_PRIORITY);
        minThread.setPriority(Thread.MIN_PRIORITY);

            // 需要抛出异常或捕获异常
            maxThread.sleep(1000);
            maxThread.start();
            minThread.start();

        }
    }

Java进阶八—多线程_第20张图片

十二.yield线程让步 

package com.thread;

public class YieldTest implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName());
            // 每被5整除就让步
            if (i % 5 == 0) {
                System.out.println(Thread.currentThread().getName()+"------让步");
                // 让步使用方法:yield()
                Thread.yield();
            }
        }
    }
}
package com.thread;

public class Main {
    public static void main(String[] args) {

        Thread yieldOne = new Thread(new YieldTest(),"one");
        Thread yieldTwo = new Thread(new YieldTest(),"two");

        yieldOne.start();
        yieldTwo.start();
    }
}

Java进阶八—多线程_第21张图片

.yield()方法可以实现线程让步,让其它线程执行,one输出一次的时候给two让步了,有时程序运行的太快了,以至于还没打印出让步输出,two已经输出完毕了 

十三. 线程状态还是斗地主吧

 Java进阶八—多线程_第22张图片

Java中线程的状态分为6种–以斗地主为例

1.新建(NEW)-新建一局游戏

2.可运行(RUNNABLE)-初始状态是可运行的

3.阻塞(BLOCKED)-谁出牌谁获得一个锁,导致阻塞,出好牌则疏通阻塞

4.等待(WAITING)-不出牌的等待通知

5.计时等待(TIMED_WAITING)-出牌时,其他人计时等待超时或通知

6.终止(TERMINATED)-游戏结束
 

十四. 发现实际问题,抛出线程通信的含义 

线程优先级

  • Win11务管理器中,线程有6个优先级设置
  • 线程的调度目的就是通知另一个线程去执行Java进阶八—多线程_第23张图片

 线程通信

线程通信就如斗地主一名玩家出完牌,要通知其他两位玩家出了什么牌。 

十五.线程的通信:wait和notify

线程通信有两种状态等待(wait)和唤醒(notify),我们接下来以nike生产者与消费者为例子;

产品:nike

生产者:Producer

消费者:Customer 

package com.thread;

public class Nike {
    // 判断nike是否有库存
    public boolean isStatus = false;
}
package com.thread;

public class Producer extends Thread{


    // 设置synchronized锁类型为Nice
    private Nike nike;

    public Producer(Nike nike) {
        this.nike = nike;
    }

    @Override
    public void run() {
       while (true) {
           // synchronized锁为nike,以产品nike同步
           synchronized (nike) {
               // nike没有库存了,打电话给生产者,消费者等待。
               if (nike.isStatus == false) {
                   try {
                       // 消费者等待使用:wait()方法
                       nike.wait();
                   } catch (InterruptedException e) {
                       throw new RuntimeException(e);
                   }
               }
               // nike买完了
               nike.isStatus = false;

               // 唤醒其他线程,唤醒生产者
               System.out.println(Thread.currentThread().getName()+"赶紧生产");

               // 唤醒生产者使用:notify()方法
               nike.notify();
           }
       }
    }
}
package com.thread;

public class Customer extends Thread{

    private Nike nike;

    // 设置synchronized锁类型为Nice
    public Customer(Nike nike) {
        this.nike = nike;
    }

    @Override
    public void run() {
        while (true) {
            // synchronized锁为nike,以产品nike同步
            synchronized (nike) {
                // 库存充足,生产者等待
                if (nike.isStatus == true) {
                    try {
                        // 生产者等待使用:wait()方法
                        nike.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                // nike生产好了
                nike.isStatus = true;

                // 唤醒其他线程,唤醒消费者
                System.out.println(Thread.currentThread().getName()+"赶紧购买");

                // 唤醒消费者使用:notify()方法
                nike.notify();
            }
        }
    }
}
package com.thread;

public class Main {
    public static void main(String[] args) {

     Nike nike = new Nike();
     new Producer(nike).start();
     new Customer(nike).start();

    }
}

Java进阶八—多线程_第24张图片

十六.notifyAll 

如果你有多个生产者或消费者要唤醒,那就使用notifyAll()方法 

               // nike买完了
               nike.isStatus = false;

               // 唤醒其他线程,唤醒生产者
               System.out.println(Thread.currentThread().getName()+"赶紧生产");

               // 唤醒全部的生产者使用:notifyAll()方法
               nike.notifyAll();
                // nike生产好了
                nike.isStatus = true;

                // 唤醒其他线程,唤醒消费者
                System.out.println(Thread.currentThread().getName()+"赶紧购买");

                // 唤醒全部的消费者使用:notifyAll()方法
                nike.notifyAll();

十七.提及Process进程

我们上面讲的都是线程,而进程可以看以下的文档简单了解

ProcessBuilder (Java Platform SE 7 ) (oracle.com)

你可能感兴趣的:(java,面试,开发语言)