进程,线程,并发相关入门

进程与线程的简单理解

进程是一个独立的执行单元,它拥有自己的内存空间文件句柄和系统资源.进程是操作系统层面的,每个应用运行就是一个进程.进程之间通常是隔离的,它们不能直接访问对方的内存空间,必须通过进程间通信(IPC)机制来进行数据交换。

线程是进程内的执行单元,归属于进程,它共享进程的内存空间和资源。一个进程可以包含多个线程。但至少需要一个(普通)线程存活(守护线程不算).

并行与并发:

并行概念:并行是指多个进程或者多个线程同时执行任务,只是看起来并行,事实上没有真正的并行(暂时不存在真正意义上的并行,以后不知道).只是人类的感官觉得是并行.(即多核CPU中央处理器也无法达到真正并行)其实都是走走停停

并发:并发是指在相同的时间段内处理多个任务或操作的能力。这并不一定意味着这些任务实际上是同时执行的,而是它们在某个时间间隔内交替执行,以创建一种错觉,似乎它们在同时进行。可以是线程,也可以是进行,一般是指多个线程同时抢占同一个资源,导致错误结果.俗称并发安全问题

创建线程的第一种方式继承Thread,重写run方法

public class Thread1 {public static void main(String[] args) {Thread t1=new MyThread1();Thread t2=new MyThread2();t1.start();t2.start();}
}
//第一种方式:继承线程
class MyThread1 extends Thread{@Overridepublic void run() {for(int i=0;i<100;i++){System.out.println("你是谁呀");}}
}
class MyThread2 extends Thread{@Overridepublic void run() {for(int i=0;i<100;i++){System.out.println("查水表的");}}
}

局部内部类写法

public class Thread1 {public static void main(String[] args) {Thread t1=new Thread(){@Overridepublic void run() {for(int i=0;i<100;i++){System.out.println("你是谁呀");}}};Thread t2=new Thread(){@Overridepublic void run() {for(int i=0;i<100;i++){System.out.println("你是谁呀");}}};t1.start();t2.start();}
}

匿名内部类写法

public class Thread1 {public static void main(String[] args) {new Thread(){@Overridepublic void run() {for(int i=0;i<100;i++){System.out.println("你是谁呀");}}}.start();new Thread(){@Overridepublic void run() {for(int i=0;i<100;i++){System.out.println("你是谁呀");}}}.start();}
}

继承Thread的优点是写起来方便(特别是匿名内部类方式),缺点是Java是单继承的.继承了Thread之后无法再继承其他类.

第二点是线程和线程要干的活有着强耦合关系

创建线程的第二种方式.实现Runable接口.定义线程要执行的任务,将任务交给线程去执行

public class Thread2 {public static void main(String[] args) {//创建任务MyRunable1 my1=new MyRunable1();MyRunable2 my2=new MyRunable2();//创建线程 并让线程启动任务Thread t1=new Thread(my1);Thread t2=new Thread(my2);t1.start();t2.start();}
}
class MyRunable1 implements Runnable{@Overridepublic void run() {for(int i=0;i<100;i++){System.out.println("你是谁呀");}}
}
class MyRunable2 implements Runnable{@Overridepublic void run() {for(int i=0;i<100;i++){System.out.println("查水表的");}}
}

内部类写法

public class Thread2 {public static void main(String[] args) {//创建线程 并让线程启动任务Thread t1=new Thread(new Runnable(){@Overridepublic void run() {for(int i=0;i<100;i++){System.out.println("你是谁呀");}}});Thread t2=new Thread(new Runnable() {@Overridepublic void run() {for(int i=0;i<100;i++){System.out.println("查水表的");}}});t1.start();t2.start();}
}

匿名内部类写法

public class Thread2 {public static void main(String[] args) {//创建线程 并让线程启动任务new Thread(new Runnable(){@Overridepublic void run() {for(int i=0;i<100;i++){System.out.println("你是谁呀");}}}).start();new Thread(new Runnable() {@Overridepublic void run() {for(int i=0;i<100;i++){System.out.println("查水表的");}}}).start();}
}

CurrentThread方法介绍:获取当前运行的线程

public class Thread3 {public static void main(String[] args) {/*** 线程提供了一个静态方法* static Thread currentThread()* 该方法用来获取运行这个方法的线程* main方法也是靠一个线程运行的.当JVM启动* 后会自动创建一个线程来执行main方法.称之为主线程*/Thread thread = Thread.currentThread();System.out.println(thread);//Thread[main,5,main]dosome();Thread t1=new Thread(){@Overridepublic void run() {Thread t = Thread.currentThread();System.out.println("运行dosome方法的线程:"+t);//Thread[Thread-0,5,main]}};Thread t2=new Thread(){@Overridepublic void run() {Thread t = Thread.currentThread();System.out.println("运行dosome方法的线程:"+t);//Thread[Thread-1,5,main]}};t1.start();t2.start();}public static void dosome(){Thread thread = Thread.currentThread();System.out.println("运行dosome方法的线程:"+thread);//谁调用就是谁}
}

线程相关信息的获取

//线程提供了获取自身信息的相关方法
public class Thread4 {public static void main(String[] args) {//获取线程名Thread thread = Thread.currentThread();String name = thread.getName();System.out.println("name:"+name);//获取线程的唯一标识(id)   线程名可以重  但是id不能重long id = thread.getId();System.out.println("该线程的id是:"+id);Thread t1=new Thread(){@Overridepublic void run() {Thread t = Thread.currentThread();System.out.println("运行dosome方法的线程:"+t);//Thread[Thread-0,5,main]System.out.println("该线程的id是:"+t.getId());}};Thread t2=new Thread(){@Overridepublic void run() {Thread t = Thread.currentThread();System.out.println("运行dosome方法的线程:"+t);//Thread[Thread-0,5,main]System.out.println("该线程的id是:"+t.getId());}};t1.start();t2.start();//获取线程的优先级  1到10之间  默认都是5int priority = thread.getPriority();System.out.println("主线程的优先级:"+priority);boolean alive = thread.isAlive();System.out.println("线程是否还活者:"+alive);boolean interrupted = thread.isInterrupted();//中断就是让线程停了 一旦线程被中断(使用 interrupt() 方法中断),它的生命周期结束,不能重新启动.等待垃圾回收器回收System.out.println("线程是否被中断:"+interrupted);//守护线程或称之为后台线程boolean daemon = thread.isDaemon();System.out.println("线程是否为守护线程:"+daemon);}
}

一个个讲上面提到的方法

首先线程优先级 int priority = thread.getPriority();  1到10之间  默认都是5

线程优先级作用:干涉线程调度器工作 只能理论上尽量让该线程多获得CPU时间片,但是实际情况,说不好
理论上:线程优先级越高,获得CPU时间片的次数越多. 优先级用整数表示: 10最高    1最低    默认5

大于10或者小于1都会报错

理论来讲应该是优先级高的先输出完,然后是默认的,然后是优先级最低的,但是实际效果是不一定

调整线程优先级的结果未必理想,但是这是唯一可以改变争取或降低CPU时间片的方式

//线程优先级
public class PriorityDemo {public static void main(String[] args) {//线程优先级作用:干涉线程调度器工作 只能理论上尽量让该线程多获得CPU时间片,但是实际情况,说不好//理论上:线程优先级越高,获得CPU时间片的次数越多. 优先级用整数表示: 10最高    1最低    默认5Thread max=new Thread(){@Overridepublic void run() {for(int i=0;i<10000;i++){System.out.println("max");}}};Thread min=new Thread(){@Overridepublic void run() {for(int i=0;i<10000;i++){System.out.println("min");}}};Thread norm=new Thread(){@Overridepublic void run() {for(int i=0;i<10000;i++){System.out.println("norm");}}};//如果设置的优先级>最高(10) 或者<(1)都会报错   抛IllegalArgumentException//max.setPriority(10);max.setPriority(Thread.MAX_PRIORITY);//用常量   设置优先级min.setPriority(Thread.MIN_PRIORITY);norm.setPriority(Thread.NORM_PRIORITY);max.start();min.start();norm.start();}
}

sleep阻塞

线程提供了一个静态方法:static void sleep(long ms)

使运行这个方法的线程阻塞指定毫秒.超时后该线程会自动回到Runnable状态,

等待再次获得CPU时间片运行 这个和计时器差不多

public class Thread5 {public static void main(String[] args) {System.out.println("程序开始了");try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("程序结束了");}
}

小案例:控制太输入倒计,每个1秒,到0停止

java中一般会引起线程阻塞的方法都要求捕获InterruptedException中断异常

public class Thread6 {public static void main(String[] args) {System.out.println("输入数字:");Scanner scanner=new Scanner(System.in);String line = scanner.nextLine();Integer num=Integer.parseInt(line);for(;num>0;num--){System.out.println(num);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("结束");}
}

sleep阻塞demo2

sleep方法要求必须处理中断异常,原因在于当一个线程调用了sleep方法处于阻塞状态的过程中其他线程中若调用了它的interupt()方法中断时,它就会在sleep方法中抛出中断异常.这时并非是将这个线程直接中断,而是中断了它的阻塞状态.

  

public class Thread7 {public static void main(String[] args) {Thread lin=new Thread(){@Overridepublic void run() {//while(!Thread.currentThread().isInterrupted()) {System.out.println("林:刚美完容,睡一觉");try {Thread.sleep(10000000);} catch (InterruptedException e) {System.out.println("林:干嘛呢?干嘛呢?");}//}System.out.println("林:醒了");}};Thread huang=new Thread(){@Overridepublic void run() {System.out.println("黄:开始砸墙!");for(int i=0;i<5;i++){System.out.println("黄:80!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("咣当!");System.out.println("黄:搞定!");//中断lin线程lin.interrupt();//这里注意:JDK8之前这样写报错,报错原因JDK8之前要求局部内部类中要使用方法内的局部变量,那个变量前要加final}};lin.start();huang.start();}
}

守护线程

守护线程又称后台线程,默认创建的线程都是普通线程或称为前台线程,线程提供了一个

方法:void setDaemon(boolean on)

只有调用该方法并传入为true时,该线程才会被设置为守护线程.

守护线程在使用上于普通线程没有差别,但是在结束时机上有一个区别,即:线程结束时所有正在运行的守护线程都会被强制停止.

进程的结束:当一个进程中所有的普通线程都结束时,进程即结束

GC就是一个守护线程

public class Thread8 {public static void main(String[] args) {Thread rose=new Thread(){@Overridepublic void run() {for(int i=0;i<5;i++){System.out.println("rose:let me go!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("rose:啊啊啊啊啊啊啊啊啊!!!!!");}};Thread jack=new Thread(){@Overridepublic void run() {while(true){System.out.println("jack:you jump! I jump!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};Thread disanzhe=new Thread(){@Overridepublic void run() {for(int i=0;i<10;i++) {System.out.println("disanzhe:吃瓜!看热闹");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};rose.start();jack.setDaemon(true);//设置为守护线程,要在启动前设置  有几个线程启动就守护几个线程jack.start();disanzhe.start();//原先的观点:main方法结束,程序退出.以后的观点就是:当所有前台线程结束,进程结束.才算结束System.out.println("main方法结束");//这里是main先结束//这样的话,守护线程永远不会结束,原因是main线程还一致活者
//        while(true){
//
//        }}
}

join:协调线程之间的同步运行

线程提供了一个方法:

void join()

该方法可以协调线程之间的同步运行

同步与异步:

同步:运行有顺序

异步运行:运行代码无顺序,多线程并发运行就是异步运行

多线程是异步运行的,但是有时候我们也需要他们同步---->利用join

public class Thread9 {private static boolean isFinish=false;//这个放在方法内编译不通过public static void main(String[] args) {int a=1;Thread download=new Thread(){@Overridepublic void run() {System.out.println("down:开始下载图片");for(int i=1;i<=100;i++){System.out.println("down:"+i+"%");try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("down:下载图片完毕");isFinish=true;}};Thread show=new Thread(){@Overridepublic void run() {System.out.println("show:开始显示图片");//加载图片前应先等待下载线程将图片下载完毕try {/*** show线程里调用download.join()方法后* 就进入了阻塞状态,知道download线程的run方法执行完毕才会解除*///这样可以使用应该和线程上下文有关download.join();//在show的线程里写  download.join() 就是等待download线程完毕} catch (InterruptedException e) {e.printStackTrace();}if(!isFinish){throw new RuntimeException("加载图片失败");}System.out.println("show:显示图片完毕");}};download.start();show.start();}
}

wait    用锁对象.wait()

wait()方法其实主要做三件事:

①把当前线程放进等待队列中

②释放锁

③被其他线程唤醒时,尝试重新获取锁

wait() 无参数版本

wait(long) 指定最大等待时间,单位毫秒

wait(long,int) 精度更高,前面是毫秒,后面是纳秒

notifyAll():唤醒加了相同锁的所有线程

wait和sleep其实是没有可比性的,因为一个是用于线程之间的通信的,一个是让线程阻塞一段时间。这两个方法设计的初心是不同的,一个是单纯为了让线程进行阻塞等待而休眠一段时间,而一个是为了解决线程的执行顺序的问题。最明显的区别就是wait需要搭配锁使用,而sleep不需要。wait 是 Object 的方法 sleep 是 Thread 的静态方法。他们唯一的相同点就是都可以让线程进入阻塞等待状态。
 

但是自测sleep无法唤醒,只能interrupt中断(不是很清楚)

notify:用锁对象.notify() 唤醒

public class Thread7 {public static void main(String[] args) {Object o=new Object();Thread lin=new Thread(){@Overridepublic void run() {//while(!Thread.currentThread().isInterrupted()) {System.out.println("林:刚美完容,睡一觉");synchronized (o) {try {//Thread.sleep(1000000);o.wait();} catch (InterruptedException e) {System.out.println("林:干嘛呢?干嘛呢?");}}//}System.out.println("林:醒了");}};Thread huang=new Thread(){@Overridepublic void run() {System.out.println("黄:开始砸墙!");for(int i=0;i<5;i++){System.out.println("黄:80!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("咣当!");System.out.println("黄:搞定!");//唤醒该锁线程,可以在线程里,方法里synchronized (o){o.notify();}//中断lin线程//lin.interrupt();//这里注意:JDK8之前这样写报错,报错原因JDK8之前要求局部内部类中要使用方法内的局部变量,那个变量前要加final}};lin.start();huang.start();}
}
public class Thread10 {public static void main(String[] args) throws InterruptedException {Object lock = new Object();Thread thread1 = new Thread(() -> {synchronized (lock) {try {lock.wait();System.out.println("Thread1被唤醒了");} catch (InterruptedException e) {e.printStackTrace();System.out.println("出错了");}}});Thread thread2 = new Thread(() -> {synchronized (lock) {try {lock.wait();System.out.println("Thread2被唤醒了");} catch (InterruptedException e) {e.printStackTrace();System.out.println("出错了");}}});thread1.start();thread2.start();Thread.sleep(5000);synchronized (lock){//lock.notify();lock.notifyAll();}}
}

并发安全问题

并发会出现的情况

多线程并发的安全问题

产生:当多个线程并发操作同一资源时,由于线程切换时机的不确定性,会导致

执行操作资源的代码顺序未按照设计顺序执行,出现操作混乱的情况.

严重时可能导致系统瘫痪.

解决:将并发操作同一资源改为同步操作,即:有先后顺序的操作

public class SyncDemo1 {public static void main(String[] args) {Table table=new Table();Thread t1=new Thread(){@Overridepublic void run() {while(true){int bean=table.getBean();Thread.yield();System.out.println(getName()+":"+bean);}}};Thread t2=new Thread(){@Overridepublic void run() {while(true){int bean=table.getBean();Thread.yield();System.out.println(getName()+":"+bean);}}};t1.start();t2.start();}
}
class Table{//20个豆子private int bean=20;public int getBean(){if(bean==0){throw new RuntimeException("没有豆子了");}//模拟线程执行到这里没有时间了 它是告诉线程调度器,愿意主动让出CPU时间片,不能确保线程一定会让出Thread.yield();return bean--;}
}

解决办法:加同步锁

在getBean方法上加锁 synchronized   对方法枷锁  就是给对象枷锁

但是在方法上加上synchronized会降低效率

那么用同步块缩小控制范围

同步块:语法

synchronized(同步监视器对象){

        需要同步运行的代码片段

}

同步块可以更精准的控制需要同步运行的代码片段.有效的缩小同步范围可以在保证并发安全

的前提下提高代码并发运行的效率

使用同步块控制多线程同步运行必须要求这些线程看到的同步监视器对象为[同一个]

有效缩小同步范围可以在保证

public class SyncDemo2 {public static void main(String[] args) {Shop shop=new Shop();Thread t1=new Thread(){@Overridepublic void run() {shop.buy();}};Thread t2=new Thread(){@Overridepublic void run() {shop.buy();}};t2.start();}
}
class Shop{public void buy(){try {Thread t=Thread.currentThread();System.out.println(t.getName()+":正在挑衣服");Thread.sleep(5000);synchronized (this) {System.out.println(t.getName() + ":正在试衣服");Thread.currentThread();}System.out.println(t.getName()+":结账离开");} catch (InterruptedException e) {e.printStackTrace();}}
}

静态方法加synchronized一定具有同步效果,锁的是类对象 Class的实例

当静态方法加了synchronized,已经和对象无关了,无论你创建了多少个对象,

必定会有同步效果

public class SyncDemo3 {public static void main(String[] args) {Foo f1=new Foo();Foo f2=new Foo();Thread t1=new Thread(){@Overridepublic void run() {//Foo.dosome();f1.dosome();}};Thread t2=new Thread(){@Overridepublic void run() {//Foo.dosome();f2.dosome();}};t1.start();t2.start();}
}
class Foo{public synchronized static void dosome(){Thread t=Thread.currentThread();System.out.println(t.getName()+":正在运行dosome方法");try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(t.getName()+":运行dosome方法完毕");}
}

synchronized的另外一种作用:

synchronized称为同步锁,还可以帮助维护一种关系,互斥关系

有你没我,有我没你.比如有A方法和B方法,我们希望出现的情况是:

调用A方法就不能调用B,调用B方法就不能调用A,就是不能同时干两个方法

比如咽下去东西和喘气就是互斥的,不能同时干

如何在程序里实现互斥关系,希望互斥的代码用synchronized锁上,然后互斥的代码使用的锁是同一个,他们之间就是互斥关系

互斥锁:

当多个代码片段被synchronized块修饰后,这些同步块的同步监听器对象又是同一个时,这些代码片段就是互斥的.多个线程不能同时在这些方法中运行.

注意:不一定是两个方法,可以是两个代码块,只要锁的对象是同一个,就有互斥效果

public class SyncDemo4 {public static void main(String[] args) {Boo boo=new Boo();Thread t1=new Thread(){@Overridepublic void run() {boo.methodA();}};Thread t2=new Thread(){@Overridepublic void run() {boo.methodA();}};t1.start();t2.start();}
}
class Boo{public synchronized void methodA(){Thread t=Thread.currentThread();try {System.out.println(t.getName()+":正在运行A方法");Thread.sleep(5000);System.out.println(t.getName()+":运行A方法结束");} catch (InterruptedException e) {e.printStackTrace();}}public synchronized void methodB(){Thread t=Thread.currentThread();try {System.out.println(t.getName()+":正在运行B方法");Thread.sleep(5000);System.out.println(t.getName()+":运行B方法结束");} catch (InterruptedException e) {e.printStackTrace();}}
}

关于Thread的静态方法 static void yield()

该方法用于使当前线程主动让出当次CPU时间片回到Runable状态,等待分配时间片

上面这图就是线程运行状态

线程.start()之后进入可运行状态(并不是立刻执行,而是交给线程调度器,进入可运行状态,线程调度器把CPU时间分配给谁,谁先执行,不一定说先start的就一定先执行),当获得CPU时间之后开始运行,CPU时间片用完了,回到可运行状态,等待线程调度分配CPU时间片,任务完成(run方法执行完毕)等待被回收.线程调度器无序分配CPU时间片,只能说尽量分配获得CPU时间片的时间均匀,但不保证

线程有三种阻塞状态,分别是IO阻塞     Sleep阻塞   Wait阻塞  阻塞的直观表现就是卡了

IO阻塞常见的比如说Scanner      Sleep阻塞比如说Thread.sleep  

执行某些特殊代码,让线程进入阻塞状态

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/110457.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker实战技巧(一):Kubernetes基础操作实战

Kubernetes定位在Saas层,重点解决了微服务大规模部署时的服务编排问题 1、关闭防火墙并设置开机禁用   systemctl stop firewalld   systemctl disable firewalld 2、配置repo   cd /etc/yum.repos.d/   下载Docker repo   wget https://mirrors.aliyun.com/docker-…

【CMU15-445 Part-10】Sorting and Aggregations

Part10-Sorting and Aggregations Part10-Sorting & Aggregations Query Plan 查询计划指的是指令或者是数据库系统如何执行一个给定查询的方式。整个查询计划是树形结构或者有向无环图。 Logical Plan&#xff1a;先SCAN A&#xff0c;交给join operator&#xff0c;SC…

教育领域数据可视化:点亮知识之路

教育领域一直以来都在不断进步和演变&#xff0c;而数据可视化技术正在为这一领域带来一场革命。在过去的几年里&#xff0c;教育者们越来越意识到&#xff0c;通过将教育数据转化为可视化图表和图形&#xff0c;可以更好地理解学生的表现、需求和趋势&#xff0c;从而提供更好…

开源项目-SeaTunnel-UI数据集成系统

哈喽,大家好,今天给大家带来一个开源项目-SeaTunnel-UI数据集成系统 系统主要有任务配置,任务实例,数据源,虚拟表,用户管理等功能 登录 数据源 mysql数据源配置参数说明 kafka参数配置 mysqlcdc配置参数说明 虚拟表

无涯教程-JavaScript - IFS函数

描述 IFS函数检查是否满足一个或多个条件,并返回与第一个TRUE条件相对应的值。此功能已在Excel 2016中添加。 语法 IFS (logical_test1, value_if_true1, [logical_test2, value_if_true2], [logical_test3, value_if_true3]…) 争论 Argument描述Required/Optionallogical…

【C++】深拷贝和浅拷贝 ② ( 默认拷贝构造函数是浅拷贝 | 代码示例 - 浅拷贝造成的问题 )

文章目录 一、默认拷贝构造函数是浅拷贝1、默认拷贝构造函数2、默认拷贝构造函数是浅拷贝机制 二、代码示例 - 浅拷贝造成的问题 一、默认拷贝构造函数是浅拷贝 1、默认拷贝构造函数 如果 C 类中 没有定义拷贝构造函数 , C 编译器会自动为该类提供一个 " 默认的拷贝构造函…

前端-layui动态渲染表格行列与复杂表头合并

说在前面&#xff1a; 最近一直在用layui处理表格 写的有些代码感觉还挺有用的&#xff0c;顺便记录下来方便以后查看使用&#xff1b; HTML处代码 拿到id 渲染位置表格 <div class"layui-table-body salaryTable"><table class"layui-table" i…

Java大文件分片上传(minio版),超详细

本文使用spring boot 结合minio文件服务做的大文件分片上传&#xff0c;思路&#xff1a;①&#xff1a;初始化文件调用后端接口&#xff0c;后端再调用minio把文件分片成几份&#xff0c;生成每个分片的minio上传url②&#xff1a;把提起分片好的文件依次调用上一把返回的url …

C语言是否快被时代所淘汰?

今日话题&#xff0c;C语言是否快被时代所淘汰&#xff1f;在移动互联网的冲击下&#xff0c;windows做的人越来越少&#xff0c;WP阵营没人做&#xff0c;后台简单的php&#xff0c;复杂的大数据处理的java&#xff0c;要求性能的c。主流一二线公司基本上没多少用C#的了。其实…

防止泄露,保护隐私!如何清除你的谷歌搜索历史记录

按照以下说明学习如何从你的谷歌帐户、谷歌Chrome浏览器、谷歌iOS或Android应用程序或谷歌应用程序中删除你的谷歌历史记录。 如何从你的谷歌帐户中删除搜索历史记录 清除你的谷歌搜索历史并不意味着谷歌实际上会删除你的搜索数据。谷歌仍然会记录你如何以及何时使用某些功能…

Vue3+ElementUI使用

<!DOCTYPE html> <html> <head><meta charset"UTF-8"><meta name"viewport" content"initial-scale1.0,maximum-scale1.0,minimum-scale1.0,user-scalable0, widthdevice-width"/><!-- 引入样式 --><lin…

mysql内连接与外连接详解

内连接与外连接 内连接外连接 在数据库中&#xff0c;连接操作是一种把两个或者多个表的记录组合在一起的操作&#xff0c;常用的有内连接&#xff08;Inner Join&#xff09;、外连接&#xff08;Outer Join&#xff09;等。 内连接 内连接&#xff08;Inner Join&#xff0…