第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 已退出的线程处于此状态

Java 线程七大状态

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

猜你喜欢

转载自blog.csdn.net/weixin_51008866/article/details/121483069