寄存器
CPU和线程的关系
线程数模拟出的CPU核心数。对于一个CPU,线程数总是大于或等于核心数的。一个核心最少对应一个线程,但通过超线程技术,一个核心可以对应两个线程,也就是说它可以同时运行两个线程。
进程,线程,时间片
进程:CPU分配资源的基本单位,程序执行时的一个实例
线程:CPU调度的基本单位,是程序执行的最小单位,一个进程可以由多个线程组成
时间片:时间片是CPU分配给线程的时间段
线程分到了CPU时间片,就可以认为这个线程所属的进程在运行,这样就看起来是进程并行,进程和线程的并行实际上是时间片的轮换使用
线程间通信
线程间通信目的:提高CPU利用率,用于线程同步
线程间通信的机制:
- wait/notify机制
- Lock/Condition机制
wait/notify
需要了解的知识:
线程安全和锁Synchronized概念
可重入锁以及Synchronized的其他基本特性
线程本地ThreadLocal
线程间通信机制的介绍与使用
java之用volatile和不用volatile的区别
使用Lock对象实现同步以及线程间通信
wait/notify注意点:
- 只能在同步块或同步方法中调用
- 调用前线程必须获得对象级别锁,执行wait()方法后当前线程会释放锁
- 执行顺序:notify()所在的synchronized中的代码块执行完→释放对象锁→被通知线程获得锁
- notifAll每次唤醒wait等待状态的线程使之重新竞争获取对象锁,优先级最高的那个线程会最先执行
- 当线程处于wait()状态时,调用线程对象的interrupt()方法会出现InterruptedException异常
- synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区
- 同步方法块,锁是括号里面的对象
- synchronized是Java的一个关键字,也就是Java语言内置的特性,如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,执行代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而获取锁的线程释放锁会有三种情况:
- 获取锁的线程执行完该代码块,然后线程释放对锁的占有;
- 线程执行发生异常,此时JVM会让线程自动释放锁;
- 调用wait方法,在等待的时候立即释放锁,方便其他的线程使用锁.
测试代码:
MyList.java
/**
* this is a list class
* @author XYM_
* @date 2021-4-21
* @version 1.0
*/
import java.util.*;
public class MyList{
private static volatile List list = new ArrayList();
public static void add(){
list.add("component");
}
public static int size(){
return list.size();
}
}
ThreadA.java
/**
* this is a thread class
* @author XYM_
* @date 2021-4-21
* @version 1.0
*/
import java.util.*;
public class ThreadA extends Thread{
private Object lock;
/**
* constructor of ThreadA
*/
public ThreadA(Object lock){
super();
this.lock = lock;
}
@Override
public void run(){
try{
synchronized(lock){
if(MyList.size() != 5){
System.out.println("wait begin " + System.currentTimeMillis());
lock.wait();
System.out.println("wait end " + System.currentTimeMillis());
}
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
ThreadB.java
/**
* this is a thread class
* @author XYM_
* @date 2021-4-21
* @version 1.0
*/
import java.util.*;
public class ThreadB extends Thread{
private Object lock;
/**
* constructor of ThreadB
*/
public ThreadB(Object lock){
super();
this.lock = lock;
}
@Override
public void run(){
try{
synchronized(lock){
for(int i = 0; i < 10; i++){
MyList.add();
if(MyList.size() == 5){
lock.notify();
System.out.println("Notified!");
}
System.out.println("Add number" + (i+1));
Thread.sleep(1000);
}
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
ThreadRun.java
/**
* this is a multithread run test with wait()/notify()
* @author XYM_
* @date 2021-4-21
* @version 1.0
*/
public class ThreadRun{
public static void main(String[] args){
try{
Object lock = new Object();
ThreadA a = new ThreadA(lock);
a.start();
Thread.sleep(50);
ThreadB b = new ThreadB(lock);
b.start();
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
运行结果:
D:\PractiseJava\CPUGPU>java ThreadRun
wait begin 1618994216673
Add number1
Add number2
Add number3
Add number4
Notified!
Add number5
Add number6
Add number7
Add number8
Add number9
Add number10
wait end 1618994226746
Lock/Condition
Lock/Condition注意点:
- Lock要保证锁一定会被释放,必须将unLock放到finally{}中(手动释放)
- 在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetarntLock,但是在很激烈的情况下,synchronized的性能会下降
- ReentrantLock增加了锁:
a. void lock(); // 无条件的锁;
b. void lockInterruptibly throws InterruptedException; // 可中断的锁;
使用ReentrantLock如果获取了锁立即返回,如果没有获取锁,当前线程处于休眠状态,直到获得锁或者当前线程可以被别的线程中断去做其他的事情;但是如果是synchronized的话,如果没有获取到锁,则会一直等待下去;
c. boolean tryLock(); // 如果获取了锁立即返回true,如果别的线程正持有,立即返回false,不会等待;
d. boolean tryLock(long timeout,TimeUnit unit); / /如果获取了锁立即返回true,如果别的线程正持有锁,会等待参数给的时间,在等待的过程中,如果获取锁,则返回true,如果等待超时,返回false; - Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法。
Object中的这些方法是和同步锁捆绑使用的;而Condition是需要与互斥锁/共享锁捆绑使用的 - Condition能够更加精细的控制多线程的休眠与唤醒。对于同一个锁,可以创建多个Condition,在不同的情况下使用不同的Condition,明确唤醒的线程。
进程间通信
进程间通信方式:
- 管道(Pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。(半双工:数据传输指数据可以在一个信号载体的两个方向上传输,但是不能同时传输。)
- 有名管道(named pipe): 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。命名管道在文件系统中有对应的文件名。命名管道通过命令 mkfifo 或系统调用 mkfifo 来创建。
- 信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生。
- 消息(Message)队列:消息队列是消息的链接表。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 共享内存(Shared Memory):使得多个进程可以访问同一块内存空间,是最快的可用 IPC 形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
- 内存映射(mapped memory):内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。
- 信号量(semaphore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 套接口(Socket):更为一般的进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。可用于不同机器之间的进程间通信。