进程和线程的几种通信方式

进程之间通信的几种方式

1. 管道:是内核里面的一串缓存
管道传输的数据是单向的,若相互进行通信的话,需要进行创建两个管道才行的。
2. 消息队列:
例如,A进程给B进程发送消息,A进程把数据放在对应的消息队列后就可以正常的返回,B进程需要的时候再进行读取数据。
消息队列是保存在内存中的消息链表,在发送数据的时候,会分成一个独立的数据单元,即就是数据块,消息体是用户自定的数据 类型,消息的发送方和接收方要约定好消息体的数据类型,所以每个消息体都是固定大小的存储块,不像管道的无格式的字节流数据,注意的就是,消息队列中读取了消息体的话,内核就会把这个消息体给删除啦。
消息队列不适合比较大数据的传输,因为在内核中每个消息体都有一个最大长度的限制,同时所有队列所包含的全部消息体的总长度也是有上限。在 Linux 内核中,会有两个宏定义 MSGMAXMSGMNB,它们以字节为单位,分别定义了一条消息的最大长度和一个队列的最大长度。

消息队列通信过程中,存在用户态与内核态之间的数据拷贝开销,因为进程写入数据到内核中的消息队列时,会发生从用户态拷贝数据到内核态的过程,同理另一进程读取内核中的消息数据时,会发生从内核态拷贝数据到用户态的过程。

3. 共享内存:
消息队列中的读取和写入的过程,都会有用户态和内核态之间的消息拷贝过程。共享内存的机制就是拿出一块虚拟化的地址空间来,映射到相同的物理内存中。这样这个进程写入东西,另外一个进程就可以看到了,大大提高了进程间通信的速度。
4. 信号量机制:
用了共享内存通信方式,带来新的问题,那就是如果多个进程同时修改同一个共享内存,很有可能就冲突了。例如两个进程都同时写一个地址,那先写的那个进程会发现内容被别人覆盖了。

为了防止多进程竞争共享资源,而造成的数据错乱,所以需要保护机制,使得共享的资源,在任意时刻只能被一个进程访问。正好,信号量就实现了这一保护机制。

信号量其实是一个整型的计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据
信号量表示资源的数量,控制信号量的方式有两种原子操作:

  • 一个是 P 操作,这个操作会把信号量减去 -1,相减后如果信号量 < 0,则表明资源已被占用,进程需阻塞等待;相减后如果信号量 >= 0,则表明还有资源可使用,进程可正常继续执行。
  • 另一个是 V 操作,这个操作会把信号量加上 1,相加后如果信号量 <= 0,则表明当前有阻塞中的进程,于是会将该进程唤醒运行;相加后如果信号量 > 0,则表明当前没有阻塞中的进程;

4. 信号:
对于异常的情况下的工作模式,需要用信号的方式来进行通知进程。
信号是进程间通信机制中的唯一的异步通信机制,任何时候给某一进程发送信息,一旦信号产生,就会有这几种的方式:
1、执行默认的操作
2、扑捉信号
3、忽略信号
5. socket:
管道、消息队列、共享内存、信号量和信号是在同一台主机上进行通信的,若想跨网络与不同的主机之间上的进程之间通信,需要用socket套接字啦

线程通信的几种方式

线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。

1. 等待通知机制

两个线程通过对同一对象调用等待 wait() 和通知 notify() 方法来进行通讯。

等待通知有着一个经典范式:

线程 A 作为消费者:

  1. 获取对象的锁。
  2. 进入 while(判断条件),并调用 wait() 方法。
  3. 当条件满足跳出循环执行具体处理逻辑。

线程 B 作为生产者:

  1. 获取对象锁。
  2. 更改与线程 A 共用的判断条件。
  3. 调用 notify() 方法。

伪代码如下:

//Thread A

synchronized(Object){
    while(条件){
        Object.wait();
    }
    //do something
}

//Thread B
synchronized(Object){
    条件=false;//改变条件
    Object.notify();
}

2. join() 方法

在 join 线程完成后会调用 notifyAll() 方法,是在 JVM 实现中调用,所以这里看不出来。

3. volatile 共享内存

4. 管道通信

5. 并发工具

  1. CountDownLatch 并发工具
  2. CyclicBarrier 并发工具

Java中创建多线程的几种方式

进程和线程的几种通信方式_第1张图片

方式1:

  1. 继承Thread类
class Number extends Thread{
    private static int num=1;

    @Override
    public void run() {
//        for (int i = 0; i < 6; i++) {
//            System.out.println(Thread.currentThread().getName()+"\t"+i);
//            System.out.println("run thread");
//        }
        System.out.println(Thread.currentThread().getName());
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        /**
         * 其实在这个地方需要注意的有
         *  一:start()方法是执行相应的准备,然后自动的启动执行run方法的内容调用 start() 方法,
         *          会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了
         *  二:run方法是:会把run方法当成一个main下面的普通的方法去执行的,并不会在某个线程中去执行它的,还是在主线程中进行执行的
         */
        Number number1 = new Number();
        Number number2 = new Number();
        number1.start();
        System.out.println("======");
        number1.run();
        System.out.println("==========");
        number2.run();
        System.out.println("================");
        number2.start();
    }
}
在这个地方尤其是特别的注意start方法和run方法的,start是开启一个线程,然后会自动的执行run方法的,而run是在main方法下的一个普通的方法。

2、实现Runnable接口

class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
public class RunnableTest  {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
//        myRunnable.run();
        Thread thread = new Thread(myRunnable);
//        thread.start();//结果为Thread0
        thread.run();//结果为main
    }
}

3、实现Callable接口的方式

class CallShare implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"Thread in callable");
        return 200;
    }
}
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        CallableTest callableTest = new CallableTest();
//        Thread thread1 = new Thread(callableTest);
        //需要找一个中间的传递者进行接受这个
//        查找jdkapi可以看到关于有一个FutureTask中间类,实现其接口的有Runnable,此时我们可以利用此来进行书写Callable的多线程
//        注意,Runnable是有返回值的
        FutureTask<Integer> integerFutureTask = new FutureTask<Integer>(new CallShare());
        new Thread(integerFutureTask,"drjackxing ").start();
        System.out.println(integerFutureTask.get());

        System.out.println("================");
        //第二个多线程的实现
        FutureTask<Integer> integerFutureTask1 = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName() + "  come in callable");
            TimeUnit.SECONDS.sleep(4);
            return 1024;
        });
        new Thread(integerFutureTask1).start();
        System.out.println(integerFutureTask1.get());
    }
}

4、线程池的方式进行创建多线程
线程池的核心原理
线程池的核心参数的

ThreadPoolExecutor

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

1、corePoolSize指的是线程池中常驻的核心线程数

2、maximumPoolSize线程池中能够容纳同时执行的最大线程树,必须要大于1

3、keepAliveTime,多余的空闲c线程的存活时间,当前线程池中的数量超过了corePoolSize时,当前空闲时间达到keepAliveTime时候,多余的线程会被销毁到只剩下corePoolSize个线程为止

4、unit:keepAliveTime的单位

5、workQueue:任务队列,被提交但尚未被执行的任务

6、threadFactory:表示生成线程池中工作线程的线程工厂,
用于创建线程,一般默认的即可

7、handler:拒绝策略,表示当队列满了,并且工作线程大于
等于线程池的最大线程数(maximumPoolSize)时如何来拒绝
请求执行的runnable的策略

public class MyThreadPoolExcetorDemo {
    public static void main(String[] args) {
        new ThreadPoolExecutor(2,//核心的线程池
                5,//最大的线程池
                3L,//空闲的线程存活时间
                TimeUnit.SECONDS,//空闲线程存活的时间单位
                new ArrayBlockingQueue<>(3));//阻塞队列的个数
    }
}

线程池的底层工作原理
进程和线程的几种通信方式_第2张图片
1、在创建了线程池后,线程池中的线程数为零。
2、当调用execute()方法添加一个请求任务时,线程池会做出如下判断:
2.1如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
2.2如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
2.3如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
2.4如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
3、当一个线程完成任务时,它会从队列中取下一个任务来执行。
4、当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。

JDK中的拒绝策略

拒绝策略的条件:阻塞线程数+最大线程数已经满啦,无法再继续的创建啦

 //new ThreadPoolExecutor.AbortPolicy()//默认的发生异常的
 new ThreadPoolExecutor.DiscardOldestPolicy()//抛弃队列中等待最久的任务,然后把当前任务加人队列中尝试再次提交当前任务。
// new ThreadPoolExecutor.CallerRunsPolicy()//当前线程超过了最大线程数+阻塞线程数的时候就会进回退,调用则来进行执行任务
// new ThreadPoolExecutor.DiscardPolicy()//直接的抛弃多余的线程就好

一般在开发的过程中只要是默认就好的

线程池来创建Java多线程的方式

public class ThreadPoolDemo {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2,//核心线程数
                5,//最大线程数
                2L, //保持线程的存活时间
                TimeUnit.SECONDS,//keepAliveTime的最大存活时间
                new ArrayBlockingQueue<>(3),//阻塞队列的线程的数
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy());
        try {
            for (int i = 1; i <=10 ; i++) {
                executor.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"\t 开始办理业务");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executor.shutdown();
        }
    }
}

你可能感兴趣的:(java,java)