韩顺平循序渐进学Java零基础 第17章 多线程编程

Posted Spring-_-Bear

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了韩顺平循序渐进学Java零基础 第17章 多线程编程相关的知识,希望对你有一定的参考价值。

第17章 多线程编程

580. 程序进程线程

  • 进程是程序的一次执行过程,或是正在进行的一个程序。是动态过程,有其自身的产生、存在和消亡过程
  • 线程是由进程创建的,是进程的一个实体;一个进程可以拥有多个线程

581. 并发并行

  • 并发:同一个时刻,多个任务交替执行,造成一种 “貌似同时” 的错觉,简单地说,单核 CPU 实现的多任务就是并发
  • 并行:同一个时刻,多个任务同时执行,多核 CPU 实现的多任务就是并行
// 获取系统可用处理器个数
Runtime runTime = Runtime.getRuntime();
System.out.println(runTime.availableProcessors());

582. 继承Thread类创建线程

  • 创建线程的两种方式:继承 Thread 类并重写 run() 方法;实现 Runnable 接口,重写 run() 方法
/**
* @author Spring-_-Bear
* @version 2021-11-21 19:53
*/
public class ThreadEx extends Thread 
   public static void main(String[] args) 
       new Dog().start();
       for (int i = 1; i <= 10; i++) 
           try 
               System.out.println(Thread.currentThread().getName() + i);
               sleep(1000);
            catch (InterruptedException e) 
               e.printStackTrace();
           
       
   


class Dog extends Thread 

   @Override
   public void run() 
       while (true) 
           try 
               System.out.println(Thread.currentThread().getName() + ":汪汪汪~~~");
               sleep(1000);
            catch (InterruptedException e) 
               e.printStackTrace();
           
       
   

  • 当启动程序时理解为开启了一个进程,进程开启了 main 线程,在 main 线程中可以开启其它线程,只有当所有线程都消亡时,进程才结束

583. 多线程机制

584. 为什么是start

  • new Dog().run() 与 new Dog().start() 的区别:前者只是单纯地调用 run() 方法,而后者启用了线程,在 start() 方法中实际调用了 start0() 方法,这是个 native 修饰的方法,真正实现了启动线程
  • start() 方法调用了 start0() 方法后,该线程并不一定会马上执行,只是将线程变成了可运行状态,具体什么时候执行,取决于 CPU

585. Runnable创建线程

  • Java 是单继承机制,若某个类已经继承了某个父类,这时只能通过实现 Runnable 接口实现线程
  • 实现 Runnable 接口的类不能直接调用 start() 方法,可以将对象作为 Thread(Runnable) 的参数,从而调用 start() 方法,也即使用了静态代理设计模式
/**
* @author Spring-_-Bear
* @version 2021-11-21 20:50
*/
public class Thread02 
   public static void main(String[] args) 
       // 静态代理设计模式
       new Thread(new Cat()).start();
   


class Cat implements Runnable 
   @Override
   public void run() 
       while (true) 
           System.out.println("喵喵喵~~~");
           try 
               Thread.sleep(1000);
            catch (InterruptedException e) 
               e.printStackTrace();
           
       
   


586. 多个子线程案例

  • 实现 Runnable 接口方式更加适合多个线程共享某个资源的情况,并且避免了单继承的显示
Cat cat = new Cat();
Thread thread1 = new Thread(cat);
Thread thread2 = new Thread(cat);

587. 多线程售票问题

/**
* @author Spring-_-Bear
* @version 2021-11-21 21:48
*/
public class Ticket 
   public static void main(String[] args) 
       SellTicket sellTicket = new SellTicket();
       Thread thread = new Thread(sellTicket);
       Thread thread1 = new Thread(sellTicket);
       Thread thread2 = new Thread(sellTicket);
       thread.start();
       thread1.start();
       thread2.start();
   


class SellTicket implements Runnable
   private int ticketNum = 100;

   @Override
   public void run() 
       while (true) 
           if (ticketNum <= 0) 
               System.out.println("售票结束···");
               break;
           
           try 
               Thread.sleep(100);
            catch (InterruptedException e) 
               e.printStackTrace();
           
           System.out.println("窗口 " + Thread.currentThread().getName() + "售出一张票,余票:" + (--ticketNum));
       
   

588. 通知线程退出

  • 通过使用变量来控制 run 方法退出的方式停止线程,即通知方式

589. 线程中断

方法名功能
setName(String)设置线程名
getName()获取线程名
start()启动线程
run()调用线程对象的 run() 方法
setPriority(int)设置线程优先级
getPriority()获得线程优先级
sleep(long)休眠线程
interrupt()中断线程,线程并未消亡
  • 线程优先级
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;

/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;

/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;

590. 线程插队

方法名功能
yield()线程礼让,让出 CPU 资源,但礼让时间不确定,是否礼让成功不确定,取决于 os
join()线程插队,一旦插队成功,则必须执行完插入线程的所有任务

591. 线程插队练习

/**
* @author Spring-_-Bear
* @version 2021-11-21 22:49
*/
public class JoinEx 
   public static void main(String[] args) throws InterruptedException 
       Thread temp = new Thread(new Temp());
       temp.start();

       for (int i = 1; i <= 20; i++) 
           System.out.println(Thread.currentThread().getName() + ":" + i);
           Thread.sleep(1000);
           if (i == 5) 
               temp.join();
               // main 线程礼让,不一定礼让成功
               // Thread.yield();
           
       
   


class Temp implements Runnable
   @Override
   public void run() 
       for (int i = 1; i <= 20; i++) 
           System.out.println(Thread.currentThread().getName() + ":" + i);
           try 
               Thread.sleep(1000);
            catch (InterruptedException e) 
               e.printStackTrace();
           
       
   

592. 守护线程

  • 用户线程:也叫工作线程,当线程的任务执行完成或以通知方式结束
  • 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束。经典守护线程:垃圾回收机制
/**
* @author Spring-_-Bear
* @version 2021-11-22 19:10
*/
public class MyDaemon 
   public static void main(String[] args) throws InterruptedException 
       Thread thread = new Thread(new Daemon());
       // 设置为 main 线程的守护线程
       thread.setDaemon(true);
       thread.start();
       Thread.sleep(5000);
       System.out.println("妈妈回家了,小明结束写作业!");
   


class Daemon implements  Runnable
   @Override
   public void run() 
       while (true) 
           System.out.println("小明写作业中···");
           try 
               Thread.sleep(1000);
            catch (InterruptedException e) 
               e.printStackTrace();
           
       
   

593. 线程7大状态

状态说明
NEW尚未启动的线程处于此状态。 该状态线程对象被创建,但还未调用start方法
RUNNABLE可运行线程的线程状态,细分为 Running 和 Ready 两个状态。处于可运行状态的线程可能正在 Java 虚拟机中执行,也可能正在等待来自操作系统的其他资源
BLOCKED被阻塞等待监视器锁的线程处于此状态。处于该状态的线程正在等待获取一个监视器锁进入同步代码或方法;也可能是在调用了Object.wait后等待一个监视器锁重新进入同步代码或方法
WAITING线程处于等待状态;处于该状态的线程可能是因为调用了Object.wait()、Thread.join()、LockSupport.park() 中的某一个方法;处于该状态的线程正在等待其他线程完成一些特定的操作
TIMED_WAITING正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。线程处于定时等待状态,这个等待是有具有指定时间的;处于这个状态的线程可能是调用了具有指定时间的 Thread.sleep(long)、Object.wait(long)、Thread.join(long)、LockSupport.parkNanos()、LockSupport.parkUntil()
TERMINATED已退出的线程处于此状态

594. 线程同步机制

  • 线程同步:当有一个线程在对内存进行操作时,其它线程都不可以对这个内存进行操作,直到该线程完成操作,其它线程才能对该内存进行操作,也即同一时刻只允许一个线程操作

  • 得到对象的锁,才可以操作对象的代码;也可以将 synchronized 加在方法声明中,表示整个方法为同步方法

595. 互斥锁

  • Java 语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应一个可称为 “互斥锁” 的标记,这个标记用来保证在任意时刻,有且仅有一个线程可以访问该对象。用关键字 synchronized 来与对象的互斥锁联系
  • 同步的局限性:程序的执行效率降低
  • 非静态同步方法的锁可以是它本身 this,也可以是其它对象(要求是同一个对象)
  • 静态同步方法的锁是当前类本身(ClassName.class)
/**
* @author Spring-_-Bear
* @version 2021-11-21 21:48
*/
public class Ticket 
   public static void main(String[] args) 
       SellTicket sellTicket = new SellTicket();
       Thread thread1 = new Thread(sellTicket);
       Thread thread2 = new Thread(sellTicket);
       Thread thread3 = new Thread(sellTicket);
       thread1.start();
       thread2.start();
       thread3.start();
   


class SellTicket implements Runnable 
   private int ticketNum = 100;
   Object object = new Object();
   
   @Override
   public void run() 
       sell();
   

   public void sell() 
       while (true) 
           synchronized (this) 
               if (ticketNum <= 0) 
                   System.out.println("售票结束···");
                   break;
               
               System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票,余票:" + (--ticketNum));
               try 
                   Thread.sleep(100);
                catch (InterruptedException e) 
                   e.printStackTrace();
               
           
       
   


/*
* 由于主方法中只创建了一个 SellTicket 对象,所以线程 0、1、2 操作的都是同一个对象
* 因而将锁加在对象本身等价于将锁将在此对象的其它对象(字段),也即下面的两段代码等价
* synchronized (this) <=> synchronized (object)
*/
  • 静态方法中的互斥锁加在类上
class SellTicket implements Runnable 
   public static void getTicket() 
       synchronized (SellTicket.class) 
           System.out.println("售出一张票");
       
   

596. 线程死锁

/**
* @author Spring-_-Bear
* @version 2021-11-22 21:02
*/
public class DeadLockDemo 
   public static void main(String[] args) 
       new DeadLock(true).start();
       new DeadLock(false).start();
   


class DeadLock extends Thread 
   static Object object1 = new Object();
   static Object object2 = new Object();
   boolean flag;

   public DeadLock(boolean flag) 
       this.flag = flag;
   

   @Override
   public void run() 
       if (flag) 
           synchronized (object1) 
               System.out.println(Thread.currentThread().getName() + " 获得对象 1 的锁,尝试获取对象 2 的锁···");
               synchronized (object2) 
                   System.out.println(Thread.currentThread().getName() + " 成功获得对象 2 的锁!");
               
           
        else 
           synchronized (object2) 
               System.out.println(Thread.currentThread().getName() + " 成功获得对象 2 的锁,尝试获取对象 1 的锁···");
               synchronized (object1) 
                   System.out.println(Thread.currentThread().getName() + " 成功获得对象 1 的锁!");
               
           
       

   

597. 释放锁

  • 当前线程的同步方法、同步代码块执行结束会释放锁
  • 当前线程在同步方法、同步代码块中遇到 break、return 会释放锁
  • 当前线程在同步方法、同步代码块中出现了未处理的 Error 或 Exception 导致结束会释放锁
  • 当前线程在同步方法、同步代码块中执行了线程对象的 wait() 方法,当前线程暂停,并释放锁
  • 当前线程在执行同步代码块、同步方法的过程中调用了 Thread.sleep()、Thread.yield() 方法暂停当前线程的执行,不会释放锁
  • 线程执行同步代码块时,其它线程调用了该线程的 suspend() 方法将该方法挂起,该线程不会释放锁。suspend()、resume() 方法均已过时,不再推荐使用

598. 线程家庭作业1

599. 线程家庭作业2

以上是关于韩顺平循序渐进学Java零基础 第17章 多线程编程的主要内容,如果未能解决你的问题,请参考以下文章

韩顺平循序渐进学Java零基础 第27章 正则表达式

韩顺平循序渐进学Java零基础 第14章 集合

韩顺平循序渐进学Java零基础 第01-08章

韩顺平循序渐进学Java零基础 第01-08章

韩顺平循序渐进学Java零基础 第13章 常用类

韩顺平循序渐进学Java零基础 第23章 反射