【狂神说Java】多线程详解笔记

【狂神说Java】多线程详解笔记

线程状态

java类的方式

  • 外部类: 在主函数所在的类的外部定义的类, 若是在同一文件, 直接new; 若是不同文件, 导包再new
  • 静态内部类: 在主函数所在的类的内部且在主函数外部定义的类, 直接new

因为main是static, 所以他也要是static

  • 局部类: 在主函数内定义的类, 直接new
  • 匿名内部类: Interface o = new Interface(){类的定义}, 需先定义interface
  • lambda:

    Runnable as = () -> {
            System.out.println("as");
        };
        as.run();
new Thread(()->System.out.println("as")).start();

线程休眠sleep

Thread.sleep(1000);单位为毫秒

线程礼让yield

  • 调用方式: 在线程内Thread.yield();
  • 将线程由运行态转为就绪态, 此时重新决定哪个线程获取cpu, 可能还是刚才的进程

    package com.zq;
    
    public class yield {
        public static void main(String[] args) {
            myYield y = new myYield();
            new Thread(y, "a").start();
            new Thread(y, "b").start();
        }
    }
    
    class myYield implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "->start");
            Thread.yield();
            System.out.println(Thread.currentThread().getName() + "->end");
        }
    }

join

  • 阻塞其他线程, 强制该线程运行, 类似插队
  • 调用: thread.join();注意是线程对象, 不是类

    package com.zq;
    
    public class join implements Runnable {
    
        public static void main(String[] args) throws InterruptedException {
            join j = new join();
            Thread thread = new Thread(j);
            thread.start();
    
            for (int i = 0; i < 10; i++) {
                if (i == 5) {
                    thread.join();
                }
                System.out.println("main->" + i);
            }
    
    
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("vip->" + i);
            }
    
        }
    }

线程优先级

默认为5, main线程优先级为5

守护线程

  • 用户线程执行完后,虚拟机即关闭,尽管守护线程没有执行完毕
  • 使用: thread.setDaemon("true");默认为false

线程同步

  • 线程同步形成条件: ==队列+锁==
  • sleep不会释放锁

synchronized

  • 同步方法默认锁的对象是this
  • 可以用同步块完全替代同步方法

死锁 活锁 饥饿

死锁: 两个或更多线程阻塞着等待其它处于死锁状态的线程所持有的锁

  • 形象的例子: 两个小朋友分别拿着对方喜欢的玩具, 而且谁都不愿先把手中的玩具先给对方
  • 死锁通常发生在多个线程同时但以不同的顺序请求同一组锁的时候
  • 死锁会让你的程序挂起无法完成任务
  • 解决方法: 只能通过中止并重启的方式来让程序重新执行
  • 在程序中, 双方不会协商, 只会一直僵持, 程序一直阻塞

死锁的四个必要条件:

1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

预防死锁--破坏死锁的四个必要条件

破坏互斥条件:使资源同时访问而非互斥使用,就没有进程会阻塞在资源上,从而不发生死锁。

破坏请求和保持条件:采用静态分配的方式,静态分配的方式是指进程必须在执行之前就申请需要的全部资源,且直至所要的资源全部得到满足后才开始执行,只要有一个资源得不到分配,也不给这个进程分配其他的资源。

破坏不剥夺条件:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源,但是只适用于内存和处理器资源。

破坏循环等待条件:给系统的所有资源编号,规定进程请求所需资源的顺序必须按照资源的编号依次进行。

活锁: 活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败, 线程为了彼此间的响应而相互礼让,使得没有一个线程能够继续前进

  • 形象的例子: 两条车道上, 两人在同一车道相向行走, 发现对方阻碍自己的道路时, 两人都向另一条车道偏移, 结果还是会阻碍对方的道路(传说中的神默契), 这样一直僵持
  • 活锁有可能自行解开
  • 活锁可以认为是一种特殊的饥饿

饥饿: 是指一个可运行的进程尽管能继续执行,但被调度器无限期地忽视,而不能被调度执行的情况

  • 饥饿可以通过先来先服务资源分配策略来避免
  • 优先级高的线程抢占资源, 导致优先级低的线程一直得不到资源
  • 某个线程长期占用资源, 导致其他线程得不到资源

lock

private ReentrantLock lock=new ReentrantLock();
lock.lock();
try {
    //放并发操作代码
    System.out.println(Thread.currentThread().getName() + "-->抢到第" + ticketNum-- + "张票");
} finally {
    lock.unlock();
}
  • 性能比ssynchronized好
  • 一般放在try-finally中, 不然容易出问题

线程通信

线程池

package com.zq;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class pool {


    public static void main(String[] args) {
        MyThread mt = new MyThread();
        ExecutorService service = Executors.newFixedThreadPool(2);
        long startTime = System.currentTimeMillis();   //获取开始时间(毫秒)
        for (int i = 0; i < 50; i++) {
            service.execute(mt);
        }
        service.shutdown();
        long endTime = System.currentTimeMillis(); //获取结束时间(毫秒)
        System.out.println("程序运行时间: " + (endTime - startTime) + "ms");

    }
}

class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

如果线程池大小小于开启的线程数, 则等待之前的线程执行完毕释放, 再执行新线程

summary

package com.zq;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Summary {

    public static void main(String[] args) {

        new MyThread1().start();
        new Thread(new MyThread2()).start();
        FutureTask<Integer> ft = new FutureTask<Integer>(new MyThread3());
        new Thread(ft).start();
        try {
            Integer integer = ft.get();
            System.out.println(integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class MyThread1 extends Thread {
    @Override
    public void run() {
        System.out.println("1继承Thread");
    }
}

class MyThread2 implements Runnable {
    @Override
    public void run() {
        System.out.println("2实现Runnable");
    }
}

class MyThread3 implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println("3实现callable");
        return 520;
    }
}

一些扩展

  • java中的锁Lock就是基于AbstractQueuedSynchronizer来实现的
  • 在大多数情况下,我们写并发代码使用synchronized就足够了,而且使用synchronized也是首选
  • 但是lock更加灵活
  • lockInterruptibly方法可以响应中断,lock方法会阻塞线程直到获取到锁,而tryLock方法则会立刻返回,返回true代表获取锁成功,而返回false则说明获取不到锁
  • newCondition方法返回一个条件变量,一个条件变量也可以做线程间通信来同步线程。多个线程可以等待在同一个条件变量上,一些线程会在某些情况下通知等待在条件变量上的线程,而有些变量在某些情况下会加入到条件变量上的等待队列中去。
  • 独占锁就是只能有一个线程获取到锁,其他线程必须在这个锁释放了锁之后才能竞争而获得锁
  • 共享锁则可以允许多个线程获取到锁

ReentrantLock

  • 是lock的子类
  • 可重入性: 同一个线程可以多次获得锁,而不同线程依然不可多次获得锁
  • 划分:

    • 公平锁: 保证等待时间最长的线程将优先获得锁
    • 非公平锁: 并不会保证多个线程获得锁的顺序,并发性能表现更好,ReentrantLock默认使用非公平锁

CopyOnWriteArrayList

  • ArrayList的线程安全版本
  • CopyOnWriteArrayList是在有写操作的时候会copy一份数据,然后写完再设置成新的数据。CopyOnWriteArrayList适用于读多写少的并发场景
  • CopyOnWriteArraySet是线程安全版本的Set实现,它的内部通过一个CopyOnWriteArrayList来代理读写等操作,使得CopyOnWriteArraySet表现出了和CopyOnWriteArrayList一致的并发行为
  • 使用了ReentrantLock来支持并发操作

参考

Java CopyOnWriteArrayList详解 - 简书 (jianshu.com)

Java可重入锁详解 - 简书 (jianshu.com)

Java可重入锁详解 - 简书 (jianshu.com)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值