进程之间通信的几种方式
1. 管道:是内核里面的一串缓存
管道传输的数据是单向的,若相互进行通信的话,需要进行创建两个管道才行的。
2. 消息队列:
例如,A进程给B进程发送消息,A进程把数据放在对应的消息队列后就可以正常的返回,B进程需要的时候再进行读取数据。
消息队列是保存在内存中的消息链表,在发送数据的时候,会分成一个独立的数据单元,即就是数据块,消息体是用户自定的数据 类型,消息的发送方和接收方要约定好消息体的数据类型,所以每个消息体都是固定大小的存储块,不像管道的无格式的字节流数据,注意的就是,消息队列中读取了消息体的话,内核就会把这个消息体给删除啦。
消息队列不适合比较大数据的传输,因为在内核中每个消息体都有一个最大长度的限制,同时所有队列所包含的全部消息体的总长度也是有上限。在 Linux 内核中,会有两个宏定义 MSGMAX
和 MSGMNB
,它们以字节为单位,分别定义了一条消息的最大长度和一个队列的最大长度。
消息队列通信过程中,存在用户态与内核态之间的数据拷贝开销,因为进程写入数据到内核中的消息队列时,会发生从用户态拷贝数据到内核态的过程,同理另一进程读取内核中的消息数据时,会发生从内核态拷贝数据到用户态的过程。
3. 共享内存:
消息队列中的读取和写入的过程,都会有用户态和内核态之间的消息拷贝过程。共享内存的机制就是拿出一块虚拟化的地址空间来,映射到相同的物理内存中。这样这个进程写入东西,另外一个进程就可以看到了,大大提高了进程间通信的速度。
4. 信号量机制:
用了共享内存通信方式,带来新的问题,那就是如果多个进程同时修改同一个共享内存,很有可能就冲突了。例如两个进程都同时写一个地址,那先写的那个进程会发现内容被别人覆盖了。
为了防止多进程竞争共享资源,而造成的数据错乱,所以需要保护机制,使得共享的资源,在任意时刻只能被一个进程访问。正好,信号量就实现了这一保护机制。
信号量其实是一个整型的计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据。
信号量表示资源的数量,控制信号量的方式有两种原子操作:
- 一个是 P 操作,这个操作会把信号量减去 -1,相减后如果信号量 < 0,则表明资源已被占用,进程需阻塞等待;相减后如果信号量 >= 0,则表明还有资源可使用,进程可正常继续执行。
- 另一个是 V 操作,这个操作会把信号量加上 1,相加后如果信号量 <= 0,则表明当前有阻塞中的进程,于是会将该进程唤醒运行;相加后如果信号量 > 0,则表明当前没有阻塞中的进程;
4. 信号:
对于异常的情况下的工作模式,需要用信号的方式来进行通知进程。
信号是进程间通信机制中的唯一的异步通信机制,任何时候给某一进程发送信息,一旦信号产生,就会有这几种的方式:
1、执行默认的操作
2、扑捉信号
3、忽略信号
5. socket:
管道、消息队列、共享内存、信号量和信号是在同一台主机上进行通信的,若想跨网络与不同的主机之间上的进程之间通信,需要用socket套接字啦
线程通信的几种方式
线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。
1. 等待通知机制
两个线程通过对同一对象调用等待 wait() 和通知 notify() 方法来进行通讯。
等待通知有着一个经典范式:
线程 A 作为消费者:
- 获取对象的锁。
- 进入 while(判断条件),并调用 wait() 方法。
- 当条件满足跳出循环执行具体处理逻辑。
线程 B 作为生产者:
- 获取对象锁。
- 更改与线程 A 共用的判断条件。
- 调用 notify() 方法。
伪代码如下:
//Thread Asynchronized(Object){while(条件){Object.wait();}//do something
}//Thread B
synchronized(Object){条件=false;//改变条件Object.notify();
}
2. join() 方法
在 join 线程完成后会调用 notifyAll() 方法,是在 JVM 实现中调用,所以这里看不出来。
3. volatile 共享内存
4. 管道通信
5. 并发工具
- CountDownLatch 并发工具
- CyclicBarrier 并发工具
Java中创建多线程的几种方式
方式1:
- 继承Thread类
class Number extends Thread{private static int num=1;@Overridepublic 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{@Overridepublic 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();//结果为Thread0thread.run();//结果为main}
}
3、实现Callable接口的方式
class CallShare implements Callable<Integer> {@Overridepublic 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));//阻塞队列的个数}
}
线程池的底层工作原理
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();}}
}