【Java并发编程】-【多线程基础】

    技术2022-07-11  89

    一、多线程基础

    1.多线程概述

    进程: 正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。 线程: 是进程中的单个顺序控制流,是一条执行路径,一个进程如果只有一条执行路径,则称为单线程程序。一个进程如果有多条执行路径,则称为多线程程序。

    2.Java程序运行原理

    java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。

    3.多线程的实现

    -多线程的实现方案1:继承Thread类

    该类要重写run()方法 不是类中的所有代码都需要被线程执行的。为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。

    public class MyThread extends Thread { @Override public void run() { for (int x = 0; x < 200; x++) { System.out.println(x); } } }

    思考1

    run()和start()的区别?

    答:

    run():仅仅是封装被线程执行的代码,直接调用是普通方法。 start():首先启动了线程,然后再由jvm去调用该线程的run()方法。

    获取和设置线程名称

    public final String getName() 获取线程名称。 public final void setName(String name) 设置线程名称。 public static Thread currentThread() 获取main方法所在的线程名称,可以获取任意方法所在的线程名称。

    -多线程的实现方案2:实现Runnable接口

    public class MyRunnable implements Runnable { @Override public void run() { for (int x = 0; x < 100; x++) { // 由于实现接口的方式就不能直接使用Thread类的方法了,但是可以间接的使用 System.out.println(Thread.currentThread().getName() + ":" + x); } } }

    4.线程调度

    线程有两种调度模型: 分时调度模型 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片。 抢占式调度模型 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。 Java使用的是抢占式调度模型。 设置和获取线程优先级 public final int getPriority() 获取线程优先级。 public final void setPriority(int newPriority) 设置线程优先级。

    5.线程控制

    ①public static void sleep(long millis) 线程休眠。 ②public final void join() 线程加入,等待该线程终止。 ③public static void yield() 线程礼让,暂停当前正在执行的线程对象,并执行其他线程。 ④public final void setDaemon(boolean on) 后台线程,将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。 ⑤public final void stop() 让线程停止,过时了,但是还可以使用。 ⑥public void interrupt() 抛出一个InterruptedException,并执行其他线程。 线程生命周期图

    二、线程安全问题

    1.线程安全问题因素

    ①是否是多线程环境 ②是否有共享数据 ③是否有多条语句操作共享数据

    2.解决线程安全问题实现1:同步代码块

    格式:

    synchronized(对象){ 需要同步的代码; }

    同步代码块的锁对象是任意对象。 同步的代码是多条语句操作共享数据的代码的部分。

    3.同步的特点

    - 同步的前提 ①多个线程 ②多个线程使用的是同一个锁对象 - 同步的好处 同步的出现解决了多线程的安全问题。 - 同步的弊端 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

    4.解决线程安全问题实现2:同步方法

    同步方法就是把同步关键字加到方法上。 格式:

    private static synchronized void sellTicket() { if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票 "); }

    静态方法的锁对象是类的字节码文件对象。 如果锁对象是this,就可以考虑使用同步方法。否则能使用同步代码块的尽量使用同步代码块。

    5.JDK5中Lock锁的使用

    为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。 void lock() 获取锁。 void unlock() 释放锁。 ReentrantLock是Lock的实现类.。

    // 定义锁对象 private Lock lock = new ReentrantLock(); try { // 加锁 lock.lock(); //此处为同步代码 } finally { // 释放锁 lock.unlock(); }

    6.死锁问题

    同步弊端 效率低,如果出现了同步嵌套,就容易产生死锁问题。 死锁问题 是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象。

    public void run() { if (flag) { synchronized (MyLock.objA) { System.out.println("if objA"); synchronized (MyLock.objB) { System.out.println("if objB"); } } } else { synchronized (MyLock.objB) { System.out.println("else objB"); synchronized (MyLock.objA) { System.out.println("else objA"); } } } }

    三、线程间通信问题

    不同种类的线程间针对同一个资源的操作。 Java等待唤醒机制 Object类中提供了三个方法: wait():等待 notify():唤醒单个线程 notifyAll():唤醒所有线程 这些方法的调用必须通过锁对象调用。

    public class SetThread implements Runnable { private Student s; private int x = 0; public SetThread(Student s) { this.s = s; } @Override public void run() { while (true) { synchronized (s) { //判断有没有 if(s.flag){ try { s.wait(); //t1等着,释放锁 } catch (InterruptedException e) { e.printStackTrace(); } } if (x % 2 == 0) { s.name = "马云"; s.age = 50; } else { s.name = "刘织忋"; s.age = 24; } x++; //x=1 //修改标记 s.flag = true; //唤醒线程 s.notify(); //唤醒t2,唤醒并不表示你立马可以执行,必须还得抢CPU的执行权。 } //t1有,或者t2有 } } }
    public class GetThread implements Runnable { private Student s; public GetThread(Student s) { this.s = s; } @Override public void run() { while (true) { synchronized (s) { if(!s.flag){ try { s.wait(); //t2就等待了。立即释放锁。将来醒过来的时候,是从这里醒过来的时候 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(s.name + "---" + s.age); //修改标记 s.flag = false; //唤醒线程 s.notify(); //唤醒t1 } } } }

    线程的状态转换图及常见执行情况

    Processed: 0.011, SQL: 10