成都建站推广,公司官网制作报价,化妆品网站的设计与实现,建设网站的分析1、说说进程,线程,协程之间的区别 简而言之,进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程.进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高.线程是进程的一个实体,是cpu调度和分派的基本单位,是比… 1、说说进程,线程,协程之间的区别 简而言之,进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程.进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高.线程是进程的一个实体,是cpu调度和分派的基本单位,是比程序更小的能独立运行的基本单位.同一进程中的多个线程之间可以并发执行. 2、你了解守护线程吗?它和非守护线程有什么区别 程序运行完毕,jvm会等待非守护线程完成后关闭,但是jvm不会等待守护线程.守护线程最典型的例子就是GC线程 3、什么是多线程上下文切换 多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。 4、创建两种线程的方式?他们有什么区别? 通过实现java.lang.Runnable或者通过扩展java.lang.Thread类.相比扩展Thread,实现Runnable接口可能更优.原因有二: Java不支持多继承.因此扩展Thread类就代表这个子类不能扩展其他类.而实现Runnable接口的类还可能扩展另一个类.类可能只要求可执行即可,因此继承整个Thread类的开销过大. 5、Thread类中的start()和run()方法有什么区别? start()方法被用来启动新创建的线程而且start()内部调用了run()方法这和直接调用run()方法的效果不一样。当你调用run()方法的时候只会是在原来的线程中调用没有新的线程启动start()方法才会启动新线程。 6、怎么检测一个线程是否持有对象监视器 Thread类提供了一个holdsLock(Object obj)方法当且仅当对象obj的监视器被某条线程持有的时候才会返回true注意这是一个static方法这意味着”某条线程”指的是当前线程。 7、Runnable和Callable的区别 Runnable接口中的run()方法的返回值是void它做的事情只是纯粹地去执行run()方法中的代码而已Callable接口中的call()方法是有返回值的是一个泛型和Future、FutureTask配合可以用来获取异步执行的结果。 这其实是很有用的一个特性因为多线程相比单线程更难、更复杂的一个重要原因就是因为多线程充满着未知性某条线程是否执行了某条线程执行了多久某条线程执行的时候我们期望的数据是否已经赋值完毕无法得知我们能做的只是等待这条多线程的任务执行完毕而已。而CallableFuture/FutureTask却可以方便获取多线程运行的结果可以在等待时间太长没获取到需要的数据的情况下取消该线程的任务 8、什么导致线程阻塞 阻塞指的是暂停一个线程的执行以等待某个条件发生如某资源就绪学过操作系统的同学对它一定已经很熟悉了。Java 提供了大量方法来支持阻塞下面让我们逐一分析。 |方法 | 说明| |------|------| |sleep()| sleep() 允许 指定以毫秒为单位的一段时间作为参数它使得线程在指定的时间内进入阻塞状态不能得到CPU 时间指定的时间一过线程重新进入可执行状态。 典型地sleep() 被用在等待某个资源就绪的情形测试发现条件不满足后让线程阻塞一段时间后重新测试直到条件满足为止| |suspend() 和 resume() |两个方法配套使用suspend()使得线程进入阻塞状态并且不会自动恢复必须其对应的resume() 被调用才能使得线程重新进入可执行状态。典型地suspend() 和 resume() 被用在等待另一个线程产生的结果的情形测试发现结果还没有产生后让线程阻塞另一个线程产生了结果后调用 resume() 使其恢复。| |yield() |yield() 使当前线程放弃当前已经分得的CPU 时间但不使当前线程阻塞即线程仍处于可执行状态随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程| |wait() 和 notify() |两个方法配套使用wait() 使得线程进入阻塞状态它有两种形式一种允许 指定以毫秒为单位的一段时间作为参数另一种没有参数前者当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态后者则必须对应的 notify() 被调用.| 9、wait(),notify()和suspend(),resume()之间的区别 初看起来它们与 suspend() 和 resume() 方法对没有什么分别但是事实上它们是截然不同的。区别的核心在于前面叙述的所有方法阻塞时都不会释放占用的锁如果占用了的话而这一对方法则相反。上述的核心区别导致了一系列的细节上的区别。 首先前面叙述的所有方法都隶属于 Thread 类但是这一对却直接隶属于 Object 类也就是说所有对象都拥有这一对方法。初看起来这十分不可思议但是实际上却是很自然的因为这一对方法阻塞时要释放占用的锁而锁是任何对象都具有的调用任意对象的 wait() 方法导致线程阻塞并且该对象上的锁被释放。而调用 任意对象的notify()方法则导致从调用该对象的 wait() 方法而阻塞的线程中随机选择的一个解除阻塞但要等到获得锁后才真正可执行。 其次前面叙述的所有方法都可在任何位置调用但是这一对方法却必须在 synchronized 方法或块中调用理由也很简单只有在synchronized 方法或块中当前线程才占有锁才有锁可以释放。同样的道理调用这一对方法的对象上的锁必须为当前线程所拥有这样才有锁可以释放。因此这一对方法调用必须放置在这样的 synchronized 方法或块中该方法或块的上锁对象就是调用这一对方法的对象。若不满足这一条件则程序虽然仍能编译但在运行时会出现IllegalMonitorStateException 异常。 wait() 和 notify() 方法的上述特性决定了它们经常和synchronized关键字一起使用将它们和操作系统进程间通信机制作一个比较就会发现它们的相似性synchronized方法或块提供了类似于操作系统原语的功能它们的执行不会受到多线程机制的干扰而这一对方法则相当于 block 和wakeup 原语这一对方法均声明为 synchronized。它们的结合使得我们可以实现操作系统上一系列精妙的进程间通信的算法如信号量算法并用于解决各种复杂的线程间通信问题。 关于 wait() 和 notify() 方法最后再说明两点 第一调用 notify() 方法导致解除阻塞的线程是从因调用该对象的 wait() 方法而阻塞的线程中随机选取的我们无法预料哪一个线程将会被选择所以编程时要特别小心避免因这种不确定性而产生问题。 第二除了 notify()还有一个方法 notifyAll() 也可起到类似作用唯一的区别在于调用 notifyAll() 方法将把因调用该对象的 wait() 方法而阻塞的所有线程一次性全部解除阻塞。当然只有获得锁的那一个线程才能进入可执行状态。 谈到阻塞就不能不谈一谈死锁略一分析就能发现suspend() 方法和不指定超时期限的 wait() 方法的调用都可能产生死锁。遗憾的是Java 并不在语言级别上支持死锁的避免我们在编程中必须小心地避免死锁。 以上我们对 Java 中实现线程阻塞的各种方法作了一番分析我们重点分析了 wait() 和 notify() 方法因为它们的功能最强大使用也最灵活但是这也导致了它们的效率较低较容易出错。实际使用中我们应该灵活使用各种方法以便更好地达到我们的目的。 11、产生死锁的条件 1.互斥条件一个资源每次只能被一个进程使用。 2.请求与保持条件一个进程因请求资源而阻塞时对已获得的资源保持不放。 3.不剥夺条件:进程已获得的资源在末使用完之前不能强行剥夺。 4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。 12、为什么wait()方法和notify()/notifyAll()方法要在同步块中被调用 这是JDK强制的wait()方法和notify()/notifyAll()方法在调用前都必须先获得对象的锁 wait()方法和notify()/notifyAll()方法在放弃对象监视器时有什么区别 wait()方法和notify()/notifyAll()方法在放弃对象监视器的时候的区别在于wait()方法立即释放对象监视器notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器。 13、wait()与sleep()的区别 关于这两者已经在上面进行详细的说明,这里就做个概括好了: sleep()来自Thread类和wait()来自Object类.调用sleep()方法的过程中线程不会释放对象锁。而 调用 wait 方法线程会释放对象锁sleep()睡眠后不出让系统资源wait让其他线程可以占用CPUsleep(milliseconds)需要指定一个睡眠时间时间一到会自动唤醒.而wait()需要配合notify()或者notifyAll()使用 14、为什么wait,nofity和nofityAll这些方法不放在Thread类当中 一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的每个对象都有锁通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中线程正在等待的是哪个锁就不明显了。简单的说由于waitnotify和notifyAll都是锁级别的操作所以把他们定义在Object类中因为锁属于对象。 15、怎么唤醒一个阻塞的线程 如果线程是因为调用了wait()、sleep()或者join()方法而导致的阻塞可以中断线程并且通过抛出InterruptedException来唤醒它如果线程遇到了IO阻塞无能为力因为IO是操作系统实现的Java代码并没有办法直接接触到操作系统。 16、什么是多线程的上下文切换 多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。 17、synchronized和ReentrantLock的区别 synchronized是和if、else、for、while一样的关键字ReentrantLock是类这是二者的本质区别。既然ReentrantLock是类那么它就提供了比synchronized更多更灵活的特性可以被继承、可以有方法、可以有各种各样的类变量ReentrantLock比synchronized的扩展性体现在几点上 1ReentrantLock可以对获取锁的等待时间进行设置这样就避免了死锁 2ReentrantLock可以获取各种锁的信息 3ReentrantLock可以灵活地实现多路通知 另外二者的锁机制其实也是不一样的:ReentrantLock底层调用的是Unsafe的park方法加锁synchronized操作的应该是对象头中mark word. 18、FutureTask是什么 这个其实前面有提到过FutureTask表示一个异步运算的任务。FutureTask里面可以传入一个Callable的具体实现类可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。当然由于FutureTask也是Runnable接口的实现类所以FutureTask也可以放入线程池中。 19、一个线程如果出现了运行时异常怎么办? 如果这个异常没有被捕获的话这个线程就停止执行了。另外重要的一点是如果这个线程持有某个某个对象的监视器那么这个对象监视器会被立即释放 20、Java当中有哪几种锁 自旋锁: 自旋锁在JDK1.6之后就默认开启了。基于之前的观察共享数据的锁定状态只会持续很短的时间为了这一小段时间而去挂起和恢复线程有点浪费所以这里就做了一个处理让后面请求锁的那个线程在稍等一会但是不放弃处理器的执行时间看看持有锁的线程能否快速释放。为了让线程等待所以需要让线程执行一个忙循环也就是自旋操作。在jdk6之后引入了自适应的自旋锁也就是等待的时间不再固定了而是由上一次在同一个锁上的自旋时间及锁的拥有者状态来决定偏向锁: 在JDK1.之后引入的一项锁优化目的是消除数据在无竞争情况下的同步原语。进一步提升程序的运行性能。 偏向锁就是偏心的偏意思是这个锁会偏向第一个获得他的线程如果接下来的执行过程中改锁没有被其他线程获取则持有偏向锁的线程将永远不需要再进行同步。偏向锁可以提高带有同步但无竞争的程序性能也就是说他并不一定总是对程序运行有利如果程序中大多数的锁都是被多个不同的线程访问那偏向模式就是多余的在具体问题具体分析的前提下可以考虑是否使用偏向锁。轻量级锁: 为了减少获得锁和释放锁所带来的性能消耗引入了“偏向锁”和“轻量级锁”所以在Java SE1.6里锁一共有四种状态无锁状态偏向锁状态轻量级锁状态和重量级锁状态它会随着竞争情况逐渐升级。锁可以升级但不能降级意味着偏向锁升级成轻量级锁后不能降级成偏向锁 21、如何在两个线程间共享数据 通过在线程之间共享对象就可以了然后通过wait/notify/notifyAll、await/signal/signalAll进行唤起和等待比方说阻塞队列BlockingQueue就是为线程之间共享数据而设计的 22、如何正确的使用wait()?使用if还是while? wait() 方法应该在循环调用因为当线程获取到 CPU 开始执行的时候其他条件可能还没有满足所以在处理前循环检测条件是否满足会更好。下面是一段标准的使用 wait 和 notify 方法的代码 synchronized (obj) {while (condition does not hold)obj.wait(); // (Releases lock, and reacquires on wakeup)... // Perform action appropriate to condition
} 23、什么是线程局部变量ThreadLocal 线程局部变量是局限于线程内部的变量属于线程自身所有不在多个线程间共享。Java提供ThreadLocal类来支持线程局部变量是一种实现线程安全的方式。但是在管理环境下如 web 服务器使用线程局部变量的时候要特别小心在这种情况下工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放Java 应用就存在内存泄露的风险。 24、ThreadLoal的作用是什么? 简单说ThreadLocal就是一种以空间换时间的做法在每个Thread里面维护了一个ThreadLocal.ThreadLocalMap把数据进行隔离数据不共享自然就没有线程安全方面的问题了. 25、生产者消费者模型的作用是什么? 1通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率这是生产者消费者模型最重要的作用 2解耦这是生产者消费者模型附带的作用解耦意味着生产者和消费者之间的联系少联系越少越可以独自发展而不需要收到相互的制约 26.写一个生产者-消费者队列 可以通过阻塞队列实现,也可以通过wait-notify来实现.使用阻塞队列来实现 //消费者
public class Producer implements Runnable{private final BlockingQueueInteger queue;public Producer(BlockingQueue q){this.queueq;}Overridepublic void run() {try {while (true){Thread.sleep(1000);//模拟耗时queue.put(produce());}}catch (InterruptedException e){}}private int produce() {int nnew Random().nextInt(10000);System.out.println(Thread: Thread.currentThread().getId() produce: n);return n;}
}
//消费者
public class Consumer implements Runnable {private final BlockingQueueInteger queue;public Consumer(BlockingQueue q){this.queueq;}Overridepublic void run() {while (true){try {Thread.sleep(2000);//模拟耗时consume(queue.take());}catch (InterruptedException e){}}}private void consume(Integer n) {System.out.println(Thread: Thread.currentThread().getId() consume: n);}
}
//测试
public class Main {public static void main(String[] args) {BlockingQueueInteger queuenew ArrayBlockingQueueInteger(100);Producer pnew Producer(queue);Consumer c1new Consumer(queue);Consumer c2new Consumer(queue);new Thread(p).start();new Thread(c1).start();new Thread(c2).start();}
} 使用wait-notify来实现 该种方式应该最经典,这里就不做说明了 27、如果你提交任务时线程池队列已满这时会发生什么 如果你使用的LinkedBlockingQueue也就是无界队列的话没关系继续添加任务到阻塞队列中等待执行因为LinkedBlockingQueue可以近乎认为是一个无穷大的队列可以无限存放任务如果你使用的是有界队列比方说ArrayBlockingQueue的话任务首先会被添加到ArrayBlockingQueue中ArrayBlockingQueue满了则会使用拒绝策略RejectedExecutionHandler处理满了的任务默认是AbortPolicy。 28、为什么要使用线程池 避免频繁地创建和销毁线程达到线程对象的重用。另外使用线程池还可以根据项目灵活地控制并发的数目。 29、java中用到的线程调度算法是什么 抢占式。一个线程用完CPU之后操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。 30、Thread.sleep(0)的作用是什么 由于Java采用抢占式的线程调度算法因此可能会出现某条线程常常获取到CPU控制权的情况为了让某些优先级比较低的线程也能获取到CPU控制权可以使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作这也是平衡CPU控制权的一种操作。 31、什么是CAS CAS全称为Compare and Swap即比较-替换。假设有三个操作数内存值V、旧的预期值A、要修改的值B当且仅当预期值A和内存值V相同时才会将内存值修改为B并返回true否则什么都不做并返回false。当然CAS一定要volatile变量配合这样才能保证每次拿到的变量是主内存中最新的那个值否则旧的预期值A对某条线程来说永远是一个不会变的值A只要某次CAS操作失败永远都不可能成功 32、什么是乐观锁和悲观锁 乐观锁乐观锁认为竞争不总是会发生因此它不需要持有锁将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量如果失败则表示发生冲突那么就应该有相应的重试逻辑。 悲观锁悲观锁认为竞争总是会发生因此每次对某资源进行操作时都会持有一个独占的锁就像synchronized不管三七二十一直接上了锁就操作资源了。 33、ConcurrentHashMap的并发度是什么? ConcurrentHashMap的并发度就是segment的大小默认为16这意味着最多同时可以有16条线程操作ConcurrentHashMap这也是ConcurrentHashMap对Hashtable的最大优势任何情况下Hashtable能同时有两条线程获取Hashtable中的数据吗 34、ConcurrentHashMap的工作原理 ConcurrentHashMap在jdk 1.6和jdk 1.8实现原理是不同的.jdk 1.6: ConcurrentHashMap是线程安全的但是与Hashtablea相比实现线程安全的方式不同。Hashtable是通过对hash表结构进行锁定是阻塞式的当一个线程占有这个锁时其他线程必须阻塞等待其释放锁。ConcurrentHashMap是采用分离锁的方式它并没有对整个hash表进行锁定而是局部锁定也就是说当一个线程占有这个局部锁时不影响其他线程对hash表其他地方的访问。 具体实现:ConcurrentHashMap内部有一个Segmentjdk 1.8 在jdk 8中ConcurrentHashMap不再使用Segment分离锁而是采用一种乐观锁CAS算法来实现同步问题但其底层还是“数组链表-红黑树”的实现。 37、CyclicBarrier和CountDownLatch区别 这两个类非常类似都在java.util.concurrent下都可以用来表示代码运行到某个点上二者的区别在于 CyclicBarrier的某个线程运行到某个点上之后该线程即停止运行直到所有的线程都到达了这个点所有线程才重新运行CountDownLatch则不是某线程运行到某个点上之后只是给某个数值-1而已该线程继续运行CyclicBarrier只能唤起一个任务CountDownLatch可以唤起多个任务CyclicBarrier可重用CountDownLatch不可重用计数值为0该CountDownLatch就不可再用了 39、java中的操作符线程安全么? 不是线程安全的操作。它涉及到多个指令如读取变量值增加然后存储回内存这个过程可能会出现多个线程交差 40、你有哪些多线程开发良好的实践? 给线程命名最小化同步范围优先使用volatile尽可能使用更高层次的并发工具而非wait和notify()来实现线程通信,如BlockingQueue,Semeaphore优先使用并发容器而非同步容器.考虑使用线程池关于volatile关键字 1、可以创建Volatile数组吗? Java 中可以创建 volatile类型数组不过只是一个指向数组的引用而不是整个数组。如果改变引用指向的数组将会受到volatile 的保护但是如果多个线程同时改变数组的元素volatile标示符就不能起到之前的保护作用了 2、volatile能使得一个非原子操作变成原子操作吗? 一个典型的例子是在类中有一个 long 类型的成员变量。如果你知道该成员变量会被多个线程访问如计数器、价格等你最好是将其设置为 volatile。为什么因为 Java 中读取 long 类型变量不是原子的需要分成两步如果一个线程正在修改该 long 变量的值另一个线程可能只能看到该值的一半前 32 位。但是对一个 volatile 型的 long 或 double 变量的读写是原子。 一种实践是用 volatile 修饰 long 和 double 变量使其能按原子类型来读写。double 和 long 都是64位宽因此对这两种类型的读是分为两部分的第一次读取第一个 32 位然后再读剩下的 32 位这个过程不是原子的但 Java 中 volatile 型的 long 或 double 变量的读写是原子的。volatile 修复符的另一个作用是提供内存屏障memory barrier例如在分布式框架中的应用。简单的说就是当你写一个 volatile 变量之前Java 内存模型会插入一个写屏障write barrier读一个 volatile 变量之前会插入一个读屏障read barrier。意思就是说在你写一个 volatile 域时能保证任何线程都能看到你写的值同时在写之前也能保证任何数值的更新对所有线程是可见的因为内存屏障会将其他所有写的值更新到缓存。 3、volatile类型变量提供什么保证? volatile 主要有两方面的作用:1.避免指令重排2.可见性保证.例如JVM 或者 JIT为了获得更好的性能会对语句重排序但是 volatile 类型变量即使在没有同步块的情况下赋值也不会与其他语句重排序。 volatile 提供 happens-before 的保证确保一个线程的修改能对其他线程是可见的。某些情况下volatile 还能提供原子性如读 64 位数据类型像 long 和 double 都不是原子的(低32位和高32位)但 volatile 类型的 double 和 long 就是原子的. 微信扫一扫关注公众号【好好学java】优质文章第一时间了解 转载于:https://www.cnblogs.com/SIHAIloveYAN/p/9363852.html