zoukankan      html  css  js  c++  java
  • 多线程

    Java开启子线程的方式

    继承Thread类
    public class MyThread extends Thread{
    public void run() {
    super.run();
    System.out.println(Thread.currentThread().getName()+"Thread方法实现多线程");
    }

    MyThread thread = new MyThread();
    thread.start();
    实现Runable接口(避免单继承的局限性)
    public class MyThread implements Runnable{
    public void run() {

        System.out.println(Thread.currentThread().getName()+"Thread方法实现多线程");
    }
    

    }
    Thread thread = new Thread(new MyThread());
    thread.start();
    实现Callable
    public class MyCallable implements Callable {
    private int count = 20;
    @Override
    public String call() throws Exception {
    for (int i = count; i > 0; i--) {
    System.out.println(Thread.currentThread().getName()+"当前票数:" + i);
    }
    return "sale out";
    }
    Callable myCallable = new MyCallable(); // 创建MyCallable对象
    // FutureTask是RunableFuture的子类
    FutureTask ft = new FutureTask(myCallable); //使用FutureTask来包装MyCallable对象
    Thread thread = new Thread(ft);
    thread.start();
    String s = ft.get(); // 获取call的返回值
    注意:Thread才是真正的线程类,Runable和Callable是对任务task的封装
    Callable:
    同Runable接口,这个接口描述的任务有返回值

    public interface Callable {
    V call() throws Exception; // 类似Runable的run方法(run方法没有返回值)
    }
    Future:
    是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

    get():等待线程执行完毕,返回线程的结果
    cancel():停止线程
    isDown():判断线程是否结束
    isCancel():判断线程是否取消
    FutureTask:
    实现了RunableFuture接口(RunableFuture继承了Runable,Future接口),这个类既可以作为Runable任务被线程类Thread执行,又可以当作Future拿到Callable任务的返回值;

    线程的终止

    suspend():暂停
    resume():继续
    stop():终止
    上面三个方法都已经过时了,这些方法的调用线程不会释放持有的资源,容易引发死锁
    协作式关闭线程
    interrupt():调用这个方法会将线程的中断标志设置为true,表示告诉这个线程你该中断了,但是当前线程并一定会理会这个中断操作
    isInterrupted():用于判断当前的线程是否执行过interrupt(),即判断中断标志位是否为true;
    interrupted():这是一个static方法,也用来判断中断标志位,不过会多一步操作,将标志位设置为false;
    生命周期&线程方法

    start()&run():
    start():
    public synchronized void start() {
    if (this.threadStatus != 0) {
    throw new IllegalThreadStateException();
    } else {
    this.group.add(this);
    boolean started = false;

            try {
                this.start0();          //  调用native方法开启子线程
                started = true;
            } finally {
                try {
                    if (!started) {
                        this.group.threadStartFailed(this);
                    }
                } catch (Throwable var8) {
                }
            }
        }
    }
    

    private native void start0();
    run():
    private Runnable target;

    public void run() {
    if (this.target != null) {
    this.target.run(); // 调用Runable(任务)的run方法(线程的逻辑代码)
    }
    }
    run是线程类的逻辑方法,start是开启线程的一个native方法

    sleep():进入阻塞状态

    yield():让出当前线程的cpu调度,使cpu重新调度(可能还会调度到当前线程)

    join():插队,让插入的线程执行完再继续执行当前线程(可以迭代插入,B插入A,C可以插入B,D可以插入C)

    wait()/notify()/notifyAll() :下文重点讲解

    线程共享与协作

    synchronized关键字:
    加锁,确保多个线程在同一时刻只能由一个线程处于这个方法或代码块中;

    对象锁:用于对象实例方法或者对象实例上
    synchronized方法
    // 共享资源
    static int ticketNum = 0;

    private synchronized void getTicketSync() {
    ticketNum--;
    System.out.println(Thread.currentThread().getName() + " 余票: " + ticketNum);
    }
    这个方法加了synchronized关键字后,对ticketNum的--操作同一时刻只能由一个线程执行;

    synchronized代码块
    private void getTicketSync() {
    synchronized (this) {
    ticketNum--;
    System.out.println(Thread.currentThread().getName() + " 余票: " + ticketNum);
    }
    }
    类锁:用于static方法(锁:类的class对象)
    private static synchronized void getTicketSync() {
    ticketNum--;
    System.out.println(Thread.currentThread().getName() + " 余票: " + ticketNum);
    }
    等待通知机制:
    是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作;

    wait():调用该方法的进程进入阻塞状态,并且该进程释放持有的锁
    wait(long):经过一段时间后如果没有唤醒就超时返回
    notify():通知一个在该对象上等待的线程,执行wait()下面的代码,要求是该线程必须获得锁;如果没有获得锁就继续阻塞;
    notifyAll():通知这个对象上的所有线程
    标准范式:
    等待方:
    获取对象锁
    循环中判断是否满足条件,不满足调用wait方法(调用wait会释放锁),满足就执行具体业务代码
    等待方:
    获取对象锁
    修改判断条件,通知等待方(notify/notifyAll)
    创建一个快递类

    public class Express {

    private int km;
    
    public Express(int km) {
        this.km = km;
        
    }
    
    public synchronized void changeKm(){
        // 获取锁
        System.out.println("通知线程:----获取对象锁");
    
        // 改变条件,通知
        System.out.println("通知线程:----改变距离超过100,通知等待线程");
        this.km = 101;
        notifyAll();
    
        // 释放锁
        System.out.println("通知线程:----释放对象锁");
    }
    
    public synchronized void waitKm(){
        // 获取锁
        System.out.println("等待线程:----获取对象锁");
    
        // 循环,不满足条件调用wait方法
        while (this.km<100){
            try {
                System.out.println("等待线程:----此时距离小于100,当前线程调用wait等待,释放对象锁");
                wait();
                System.out.println("等待线程:----当前线程从wait返回并且获得锁");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 满足条件,执行具体业务逻辑
        System.out.println("等待线程:----距离大于100(等待方等待到通知信号后),执行具体的业务逻辑");
    
        // 释放锁
        System.out.println("等待线程:----释放对象锁");
    }
    

    }
    创建两个线程,wait线程,notify线程

    public class Main {

    static Express express = new Express(0);
    
    static class WaitThread extends Thread{
        @Override
        public void run() {
            express.waitKm();
        }
    }
    static class NotifyThread extends Thread{
        @Override
        public void run() {
            express.changeKm();
        }
    }
    
    public static void main(String[] args) {
        new WaitThread().start();
        new NotifyThread().start();
    }
    

    }
    先执行wait线程,在执行notiify线程,运行结果:

    首先等待线程先获得锁,循环判断当前的距离是否小于100,如果小于100就wait,释放锁,然后通知线程获取对象锁,改变距离超过100,notify通知,然后释放对象锁,等待线程收到notify通知并且获得对象锁从wait方法返回,执行具体的业务逻辑,最后释放锁;

    如果是先执行notify线程,再执行wait线程:
    public static void main(String[] args) {
    new NotifyThread().start();
    new WaitThread().start();
    }

    通知线程获得锁,改变距离超过100,释放锁;等待线程获得锁,判断直接满足条件距离大于100,不需要wait操作,直接执行业务代码;

    ThreadLocal:

    ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。

    ThreadLocal往往用来实现变量在线程之间的隔离

    创建一个ThreadLocal对象,指定存储的数据为integer,初始值为100

    static ThreadLocal threadLocal = new ThreadLocal<>(){
    @Override
    protected Integer initialValue() {
    return 100;
    }
    };
    创建一个add线程(加法)和sub线程(减法)

    static class ADDThread extends Thread{
    @Override
    public void run() {
    Integer integer = threadLocal.get();
    System.out.println(Thread.currentThread().getName()+"----初始值----"+integer);

            integer +=100;
            System.out.println(Thread.currentThread().getName()+"----线程 操作int+100 ----");
    
            threadLocal.set(integer);
            System.out.println(Thread.currentThread().getName()+"----调用set存放新的integer到threadlocal,保存修改");
    
            System.out.println(Thread.currentThread().getName()+"----当前线程的副本值"+threadLocal.get());
    
            new SubThread().start();
        }
    }
    
    static class SubThread extends Thread{
        @Override
        public void run() {
            Integer integer = threadLocal.get();
            System.out.println(Thread.currentThread().getName()+"----初始值----"+integer);
        }
    }
    

    启动addThread

    public static void main(String[] args) {
    new ADDThread().start();
    }

    我们在add线程中操作了这个int,将他加了100,在add线程中打印出来时200,启动sub线程打印threadlocal中存放的这个值发现还是100;

    ThreadLocal原理:
    ThreadLocal内部有一个内部类ThreadLocalMap;ThreadLocalMap的内部有一个Entry内部类,ThreadLocalMap内部类维护了一个Entry数组,Entry(类似Map.Entry)key是ThreadLocal,value是Object;每一个线程都有一个ThreadLocalMap的实例,这个ThreadLocalMap内部又有一个Entry数组,将threadLocal作为key获取每个线程中独立的副本,因为threadLocal可以有多个,所以Entry以数组的形式存放;

    static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    

    显式锁 Lock

    Lock接口:
    lock接口的方法
    lock(): 获取锁
    lockInterruptibly():可中断的获取锁
    unlock(): 释放锁
    tryLock(): 尝试获取锁
    newCondition(): 用于lock的通知等待机制,下面会详细介绍
    显示锁的范式
    先获取锁lock.lock();
    处理业务后,必须在finally中lock.unlock();
    必须在finally中释放锁,因为如果在业务代码中抛出异常,这个锁就永远无法释放了

    Lock lock = new ReentrantLock(); // Lock接口的实现类(可重入锁)

    public void lockTest(){
        lock.lock(); // 获取锁
        try {
            // 业务代码
            System.out.println("拿到锁后,执行相应的业务代码");
        }finally {
            lock.unlock(); // 释放锁,必须在finally中释放锁
        }
    }
    

    可重入锁:
    ReentrantLock是一个可重入锁,执行线程在调用lock.lock()获取了这个锁以后,如果这个方法是一个递归方法,会继续调用lock.lock(),这时可重入锁就可以再次被这个线程获得,即同一线程可以多次获得这个锁,在synchronized内部jdk也加入了可重入机制

    公平/非公平锁:
    公平锁:等待时间越长的线程先获得锁
    非公平锁: 后创建的线程先获得锁(等待时间短的先获得锁)
    public ReentrantLock(boolean fair) {
    this.sync = (ReentrantLock.Sync)(fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync());
    }
    ReentrantLock提供了一个构造方法,创建的锁是否公平;

    非公平锁为什么比公平锁效率高?
    因为线程在切换的时候会设计到操作系统的上下文切换,这是一个消耗资源的操作,现在有AB两个线程,A获取了锁,B正在等待(处于阻塞状态),如果这时A释放了锁,创建了一个新线程C,根据非公平锁的概念,这个锁应该给C线程,C线程省去了上下文切换的步骤,所以非公平锁的效率比公平锁高;

    读写锁:
    ReadWriteLock接口
    当读写锁被读线程持有时,其他的读线程可以共享这个资源,写线程不可以;当这个锁被写线程持有时,其他所有的读线程,写线程都不可以共享;

    读写锁的使用
    ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); // ReadWriteLock的实现类,可重入读写锁
    Lock readLock = readWriteLock.readLock();
    Lock writeLock = readWriteLock.writeLock();

    public void read(){
        readLock.lock();
        try {
            // 业务代码
        }finally {
            readLock.unlock();
        }
    }
    
    public void write(){
        writeLock.lock();
        try {
            // 业务代码
        }finally {
            writeLock.unlock();
        }
    }
    

    Condition实现显示锁Lock的等待通知机制:
    将上面等待通知的例子做如下改写,其他代码都不变

    public class Express {

    private int km;
    
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    
    public Express(int km) {
        this.km = km;
    
    }
    
    public  void changeKm(){
        // 获取锁
        System.out.println("通知线程:----获取对象锁");
        lock.lock();
    
        // 改变条件,通知
        try {
            System.out.println("通知线程:----改变距离超过100,通知等待线程");
            this.km = 101;
            condition.signalAll();
        }finally {
            // 释放锁
            System.out.println("通知线程:----释放对象锁");
            lock.unlock();
        }
    
    }
    
    public  void waitKm(){
        // 获取锁
        lock.lock();
        System.out.println("等待线程:----获取对象锁");
    
        // 循环,不满足条件调用wait方法
        try {
            while (this.km<100){
                try {
                    System.out.println("等待线程:----此时距离小于100,当前线程调用wait等待,释放对象锁");
                    condition.await();
                    System.out.println("等待线程:----当前线程从wait返回并且获得锁");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 满足条件,执行具体业务逻辑
            System.out.println("等待线程:----距离大于100(等待方等待到通知信号后),执行具体的业务逻辑");
        }finally {
            // 释放锁
            System.out.println("等待线程:----释放对象锁");
            lock.unlock();
        }
    
    
    }
    

    }
    先执行等待线程

    先执行通知线程
    效果和上面的是一样的,分析参照上面的分析;

    转:https://www.jianshu.com/p/1d7ca61d7ccf

  • 相关阅读:
    HttpClient-----待补充
    JDK8的新特性
    关于日期转换的知识点(SimpleDateFormat)
    mybatis中的增删改查操作
    mybatis的快速入门
    018 HDFS中,namenode与datanode的交互
    Unit的各种断言
    分组数据
    Javassist学习总结
    hibernate Validator 6.X 的学习,bean的约束(字段,get方法上的验证)
  • 原文地址:https://www.cnblogs.com/AronJudge/p/14600251.html
Copyright © 2011-2022 走看看