Java多线程

Java语言内置多线程支持:

  • 一个Java程序实际上是一个JVM进程
  • JVM用一个主线程来执行main()方法
  • 在main()方法中又可以启动多个线程
多线程特点:
  • 多线程需要读写共享数据
  • 多线程经常需要同步
  • 多线程编程的复杂度高,调试更困难
Java多线程编程特点:
  • 多线程模型是Java程序最基本的并发模型
  • 网络、数据库、Web等都依赖多线程模型

Java多线程
  • 直接调用run()方法是无效的,需要调用start()方法启动新线程
线程的优先级:
  • 可以对线程设定优先级
    • Thread.setPriority(int n) // 1~10,默认值5
    • 优先级高的线程被操作系统调度的优先级高
    • 不能通过设置优先级来确保功能的执行顺序  不建议设置优先级
  • Java用Thread对象表示一个线程,通过调用start()启动一个线程
  • 一个线程对象只能调用一次start()
  • 线程的执行代码是run()方法
  • 线程调度由操作系统决定,程序本身无法决定
  • Thread.sleep()可以把当前线程暂停一段时间
线程的状态:
  • New(新建)
  • Runnable(运行中)
  • Blocked(被阻塞)
  • Waiting(等待)
  • Timed Waiting(计时等待)
  • Terminated(已终止)
线程终止的原因:
    • run()方法执行到return语句返回(线程正常终止)
    • 因为未捕获的异常导致线程终止(线程意外终止)
    • 对某个线程的Thread实例调用stop()方法强制终止(不推荐)
  • 可以通过对另一个线程对象调用join()方法可以等待其执行结束
  • 可以指定等待时间,超过等待时间线程仍然没有结束就不再等待
  • 对已经运行结束的线程调用join()方法会立刻返回

  • 调用interrupt()中断线程
  • 线程间共享变量需要使用volatile关键字标记,确保线程能读取到更新后的变量值
  • volatile关键字的目的是告诉虚拟机:
    • 每次访问变量时,总是获取内存的最新值
    • 每次修改变量后,立刻回写到主内存
  • volatile关键字解决的是可见性问题:
    • 当一个线程修改了某个共享变量的值,其他线程能够立刻看到修改后的值
  • 通过检测isInterrupted()标志获取当前线程是否中断
  • 如果线程处于等待状态,该线程会捕获InterruptedException
  • isInterrupted()为true或者捕获了InterruptedException都应该立刻结束
  • 通过标志位判断需要使用volatile关键字
  • volatile关键字解决了共享变量在线程间的可见性问题

守护线程特点:
  • 守护线程不能持有资源(如打开文件等)
  • 创建守护线程
    • setDaemon(true)
  • 守护线程是为其他线程服务的线程
  • 所有非守护线程都执行完毕后,虚拟机退出

线程同步:
  • 对共享变量进行写入时,必须保证是原子操作
  • 原子操作是指不能被中断的一个或一系列操作
Synchronized保证了代码块在任意时刻最多只有一个线程能执行
Synchronized问题
  • 无法并发执行,性能下降
如何使用?
    • 找出修改共享变量的线程代码块
    • 选择一个实例作为锁
    • 使用synchronized(lockObject){...}
  • 同步的本质就是给制定对象加锁
  • 加锁对象必须是同一个实例
  • 对JVM定义的单个原子操作不需要同步

数据封装:把同步逻辑封装到持有数据的实例中
synchronized两种写法

 
//同步方法
public synchronized void add(int n) {
    count += n;
    total += n;
}
//同步代码块
public void add(int n) {
    synchronized(this) {
        count += n;
        total += n;
    }
}
//静态方法锁住的是Class实例
public class A {
    static count;
    static synchronized void add(int n) {
        count += n;
    }
}
public class A {
    static count;
    static void add(int n) {
        synchronized(A.class) {
            count +=n;
        }
    }
}

如果一个类被设计为允许多线程正确访问:
  • 在这个类就是“线程安全”的(thread-safe) 如StringBuffer (方法全部用synchronized修饰)


死锁:
  • Java的线程可以获取多个不同对象的锁(嵌套)
  • 不同线程获取多个不同对象的锁可能导致死锁
Java多线程_第1张图片
死锁形成的条件:
  • 两个线程各自持有不同的锁
  • 两个线程各自试图获取对方已持有的锁
  • 双方无线等待下去:导致死锁
死锁发生后
  • 没有任何机制能解除死锁
  • 只能强制结束JVM进程
如何避免死锁:
  • 多线程获取锁的顺序要一致
synchronized解决了多线程竞争的问题,但没有解决多线程协调的问题
多线程协调运行:当条件不满足时,线程进入等待状态
线程协调机制:wait/notify
  • wait释放锁
Java多线程_第2张图片
wait/notify用于多线程协调运行:
  • 在synchronized内部可以调用wait()使线程进入等待状态
  • 必须在已获得的锁对象上调用wait()方法
  • 在synchronized内部可以调用notify/notifyAll()唤醒其他等待线程
  • 必须在已获得的锁对象上调用notify/notifyAll()方法

Thread对象代表一个线程:
  • 调用Thread.currentThread()获取当前线程

JDK提供了ThreadLocal,在一个线程中传递同一个对象
Java多线程_第3张图片
ThreadLocal一定要在finally中清除
  • ThreadLocal表示线程的“局部变量”,它确保每个线程的ThreadLocal变量都是各自独立的
  • ThreadLocal适合在一个线程的处理流程中保持上下文(避免了同一参数在所有方法中传递)
  • 使用ThreadLocal要用try .. finally结构

















你可能感兴趣的:(java基础)