代码编织梦想

1 wait/notify机制

1.1 机制原理

拥有相同锁的线程才可以实现wait/notify机制。它们都是Object类中的方法。wai使线程暂停运行,而notify通知暂停的线程继续运行。

wait是使执行该方法的线程进行等待,在wait()所在的代码处暂停执行,并释放锁,直到接到通知或被中断为止。

notify()也要在同步方法或同步块中调用,在调用前线程必须获得锁。执行notify()方法后,当前线程不会马上释放该锁,且等待状态的线程也并不能马上获取该对象锁。

1.2 wait(long)

等待某一时间内是否有线程对锁进行唤醒,如果超过这一个时间则自动唤醒。能继续向下运行的前提是再次持有锁

public class WaitLong {

    private static final String lock = "lock";

    public static void main(String[] args) throws InterruptedException {
        Thread waitThread = new Thread(() -> {
            synchronized (lock) {
                System.out.println("waitThread begin");
                long begin = System.currentTimeMillis();
                try {

                    lock.wait(2000); // 等待两秒继续运行
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("waitThread end。耗时:" + (System.currentTimeMillis() - begin) / 1000 + "s");
            }
        });

        Thread thread = new Thread(() -> {
            synchronized (lock) {
                System.out.println("其他线程运行中");
                for (int i = 0; i < 100000000; i++) {
                    String s = new String("消化时间,不用sleep");
                    System.out.print(s.replace("消化时间,不用sleep",""));
                }
                System.out.println("其他线程运行结束 lock锁释放");
            }
        });

        waitThread.start();
        TimeUnit.SECONDS.sleep(1);
        thread.start();
    }

}

图 wait(long) 在超过设定时间后,并不一定能继续运行,还需重新获得锁

1.3 生产者/消费者

1.3.1 if 与 while

public class IfAndWhile {

    private static String lock = "lock";
    private static AtomicBoolean ready = new AtomicBoolean(false); //是否生产好
    private static AtomicLong count = new AtomicLong();

    private static class ProductThread extends Thread {
        @Override
        public void run() {
            while (true) {
                synchronized (lock) {
                    if (ready.get()) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    lock.notifyAll();
                    count.getAndIncrement();
                    ready.set(true);
                }
            }
        }
    }

    private static class ConsumerThread extends Thread {
        @Override
        public void run() {
            while (true) {
                synchronized (lock) {
                    if (!ready.get()) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    lock.notifyAll();
                    System.out.println("消费:" + count);
                    ready.set(false);
                }
            }
        }
    }

    public static void main(String[] args) {
        for(int i = 0; i < 10; i++) {
            new ProductThread().start();
        }

        new ConsumerThread().start();
    }

}

图 多生产者单消费者运行结果部分截图

消费数字非递增,出现生产多次消费一次的错误情况。 这是因为在判断是否生产或消费的时候,用的是if。

图 上段代码产生错误的原因

分析:代码在wait()处被唤醒后,会继续向下执行,并不会再判断if里面的条件。如果,将if判断改成while,则wait()处被唤醒后,会再判断一下while里面的条件,如果条件正确,才会跳出循环。

图 修改后的代码

1.4 管道流中传递字节/字符流

JDK提供了4个类来使线程间可以进行通信:PipedInputStream、PipedOutputStream、PipedReader和PipedWriter。

public class PipedCommunication {

    public static void main(String[] args) throws IOException, InterruptedException {
        PipedInputStream inputStream = new PipedInputStream();
        PipedOutputStream outputStream = new PipedOutputStream();
        inputStream.connect(outputStream);

        Thread writeThread = new Thread(() -> {
            String content = "hello Piped!";
            System.out.println("write:");
            try {
                outputStream.write(content.getBytes());
                outputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });

        Thread readThread = new Thread(() -> {
            byte bytes[] = new byte[20];
            System.out.println("read:");
            int readLength = 0;
            try {
                while ((readLength = inputStream.read(bytes)) != -1) {
                    System.out.print(new String(bytes,0,readLength));
                }
                inputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });

        writeThread.start();
        TimeUnit.SECONDS.sleep(2);
        readThread.start();

    }

}

图 管道流运行结果

2 join的使用

使所属的线程对象x正常执行run()方法中的任务,而使当前线程z进行无限期阻塞,等待线程x销毁后再继续执行线程z后面的代码。

图 Thread类join关键源码

Thread里的join方法是一个同步方法(锁是当前thread对象),当thread这个对象还存活的时候,join方法使当前这个线程进入wait状态。当线程执行完run方法后,一定会自动执行notifyAll()方法。

2.1 join(long)

直到获得锁后才会继续向下执行。

public class JoinLong {

    public static void main(String[] args) throws InterruptedException {
        Thread lockThread = new Thread(() -> {
            System.out.println("lockThread start");
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("lockThread end");
        });

        Thread thread = new Thread(() -> {
           synchronized (lockThread) {
               System.out.println("其他线程启动");
               try {
                   TimeUnit.SECONDS.sleep(15);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
               System.out.println("其他线程终止");
           }
        });

        lockThread.start();
        TimeUnit.SECONDS.sleep(1);
        thread.start();
        long begin = System.currentTimeMillis();
        lockThread.join(4000); //预期最多过4秒,就会继续执行
        System.out.println("main 进程结束。 join耗时:" + (System.currentTimeMillis() - begin) / 1000 + "s");
    }

}

图 join(long)需要获得锁后才会继续向下执行运行结果

3 ThreadLocal的使用

将数据放入当前线程对象的Map中。

3.1 源码分析

图 ThreadLocal set方法

图 ThreadLocal createMap方法

Thread 中包含:ThreadLocal.ThreadLocalMap threadLocals = null;

图 ThreadLocaly与Thread的关系

3.2 使用remove()方法的必要性

ThreadLocalMap中的静态内置类Entry是弱引用类型。

图 ThreadLocalMap部分源码

弱引用的特点是,垃圾回收器扫描发现弱引用对象,不管内存是否足够,都会回收弱引用的对象。也就是只要执行gc操作,ThreadLocal对象就立即销毁,代表key的ThreadLocal对象会随着gc操作而消耗,释放内存空间,但是value值却不会随着gc操作而销毁。

public class ThreadLocalRemove {

    private static class MyThreadLocal extends ThreadLocal {
        private static AtomicInteger count = new AtomicInteger(0);

        @Override
        protected void finalize() throws Throwable {
            System.out.println("MyThreadLocal finalize() " + count.addAndGet(1));
        }
    }

    private static class UserInfo {
        private static AtomicInteger count = new AtomicInteger(0);

        @Override
        protected void finalize() throws Throwable {
            System.out.println("-------Userinfo protected void finalize() " + count.addAndGet(1));
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 9000; i++) {
            MyThreadLocal threadLocal = new MyThreadLocal();
            UserInfo userInfo = new UserInfo();
            threadLocal.set(userInfo);
            threadLocal.remove();
        }
        MyThreadLocal threadLocal = new MyThreadLocal();
        for (int i = 0; i < 90000000; i++) {
            String newStr = new String("" + (i + 1));
            Thread.yield();
            Thread.yield();
            Thread.yield();
            Thread.yield();
        }
        System.out.println("main 结束:" + threadLocal);
    }

}

图 程序运行结果部分截图

图 程序运行过程的内存监控

需要及时使用remove方法删除不用的值,否则value不会随着gc操作而销毁,最后可能会内存溢出。

图 ThreadLocalRemove修改后的代码

3.3 InheritableThreadLocal

可以在子线程中取得父线程继承下来的值。

3.3.1 源码分析

图 InheritableThreadLocal源码

Thread中包含:ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

在创建线程时,子线程主动引用父线程的inheritableThreadLocals的值。

图 Thread init方法部份源码

其中 ThreadLocal.createInheritedMap源码为:

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}

3.3.2子线程继承的是父线程旧值,父线程新值不可继承。

public class InheritableThreadLocalTest {

    private static InheritableThreadLocal<Object> inheritableThreadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        inheritableThreadLocal.set("main线程存储的值");

        Thread thread1 = new Thread(() -> {
            System.out.println("thread1 读取:" + inheritableThreadLocal.get());
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("thread1 2s后读取:" + inheritableThreadLocal.get());
        });
        thread1.start();

        TimeUnit.SECONDS.sleep(2);
        inheritableThreadLocal.set("新值由main线程设置");
        System.out.println("main 设置新值:" + inheritableThreadLocal.get());
    }

}

图 子线程无法继承父线程新值

3.3.3 重写childValue方法

public class InheritableThreadLocalTest2 {

    private static class MyInheritableThreadLocal extends InheritableThreadLocal {
        @Override
        protected Object childValue(Object parentValue) {
            return Thread.currentThread().getName() + "存储的:" + super.childValue(parentValue);
        }
    }

    private static MyInheritableThreadLocal local = new MyInheritableThreadLocal();

    public static void main(String[] args) {
        local.set("我是main");
        new Thread(() -> {
            System.out.println(local.get());
        }).start();
    }

}

图 重写childValue的运行结果

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_25308331/article/details/129409822

进程间通信&线程间通信-爱代码爱编程

一、多进程: 首先,先来讲一下fork之后,发生了什么事情。 由fork创建的新进程被称为子进程(child process)。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新进程(子进程)的进程 id。将子进程id返回给父进程的理由是:因为一个进程的子进程可以多于一个,没有一个函数使一个进程可以获得其所有子进程

android线程间通信的几种方法_Android进程间和线程间通信方式-爱代码爱编程

进程:是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。 线程:是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一些在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部

android线程间通信的几种方法_Android线程间通信机制-爱代码爱编程

讲解Handler机制的博文很多,我也看了很多,但说实话,在我对Handler几乎不怎么了解的情况下,每一篇文章我都没太看懂,看完之后脑子里还是充满了疑问。究其原因,是因为几乎每一篇文章一上来就开始深入Handler源码,使得在一些宏观的问题上还是充满疑问,如果你从来没接触过Handler,对一些基础的问题还充满疑问,那深入源码去探究根源肯定会有些吃力

c语言线程间传递消息,线程间通信-爱代码爱编程

线程间通信 前面一章讲了线程间同步,提到了信号量、互斥量、事件集等概念;本章接着上一章的内容,讲解线程间通信。在裸机编程中,经常会使用全局变量进行功能间的通信,如某些功能可能由于一些操作而改变全局变量的值,另一个功能对此全局变量进行读取,根据读取到的全局变量值执行相应的动作,达到通信协作的目的。RT-Thread 中则提供了更多的工具帮助在不同的线程

c语言线程通信方式,线程间通信及同步方法介绍-爱代码爱编程

线程间如何通信/同步?此前小编给大家介绍了进程间通信的方法,于是一些伙伴又好奇线程间的通信及同步方法,没关系,下面小编就继续给大家科普下线程间通信及同步的方法。 线程间通信及同步方法介绍: 一、线程间的通信方式 1、使用全局变量 主要由于多个线程可能更改全局变量,因此全局变量最好声明为volatile。 2、使用消息实现通信 在Wind

linux 线程及线程间通信_孤帆影的博客-爱代码爱编程

线程1.线程相关接口函数1)创建线程2)结束线程3)等待线程 2.线程间通信 线程 每一个进程的地址空间是相互独立的 每一个进程都有一个task_struct任务结构

java异常详解-爱代码爱编程

文章目录 1. 异常1.1 异常概述1.2 异常机制概述1.3 程序错误一般分为三种1.4 异常继承结构1.5 编译时异常和运行时异常别称1.6 编译时异常和运行时异常的区别1.7 Throwable中java的异常

[2.1.6]进程管理——线程的实现方式和多线程模型-爱代码爱编程

文章目录 第二章 进程管理线程的实现方式和多线程模型一、线程的实现方式(一)用户级线程(二)内核级线程 二、多线程模型(一)一对一模型(二)多对一模型(三)多对多模型 小结 第二章 进程管理

java 线程间通信_java线程间通信-爱代码爱编程

⭐写在前面⭐ 🎉 内容回顾 Java 多线程介绍及线程创建 Java 多线程七大状态 Java 多线程方法详解 Java synchronized关键字实现线程同步 📢今天我们进行 JDBC 获取数据库连接的5种方

c++线程间通信_dword winapi threadfun-爱代码爱编程

c++线程间通过PostThreadMessage和GetMessage函数进行通信,下面用代码演示两个线程间的通信: // ConsoleApplication1.cpp : 定义控制台应用程序的入口点。 // #inc