福州网站开发一般多少钱,超可爱做头像的网站,沈阳网站制作 600元,做网站南宁一)Volitaile关键字的作用: 1)保证多线程环境下共享变量的可见性#xff0c;对于一个线程对于一个共享表变量的修改#xff0c;其他线程可以立即看到修改之后的共享变量的值 2)可以增加内存屏障来放置多个指令之间的重排序 volatile的使用:常常用于一写多读的情况下#xff… 一)Volitaile关键字的作用: 1)保证多线程环境下共享变量的可见性对于一个线程对于一个共享表变量的修改其他线程可以立即看到修改之后的共享变量的值 2)可以增加内存屏障来放置多个指令之间的重排序 volatile的使用:常常用于一写多读的情况下解决内存可见性和指令重排序 JAVA内存的JMM模型:主要是用来屏蔽不同硬件和操作系统的内存访问差异的在不同的硬件和不同的操作系统内存的访问是有差异的这种差异会导致相同的代码在不同的硬件和操作系统会有不同的行为JMM内存模型就是为了解决这个差异统一相同代码在不同硬件和不同操作系统的差异的 JAVA内存模型规定:所有的变量(包括普通成员变量和静态成员变量)都是必须存储在主内存里面每一个线程都有自己的工作内存线程的工作内存保存了该线程用到的变量和主内存的副本拷贝线程对变量的操作都在工作内存中进行线程是不可以直接读写主内存的变量 但是Java的内存模型会带来一个新的问题那就是说当某一线程修改了主内存共享变量的值之后那么其他线程可能就不会感知到此值被修改了它会一直使用工作内存的旧值这样程序的执行就不会符合我们的预期了 内存可见性:指的是多个线程同时进行操作同一个变量其中某一个线程修改了变量的值之后其他线程无法进行感知变量的修改这就是内存可见性问题 关键字volitaile和synchronized就可以强制保证接下来的操作是在操作内存在生成的java字节码中强制插入一些内存屏障的指令这些指令的效果就是强制刷新内存同步更新主内存和工作内存中的内容在牺牲效率的时候保证了准确性 synchronized双重ifvolatile 指令重排序是指编译器或者CPU优化程序的一种手段调整指令执行的先后顺序提高程序的执行性能但是在多线程情况下会出现问题 1)之前咱们在说volatile的时候是说此处的volatile是为了保证让其他线程修改了这里面的instance之后保证后面的线程可以及时感知到修改因为其他线程不也是加上synchronized来进行修改的吗? 2)当我们去执行instancenew instance()的时候我们本质上干了三件事情 2.1)创建内存 2.2)针对内存空间进行初始化 2.3)把内存的地址赋值给引用 3)上面的这三个步骤可能会触发指令重排序也就是说乱序执行这里的执行顺序可能是123也可能是132可能就是说把地址空间赋给引用了然后再进行初始化 咱们加上了volatile就可以保证这里面的指令就是按照1,2,3的顺序来进行执行的保证其他线程拿到的实例也是一个完整的实例 private Singleton(){};private static Singleton singletonnull;public static Singleton GetInstance(){if(singletonnull){synchronized(Object.class){if(singletonnull){singletonnew Singleton();}}}return singleton;}
} 单例模式适用于经常被访问的对象 或者是创建和销毁需要需要进行调用大量资源和时间的对象 1)创建一个私有的构造方法:防止外部直接new破坏单例模式 2)创建一个私有变量static保存该单例对象 3)提供公开的static方法返回单例对象 饿汉模式:在类加载的时候直接创建并进行初始化对象在程序启动的时候只进行加载一次 实现简单不存在线程安全问题但是因为类加载的时候就创建了该对象 创建之后如果没有进行使用那么就造成了资源浪费依赖的是classLoader机制 懒汉模式:延迟加载只有被使用的时候才会被初始化 枚举:在第一次被使用的时候才可以被JAVA虚拟机进行加载并初始化所以他也是线程安全并且是懒加载 enum TestEnum{//不要加classRED,Blue;//加上分号public static TestEnum GetInstance(){//返回类型是你自定义的类名,不是enumreturn RED;}
} 二)synchronized的底层实现原理: synchronized底层是通过JVM内置的监视器锁来实现的而监视器锁有是依靠于操作系统的底层mutex互斥量来实现的进入到synchronized修饰的代码相当于加了moniterenter结束synchronized修饰的代码相当于是moniterexit 监视器:监视器是一种机制用来进行保障任何时候都只有一个线程来进行执行指定区域的代码 1)一个监视器就类似于一个建筑建筑里面有一个特殊的房间这个房间同一时刻只能被一个线程所占有一个线程从进入到该房间到离开该房间可以全程占有该房间的所有数据; 2)进入该建筑叫做进入监视器进入该房间叫做获得监视器独自占有该房间叫做拥有监视器离开该房间叫做释放监视器离开该建筑叫做退出监视器 synchronized修饰的代码块进入到代码块被moniterenter然后退出代码块moniterexit 监视器锁就是类似于一个房间同一时刻只会允许一个人进来在任何时候都是只能有一个人进来是依靠ObjectMoniter实现的 1)_recursions是某一个线程某一次重复获取到锁的次数可重入锁代表某一个线程可以重复的获取锁因为synchronized是可重入锁线程是可以重复的获取到这把锁那么某一个线程每一次获取到锁的时候计数器就会记录该线程和获取到锁的次数每获取到一次锁进入到这个房间_recursions每当离开这个房间一次那么这个计数器就--当_recursions0的时候说明此时这个监视器是没有人的就放开房间让其他线程进入 2)count记录每一个线程获取到锁的次数就是前前后后这个这个线程一共获取这把锁多少次 3)_owner:The Owner的拥有者是持有该ObjectMonitor监视器对象的线程 4)_EntryList:EntryList监控集合存放的是处于阻塞状态的线程队列在多线程情况下竞争失败的线程会进入到EntryList阻塞队列 5)WaitSet:存放的是处于wait状态的线程队列当线程拥有监视器锁得时候调用到了wait()方法之后会自动释放监视器锁this.ownernull释放监视器锁的线程会进入到waitSet队列 监视器的执行流程如下: 1)线程通过CAS(对比并进行替换)尝试获取该锁如果获取成功那么将owner字段设置成当前线程表明该线程已经持有这把锁并将_recursions冲入次数的属性1如果获取失败就先通过自旋CAS来进行获取该锁如果还是失败那么就把当前线程放入到EntryList监测队列进入到阻塞状态 2)当拥有锁的线程执行了wait方法之后调用wait的线程释放锁将owner变量设置成null状态同时把该线程放入到waitSet带授权队列中等待被唤醒 3)当调用某一个拥有监视器锁的线程调用notify方法时随机唤醒WaitSet队列中的某一个线程来尝试获取锁等待拥有监视器锁的调用notify的线程释放锁后当调用notifyAll时随机唤醒所有WaitSet的队列的线程尝试获取该锁 4)当拥有监视器的线程执行完了释放锁之后会唤醒EntryList中所有线程尝试获取到该锁 wait方法也是可以指定休眠时间的比如说现在有两个线程线程1进入到了synchronized修饰的方法之后调用wait方法的那一刻线程1会放弃synchronzied的那把锁线程1从进入到waitting状态线程2获取到了同一把锁然后执行对象的notifyAll方法执行完线程2的synchronized方法之后线程2释放锁然后去尝试唤醒所有wait的线程然后所有的wait的线程都去尝试争夺这同一把锁但是如果是线程2调用的是notify方法然后其他wait的线程只会被唤醒一个然后尝试获取到锁执行 三)说一说synchronized锁升级的流程: 偏向锁指的是偏向某一个线程指的是所有的线程来了之后会进行判断对象头中的头部保存当前拥有的锁的线程ID判断当前线程ID是否等于_owner的线程ID等于说明你拥有这个线程就可以进入执行 1)无锁:刚一开始的时候没有线程访问synchronized修饰的代码说明此时是处于无锁状态 2)偏向锁:当某一个线程第一次访问同步代码块并获取到这把锁的时候锁的对象头里面将线程的ID记录下来下一次再有线程过来的时候程序会直接判断对象头中的线程ID(第一次访问锁的线程ID)和实际访问程序的线程ID是否相同如果是同一个那么程序会继续向下访问如果不相同说明有两个线程以上进行争夺锁于是尝试通过CAS获取到这把锁如果获取不到就升级成轻量级锁 3)轻量级锁:这个还没有放弃挣扎还会通过自旋的方式尝试得到锁如果通过一定的次数得不到锁因为synchronized是自适应自旋锁synchronized是根据上一次自旋的结果来去决定这一次自旋的次数的如果这个线程是通过上一次自旋来获取到锁的话那么会有极大的大概率这一次也是可能通过自旋的方式来获取到锁的如果上一次获取次数也比较少那么这一次自旋的次数也会变少如果一定的自旋次数获取不到锁直接阻塞到EntryList 4)重量级锁:升级成重量级锁 四)synchronized是固定自旋次数吗 synchronized本身是一个自适应自旋锁自适应自旋锁指的是线程尝试获取到锁的次数不是一个固定值而是一个动态变化的值这个值会根据前一次线程自旋的次数获取到锁的状态来决定此次自选的次数比如说上一次通过自选成功的获取到了锁那么synchronized会自动判断通过这一次自旋获取到锁的概率也会大一些那么这一次自旋的次数就会多一些如果通过上一次自旋没有成功获取到锁那么这一次成功获取到锁的概率也会变得非常低所以为了避免资源的浪费就会少循环或者是不循环简单来说就是如果这一次自旋成功了下一次自旋的次数会多一些否则下一次自选的次数会少一些 五)线程通讯的方法都有哪些 线程通讯指的是多个线程之间通过某一种机制进行协调和交互例如线程等待和通知机制就是线程通讯的主要手段之一就是一个线程休眠了另外一个线程进行唤醒每一个等待唤醒的手段都是有着不同的应用场景下一个唤醒手段就是上一个唤醒手段的补充 1)wait和notify使用必须和synchronized搭配一起使用况且wait会主动释放锁 2)可以唤醒加了同一把锁下面的两个不同的线程组Condition可以有更多的分支能唤醒的更加精准每一组线程都可以使用一个Condition来进行等待和唤醒生产者不要唤醒生产者消费者不要唤醒消费者在生产者里面可以调用消费者的Condition2进行唤醒 3)可以指定某一个线程来唤醒LockSupport.park()休眠当前线程park和unpark本身就是静态方法LockSupport.unpark(线程对象)LockSupport可以不搭配synchronized和lock来结合使用这里面得park方法那个线程调用LockSupport.park()方法拿一个线程就会阻塞 2)一个lock可以创建多个Conidtion此时就可以调用Condition的await()方法和signal()方法 一个Lock可以创建多个Condition对象搞一个Condition叫做生产者再Condition搞一个叫做消费者可以有更多的分支唤醒就变的更加的精准每一组线程可以使用一个Condition来进行等待和唤醒的操作分两组绑定Condition 2.1)一堆生产者可以使用一个Condtion对象1来进行唤醒可以使用Condition对象1调用await()方法进行休眠生产者如果想要唤醒生产者就可以调用Condition对象1的signal来唤醒生产者 2.2)一堆消费者可以使用一个Condtion对象2来进行唤醒可以使用Condition对象2调用await()方法进行休眠消费者如果想要唤醒消费者就可以调用Condition对象1的signal来唤醒消费者 2.3)但是生产者和消费者加的都是同一把锁这样使用Condition类就可以唤醒加了同一把锁的两组线程进行唤醒了可以指定的某一组线程中的某一个线程进行唤醒 但是两堆生产者和消费者都是加的同一把锁所以就可以根据哪一个Condition对象来唤醒的是生产者还是消费者也是随机唤醒但是也是可以指定唤醒那一组是生产者还是消费者但是wait和notify一个锁一个对象只能有一组同时生产者也是可以调用消费者的一个Condition进行唤醒了 1)现在有一个生产者消费者模型生产者会产生一些任务存放到任务队列中消费者是从任务队列中取出任务进行消费执行生产者和消费者都是一组线程 2)没有任务生产者休眠为了保证资源不被浪费消息队列没有任务消费者也会休眠假设生产者线程组的某一个生产者有任务开始就开始被唤醒将任务放到消息队列里面此时被唤醒的生产者将任务推动到消息队列里面第二步就是休眠唤醒消费者去消费任务如果此时使用的是Object中的唤醒机制是将加了锁的线程随机唤醒此时就会发生严重的问题此时可能唤醒的是生产者和消费者因为生产者和消费者加的是同一把锁如果是唤醒的是生产者此时会浪费资源可能会导致消费者永远也不会消费消息队列中的元素 public class DemoWorld {public static void main(String[] args) throws InterruptedException {Thread t1new Thread(()-{System.out.println(线程1开始阻塞);LockSupport.park();System.out.println(线程1继续执行);});Thread t2new Thread(()-{System.out.println(线程2开始阻塞);LockSupport.park();System.out.println(线程2继续执行);});Thread t3new Thread(()-{LockSupport.unpark(t1);});t1.start();t2.start();Thread.sleep(3000);t3.start();}
} 六)读写锁:创建读写锁,提高程序的执行性能适用于读多写少 读写锁是将一把锁分成两部分读锁和写锁读锁是允许多个线程同时获得的因为读操本身就是线程安全的而写锁是互斥锁是不允许多个线程同时获得些写锁的况且写操作和读操作也是互斥的读读不互斥写写互斥读写互斥 1)提高了程序执行的性能多个读锁可以同时进行相对于普通锁来说在任何情况下都要排队执行来说读写锁提高了并发程序的执行性能 2)避免读到临时数据读锁和写锁是互斥排队执行的这样就保证了读取操作不会读到写一半的临时数据 多个线程获取到读锁称之为读读不互斥一个线程不能同时获取到读锁和写锁写锁和写锁之间进行互斥 1)读读不互斥 public static void main(String[] args) throws InterruptedException {final ReentrantReadWriteLock commonLocknew ReentrantReadWriteLock();final ReentrantReadWriteLock.ReadLock readLockcommonLock.readLock();//获取到读写锁中的读锁final ReentrantReadWriteLock.WriteLock writeLock commonLock.writeLock();//获取到读写锁中的写锁Thread t1new Thread(()-{try {readLock.lock();System.out.println(线程1获取到了读锁);}finally {readLock.unlock();System.out.println(线程1释放了读锁);}});Thread t2new Thread(()-{try {readLock.lock();System.out.println(线程2获取到了读锁);}finally {readLock.unlock();System.out.println(线程2释放了读锁);}});t1.start();t2.start();} 2)读写互斥可以看到一个线程不能同时获取到读写锁中的读锁和写锁 public static void main(String[] args) throws InterruptedException {final ReentrantReadWriteLock commonLocknew ReentrantReadWriteLock();final ReentrantReadWriteLock.ReadLock readLockcommonLock.readLock();//获取到读写锁中的读锁final ReentrantReadWriteLock.WriteLock writeLock commonLock.writeLock();//获取到读写锁中的写锁Thread t1new Thread(()-{try {writeLock.lock();System.out.println(线程1获取到了写锁);}finally {writeLock.unlock();System.out.println(线程1释放了写锁);}});Thread t2new Thread(()-{try {readLock.lock();System.out.println(线程2获取到了读锁);}finally {readLock.unlock();System.out.println(线程2释放了读锁);}});t1.start();t2.start();} public class DemoWorld {public static void main(String[] args) throws InterruptedException {final ReentrantReadWriteLock commonLocknew ReentrantReadWriteLock();final ReentrantReadWriteLock.ReadLock readLockcommonLock.readLock();//获取到读写锁中的读锁final ReentrantReadWriteLock.WriteLock writeLock commonLock.writeLock();//获取到读写锁中的写锁Thread t1new Thread(()-{try {writeLock.lock();System.out.println(线程1获取到了写锁);}finally {writeLock.unlock();System.out.println(线程1释放了写锁);}});Thread t2new Thread(()-{try {writeLock.lock();System.out.println(线程2获取到了写锁);}finally {writeLock.unlock();System.out.println(线程2释放了写锁);}});t1.start();t2.start();}
} 七)公平锁和非公平锁有什么区别 公平锁:每一个线程获取到锁的的顺序总是按照线程访问锁的先后顺序来进行获取的最前面访问锁的那个线程总是能最先获取到锁 非公平锁:每一个线程获取锁的顺序是随机的并不会遵循先来后到的规则所有线程会竞争并获取锁 公平锁的运行原理: 3.1)获取到锁的时候先将线程自己添加到等待队列的队尾并休眠当某线程用完锁之后会先唤醒等待队列的队首的线程去获取到锁锁的使用顺序就是队列中的先后顺序在整个过程中线程会从运行状态切换成休眠状态再从休眠状态变成运行状态 3.2)在整个过程中线程每一次休眠和恢复都需要进行用户态和内核态的切换这个状态的转换是比较慢的更注重的是资源的平均分配但是按序唤醒线程的开销比较大所以公平锁执行效率比较慢 非公平锁的运行原理: 4.1)当线程尝试获取到锁的时候会先通过CAS来进行尝试获取到锁如果获取到锁就直接拥有锁如果锁获取失败就进入到阻塞队列等待下一次获取到锁获取到锁不用遵循先来后到的规则避免线程恢复和休眠的操作加速了程序的执行效率不用遵循先来先到的规则 4.2)非公平锁的吞吐率(单位时间内获取到锁的速率)要比公平锁的概率更高但是可能会出现线程饿死的情况资源分配随机性比较强非公平锁性能更高非公平锁可能出现线程饥饿的情况 八)JUC包下面的Exchange交换器:实现两个线程之间的数据交换的 1)exchange(V x):等待另一个线程到达此交换点然后将对象传输给另一个线程并从另一个线程中得到交换的对象如果另一个线程未到达交换点那么调用exchange得线程会一直进行休眠除非遇到了线程中断 2)exchange(V xlong timeoutTimeunit unit):等待另一个线程到达交换点然后将这个对象传输给另一个线程了并从另一个线程中得到要交换的对象如果说另一个线程未达到次交换点那么此线程会一直进行休眠直到遇到了线程中断或者等待的时间超过了设定的时间那么会直接抛出异常 3)也就是说exchange方法到达了一个交换点之后线程会在这个交换点进行休眠等待直到另一个线程也调用了exchange方法他们会进行相互交换数据然后会执行后续的代码 4)Exchange是用来实现两个线程之间的数据交换的它可以进行传输任意类型的数据只需要在进行创建的时候定义泛型类型就可以了它的核心方法是exchange方法 当线程执行到这个方法之后当前线程会执行休眠操作会进行等待另一个线程进行这个交换点如果说另一个线程进入到了交换点那么两者会进行交换数据并执行接下来的流程 class Person{public String username;public String desc;
}
public class DemoWorld {public static void main(String[] args) throws InterruptedException {ExchangerPerson exchangernew Exchanger();Thread t1new Thread(()-{Person person1new Person();person1.username线程1;person1.desc我是在t1线程创建的,现在过得很好;try {Thread.sleep(1000);Person personexchanger.exchange(person1);//此时交换完成之后获取到了线程2的person2Thread.sleep(1000);System.out.println(当前打印的线程是Thread.currentThread().getName()person.usernameperson.desc);} catch (InterruptedException e) {e.printStackTrace();}},线程1);Thread t2new Thread(()-{Person person2new Person();person2.username线程1;person2.desc我是在t1线程创建的,现在过得很好;try {Thread.sleep(1000);Person personexchanger.exchange(person2);//此时交换之后获取到了线程1中的person1Thread.sleep(1000);System.out.println(当前打印的线程是Thread.currentThread().getName()person.usernameperson.desc);} catch (InterruptedException e) {e.printStackTrace();}},线程2);t1.start();t2.start();}
}九)进程和线程有什么区别 为什么进程之间是相互独立的不能访问相互的资源和文件 为什么屏蔽进程之间内存的获取和共享有一些敏感的进程不能让其他非当前进程来进行访问比如说我打开一个进程一个工商银行不能让其他进程能访问我的私密信息这就是为什么进程之间不可以相互访问保护隐私进程就好比一家公司线程就是公司里面的一个一个的员工 上下文状态优先级记账信息不共享操作系统的调度器会非常频繁的进行线程切换哪怕某个进程做某个工作做了一半也有可能被打断 单个CPU已经达到极限了多核CPU代替单核CPU 在代码执行任务的时候先把任务进行拆分又有多个CPU来并发式的执行 什么情况下会造成线程从用户态到内核态的切换呢 1)首先如果在程序运行过程中发生中断或者异常系统将自动切换到内核态来运行中断或异常处理机制 2)此外程序进行系统调用也会从用户态切换到内核态 1)进程包含线程如果将进程比作工厂那么线程就是工厂中的若干流水线 2)创建线程比创建进程更轻量销毁线程比销毁进程更轻量调度线程比调度进程更轻量 3)切换速度不同:线程切换上下文速度是很快的但是进程的上下文切换速度比较慢 4)操作系统创建进程要给进程分配资源进程是操作系统进行资源分配的最小单位操作系统创建的线程是要在CPU上面进行调度执行线程是操作系统进行调度执行的最小单位 5)进程具有独立性进程与进程之间资源不共享每一个进程都有自己的虚拟地址空间同一个进程的多个线程之间共用这一块虚拟地址空间一个进程挂了不会影响到其他进程但是同一个进程的多个线程是在用同一个虚拟内存空间一个线程挂了是可能影响到其他线程的甚至可能会导致整个进程崩溃 十)start和run方法有什么区别 run只是一个普通的方法描述了任务的内容start是一个特殊的方法会在系统中创建线程 1)方法性质不同:调用start方法可以直接启动线程并使线程进入就绪当run方法执行完了线程也就结束了但是如果直接执行run方法会当作普通方法来调用还是在main方法进行的不会创建一个新线程 2)执行速度不同:run方法也叫作线程体它里面包含了具体要执行的业务代码当进行调用run方法的时候会立即执行run方法的代码但是当我们调用start方法的时候本质上是启动了一个线程并将这个线程的状态设置为就绪状态也就是说调用start()方法程序不会立即执行 3)调用次数不同:run方法是普通方法普通方法是可以被调用多次但是start方法是创建新线程执行任务而start方法只能调用一次否则就会出现IllegalThreadStateException非法线程状态 Start()方法会改变线程的状态从NEW状态编程running状态futureTask属于同步阻塞 为什么start方法只能调用一次呢 原因是当start代码实现的第一行会先进行判断当前的状态是不是0也就是说是否是新建状态如果不是新建状态NEW那么就会抛出IllegalThreadStateException非法线程状态异常 当线程调用了第一个start方法之后线程的状态就会由新建状态NEW变成RUNNABLE状态此时再次调用start方法JVM就会判断当前线程已经不等于新建状态了从而会抛出IllegalThreadStateException异常所以线程状态是不可逆的 public static void main(String[] args) throws IOException, InterruptedException {Thread threadnew Thread(){public void run(){System.out.println(Thread.currentThread().getName()正在执行);}};thread.start();//thread.run()thread.sleep(1000);System.out.println(main线程正在执行);} 十二)synchronized的三种用法 1)修饰普通方法:加在访问修饰限定符方法返回值之间 public synchronized void method(){};修饰普通方法作用的对象是调用这个方法的对象 2)修饰静态方法:public static synchronized void staticMethod{};当synchronized修饰静态方法的时候锁的是类对象这个锁对于所有调用这个锁的对象都是互斥的:注意当修饰静态方法的时候所有调用这个静态方法的对象都是互斥的但是普通方法是指对对象级别的不同的对象有着不同的锁 3)修饰代码块:在我们的日常开发中最常用的是给代码块加锁而不是给方法进行加锁因为给方法进行加锁相当于是给整个方法全部进行加锁这样的话锁的粒度就太大了程序的执行性能就会受到影响加锁的对象常用this或者xxx.class这样的形式来进行表示 十三)线程的中断: 线程的中断核心就是让线程的入口函数也就是run方法执行完毕它指的是内存中的线程结束了而不一定是Thread对象销毁 在JAVA中停止线程有三种方法: 1)使用标志位在程序的执行代码中使用一个标志位来控制程序的执行当标志位是true的时候线程可以继续执行当标志位是false的时候线程退出循环或者执行完任务之后停止所以可以通过设置标志位来停止线程的执行缺点就是线程中断的不够及时因为在线程执行过程中无法调用while(flag)来及时判断线程是否处于终止状态只能在下一轮中进行判断是否要终止当前线程所以中断线程不及时 2)调用interrupt方法来中断线程的执行当线程被中断的时候线程本身会受到一个中断信号可以在代码中检查线程的中断状态并进行处理线程在接收到中断指令之后立即中断了线程相比于上一种自定义中断标识符的方法来说它能更及时的响应中断线程指令 3)stop方法但是现在已经是一种被弃用的方法了是一个非安全的方法因为他可能会导致线程的资源不会被正确的释放可能会导致资源泄露等问题 十四)wait和sleep有什么区别 wait方法和sleep方法都是用来将线程进入到休眠状态的并且咱们的sleep方法和wait方法都是可以响应interrupt中断也就是说在线程进行休眠的过程中如果收到interrupt的中断信号都可以进行响应并进行中断并且都可以抛出InterruptedException异常 1)wait 方法属于 Object 类的方法而 sleep 属于 Thread 类的方法 2)语法使用不同wait必须和synchronized一起进行搭配使用否则就会抛出IIIegalMonitorStateException异常而sleep无需和synchronized一起使用 Object objectnew Object();System.out.println(wait前);object.wait();System.out.println(wait执行完成之后); Exception in thread main java.lang.IllegalMonitorStateException 3)wait会自动进行释放锁调用wait的线程会主动进入到waitset队列里面但是sleep不会主动释放锁sleep在休眠状态并不会释放锁 4)调用sleep方法会自动进入到time-waitting状态但是调用wait方法会进入到waitting状态 5)等待机制:sleep是指定一个固定的时间去进行阻塞等待wait既可以指定时间又可以无限进行等待 6)唤醒机制:wait唤醒是可以通过notify机制或者interrupt或者时间到来进行唤醒sleep通过时间到或者interrupt来唤醒 7)方法设计初衷:wait的作用主要是为了协调线程之间的先后顺序这样的场景并不适合sleepsleep只是为了让线程休眠并不会涉及到多个线程之间的配合 public static void main(String[] args) {Object lockernew Object();Thread t1new Thread(()-{synchronized (locker){try {System.out.println(线程1获取到了锁,开始调用wait方法等待);locker.wait();System.out.println(线程1结束等待又重新获取到了锁);} catch (InterruptedException e) {e.printStackTrace();}}});Thread t2new Thread(()-{System.out.println(请输入一个整数);Scanner scannernew Scanner(System.in);int numscanner.nextInt();synchronized (locker){locker.notify();}});t1.start();t2.start();} public static void main(String[] args) {Object objectnew Object();Thread t1new Thread(()-{synchronized (object){try {object.wait();} catch (InterruptedException e) {e.printStackTrace();}}});}
1)在上面的这个代码中,wait那个对象,就需要针对哪一个对象来进行加锁
2)我们synchronized锁住的对象和调用wait的对象是一样子的 十五)wait操作为什么是原子的wait和notify为什么一定要搭配synchronized进行使用 synchronized加锁的对象和调用wait和notify的对象必须是一致 wait的操作一共有三步: 1)wait主动释放当前线程持有的锁 2)等待当前线程被唤醒 3)wait不断去尝试重新获取到锁 上面的这个过程没有任何问题但是下面这个过程就会导致线程1一直阻塞 1)是为了防止多线程并发过程中程序的执行混乱问题 2)咱们现在来进行实现一个阻塞队列假设wait方法和notify方法不需要进行加锁操作当我们进行读取数据的时候如果有数据就会进行返回数据没有数据就会阻塞等待数据实现代码如下: class MyBlockingQueue{QueueString queuenew LinkedList();public void put(String data){//像咱们的阻塞队列里面加入数据queue.add(data);//唤醒线程继续向下执行,此时的唤醒是指唤醒take方法的线程notify();}public String take() throws InterruptedException{while(queue.isEmpty()){wait();}return queue.remove();}} 上面的程序执行过程分成三步: 1)线程1执行take方法首先判断当前队列中是否存在数据 2)如果说当前队列中没有数据那么执行wait休眠操作(在while循环里面进行执行wait操作等待进行唤醒) 3)线程2给队列中添加元素并唤醒线程1继续执行 1)上述执行流程是有问题的假设线程1执行完take方法的时候进行判断队列为空刚要执行wait操作进行休眠但是此时线程2执行put操作突然进行添加数据 2)然而之前线程1已经执行完判断了所以就会直接进入到休眠状态此时线程1一直进行wait操作此时况且线程2进行插入的数据永远不能被线程1读取那么就会造成程序并发执行导致执行结果混乱的问题会导致线程一直进行休眠的问题 十六)为什么wait被定义在Object类中而sleep定义在Thread类中 1)因为JAVA中每一个对象上都有一把监视器锁因为每一个对象都可以上锁这就要求在对象头上要求有一个用来保存锁信息的位置因为这个锁是对象级别的而不是线程级别的waitnotify都是针对于锁级别的操作他们的锁属于对象所以定义在Object类中最合适因为Object是所有对象的父类因为如果把wait/notify/notifyAll方法定义在Thread类中会带来很大的局限性比如一个线程可能持有多把锁以便实现相互配合的复杂逻辑假设此时wait方法定义到Thread类中既然我们是让当前线程去等待某个对象的锁自然应该通过操作对象来实现而不是操作线程2)让某个线程暂停运行一段时间,其控制范围是由当前线程决定,也就是说,在线程里面决定 两者都可以让线程暂停一段时间但是本质的区别是一个线程的运行状态控制一个是线程之间的通讯的问题 十七)sleep和yield有什么区别 sleep和yield都是Thread类中的静态方法yield是暂停当前执行的线程对象就是放弃当前拥有的CPU资源并执行其他线程就是让当前运行的线程回到就绪状态也就是可执行状态就是以保证相同优先级的状态具有执行机会 1)sleep()方法给其他线程运行机会时不考虑线程的优先级因此会给优先级低的线程以运行的机会而yield()方法只会给相同优先级或者更高优先级的线程以运行机会yield()虽然不会经常用到让线程主动让出CPU但是不会改变线程的状态 2)线程执行sleep()方法后会转入阻塞状态所以执行sleep()方法的线程在指定的时间内肯定不会被执行而yield()方法只是使当前线程重新回到可执行状态所以执行yield()方法的线程有可能在进入到可执行状态后马上又被执行。 3)sleep()方法声明抛出InterruptedException而yield()方法没有声明任何异常; 线程休眠:sleep 操作系统是如何管理进程的是通过一个类来进行描述的通过一个双向链表来进行组织的这个说法可以是说针对只有一个线程的进程是如此的但是如果说一个进程里面有多个线程每一个线程里面都有一个PCB一个进程对应的就是一组PCB 1)咱们的上面的这个双向链表属于就绪队列但是在我们的操作系统内核里面这样的队列却不是有多个如果某一个线程调用了sleep方法这个PCB就会进入到阻塞队列 2)咱们的操作系统进行调度线程的时候就是从就绪队列里面挑选合适的PCB到CPU上面运行那么我们阻塞队列里面的PCB就只能干等着啥时候这个PCB可以回到就绪队列里面呢那么只有说睡眠时间到了咱们的系统才会把刚才的这个PCB从阻塞队列挪回到就绪队列里面 3)只有就绪队列里面的PCB才有被调度上CPU执行的权力阻塞队列里面的PCB没有资格进入到CPU上面执行 4)一个进程对应的就是一组PCB每一个PCB上面就有一个字段叫做tgroupID这个ID其实就是进程的tgroupID同一个进程中的若干个线程的tgroupID其实是相同linux的系统内核是不区分进程和线程的linux内核只认PCB进程和线程其实是咱们程序员写有关于应用程序的代码才搞出来的词实际上linux内核只认PCB在linux系统内核里面我们把线程称之为轻量级进程只不过是有些PCB共用同一个内存有些PCB共用同一块虚拟地址空间有些PCB有不同的虚拟地址空间前面所说的进程线程概念是站在一个更加抽象的角度站在用户写代码的角度来进行看待的但在操作系统内核实现的角度是一视同仁使用同样的方式来进行表述的 5)咱们的上面的这个链表就是就绪队列的双向链表如果我们的某个线程调用了sleep方法这个PCB就会进入到阻塞队列实际上咱们的操作系统在进行调度线程的时候就是从我们的就绪队列中查找合适的PCB到我们的CPU上面执行当我们的睡眠时间到了系统就会把刚才这个PCB从阻塞队列挪回到就绪队列join也会导致线程进入到阻塞队列里面 使用JAVA来进行打印线程的所有状态: public class Teacher {public static void main(String[] args) {for(Thread.State value:Thread.State.values()){System.out.println(value);}}
}也就是说这些状态是JAVA自己搞出来的就和操作系统中的PCB的状态没有啥关系 public static void main(String[] args)throws InterruptedException{Thread t1new Thread(){public void run() {for(int i0;i10;i){// System.out.println(Thread.currentThread().getName());}try{sleep(100);}catch(InterruptedException e){e.printStackTrace();;}System.out.println(线程结束);}};System.out.println(t1.getName());//获取线程名字System.out.println(t1.getPriority());//获线程优先级System.out.println(t1.isDaemon());//该线程是否为守护线程System.out.println(t1.getId());System.out.println(t1.isAlive());System.out.println(t1.isInterrupted());System.out.println(t1.getState());//获取到指定线程的状态t1.start();while(t1.isAlive()){System.out.println(t1.getState());System.out.println(t1.isInterrupted());}System.out.println(t1.getState());}} 十八)线程的状态有哪些