Java 多线程学习笔记

参考

线程创建的方式

线程操作方法

线程停止

线程休眠

  • 使用 Thread.sleep(毫秒数) 使线程休眠,**不会释放锁! **

线程礼让

  • 使用 yield() 使当前线程暂停(非堵塞),线程转为就绪状态,但是礼让不一定成功。

线程强制执行

  • 使用 join() 使指定线程执行,其他线程堵塞;也可以通过 join(毫秒) 让指定线程执行一定时间后其他线程编程非堵塞状态。

线程状态

  • 通过 getState()获取线程的状态 java线程中的6种状态及切换
    1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
    2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
      线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
    3. 阻塞(BLOCKED):表示线程阻塞于锁。
    4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
    5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
    6. 终止(TERMINATED):表示该线程已经执行完毕。

线程优先级

  • 通过 setPriority() 在 start() 前设置指定线程的优先级,不一定先执行。优先级范围为1-10,越大优先级越高。什么是java线程优先级

守护线程

  • 通过 setDaemon(boolean) 来设置是否为守护线程,守护线程是守护别的线程。当用户线程结束后,jvm不会等待守护线程执行结束。

线程同步机制

  1. 线程同步

    通过 synchronized 关键子标声明到 方法/静态方法/代码块 的方式实现线程同步,每个对象/类都有一把锁,需要获得这个锁才可以执行,如果没有获取到就会堵塞等待锁被释放后获得锁。

    线程同步是高开销

    • synchronized 标注到方法:会锁住这个对象,每次在调用本方法的时候都会检测有没有其他线程调用本对象的本方法,如果没有可以直接调用,如果有就会堵塞(其他非synchronized方法还是可以访问的)。
    • synchronized 标注到静态:会锁住这个类,每次在调用本方法的时候都会检测有没有其他线程调用本类的本静态方法,如果没有可以直接调用,如果有就会堵塞(其他非synchronized方法还是可以访问的)。
    • synchronized(要锁住的共享对象){} 代码块:传入的那个对象会被锁住
  2. volatile关键字可以保证共享变量的可见性和有序性,但并不能保证原子性,也就是线程不安全的。

  3. 示例代码

package thread;

import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @Author 夏秋初
 * @Date 2022/2/26 09:37
 */
public class TestThread3 {
    public static void main(String[] args) {
        User user = new User();
        new Thread(()->{
            user.loop();
        }, "setAge2").start();
        new Thread(()->{
            user.loop();
        }, "setAge1").start();
    }
}
class User{
    public synchronized void loop(){
        while (true){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName());
        }
    }
}

JUC 线程工具包

  • 一般指 java.util.concurrent 处理线程的工具包,简称JUC。

死锁

  • 多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。Java 实例 - 死锁及解决方法
  • java 死锁产生的四个必要条件:
    1. 互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
    2. 不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
    3. 请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
    4. 循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
  • 当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。

Lock锁

  • 在java中可以使用 synchronized 来实现多线程下对象的同步访问,为了获得更加灵活使用场景、高效的性能,java还提供了Lock接口及其实现类ReentrantLock和读写锁 ReentrantReadWriteLock Java中的Lock锁 java 锁 Lock接口详解

synchronized是java中的一个关键字,也就是说是Java语言内置的特性。那么为什么会出现Lock呢?

  1. Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
  2. Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
  • synchronized 的局限性 与 Lock 的优点 

如果一个代码块被synchronized关键字修饰,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待直至占有锁的线程释放锁。事实上,占有锁的线程释放锁一般会是以下三种情况之一:

  1. 占有锁的线程执行完了该代码块,然后释放对锁的占有;
  2. 占有锁线程执行发生异常,此时JVM会让线程自动释放锁;
  3. 占有锁线程进入 WAITING 状态从而释放锁,例如在该线程中调用wait()方法等。

消费者生产者问题

  • 可以通过管程法,信号灯法去处理

线程池

  • 线程池可以节省线程创建的时间,线程用完之后不会释放而是放回线程池,当有需要的时候直接从线程池中取出就可以使用。通过配置参数可以指定线程池的大小,初始大小与最大线程数量以及关闭时间等等。
上一篇:沙箱安全机制


下一篇:Markdown学习笔记