并发编程基础

一、基础函数

1.wait

线程等待, 此时线程会被阻塞

  • wait()
    超时时间为0;

  • wait(long timeout)
    超时时间为timeout;

  • wait(long timeout, int nanos)
    超时时间为(timeout++);
    PS: 0 <= nanos <= 999999

2.notify

共享对象, 随机被唤醒

  • notify()

3.notifyAll

共享对象, 全部被唤醒

  • notifyAll()

4.join

等待线程执行终止

  • join()

5.sleep

让线程睡眠

  • sleep(long millis)

    millis >= 0

  • sleep(long millis, int nanos)

    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
        millis++;
    }
    

    PS: millis >= 0, 0 <= nanos <= 999999

6.yield

让出CPU的执行权, 然后处于就绪状态

  • 与sleep的区别
    • sleep: 调用线程会被阻塞挂起指定时间, 在这个期间线程调度器不会调用该线程
    • yield: 线程让出自己剩余的时间片, 并没有被阻塞挂起, 而是处于就绪状态, 线程调度器下一次调用的时候还是有可能调度到当前线程

7.interrupt

中断线程
1.实际没有被中断, 只是设置中断标志且仅仅是设置中断标志, 会继续执行下去
2.对于处于wait、sleep和join的线程, 调用interrupted会抛出InterruptedException异常而返回

  • interrupt()
    设置中断标志并返回true

  • isInterrupted()
    检查线程是否被中断(true:是, false:否), 不会清除中断状态

  • interrupted()
    1.静态方法
    2.检查线程是否被中断(true:是, false:否), 清除中断状态
    3.获取的是当前线程的中断状态, 并非调用此方法的实例对象的中断标志

    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
    

二、死锁

  • 形成条件

    • 互斥

    • 不可剥夺

    • 请求并持有

    • 环路等待

  • 示例

public static void main(String[] args) {
   Thread thread1 = new Thread(() -> {
        synchronized (RESOURCE1) {
            System.out.println(Thread.currentThread() + "get resource1");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + "waiting resource2");
            synchronized (RESOURCE2) {
                System.out.println(Thread.currentThread() + "get resource2");
            }
        }
    });

    Thread thread2 = new Thread(() -> {
        synchronized (RESOURCE2) {
            System.out.println(Thread.currentThread() + "get resource2");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + "waiting resource1");
            synchronized (RESOURCE1) {
                System.out.println(Thread.currentThread() + "get resource1");
            }
        }
    });

    thread1.start();
    thread2.start();
}
  • 如何避免

破坏形成条件中其一即可, 其中"请求并持有"以及"环路等待"可以被破坏

三、线程类型

1.守护线程

  • 即, daemon线程

  • 如何创建

    public static void main(String[] args) {
        Thread thread = new Thread(() -> { 
            
        });
    
        // 设置为守护线程
        thread.setDaemon(true);
        thread.start();
    }
    

2.用户线程

  • 即, user线程

  • 默认

PS: JVM进程终止相关
当最后一个非守护线程结束时, 会正常退出, 不管当前是否存在守护线程, 即只要有一个用户线程存在JVM就不会退出

四、ThreadLocal

将共享变量本地副本化

  • 线程实际操作的是自己本地内存变量

  • 避免了线程安全问题

  • 调用后应调用remove()函数
    如果线程一直不消亡, 则这些本地变量会一直存在, 可能导致内存溢出

  • 不支持继承性

    即子线程无法访问父线程的本地变量信息

    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        threadLocal.set("hello world");
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
            	// thread: null
                System.out.println("thread: " + threadLocal.get());
            }
        });
        thread.start();
    	// main: hello world
        System.out.println("main: " + threadLocal.get());
    }
    
    • InheritableThreadLocal——其子类, 可以解决上述问题
    private static ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
    public static void main(String[] args) {
        threadLocal.set("hello world");
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
            	// thread: hello world
                System.out.println("thread: " + threadLocal.get());
            }
        });
        thread.start();
        // main: hello world
        System.out.println("main: " + threadLocal.get());
    }
    

五、其他

1.synchronized

  • 共享变量内存可见性

  • 原子性

  • 独占锁

2.volatile

  • 共享变量内存可见性

  • 非阻塞算法

  • 非操作原子性

  • 避免指令重排序

共享变量内存可见性: 直接将值刷新到主内存, 即不缓存到本地

3.伪共享

1.多线程下访问同一缓存行的多个变量时则会出现伪共享.
2.这就会导致多线程情况下效率降低, 单线程效率反而会更高
3.什么是缓存行: 缓存与内存交互的数据单位

  • 如何避免:
    • java8之前填充变量所在缓存行
    • java8之后@sun.misc.Contended
      • 类——需要添加JVM参数: -XX:-RestrictContented
      • 填充宽度默认128, 可自定义参数:-XX:ContentedPaddingWidth

4.锁概念

  • 悲观锁

来源数据库, 对所有操作持保守状态
1.认为数据很容易就会被修改
2.所以在数据修改前就会对数据进行加锁, 在整个过程中数据都会处于被加锁状态

  • 乐观锁

来源数据库, 认为数据不会那么容易起冲突
1.在访问的时候不会对其进行加锁
2.只有真正对数据进行修改的时候, 才会对数据的冲突与否进行检查
3.通常做法添加version字段进行控制(有点类似CAS操作)
4.较悲观锁, 乐观锁不会产生任何死锁(在数据库中不会使用其提供的锁机制)

  • 公平锁

根据线程请求锁的早晚时间进行决定, 来获取锁
1.带来性能开销
2.无公平性需求的情况下, 尽量使用非公平锁

  • 非公平锁

运行时闯入, 先来不一定先得到

  • 独占锁

任何时候只有一个线程能获得锁(一种悲观锁)
例如: ReentrantLock

  • 共享锁

可同时被多个线程持有(一种乐观锁)
例如: ReadWriteLock, 允许一个资源被多个线程进行读操作

  • 可重复锁

当一个线程再次获取它自己已经获取的锁时不会被阻塞, 则说明该锁是可重入锁
例如: synchronized其内部锁就是可重入锁

  • 自旋锁

在当前线程获取锁时, 如发现已经被其他线程获取, 不立即进行阻塞且不放弃CPU的使用权, 而是进行多次尝试(默认10, -XX:PreBlockSpinsh参数可以设置该值), 尝试结束后如还未获取到则被阻塞挂起
1.使用CPU时间换取线程阻塞和调度的开销
2.可能CPU时间会白白浪费

六、参考资料

  • 《Java并发编程之美》
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值