多线程
1.进程间的常见通信
- 无名管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。例如:Linux中的cat 后面加|(就是匿名管道),前一部分的输出作为后一部分的输入。
- 命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
- 消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 共享内存+信号量:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间 。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
- 信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 套接字Socket:套解口也是一种进程间通信机制,更多的是用于网络之间的进程传输。
- 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。(例如可以告诉CPU,硬盘不要处理其他东西了,马上处理出问题的东西)。
2.Java中sleep,wait,yield,join的区别
方法 | 作用 |
---|---|
sleep()方法 | sleep()在指定时间内让当前正在执行的线程暂停执行,使线程进入阻塞状态,但是不会释放锁,释放CPU |
wait()方法 | 在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的“锁标志”,释放CPU |
yield方法 | 暂停当前正在执行的线程对象,yield()只是使当前线程重新回到就绪状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行,yield()只能使同优先级或更高优先级的线程有执行的机会,不释放放锁,释放cpu. |
join方法 | 等待调用join方法的线程结束,再继续执行。释放锁,释放cpu. |
使用:
sleep(1000);
yield();
thread1.wait();
thread1.notifyAll();
thread1.notify();
thread1.join();
join方法
等待该线程终止。
等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测。
publicclass Test {
publicstaticvoid main(String[] args) {
Thread t1 = new MyThread1();
t1.start();
for (int i = 0; i < 20; i++) {
System.out.println("主线程第" + i +"次执行!");
if (i > 2)try {
//t1线程合并到主线程中,主线程停止执行过程,转而执行t1线程,直到t1执行完毕后继续。
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class MyThread1 extends Thread {
publicvoid run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程1第" + i + "次执行!");
}
}
}
join(long),能够释放当前线程的锁,其他线程可以调用次线程中的同步方法。
sleep(long)不能释放锁。
## 3.内核态与用户态
3.1内核态和用户态
内核态:当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。
用户态:当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。即此时处理器在特权级最低的(3级)用户代码中运行。
简单来讲一个进程由于执行系统调用而开始执行内核代码,我们称该进程处于内核态中.例如:调用read( ),write( ),open( )等系统函数就是系统调用。
一个进程执行应用程序自身代码则称该进程处于用户态.
intel x86 架构的 CPU 分为好几个运行级别,从 0–3 , 0 为最高级别, 3 为最低级别
当操作系统刚引导时, CPU 处于实模式,这时就相当于是 0 级,于是操作系统就自动得到最高权限,然后切到保护模式时就是0级,这时操作系统就占了先机,成为了最高级别的运行者,由于你的程序都是由操作系统来加载的,所以当它把你加载上来后,就把你的运行状态设为 3 级。
3.2从用户态如何转化到内核态
- 系统调用
- 异常
- 外围设备的中断
这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的
3.3用户空间和内核空间
用户空间就是用户进程所在的内存区域,相对的,系统空间就是操作系统占据的内存区域。用户进程和系统进程的所有数据都在内存中。
4.ThreadLocal
ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,如何防止自己的变量被其它线程篡改。
- ThreadLocal针对的是一个线程的全局变量,而不是针对整个程序的全局变量。
ThreadLocal使用:
public class Main1 {
static ThreadLocal threadLocal = new ThreadLocal();
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(()->{
System.out.println(threadLocal.get());
threadLocal.set(0);
System.out.println(threadLocal.get());
});
Thread thread2 = new Thread(()->{
System.out.println(threadLocal.get());
threadLocal.set(1);
System.out.println(threadLocal.get());
});
thread1.start();
thread1.join();;
thread2.start();
}
}
//result
/*
null
0
null
1
*/
ThreadLocal底层原理:
- 每次使用完要移除:例如:t1.remove();
- ThreadLocalMap中的Entry节点类是继承于弱引用的,gc的时候就会被清除,防止内存溢出。
ThreadLocal在保存的时候会把自己当做Key存在ThreadLocalMap中,正常情况应该是key和value都应该被外界强引用才对,但是现在key被设计成WeakReference弱引用了。
这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
就比如线程池里面的线程,线程都是复用的,那么之前的线程实例处理完之后,出于复用的目的线程依然存活,所以,ThreadLocal设定的value值被持有,导致内存泄露。
如何解决内存泄露:
在代码的最后使用remove就好了,我们只要记得在使用的最后用remove把值清空就好了。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
使用场景ThreadLocal使用场景:
1.解决权限问题:
@Component
public class MyFilter extends OncePerRequestFilter {
public static ThreadLocal<Integer> auth = new ThreadLocal<>();
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
auth.set(0);
if (httpServletRequest.getHeader("Authorization").equals("1")){
auth.set(1);
}
doFilter(httpServletRequest,httpServletResponse,filterChain);
}
}
@GetMapping("/")
public ResponseEntity index(){
try{
if(MyFilter.auth.get()==0){
return ResponseEntity.ok("没有权限");
}else{
return ResponseEntity.ok("有权限");
}finally{
MyFilter.auth.remove();//必须要清除,在线程池中,下次线程在使用的时候,会访问前世的内容。
}
}
}
5.线程池的四种创建方式
- newCachedThreadPool :创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool :创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool: 创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代。
- newSingleThreadExecutor: 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
5.1 newCachedThreadPool executor [ɪɡˈzekjətər]
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程:
public class test11 {
public static void main(String[] args) {
//1.创建可缓存的线程池,可重复利用
ExecutorService executor = Executors.newCachedThreadPool();
//创建10个线程
for(int i=0;i<10;i++){
int temp=i;
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名:"+Thread.currentThread().getName()+" ,i:"+temp);
}
});
}
executor.shutdown();
}
}
可以看到本来创建了10个线程池,这里只用了5个,因为newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
5.2 newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
public class test22 {
public static void main(String[] args) {
//1.创建可缓存的线程池,可重复利用
ExecutorService executor = Executors.newFixedThreadPool(3);
//创建10个线程
for(int i=0;i<10;i++){
int temp=i;
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名:"+Thread.currentThread().getName()+" ,i:"+temp);
}
});
}
executor.shutdown();
}
}
可以看到newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
5.3 newScheduledThreadPool [ˈskedʒuːld]
创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下:
public static void main(String[] args) {
//1.创建可定时线程池
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 10; i++) {
final int temp = i;
newScheduledThreadPool.schedule(new Runnable() {
public void run() {
System.out.println("i:" + temp);
}
}, 3, TimeUnit.SECONDS);
}
}
表示延迟3秒执行。
5.4 newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:
public class test44 {
public static void main(String[] args) {
//1.创建单线程
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
newSingleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("index:" + index);
try {
Thread.sleep(200);
} catch (Exception e) {
// TODO: handle exception
}
}
});
}
newSingleThreadExecutor.shutdown();
}
}
注意: 结果依次输出,相当于顺序执行各个任务。当shutdown时停止线程
但是这四种是阿里巴巴不推荐使用的。
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 3, 20, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>());
6.顺序输出ABC问题
6.1单例线程池实现
public class test44 {
public static class thread11 implements Runnable{
@Override
public void run() {
char [] ch= {'A','B','C'};
for (int i = 0; i < 100; i++) {
final int index = i;
int temp = i;
System.out.println("index:" + ch[temp %3]);
// try {
// Thread.sleep(200);
// } catch (Exception e) {
// // TODO: handle exception
// }
}
}
}
public static void main(String[] args) {
//1.创建单线程
thread11 thread = new thread11();
new Thread(thread).start();
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
newSingleThreadExecutor.execute(thread);
newSingleThreadExecutor.shutdown();
}
}
6.2线程计数器
public class ThreadDemo4 implements Runnable{
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(3); //线程计数器
private static Integer currentCount = 0;
private static final Integer MAX_COUNT = 30;
private static String [] chars = {"a", "b", "c"};
private String name;
public ThreadDemo4(String name) {
this.name = name;
}
@Override
public void run() {
while(currentCount<MAX_COUNT){
while(this.name.equals(chars[currentCount%3]))
printAndPlusOne(this.name + "\t" + currentCount);
try {
cyclicBarrier.await();
}catch (Exception e){
e.printStackTrace();
}
}
}
public void printAndPlusOne(String name){
System.out.println(name);
currentCount ++;
}
public static void main(String [] args){
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 3, 20, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>());
threadPoolExecutor.execute(new ThreadDemo4("a"));
threadPoolExecutor.execute(new ThreadDemo4("b"));
threadPoolExecutor.execute(new ThreadDemo4("c"));
threadPoolExecutor.shutdown();
}
}