第17章 多线程编程

Posted Spring-_-Bear

tags:

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

韩顺平_循序渐进学Java零基础_第17章 多线程编程(P580 - P599)

第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 资源,当礼让时间不缺点,是否礼让成功不确定
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

以上是关于第17章 多线程编程的主要内容,如果未能解决你的问题,请参考以下文章

第 4 章 多线程

《Linux 应用编程》—第13章 Linux 多线程编程

学习笔记导航

学习笔记导航

学习笔记导航

第1章 并发编程的挑战