您现在的位置是:主页 > news > 高端的网站设计多少钱/环球网
高端的网站设计多少钱/环球网
admin2025/5/22 2:16:43【news】
简介高端的网站设计多少钱,环球网,wordpress 读取最新文章,做车贴网站线程和进程的关系还有其包含的特点 一个进程可以包含多个线程,在进程当中,存在两个区域,分别时堆和方法区,堆当中存放的是new创建的实例和数组,而方法区,顾名思义,就是存放方法的地方ÿ…
线程和进程的关系还有其包含的特点
一个进程可以包含多个线程,在进程当中,存在两个区域,分别时堆和方法区,堆当中存放的是new创建的实例和数组,而方法区,顾名思义,就是存放方法的地方;而线程当中包含的是程序计数器和栈,程序计数器是记录线程要执行的指令地址,由于分配资源的时候是通过时间片轮转的方式让线程进行占用的,因此当这个时间片用完之后,线程需要让出这个位置,那么此时程序计数器就是记录让出CPU的时候执行地址的,待再次分配到时间片时线程就可以从自己私有的计数器指定地址继续执行。
堆和栈的对比
一、栈解决的是程序的运行问题,即程序如何执行,或者说如何处理数据;堆解决的是数据存储的问题,即数据怎么放,放在哪儿。
二、栈因为是运行单位,因此里面存储的信息都是当前线程相关的信息。包括:局部变量、程序运行状态、方法返回值等等;而堆只负责存储对象信息。
三、在方法中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。堆内存用于存放由new创建的对象和数组。
四、在Java中一个线程就会相应有一个线程栈与之对应,这点保证了程序的并发运行。
而堆则是所有线程共享的,也可以理解为多个线程访问同一个对象,比如多线程去读写同一个对象的值
五、栈内存溢出包括StackOverflowError和OutOfMemoryError。StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。OutOfMemoryError:如果虚拟机栈可以动态扩展,而扩展时无法申请到足够的内存;堆内存溢出是OutOfMemoryError。如果堆中没有内存完成实例分配,并且堆也无法再扩展时,抛出OutOfMemoryError异常。
代码分析栈和堆当中的数据关系
class BirthDate{
private int day;
private int month;
private int year;
public Birthday(int d,int m,int y){
day=d;
month=m;
year=y;
}省略get()和set()方法
}
public class Test{
public static void main(String args[]){
int date=9;
Test test=new Test();
test.change(date);
BirthDate d1=new BirthDate(7,7);
}public void change1(int i)
{
i=1234;
}
对于上述的代码,date为局部变量,i,d,m,y都是形参为局部变量,day,month,year为成员变量。下面分析以下代码执行时候的变化:
1.main
方法开始执行:int date=9;
date
局部变量,基础类型,引用和值都存在栈中。
2.Test test=new Test();
test
为对象引用,存在栈中,对象(new Test())存在堆中。
3. test.change(date);
调用change(int i)方法,i为局部变量,引用和值存在栈中。当方法change执行完成后,i就会从栈中消失。
- BirthDate d1= new BirthDate(7,7,1970);
调用BIrthDate类的构造函数生成对象。
d1为对象引用,存在栈中;
对象(new BirthDate())存在堆中;
其中d,m,y为局部变量存储在栈中,且它们的类型为基础类型,因此它们的数据也存储在栈中;
day,month,year为BirthDate对象的的成员变量,它们存储在堆中存储的new BirthDate()对象里面;
当BirthDate构造方法执行完之后,d,m,y将从栈中消失。
5.main方法执行完之后。
date变量,test,d1引用将从栈中消失;
new Test(),new BirthDate()将等待垃圾回收器进行回收。
死锁和生产者与消费者这一类的知识
下面从一个简单的生产者和消费者例子来加深理解。如下面代码所示,其中queue为共享变量,生产者线程在调用queue的wait()方法前,使用synchronized关键字拿到了该共享变量queue的监视器锁,所以调用wait()方法才不会抛出IllegalMonitorStateException异常。如果当前队列没有空闲容量则会调用queued的wait()方法挂起当前线程,这里使用循环就是为了避免上面说的虚假唤醒问题。假如当前线程被虚假唤醒了,但是队列还是没有空余容量,那么当前线程还是会调用wait()方法把自己挂起。
//生产线程
synchronized (queue) {//消费队列满,则等待队列空闲while (queue.size() == MAX_SIZE) {try {//挂起当前线程,并释放通过同步块获取的queue上的锁,让消费者线程可以获取该锁,然后获取队列里面的元素queue.wait();} catch (Exception ex) {ex.printStackTrace();}}//空闲则生成元素,并通知消费者线程queue.add(ele);queue.notifyAll();}
}
//消费者线程
synchronized (queue) {//消费队列为空while (queue.size() == 0) {try//挂起当前线程,并释放通过同步块获取的queue上的锁,让生产者线程可以获取该锁,将生产元素放入队列queue.wait();} catch (Exception ex) {ex.printStackTrace();}}//消费元素,并通知唤醒生产者线程queue.take();queue.notifyAll();}
}
在这当中当进入了一个锁的模块之后必须要做的就是使自己通过wait()来进行阻塞,然后再对自己进行一个锁的释放,这样做的目的就是为了防止死锁。
另外需要注意的是,当前线程调用共享变量的wait()方法之后只会释放当前共享变量上的锁,如果当前线程还持有其他共享变量的锁,则这些锁是不会被释放的。下面来看一个例子。
在举这个例子之前呢我想介绍以下volatile这个关键字
1.volatile的本意是“易变的” 因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读脏数据。当要求使用volatile声明变量值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。精确地说就是,遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问;如果不使用valatile,则编译器将对所声明的语句进行优化。(简洁的说就是:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)
代码如下所示
// 创建资源private static volatile Object resourceA = new Object();private static volatile Object resourceB = new Object();public static void main(String[] args) throws InterruptedException {// 创建线程Thread threadA = new Thread(new Runnable() {public void run() {try {// 获取resourceA共享资源的监视器锁synchronized (resourceA) {System.out.println("threadA get resourceA lock");// 获取resourceB共享资源的监视器锁synchronized (resourceB) {System.out.println("threadA get resourceB lock");// 线程A阻塞,并释放获取到的resourceA的锁System.out.println("threadA release resourceA lock");resourceA.wait();}}} catch (InterruptedException e) {e.printStackTrace();}}});// 创建线程Thread threadB = new Thread(new Runnable() {public void run() {try {//休眠1sThread.sleep(1000);// 获取resourceA共享资源的监视器锁synchronized (resourceA) {System.out.println("threadB get resourceA lock");System.out.println("threadB try get resourceB lock...");// 获取resourceB共享资源的监视器锁synchronized (resourceB) {System.out.println("threadB get resourceB lock");// 线程B阻塞,并释放获取到的resourceA的锁System.out.println("threadB release resourceA lock");resourceA.wait();}}} catch (InterruptedException e) {e.printStackTrace();}}});// 启动线程threadA.start();threadB.start();// 等待两个线程结束threadA.join();threadB.join();System.out.println("main over");}
输出结果如下:
这个时候我们会发现为什么在线程B当中不能利用资源类B呢,这是因为在线程A释放锁之后,释放的只是类resourceA对应的锁,而线程B却不能拿到锁住资源B的锁,因此就不会显示出来啦!
2.wait(long timeout)函数
该方法相比wait()方法多了一个超时参数,它的不同之处在于,如果一个线程调用共享对象的该方法挂起后,没有在指定的timeout ms时间内被其他线程调用该共享变量的notify()或者notifyAll()方法唤醒,那么该函数还是会因为超时而返回。如果将timeout设置为0则和wait方法效果一样,因为在wait方法内部就是调用了wait(0)。需要注意的是,如果在调用该函数时,传递了一个负的timeout则会抛出IllegalArgumentException异常。
3.wait(long timeout, int nanos) 函数
在其内部调用的是wait(long timeout)函数,如下代码只有在nanos>0时才使参数timeout递增1。
public final void wait(long timeout, int nanos) throws InterruptedException {if (timeout < 0) {throw new IllegalArgumentException("timeout value is negative");}if (nanos < 0 || nanos > 999999) {throw new IllegalArgumentException("nanosecond timeout value out of range");}if (nanos > 0) {timeout++;}wait(timeout);}
4.notify() 函数
一个线程调用共享对象的notify()方法后,会唤醒一个在该共享变量上调用wait系列方法后被挂起的线程。一个共享变量上可能会有多个线程在等待,具体唤醒哪个等待的线程是随机的。
此外,被唤醒的线程不能马上从wait方法返回并继续执行,它必须在获取了共享对象的监视器锁后才可以返回,也就是唤醒它的线程释放了共享变量上的监视器锁后,被唤醒的线程也不一定会获取到共享对象的监视器锁,这是因为该线程还需要和其他线程一起竞争该锁,只有该线程竞争到了共享变量的监视器锁后才可以继续执行。
类似wait系列方法,只有当前线程获取到了共享变量的监视器锁后,才可以调用共享变量的notify()方法,否则会抛出IllegalMonitorStateException异常。
5.notifyAll() 函数
不同于在共享变量上调用notify()函数会唤醒被阻塞到该共享变量上的一个线程,notifyAll()方法则会唤醒所有在该共享变量上由于调用wait系列方法而被挂起的线程。
下面举一个例子来说明notify()和notifyAll()方法的具体含义及一些需要注意的地方,代码如下。
// 创建资源
private static volatile Object resourceA = new Object();
public static void main(String[] args) throws InterruptedException {// 创建线程Thread threadA = new Thread(new Runnable() {public void run() {// 获取resourceA共享资源的监视器锁synchronized (resourceA) {System.out.println("threadA get resourceA lock");try {System.out.println("threadA begin wait");resourceA.wait();System.out.println("threadA end wait");} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}});// 创建线程Thread threadB = new Thread(new Runnable() {public void run() {synchronized (resourceA) {System.out.println("threadB get resourceA lock");try {System.out.println("threadB begin wait");resourceA.wait();System.out.println("threadB end wait");} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}});// 创建线程Thread threadC = new Thread(new Runnable() {public void run() {synchronized (resourceA) {System.out.println("threadC begin notify");resourceA.notify();}}});// 启动线程threadA.start();
threadB.start();Thread.sleep(1000);threadC.start();// 等待线程结束threadA.join();threadB.join();threadC.join();System.out.println("main over");
}
输出结果如下:
注意观察就会发现,在这个当中,在最后时刻才出现的ThreadA end wait这样的一个结果,这是因为用打了resourceA.notify(),这个是很重要的一个内容在里面。
yield方法与sleep方法的区别
其实在写代码的过程当中,我们用到更多的还是sleep()这个方法,二者的区别是当线程调用sleep方法时调用线程会被阻塞挂起指定的时间,在这期间线程调度器不会去调度该线程。而调用yield方法时,线程只是让出自己剩余的时间片,并没有被阻塞挂起,而是处于就绪状态,线程调度器下一次调度到当前线程执行。
理解线程上下文切换
在多线程编程中,线程个数一般都是大于CPU的个数,而每个CPU同一时刻只能被一个线程使用,为了让多个用户感觉多个线程是在同时执行的,CPU资源的分配采用了时间片轮转的策略,也就是给发每个线程分配一个时间片,线程在时间片内占用CPU执行任务。线程上下文切换时机有:当前线程的CPU时间片使用完处于就绪状态时,当前线程被其他线程中断时。
ThreadLocal
当多个线程共同访问同一个变量的时候,可能会出现并发的问题,这个时候无非就是设置锁来使得线程能够达到安全,这就需要使用者对锁有一定的了解,这显然加重了使用者的负担。那么有没有一种方法可以做到,当创建一个变量之后,每个线程对其进行访问的时候访问的就是线程的变量呢?那么这个时候用到ThreadLocal就是一个很不错的方式。
public class ThreadLocalTest {//(1)print函数static void print(String str){//1.1 打印当前线程本地内存中localVariable变量的值System.out.println(str + ":" +localVariable.get());//1.2 清除当前线程本地内存中的localVariable变量//localVariable.remove();}//(2) 创建ThreadLocal变量static ThreadLocal<String> localVariable = new ThreadLocal<>();public static void main(String[] args) {//(3) 创建线程oneThread threadOne = new Thread(new Runnable() {public void run() {//3.1 设置线程One中本地变量localVariable的值localVariable.set("threadOne local variable");//3.2 调用打印函数print("threadOne");//3.3 打印本地变量值System.out.println("threadOne remove after" + ":" +localVariable.get());}});//(4) 创建线程twoThread threadTwo = new Thread(new Runnable() {public void run() {//4.1 设置线程Two中本地变量localVariable的值localVariable.set("threadTwo local variable");//4.2 调用打印函数print("threadTwo");//4.3 打印本地变量值System.out.println("threadTwo remove after" + ":" +localVariable.get());}});//(5)启动线程threadOne.start();threadTwo.start();}
运行结果如下。threadOne:threadOne local variable
threadTwo:threadTwo local variable
threadOne remove after:threadOne local variable
threadTwo remove after:threadTwo local variable
代码(2)创建了一个ThreadLocal变量。
代码(3)和(4)分别创建了线程One和Two。
代码(5)启动了两个线程。
线程One中的代码3.1通过set方法设置了localVariable的值,这其实设置的是线程One本地内存中的一个副本,这个副本线程Two是访问不了的。然后代码3.2调用了print函数,代码1.1通过get函数获取了当前线程(线程One)本地内存中localVariable的值。
线程Two的执行类似于线程One。
打开代码1.2的注释后,再次运行,运行结果如下。
threadOne:threadOne local variable
threadOne remove after:null
threadTwo:threadTwo local variable
threadTwo remove after:null
ava中根据某一特性定义的锁进行分类,
1、悲观锁与乐观锁
分类定义:根据线程要不要锁住同步资源
针对于同一并发数据操作
悲观锁:悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。Java中,synchronized关键字和Lock的实现类都是悲观锁。
乐观锁:乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作(例如报错或者自动重试)。
乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。
悲观锁:悲观锁适合写操作多的场景,先加锁报装数据的准确性
乐观锁:乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。
例:
针对乐观锁主要实现方式:CAS全称 Compare And Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。java.util.concurrent包中的原子类就是通过CAS来实现了乐观锁。
CAS算法涉及到三个操作数:
需要读写的内存值 V。
进行比较的值 A。
要写入的新值 B。
过程: 当且仅当 V 的值等于 A 时,CAS通过原子方式用新值B来更新V的值(“比较+更新”整体是一个原子操作),否则不会执行任何操作。一般情况下,“更新”是一个不断重试的操作。
根据OpenJDK 8的源码我们可以看出,getAndAddInt()循环获取给定对象o中的偏移量处的值v,然后判断内存值是否等于v。如果相等则将内存值设置为 v + delta,否则返回false,继续循环进行重试,直到设置成功才能退出循环,并且将旧值返回。整个“比较+更新”操作封装在compareAndSwapInt()中,在JNI里是借助于一个CPU指令完成的,属于原子操作,可以保证多个线程都能够看到同一个变量的修改值。
CAS虽然很高效,但是它也存在三大问题,这里也简单说一下:
- ABA问题。CAS需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是A,后来变成了B,然后又变成了A,那么CAS进行检查时会发现值没有发生变化,但是实际上是有变化的。ABA问题的解决思路就是在变量前面添加版本号,每次变量更新的时候都把版本号加一,这样变化过程就从“A-B-A”变成了“1A-2B-3A”。
JDK从1.5开始提供了AtomicStampedReference类来解决ABA问题,具体操作封装在compareAndSet()中。compareAndSet()首先检查当前引用和当前标志与预期引用和预期标志是否相等,如果都相等,则以原子方式将引用值和标志的值设置为给定的更新值。
-
循环时间长开销大。CAS操作如果长时间不成功,会导致其一直自旋,给CPU带来非常大的开销。
-
只能保证一个共享变量的原子操作。对一个共享变量执行操作时,CAS能够保证原子操作,但是对多个共享变量操作时,CAS是无法保证操作的原子性的。
Java从1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作。