IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 线程笔记 -> 正文阅读

[Java知识库]线程笔记

1.什么是线程,什么是进程,它们有什么区别和联系,一个进程里面是否必须有个线程

(先讲进程)

进程本质上是一个执行的程序,一个进程可以有多个线程。它允许计算机同时运行两个或多个程序。一个进程至少会有一个线程。线程是进程的最小执行单位。
区别:多进程程序不受Java的控制,而多线程则受Java控制。多线程比多进程需要更少的管理费用。

2.实现一个线程有哪几种方式,各有什么优缺点,比较常用的是那种,为什么

线程有4种实现方式:
①.通过继承Thread类,优点:可以直接调用start方法启动。缺点:继承一个类后,不能再继承别的类。需要重写run方法。无返回值。

②.实现Runnable接口,优点:可以实现多个接口或继承一个类;缺点:不能直接启动,要通过构造一个Thread把自己传进去。需要重写run方法,无返回值。

③.实现Callable接口,优点:可以抛出异常,有返回值;缺点:只有jkd1.5以后才支持。需要重写call方法。结合FutureTask和Thread类一起使用,最后调用start启动。

4 线程池;

一般最常用的是第二种,实现Runnable接口。比较方便,可扩展性高。

3. 一般情况下我们实现自己线程时候要重写什么方法

答案run();

使用Thread类,要重写run方法,或实现Runnable接口时,要实现run()方法

使用Callable接口时,要重写call方法,且有返回值。

4.start方法和run方法有什么区别,我们一般调用的那个方法,系统调用的是那个方法

start用于启动线程,当调用start后,线程并不会马上运行,而是处于就绪状态,是否要运行取决于cpu给的时间片

run用于子类重写来实现线程的功能。

我们一般调用的是start方法,系统调用的是run方法。

5.sleep方法有什么作用,一般用来做什么

sleep是一个Thread类的静态方法,让调用它的线程休眠指定的时间,可用于暂停线程,但不会把锁让给其他线程,时间一到,线程会继续执行

6. 讲下join,yield方法的作用,以及什么场合用它们

join线程有严格的先后顺序,调用它的线程需要执行完以后其他线程才会跟着执行。
yield是暂停当前正在执行的线程对象,把时间让给其他线程。
使用场合:join线程有严格的先后顺序,yield当前线程占用cpu使用率很高时,把时间让出来。(死循环时

7.线程中断是否能直接调用stop,为什么?

不可以,stop方法是从外部强行终止一个线程,会导致不可预知的错误。如使用IO流时不能关流

8.列举出一般情况下线程中断的几种方式,并说明他们之间的优缺点,并且说明那种中断方式最好

中断线程有4种方式:
①.由interrupt发出中断信号,用户接收中断信号,通过isInterrupted判断线程是否中断。
②.由interrupt发出中断信号,系统接收中断信号,通过sleep抛出中断异常,并把中断信号清除,只能抛出一次。
③.用户自定义中断信号,并将该信号发出,自己接收该中断信号。
④.调用interrupted(),会把中断信号清除,并中断线程。

9.线程有几种状态,他们是怎么转化的

线程一般分为:新生、可运行、运行、阻塞、死亡五种状态。
当创建一个线程后,并没有运行,还处于新生状态,需要通过调用start方法,让线程处于可运行状态,但是否运行取决cpu分配的时间片,当得到cpu的时间片后,线程就会马上运行,一个正在执行的线程可以通过很多方式进入阻塞状态(等待输入/输出 ,sleep,wait,get)当执行完所有操作后就进入死亡状态。

10.在实现Runnable的接口中怎么样访问当前线程对象,比如拿到当前线程的名字

通过currentThread()方法访问当前线程对象,通过getName()可获得当前线程的名字。

11. 讲下什么是守护线程,以及在什么场合来使用它

守护线程一般在后台提供通用性支持,只有非守护线程全部退出时,守护线程才会退出。
当主线程和主线程创建的子线程全部退出,守护线程一定会跟着退出

12.一般的线程优先级是什么回事,线程优先级高的线程一定会先执行吗?如果不设置优先级的话,那么线程优先级是多少,设置线程优先级用那个函数

线程的优先级就是设置哪个线程优先执行,但也不是绝对的,只是让优先级高的线程优先运行的几率高一些。线程默认是NORM_PRIORITY = 5; 设置优先级使用的是setPriority()函数。

13.为什么Thread里面的大部分方法都是final的

不能被重写,线程的很多方法都是由系统调用的,不能通过子类覆写去改变他们的行为。

14.什么是线程同步,什么是线程安全

当两个或两个以上的线程需要共享资源,他们就需要某种方法来确定资源在某一刻仅被一个线程占用。
线程安全就是多线程操作同一个对象不会有问题,线程同步一般来保护线程安全,final修饰的也是线程安全

15.讲下同步方法和同步块的区别,以及什么时候用它们

同步方法就是被synchronized修饰的方法,同步整个方法,且整个方法都会被锁住,同一时间只有一个线程可以访问该方法。缺点:性能差
同步块就是使用synchronized修饰的代码块,可以同步一小部分代码
同步块越小性能越好,当性能要求比较高时,用同步块

16.简单说下Lock对象的实现类的锁机制和同步方法或同步块有什么区别

答:可重入锁是jdk1.5以后出现的,比同步方法或同步块更加灵活,可以控制在什么时候上锁,什么时候解锁,而使用同步块或同步方法后,必须要等代码执行完后才会解锁。

17. 同步块里面的同步监视器是怎么写的,默认的同步方法里面的同步监视器是那个

synchronized(对象){
//代码块
}

默认的同步监视器this

18.讲下什么 是死锁,死锁发生的几个条件是什么

死锁就是当有两个或两个以上的线程都获得对方的资源,但彼此有不肯放开,处于僵持状态,此时便造成了死锁。
条件:两个或两个以上的线程
同时想要获取对方的资源,彼此又不肯放开

19.线程间是什么通信的,通过调用几个方法来交互的

线程间是通过相互作用,共同完成一个任务当一个线程调用wait方法后便进入等待状态,需要另一线程调用notify()方法对它进行唤醒。notifyAll可以唤醒所有线程,都必须在synchronized方法或synchronized块里使用;notify()方法是随机唤醒一个线程,将等待队列中的一个等待线程从等待队列中移到同步队列中。(2)在执行完notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也不能马上获得该对象锁。要等到执行notify方法的线程将程序执行完 ,也就是退出sychronized代码块后,当前线程才会释放锁。而在同步队列中的该线程才可以获取该对象锁。2、对象所释放的三个场景:
(1)执行完同步代码块就会释放对象锁;
(2)在执行代码块的过程中,遇到异常而导致线程终止,也会释放对象锁;
(3)在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,而此线程对象会进入线程等待池中,等待被唤醒

20.wait,notify,notifyAll在什么地方使用才有效,他们是那个类的方法

wait ,notify , notifyAll都必须在synchronized修饰的方法或synchronized块中使用,都属于Object的方法,可以被所有类继承,都是final修饰的方法,不能通过子类覆写去改变他们的行为。

21.wait和sleep有什么区别和联系,他们执行的时候是否都会释放锁

wait和sleep都可以使线程暂停,但wait必须在synchronized修饰的方法或synchronized块中使用,wait可以使锁定解除,而sleep不会解锁,wait不被唤醒是一直会在等待,而sleep会在休眠时间结束之后便会执行,sleep使用的范围更加的广泛,

22.yield,sleep方法有什么区别和联系

yield和sleep都是可以让线程暂停,但yield会暂停当前正在执行的线程,把时间片让给其他线程,而sleep虽然也是暂停当前线程,但只会暂停指定的时间,不会把同步锁让给其他线程,时间到了当前线程还会继续执行。

sleep()和wait()的区别。

1wait和sleep都可以使线程暂停,但wait必须在synchronized修饰的方法或synchronized块中使用,wait可以使锁定解除,而sleep不会解锁,wait不被唤醒是一直会在等待,而sleep会在休眠时间结束之后便会执行

2线程的启动是哪个方法,调用的是哪个方法?

start用于启动线程,当调用start后,线程并不会马上运行,而是处于就绪状态,是否要运行取决于cpu给的时间片。
run用于子类重写来实现线程的功能。
我们一般调用的是start方法,系统调用的
是run方法

线程安全与线程不安全的区别

当两个或两个以上的线程需要共享资源,他们就需要某种方法来确定资源在某一刻仅被一个线程占用。
线程安全就是多线程操作同一个对象不会有问题,线程同步一般来保护线程安全,final修饰的也是线程安全

线程的实现方式,线程的生命周期等

1.线程的生命周期线程是一个动态执行的过程,它也有一个从产生到死亡的过程。
(1)生命周期的五种状态
新建(new Thread)当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。
例如:Thread t1=new Thread();
就绪(runnable)线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源。例如:t1.start();
运行(running)线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。
死亡(dead)
当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。
自然终止:正常运行run()方法后终止
异常终止:调用stop()方法让一个线程终止运行
堵塞(blocked)
由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。
正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。
正在等待:调用wait()方法。(调用motify()方法回到就绪状态)
被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)

如何处理线程不安全问题 有2种解决方法。

1.放在栈里面的数据都是线程安全

2.同步块,同步关键字修饰的都是线程安全

3.final修饰的变量都是线程安全

4.ThreadLoacl放置的变量可以解决线程安全

5.可以考虑JDK5提供的线程安全集合和类

第一,是采用原子变量,毕竟线程安全问题最根本上是由于全局变量和静态变量引起的,只要保证了对于变量的写操作要么全写要么不写,就可以解决线程安全,定义变量用sig_atomic_t和volatile。
第二,就是实现线程间同步啦,用互斥锁,信号量。让线程有序的访问变量就可以啦

线程中常用方法的区别,

首先,线程中最多用到的是start方法,它的作用是用来启动一个线程。(一个Thread类的对象就是一个线程,用这个对象.start()就是启动一个线程)
其次,线程中用的多的就是sleep,join,wait这种会引发InterruptedException异常的方法,

sleep是用来休眠一个线程一段时间,

join是用来强制执行一个线程,

wait这个方法是Object类中的方法,用于等待。
除yield(),礼让的意思就是让另外一个线程执行一会,然后自己再执行,不同于sleep。
还有获得当前线程的对象,这个方法也很重要,currentThread()。
对于线程中,还应了解到线程的死锁的概念,不需要掌握这个概念,但是应该知道可以通过哪些途径避免死锁,java中提供了使用Synchronized关键字和Synchronized同步方法来解决

多线程和单线程有什么区别?

单线程的也就是程序执行时,所跑的程序路径(处理的东西)是连续顺序下来的,必须前面的处理好,后面的才会执行到。
多线程嘛,举个例子也就是说程序可以同时执行2个以上相同类似的操作,比如一些搜索代理或者群发email的多线程软件,由于操作一次需要网络的返回信 息 花的时间比较长,而对cpu来说却是空闲的,如果是一个一个顺序执行,那么搜索几千个IP就会花上好久好久。 而如果用多线程就可以在等待期间 加入 其他的搜索,然后等待,这样可以提高效率。

用同步块与同步方法的区别?

同步方法就是被synchronized修饰的方法,同步整个方法,且整个方法都会被锁住,同一时间只有一个线程可以访问该方法。整个业务,缺点:性能差
同步块就是使用synchronized修饰的代码块,可以同步一小部分代码
同步块越小性能越好,当性能要求比较高时,用同步块
写二个线程,对一个int类型一个i++,一个i–

public class ThreadTest implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(“我是子线程”+i);
}
}
public static void main(String[] args) {
Runnable runnable=new ThreadTest();
Thread thread=new Thread(runnable);
thread.start();
for (int i = 10; i >0; i–) {
System.out.println(“我是主线程”+i);
}
}
}

说说stop为什么不建议使用。

stop()方法作为一种粗暴的线程终止行为,在线程终止之前没有对其做任何的清除操作,因此具有固有的不安全性。

什么是同步和异步,分别用例子说明,同步有几种方式?

同步就是排队去做事情,异步就是各做各的

什么是对象锁?

对象锁。同一时间只保证 一个线程访问方法或变量。
在Java语言中,通过被关键字synchronized修饰的方法或synchronized语句块实现对代码的同步
包含在synchronized方法或语句块中的代码称为被同步的代码(Synchronized Code)
当线程访问被同步的代码时,必须首先竞争代码所属的类的【对象上的锁】,否则线程将等待(阻塞),直到锁被释放.
列子:
同步语句(synchronized statements)的一般形式如下:
synchronized(<锁对象引用>){
…被同步的代码

线程的死锁问题

Icon

死锁就是当有两个或两个以上的线程都获得对方的资源,但彼此有不肯放开,处于僵持状态,此时便造成了死锁。
条件:两个或两个以上的线程
同时想要获取对方的资源,彼此又不肯放开

"5.下列哪个是Runable接口的方法()

Icon

A.run B.start C.yicld D.stop
"
答:A

写一个生产者-消费者模型


博客园

) 什么是线程?

线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。程序员可以通过它进行多处理器编程,你可以使用多线程对 运算密集型任务提速。比如,如果一个线程完成一个任务要100毫秒,那么用十个线程完成改任务只需10毫秒。Java在语言层面对多线程提供了卓越的支 持,它也是一个很好的卖点。

2) 线程和进程有什么区别?

线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。别把它和栈内存搞混,每个线程都拥有单独的栈内存用来存储本地数据。

3) 如何在Java中实现线程?

在语言层面有两种方式。java.lang.Thread 类的实例就是一个线程但是它需要调用java.lang.Runnable接口来执行,由于线程类本身就是调用的Runnable接口所以你可以继承 java.lang.Thread 类或者直接调用Runnable接口来重写run()方法实现线程。

4) 用Runnable还是Thread?

这个问题是上题的后续,大家都知道我们可以通过继承Thread类或者调用Runnable接口来实现线程,问题是,那个方法更好呢?什么情况下使 用它?这个问题很容易回答,如果你知道Java不支持类的多重继承,但允许你调用多个接口。所以如果你要继承其他类,当然是调用Runnable接口好 了。

6) Thread 类中的start() 和 run() 方法有什么区别?

这个问题经常被问到,但还是能从此区分出面试者对Java线程模型的理解程度。start()方法被用来启动新创建的线程,而且start()内部 调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启 动,start()方法才会启动新线程。

7) Java中Runnable和Callable有什么不同?

Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在 JDK1.5增加的。它们的主要区别是Callable的 call() 方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。

8) Java中CyclicBarrier 和 CountDownLatch有什么不同?

CyclicBarrier 和 CountDownLatch 都可以用来让一组线程等待其它线程。与 CyclicBarrier 不同的是,CountdownLatch 不能重新使用。

9) Java内存模型是什么?

Java内存模型规定和指引Java程序在不同的内存架构、CPU和操作系统间有确定性地行为。它在多线程的情况下尤其重要。Java内存模型对一 个线程所做的变动能被其它线程可见提供了保证,它们之间是先行发生关系。这个关系定义了一些规则让程序员在并发编程时思路更清晰。比如,先行发生关系确保 了:

  • 线程内的代码能够按先后顺序执行,这被称为程序次序规则。
  • 对于同一个锁,一个解锁操作一定要发生在时间上后发生的另一个锁定操作之前,也叫做管程锁定规则。
  • 前一个对volatile的写操作在后一个volatile的读操作之前,也叫volatile变量规则。
  • 一个线程内的任何操作必需在这个线程的start()调用之后,也叫作线程启动规则。
  • 一个线程的所有操作都会在线程终止之前,线程终止规则。
  • 一个对象的终结操作必需在这个对象构造完成之后,也叫对象终结规则。
  • 可传递性

我强烈建议大家阅读《Java并发编程实践》第十六章来加深对Java内存模型的理解。

10) Java中的volatile 变量是什么?

volatile是一个特殊的修饰符,只有成员变量才能使用它。在Java并发程序缺少同步类的情况下,多线程对成员变量的操作对其它线程是透明的。volatile变量可以保证下一个读取操作会在前一个写操作之后发生,就是上一题的volatile变量规则。

11) 什么是线程安全?Vector是一个线程安全类吗?

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量 的值也和预期的是一样的,就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的情况下也不会出现计算失误。很显然你可以将集合类分 成两组,线程安全和非线程安全的。Vector 是用同步方法来实现线程安全的, 而和它相似的ArrayList不是线程安全的。

12) Java中什么是竞态条件? 举个例子说明。

竞态条件会导致程序在并发情况下出现一些bugs。多线程对一些资源的竞争的时候就会产生竞态条件,如果首先要执行的程序竞争失败排到后面执行了, 那么整个程序就会出现一些不确定的bugs。这种bugs很难发现而且会重复出现,因为线程间的随机竞争。

13) Java中如何停止一个线程?

Java提供了很丰富的API但没有为停止线程提供API。JDK 1.0本来有一些像stop(), suspend() 和 resume()的控制方法但是由于潜在的死锁威胁因此在后续的JDK版本中他们被弃用了,之后Java API的设计者就没有提供一个兼容且线程安全的方法来停止一个线程。当run() 或者 call() 方法执行完的时候线程会自动结束,如果要手动结束一个线程,你可以用volatile 布尔变量来退出run()方法的循环或者是取消任务来中断线程。

14) 一个线程运行时发生异常会怎样?

这是我在一次面试中遇到的一个很刁钻的Java面试题, 简单的说,如果异常没有被捕获该线程将会停止执行。Thread.UncaughtExceptionHandler是用于处理未捕获异常造成线程突然中 断情况的一个内嵌接口。当一个未捕获异常将造成线程中断的时候JVM会使用Thread.getUncaughtExceptionHandler()来 查询线程的UncaughtExceptionHandler并将线程和异常作为参数传递给handler的uncaughtException()方法 进行处理。

15) 如何在两个线程间共享数据?

你可以通过共享对象来实现这个目的,或者是使用像阻塞队列这样并发的数据结构。这篇教程《Java线程间通信》(涉及到在两个线程间共享对象)用wait和notify方法实现了生产者消费者模型。

16) Java中notify 和 notifyAll有什么区别?

这又是一个刁钻的问题,因为多线程可以等待单监控锁,Java API 的设计人员提供了一些方法当等待条件改变的时候通知它们,但是这些方法没有完全实现。notify()方法不能唤醒某个具体的线程,所以只有一个线程在等 待的时候它才有用武之地。而notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行。

17) 为什么wait, notify 和 notifyAll这些方法不在thread类里面?

这是个设计相关的问题,它考察的是面试者对现有系统和一些普遍存在但看起来不合理的事物的看法。回答这些问题的时候,你要说明为什么把这些方法放在 Object类里是有意义的,还有不把它放在Thread类里的原因。一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通 过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁 就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。

18) 什么是ThreadLocal变量?

ThreadLocal是Java里一种特殊的变量。每个线程都有一个ThreadLocal就是每个线程都拥有了自己独立的一个变量,竞争条件被 彻底消除了。它是为创建代价高昂的对象获取线程安全的好方法,比如你可以用ThreadLocal让SimpleDateFormat变成线程安全的,因 为那个类创建代价高昂且每次调用都需要创建不同的实例所以不值得在局部范围使用它,如果为每个线程提供一个自己独有的变量拷贝,将大大提高效率。首先,通 过复用减少了代价高昂的对象的创建个数。其次,你在没有使用高代价的同步或者不变性的情况下获得了线程安全。线程局部变量的另一个不错的例子是 ThreadLocalRandom类,它在多线程环境中减少了创建代价高昂的Random对象的个数。

19) 什么是FutureTask?

在Java并发程序中FutureTask表示一个可以取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完 成的时候结果才能取回,如果运算尚未完成get方法将会阻塞。一个FutureTask对象可以对调用了Callable和Runnable的对象进行包 装,由于FutureTask也是调用了Runnable接口所以它可以提交给Executor来执行。

20) Java中interrupted 和 isInterruptedd方法的区别?

interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除而后者不会。Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。当中断线程调用静态方法Thread.interrupted()来 检查中断状态时,中断状态会被清零。而非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛 出InterruptedException异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其它线程调用中断来改变。

21) 为什么wait和notify方法要在同步块中调用?

主要是因为Java API强制要求这样做,如果你不这么做,你的代码会抛出IllegalMonitorStateException异常。还有一个原因是为了避免wait和notify之间产生竞态条件。

22) 为什么你应该在循环中检查等待条件?

处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。因此,当一个等待线程醒来 时,不能认为它原来的等待状态仍然是有效的,在notify()方法调用之后和等待线程醒来之前这段时间它可能会改变。这就是在循环中使用wait()方 法效果更好的原因,你可以在Eclipse中创建模板调用wait和notify试一试。如果你想了解更多关于这个问题的内容,我推荐你阅读《Effective Java》这本书中的线程和同步章节。

23) Java中的同步集合与并发集合有什么区别?

同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。在Java1.5之前程序员们只有同步集合来用且在 多线程并发的时候会导致争用,阻碍了系统的扩展性。Java5介绍了并发集合像ConcurrentHashMap,不仅提供线程安全还用锁分离和内部分 区等现代技术提高了可扩展性。

24) Java中堆和栈有什么不同?

为什么把这个问题归类在多线程和并发面试题里?因为栈是一块和线程紧密相关的内存区域。每个线程都有自己的栈内存,用于存储本地变量,方法参数和栈 调用,一个线程中存储的变量对其它线程是不可见的。而堆是所有线程共享的一片公用内存区域。对象都在堆里创建,为了提升效率线程会从堆中弄一个缓存到自己 的栈,如果多个线程使用该变量就可能引发问题,这时volatile 变量就可以发挥作用了,它要求线程从主存中读取变量的值。

25) 什么是线程池? 为什么要使用它?

创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时 候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。从JDK1.5开始,Java API提供了Executor框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短 的任务的程序的可扩展线程池)。

26) 如何写代码来解决生产者消费者问题?

在现实中你解决的许多线程问题都属于生产者消费者模型,就是一个线程生产任务供其它线程进行消费,你必须知道怎么进行线程间通信来解决这个问题。比 较低级的办法是用wait和notify来解决这个问题,比较赞的办法是用Semaphore 或者 BlockingQueue来实现生产者消费者模型,这篇教程有实现它。

27) 如何避免死锁?

deadlock in Java
Java多线程中的死锁
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。这是一个严重的问题,因为死锁会让你的程序挂起无法完成任务,死锁的发生必须满足以下四个条件:

  • 互斥条件:一个资源每次只能被一个进程使用。
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。

28) Java中活锁和死锁有什么区别?

这是上题的扩展,活锁和死锁类似,不同之处在于处于活锁的线程或进程的状态是不断改变的,活锁可以认为是一种特殊的饥饿。一个现实的活锁例子是两个 人在狭小的走廊碰到,两个人都试着避让对方好让彼此通过,但是因为避让的方向都一样导致最后谁都不能通过走廊。简单的说就是,活锁和死锁的主要区别是前者 进程的状态可以改变但是却不能继续执行。

29) 怎么检测一个线程是否拥有锁?

我一直不知道我们竟然可以检测一个线程是否拥有锁,直到我参加了一次电话面试。在java.lang.Thread中有一个方法叫holdsLock(),它返回true如果当且仅当当前线程拥有某个具体对象的锁。

30) 你如何在Java中获取线程堆栈?

对于不同的操作系统,有多种方法来获得Java进程的线程堆栈。当你获取线程堆栈时,JVM会把所有线程的状态存到日志文件或者输出到控制台。在 Windows你可以使用Ctrl + Break组合键来获取线程堆栈,Linux下用kill -3命令。你也可以用jstack这个工具来获取,它对线程id进行操作,你可以用jps这个工具找到id。

31) JVM中哪个参数是用来控制线程的栈堆栈小的

这个问题很简单, -Xss参数用来控制线程的堆栈大小。

32) Java中synchronized 和 ReentrantLock 有什么不同?

Java在过去很长一段时间只能通过synchronized关键字来实现互斥,它有一些缺点。比如你不能扩展锁之外的方法或者块边界,尝试获取锁 时不能中途取消等。Java 5 通过Lock接口提供了更复杂的控制来解决这些问题。 ReentrantLock 类实现了 Lock,它拥有与 synchronized 相同的并发性和内存语义且它还具有可扩展性。

33) 有三个线程T1,T2,T3,怎么确保它们按顺序执行?

在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。

34) Thread类中的yield方法有什么作用?

Yield方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法而且只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU,执行yield()的线程有可能在进入到暂停状态后马上又被执行。

35) Java中ConcurrentHashMap的并发度是什么?

ConcurrentHashMap把实际map划分成若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度获得的,它是 ConcurrentHashMap类构造函数的一个可选参数,默认值为16,这样在多线程情况下就能避免争用。

36) Java中Semaphore是什么?

Java中的Semaphore是一种新的同步类,它是一个计数信号。从概念上讲,从概念上讲,信号量维护了一个许可集合。如有必要,在许可可用前 会阻塞每一个 acquire(),然后再获取该许可。每个 release()添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore只对可用许可的号码进行计数,并采 取相应的行动。信号量常常用于多线程的代码中,比如数据库连接池。

37)如果你提交任务时,线程池队列已满。会时发会生什么?

这个问题问得很狡猾,许多程序员会认为该任务会阻塞直到线程池队列有空位。事实上如果一个任务不能被调度执行那么ThreadPoolExecutor’s submit()方法将会抛出一个RejectedExecutionException异常。

38) Java线程池中submit() 和 execute()方法有什么区别?

两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线 程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。

39) 什么是阻塞式方法?

阻塞式方法是指程序会一直等待该方法完成期间不做其他事情,ServerSocket的accept()方法就是一直等待客户端连接。这里的阻塞是 指调用结果返回之前,当前线程会被挂起,直到得到结果之后才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。

40) Swing是线程安全的吗? 为什么?

你可以很肯定的给出回答,Swing不是线程安全的,但是你应该解释这么回答的原因即便面试官没有问你为什么。当我们说swing不是线程安全的常 常提到它的组件,这些组件不能在多线程中进行修改,所有对GUI组件的更新都要在AWT线程中完成,而Swing提供了同步和异步两种回调方法来进行更 新。

41) Java中invokeAndWait 和 invokeLater有什么区别?

这两个方法是Swing API 提供给Java开发者用来从当前线程而不是事件派发线程更新GUI组件用的。InvokeAndWait()同步更新GUI组件,比如一个进度条,一旦进 度更新了,进度条也要做出相应改变。如果进度被多个线程跟踪,那么就调用invokeAndWait()方法请求事件派发线程对组件进行相应更新。而 invokeLater()方法是异步调用更新组件的。

42) Swing API中那些方法是线程安全的?

这个问题又提到了swing和线程安全,虽然组件不是线程安全的但是有一些方法是可以被多线程安全调用的,比如repaint(), revalidate()。 JTextComponent的setText()方法和JTextArea的insert() 和 append() 方法也是线程安全的。

43) 如何在Java中创建Immutable对象?

这个问题看起来和多线程没什么关系, 但不变性有助于简化已经很复杂的并发程序。Immutable对象可以在没有同步的情况下共享,降低了对该对象进行并发访问时的同步化开销。可是Java 没有@Immutable这个注解符,要创建不可变类,要实现下面几个步骤:通过构造方法初始化所有成员、对变量不要提供setter方法、将所有的成员 声明为私有的,这样就不允许直接访问这些成员、在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝。我的文章how to make an object Immutable in Java有详细的教程,看完你可以充满自信。

44) Java中的ReadWriteLock是什么?

一般而言,读写锁是用来提升并发程序性能的锁分离技术的成果。Java中的ReadWriteLock是Java 5 中新增的一个接口,一个ReadWriteLock维护一对关联的锁,一个用于只读操作一个用于写。在没有写线程的情况下一个读锁可能会同时被多个读线程 持有。写锁是独占的,你可以使用JDK中的ReentrantReadWriteLock来实现这个规则,它最多支持65535个写锁和65535个读 锁。

45) 多线程中的忙循环是什么?

忙循环就是程序员用循环让一个线程等待,不像传统方法wait(), sleep() 或 yield() 它们都放弃了CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么做的目的是为了保留CPU缓存,在多核系统中,一个等待线程醒来的时候可 能会在另一个内核运行,这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使用它了。

46)volatile 变量和 atomic 变量有什么不同?

这是个有趣的问题。首先,volatile 变量和 atomic 变量看起来很像,但功能却不一样。Volatile变量可以确保先行关系,即写操作会发生在后续的读操作之前, 但它并不能保证原子性。例如用volatile修饰count变量那么 count++ 操作就不是原子性的。而AtomicInteger类提供的atomic方法可以让这种操作具有原子性如getAndIncrement()方法会原子性 的进行增量操作把当前值加一,其它数据类型和引用变量也可以进行相似操作。

47) 如果同步块内的线程抛出异常会发生什么?

这个问题坑了很多Java程序员,若你能想到锁是否释放这条线索来回答还有点希望答对。无论你的同步块是正常还是异常退出的,里面的线程都会释放锁,所以对比锁接口我更喜欢同步块,因为它不用我花费精力去释放锁,该功能可以在finally block里释放锁实现。

48) 单例模式的双检锁是什么?

这个问题在Java面试中经常被问到,但是面试官对回答此问题的满意度仅为50%。一半的人写不出双检锁还有一半的人说不出它的隐患和 Java1.5是如何对它修正的。它其实是一个用来创建线程安全的单例的老方法,当单例实例第一次被创建时它试图用单个锁进行性能优化,但是由于太过于复 杂在JDK1.4中它是失败的,我个人也不喜欢它。无论如何,即便你也不喜欢它但是还是要了解一下,因为它经常被问到。

49) 如何在Java中创建线程安全的Singleton?

这是上面那个问题的后续,如果你不喜欢双检锁而面试官问了创建Singleton类的替代方法,你可以利用JVM的类加载和静态变量初始化特征来创建Singleton实例,或者是利用枚举类型来创建Singleton,我很喜欢用这种方法。

50) 写出3条你遵循的多线程最佳实践

这种问题我最喜欢了,我相信你在写并发代码来提升性能的时候也会遵循某些最佳实践。以下三条最佳实践我觉得大多数Java程序员都应该遵循:

  • 给你的线程起个有意义的名字。
    这样可以方便找bug或追踪。OrderProcessor, QuoteProcessor or TradeProcessor 这种名字比 Thread-1. Thread-2 and Thread-3 好多了,给线程起一个和它要完成的任务相关的名字,所有的主要框架甚至JDK都遵循这个最佳实践。
  • 避免锁定和缩小同步的范围
    锁花费的代价高昂且上下文切换更耗费时间空间,试试最低限度的使用同步和锁,缩小临界区。因此相对于同步方法我更喜欢同步块,它给我拥有对锁的绝对控制权。
  • 多用同步类少用wait 和 notify
    首先,CountDownLatch, Semaphore, CyclicBarrier 和 Exchanger 这些同步类简化了编码操作,而用wait和notify很难实现对复杂控制流的控制。其次,这些类是由最好的企业编写和维护在后续的JDK中它们还会不断 优化和完善,使用这些更高等级的同步工具你的程序可以不费吹灰之力获得优化。
  • 多用并发集合少用同步集合
    这是另外一个容易遵循且受益巨大的最佳实践,并发集合比同步集合的可扩展性更好,所以在并发编程时使用并发集合效果更好。如果下一次你需要用到map,你应该首先想到用ConcurrentHashMap。

51) 如何强制启动一个线程?

这个问题就像是如何强制进行Java垃圾回收,目前还没有觉得方法,虽然你可以使用System.gc()来进行垃圾回收,但是不保证能成功。在Java里面没有办法强制启动一个线程,它是被线程调度器控制着且Java没有公布相关的API。

52) Java中的fork join框架是什么?

fork join框架是JDK7中出现的一款高效的工具,Java开发人员可以通过它充分利用现代服务器上的多处理器。它是专门为了那些可以递归划分成许多子模块 设计的,目的是将所有可用的处理能力用来提升程序的性能。fork join框架一个巨大的优势是它使用了工作窃取算法,可以完成更多任务的工作线程可以从其它线程中窃取任务来执行。

53) Java多线程中调用wait() 和 sleep()方法有什么不同?

Java程序中wait 和 sleep都会造成某种形式的暂停,它们可以满足不同的需要。wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时它会释放锁,而 sleep()方法仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁。

csdn----------------------------------------------

下面是Java线程相关的热门面试题,你可以用它来好好准备面试。

  1. 什么是线程?
  2. 什么是线程安全和线程不安全?
  3. 什么是自旋锁?
  4. 什么是Java内存模型?
  5. 什么是CAS?
  6. 什么是乐观锁和悲观锁?
  7. 什么是AQS?
  8. 什么是原子操作?在Java Concurrency API中有哪些原子类(atomic classes)?
  9. 什么是Executors框架?
  10. 什么是阻塞队列?如何使用阻塞队列来实现生产者-消费者模型?
  11. 什么是Callable和Future?
  12. 什么是FutureTask?
  13. 什么是同步容器和并发容器的实现?
  14. 什么是多线程?优缺点?
  15. 什么是多线程的上下文切换?
  16. ThreadLocal的设计理念与作用?
  17. ThreadPool(线程池)用法与优势?
  18. Concurrent包里的其他东西:ArrayBlockingQueue、CountDownLatch等等。
  19. synchronized和ReentrantLock的区别?
  20. Semaphore有什么作用?
  21. Java Concurrency API中的Lock接口(Lock interface)是什么?对比同步它有什么优势?
  22. Hashtable的size()方法中明明只有一条语句”return count”,为什么还要做同步?
  23. ConcurrentHashMap的并发度是什么?
  24. ReentrantReadWriteLock读写锁的使用?
  25. CyclicBarrier和CountDownLatch的用法及区别?
  26. LockSupport工具?
  27. Condition接口及其实现原理?
  28. Fork/Join框架的理解?
  29. wait()和sleep()的区别?
  30. 线程的五个状态(五种状态,创建、就绪、运行、阻塞和死亡)?
  31. start()方法和run()方法的区别?
  32. Runnable接口和Callable接口的区别?
  33. volatile关键字的作用?
  34. Java中如何获取到线程dump文件?
  35. 线程和进程有什么区别?
  36. 线程实现的方式有几种(四种)?
  37. 高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池?
  38. 如果你提交任务时,线程池队列已满,这时会发生什么?
  39. 锁的等级:方法锁、对象锁、类锁?
  40. 如果同步块内的线程抛出异常会发生什么?
  41. 并发编程(concurrency)并行编程(parallellism)有什么区别?
  42. 如何保证多线程下 i++ 结果正确?
  43. 一个线程如果出现了运行时异常会怎么样?
  44. 如何在两个线程之间共享数据?
  45. 生产者消费者模型的作用是什么?
  46. 怎么唤醒一个阻塞的线程?
  47. Java中用到的线程调度算法是什么
  48. 单例模式的线程安全性?
  49. 线程类的构造方法、静态块是被哪个线程调用的?
  50. 同步方法和同步块,哪个是更好的选择?
  51. 如何检测死锁?怎么预防死锁?

什么是线程?

线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位,可以使用多线程对进行运算提速。

比如,如果一个线程完成一个任务要100毫秒,那么用十个线程完成改任务只需10毫秒

什么是线程安全和线程不安全?

通俗的说:加锁的就是是线程安全的,不加锁的就是是线程不安全的

线程安全

线程安全: 就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问,直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染

一个线程安全的计数器类的同一个实例对象在被多个线程使用的情况下也不会出现计算失误。很显然你可以将集合类分成两组,线程安全和非线程安全的。Vector 是用同步方法来实现线程安全的, 而和它相似的ArrayList不是线程安全的。

线程不安全

线程不安全:就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

什么是自旋锁?

基本概念

自旋锁是SMP架构中的一种low-level的同步机制

当线程A想要获取一把自选锁而该锁又被其它线程锁持有时,线程A会在一个循环中自选以检测锁是不是已经可用了。

自选锁需要注意

  • 由于自旋时不释放CPU,因而持有自旋锁的线程应该尽快释放自旋锁,否则等待该自旋锁的线程会一直在那里自旋,这就会浪费CPU时间。
  • 持有自旋锁的线程在sleep之前应该释放自旋锁以便其它线程可以获得自旋锁

实现自旋锁

参考

segmentfault.com/q/101000000…

一个简单的while就可以满足你的要求。

目前的JVM实现自旋会消耗CPU,如果长时间不调用doNotify方法,doWait方法会一直自旋,CPU会消耗太大。

public class MyWaitNotify3{



 



  MonitorObject myMonitorObject = new MonitorObject();



  boolean wasSignalled = false;



 



  public void doWait(){



    synchronized(myMonitorObject){



      while(!wasSignalled){



        try{



          myMonitorObject.wait();



         } catch(InterruptedException e){...}



      }



      //clear signal and continue running.



      wasSignalled = false;



    }



  }



 



  public void doNotify(){



    synchronized(myMonitorObject){



      wasSignalled = true;



      myMonitorObject.notify();



    }



  }



}

什么是Java内存模型?

Java内存模型描述了在多线程代码中哪些行为是合法的,以及线程如何通过内存进行交互。它描述了“程序中的变量“ 和 ”从内存或者寄存器获取或存储它们的底层细节”之间的关系。Java内存模型通过使用各种各样的硬件和编译器的优化来正确实现以上事情

Java包含了几个语言级别的关键字,包括:volatile, final以及synchronized,目的是为了帮助程序员向编译器描述一个程序的并发需求。Java内存模型定义了volatile和synchronized的行为,更重要的是保证了同步的java程序在所有的处理器架构下面都能正确的运行。

“一个线程的写操作对其他线程可见”这个问题是因为编译器对代码进行重排序导致的。例如,只要代码移动不会改变程序的语义,当编译器认为程序中移动一个写操作到后面会更有效的时候,编译器就会对代码进行移动。如果编译器推迟执行一个操作,其他线程可能在这个操作执行完之前都不会看到该操作的结果,这反映了缓存的影响。

此外,写入内存的操作能够被移动到程序里更前的时候。在这种情况下,其他的线程在程序中可能看到一个比它实际发生更早的写操作。所有的这些灵活性的设计是为了通过给编译器,运行时或硬件灵活性使其能在最佳顺序的情况下来执行操作。在内存模型的限定之内,我们能够获取到更高的性能。

看下面代码展示的一个简单例子:

ClassReordering {



    



    int x = 0, y = 0;



   



    public void writer() {



        x = 1;



        y = 2;



    }



 



    public void reader() {



        int r1 = y;



        int r2 = x;



    }



}

让我们看在两个并发线程中执行这段代码,读取Y变量将会得到2这个值。因为这个写入比写到X变量更晚一些,程序员可能认为读取X变量将肯定会得到1。但是,写入操作可能被重排序过。如果重排序发生了,那么,就能发生对Y变量的写入操作,读取两个变量的操作紧随其后,而且写入到X这个操作能发生。程序的结果可能是r1变量的值是2,但是r2变量的值为0。

但是面试官,有时候不这么认为,认为就是JVM内存结构

JVM内存结构主要有三大块:堆内存、方法区和栈

堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,Eden空间、From Survivor空间、To Survivor空间,默认情况下年轻代按照8:1:1的比例来分配;方法区存储类信息、常量、静态变量等数据,是线程共享的区域,为与Java堆区分,方法区还有一个别名Non-Heap(非堆);栈又分为java虚拟机栈和本地方法栈主要用于方法的执行。

JAVA的JVM的内存可分为3个区:堆(heap)、栈(stack)和方法区(method)

java堆(Java Heap)

  • 可通过参数 -Xms 和-Xmx设置
  1. Java堆是被所有线程共享,是Java虚拟机所管理的内存中最大的一块 Java堆在虚拟机启动时创建
  2. Java堆唯一的目的是存放对象实例,几乎所有的对象实例和数组都在这里
  3. Java堆为了便于更好的回收和分配内存,可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor区
  • 新生代:包括Eden区、From Survivor区、To Survivor区,系统默认大小Eden:Survivor=8:1。
  • 老年代:在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
  1. Survivor空间等Java堆可以处在物理上不连续的内存空间中,只要逻辑上是连续的即可(就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的)。

据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常

java虚拟机栈(stack)

可通过参数 栈帧是方法运行期的基础数据结构栈容量可由-Xss设置

1.Java虚拟机栈是线程私有的,它的生命周期与线程相同

  1. 每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
  2. 虚拟机栈是执行Java方法的内存模型(也就是字节码)服务:每个方法在执行的同时都会创建一个栈帧用于存储 局部变量表操作数栈动态链接方法出口等信息。
  • 局部变量表:32位变量槽,存放了编译期可知的各种基本数据类型、对象引用、returnAddress类型
  • 操作数栈:基于栈的执行引擎,虚拟机把操作数栈作为它的工作区,大多数指令都要从这里弹出数据、执行运算,然后把结果压回操作数栈。
  • 动态连接每个栈帧都包含一个指向运行时常量池(方法区的一部分)中该栈帧所属方法的引用。持有这个引用是为了支持方法调用过程中的动态连接。Class文件的常量池中有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化称为静态解析。另一部分将在每一次的运行期间转化为直接应用,这部分称为动态连接
  • 方法出口:返回方法被调用的位置,恢复上层方法的局部变量和操作数栈,如果无返回值,则把它压入调用者的操作数栈。
  1. 局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的。
  2. 在方法运行期间不会改变局部变量表的大小。主要存放了编译期可知的各种基本数据类型、对象引用 (reference类型)、returnAddress类型)

java虚拟机栈,规定了两种异常状况:

  1. 如果线程请求的深度大于虚拟机所允许的深度,将抛出StackOverflowError异常
  2. 如果虚拟机栈动态扩展,而扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常

本地方法栈

可通过参数 栈容量可由-Xss设置

  1. 虚拟机栈为虚拟机执行Java方法(也就是字节码)服务。
  2. 本地方法栈则是为虚拟机使用到的Native方法服务。有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一

方法区(Method Area)

可通过参数-XX:MaxPermSize设置

  1. 线程共享内存区域,用于储存已被虚拟机加载的类信息、常量、静态变量,即编译器编译后的代码,方法区也称持久代(Permanent Generation)
  2. 虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。
  3. 如何实现方法区,属于虚拟机的实现细节,不受虚拟机规范约束。
  4. 方法区主要存放java类定义信息,与垃圾回收关系不大,方法区可以选择不实现垃圾回收,但不是没有垃圾回收。
  5. 方法区域的内存回收目标主要是针对常量池的回收和对类型的卸载
  6. 运行时常量池,也是方法区的一部分,虚拟机加载Class后把常量池中的数据放入运行时常量池

运行时常量池

JDK1.6之前字符串常量池位于方法区之中JDK1.7字符串常量池已经被挪到堆之中

可通过参数-XX:PermSize和-XX:MaxPermSize设置

  • 常量池(Constant Pool):常量池数据编译期被确定,是Class文件中的一部分。存储了类、方法、接口等中的常量,当然也包括字符串常量
  • 字符串池/字符串常量池(String Pool/String Constant Pool):是常量池中的一部分,存储编译期类中产生的字符串类型数据。
  • 运行时常量池(Runtime Constant Pool):方法区的一部分,所有线程共享。虚拟机加载Class后把常量池中的数据放入到运行时常量池。常量池:可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目资源关联最多的数据类型。
  1. 常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic Reference)。
  2. 字面量:文本字符串、声明为final的常量值等。
  3. 符号引用:类和接口的完全限定名(Fully Qualified Name)、字段的名称和描述符(Descriptor)、方法的名称和描述符。

直接内存

可通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆的最大值(-Xmx指定)一样

  • 直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常出现

总结的简单一点

java堆(Java Heap)

可通过参数 -Xms 和-Xmx设置

  1. Java堆是被所有线程共享,是Java虚拟机所管理的内存中最大的一块 Java堆在虚拟机启动时创建
  2. Java堆唯一的目的是存放对象实例,几乎所有的对象实例和数组都在这里
  3. Java堆为了便于更好的回收和分配内存,可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor区
  • 新生代:包括Eden区、From Survivor区、To Survivor区,系统默认大小Eden:Survivor=8:1。
  • 老年代:在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

java虚拟机栈(stack)

可通过参数 栈帧是方法运行期的基础数据结构栈容量可由-Xss设置

  1. Java虚拟机栈是线程私有的,它的生命周期与线程相同
  2. 每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
  3. 虚拟机栈是执行Java方法的内存模型(也就是字节码)服务:每个方法在执行的同时都会创建一个栈帧,用于存储 局部变量表操作数栈动态链接方法出口等信息

方法区(Method Area)

可通过参数-XX:MaxPermSize设置

  1. 线程共享内存区域),用于储存已被虚拟机加载的类信息、常量、静态变量,即编译器编译后的代码方法区也称持久代(Permanent Generation)
  2. 方法区主要存放java类定义信息,与垃圾回收关系不大,方法区可以选择不实现垃圾回收,但不是没有垃圾回收。
  3. 方法区域的内存回收目标主要是针对常量池的回收和对类型的卸载。
  4. 运行时常量池,也是方法区的一部分,虚拟机加载Class后把常量池中的数据放入运行时常量池

什么是CAS?

CAS(compare and swap)的缩写,中文翻译成比较并交换

CAS 不通过JVM,直接利用java本地方 JNI(Java Native Interface为JAVA本地调用),直接调用CPU 的cmpxchg(是汇编指令)指令。

利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法,实现原子操作。其它原子操作都是利用类似的特性完成的

整个java.util.concurrent都是建立在CAS之上的,因此对于synchronized阻塞算法,J.U.C在性能上有了很大的提升。

CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

CAS应用

CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

CAS优点

确保对内存的读-改-写操作都是原子操作执行

CAS缺点

CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题。ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作

总结

  1. 使用CAS在线程冲突严重时,会大幅降低程序性能;CAS只适合于线程冲突较少的情况使用
  2. synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS

参考blog.52itstyle.com/archives/94…

什么是乐观锁和悲观锁?

悲观锁

Java在JDK1.5之前都是靠synchronized关键字保证同步的,这种通过使用一致的锁定协议来协调对共享状态的访问,可以确保无论哪个线程持有共享变量的锁,都采用独占的方式来访问这些变量。独占锁其实就是一种悲观锁,所以可以说synchronized是悲观锁。

乐观锁

乐观锁( Optimistic Locking)其实是一种思想。相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。

什么是AQS?

AbstractQueuedSynchronizer简称AQS,是一个用于构建锁和同步容器的框架。事实上concurrent包内许多类都是基于AQS构建,例如ReentrantLock,Semaphore,CountDownLatch,ReentrantReadWriteLock,FutureTask等。AQS解决了在实现同步容器时设计的大量细节问题。

AQS使用一个FIFO的队列表示排队等待锁的线程,队列头节点称作“哨兵节点”或者“哑节点”,它不与任何线程关联。其他的节点与等待线程关联,每个节点维护一个等待状态waitStatus。

CAS 原子操作在concurrent包的实现

参考blog.52itstyle.com/archives/94…

由于java的CAS同时具有 volatile 读和volatile写的内存语义,因此Java线程之间的通信现在有了下面四种方式:

  • A线程写volatile变量,随后B线程读这个volatile变量。
  • A线程写volatile变量,随后B线程用CAS更新这个volatile变量。
  • A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。
  • A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。

Java的CAS会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读-改-写操作,这是在多处理器中实现同步的关键(从本质上来说,能够支持原子性读-改-写指令的计算机器,是顺序计算图灵机的异步等价机器,因此任何现代的多处理器都会去支持某种能对内存执行原子性读-改-写操作的原子指令)。同时,volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent包得以实现的基石。如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:

首先,声明共享变量为volatile;然后,使用CAS的原子条件更新来实现线程之间的同步;

同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。

AQS,非阻塞数据结构和原子变量类(Java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看,concurrent包的实现示意图如下:

AQS没有锁之类的概念,它有个state变量,是个int类型,在不同场合有着不同含义。

AQS围绕state提供两种基本操作“获取”和“释放”,有条双向队列存放阻塞的等待线程,并提供一系列判断和处理方法,简单说几点:

  • state是独占的,还是共享的;
  • state被获取后,其他线程需要等待;
  • state被释放后,唤醒等待线程;
  • 线程等不及时,如何退出等待。

至于线程是否可以获得state,如何释放state,就不是AQS关心的了,要由子类具体实现。

AQS中还有一个表示状态的字段state,例如ReentrantLocky用它表示线程重入锁的次数,Semaphore用它表示剩余的许可数量,FutureTask用它表示任务的状态。对state变量值的更新都采用CAS操作保证更新操作的原子性

AbstractQueuedSynchronizer继承了AbstractOwnableSynchronizer,这个类只有一个变量:exclusiveOwnerThread,表示当前占用该锁的线程,并且提供了相应的get,set方法。

ReentrantLock实现原理

www.cnblogs.com/maypattis/p…

什么是原子操作?在Java Concurrency API中有哪些原子类(atomic classes)?

原子操作是指一个不受其他操作影响的操作任务单元。原子操作是在多线程环境下避免数据不一致必须的手段。

int++并不是一个原子操作,所以当一个线程读取它的值并加1时,另外一个线程有可能会读到之前的值,这就会引发错误。

为了解决这个问题,必须保证增加操作是原子的,在JDK1.5之前我们可以使用同步技术来做到这一点。

到JDK1.5,java.util.concurrent.atomic包提供了int和long类型的装类,它们可以自动的保证对于他们的操作是原子的并且不需要使用同步。

什么是Executors框架?

Executor框架同java.util.concurrent.Executor 接口在Java 5中被引入。

Executor框架是一个根据一组执行策略调用,调度,执行和控制的异步任务的框架。

无限制的创建线程会引起应用程序内存溢出。所以创建一个线程池是个更好的的解决方案,因为可以限制线程的数量并且可以回收再利用这些线程。

利用Executors框架可以非常方便的创建一个线程池,

Java通过Executors提供四种线程池,分别为:

newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

什么是阻塞队列?如何使用阻塞队列来实现生产者-消费者模型?

JDK7提供了7个阻塞队列。(也属于并发容器)

  1. ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
  2. LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
  3. PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
  4. DelayQueue:一个使用优先级队列实现的无界阻塞队列。
  5. SynchronousQueue:一个不存储元素的阻塞队列。
  6. LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
  7. LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

什么是阻塞队列?

阻塞队列是一个在队列基础上又支持了两个附加操作的队列。

2个附加操作:

支持阻塞的插入方法:队列满时,队列会阻塞插入元素的线程,直到队列不满。支持阻塞的移除方法:队列空时,获取元素的线程会等待队列变为非空。

阻塞队列的应用场景

阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者是从队列里取元素的线程。简而言之,阻塞队列是生产者用来存放元素、消费者获取元素的容器。

几个方法

在阻塞队列不可用的时候,上述2个附加操作提供了四种处理方法

方法\处理方式抛出异常返回特殊值一直阻塞超时退出
插入方法add(e)offer(e)put(e)offer(e,time,unit)
移除方法remove()poll()take()poll(time,unit)
检查方法element()peek()不可用不可用

JAVA里的阻塞队列

JDK 7 提供了7个阻塞队列,如下

1、ArrayBlockingQueue 数组结构组成的有界阻塞队列。

此队列按照先进先出(FIFO)的原则对元素进行排序,但是默认情况下不保证线程公平的访问队列,即如果队列满了,那么被阻塞在外面的线程对队列访问的顺序是不能保证线程公平(即先阻塞,先插入)的。

2、LinkedBlockingQueue一个由链表结构组成的有界阻塞队列

此队列按照先出先进的原则对元素进行排序

3、PriorityBlockingQueue支持优先级的无界阻塞队列

4、DelayQueue支持延时获取元素的无界阻塞队列,即可以指定多久才能从队列中获取当前元素

5、SynchronousQueue不存储元素的阻塞队列,每一个put必须等待一个take操作,否则不能继续添加元素。并且他支持公平访问队列。

6、LinkedTransferQueue由链表结构组成的无界阻塞TransferQueue队列。相对于其他阻塞队列,多了tryTransfer和transfer方法

transfer方法

如果当前有消费者正在等待接收元素(take或者待时间限制的poll方法),transfer可以把生产者传入的元素立刻传给消费者。如果没有消费者等待接收元素,则将元素放在队列的tail节点,并等到该元素被消费者消费了才返回。

tryTransfer方法

用来试探生产者传入的元素能否直接传给消费者。,如果没有消费者在等待,则返回false。和上述方法的区别是该方法无论消费者是否接收,方法立即返回。而transfer方法是必须等到消费者消费了才返回。

7、LinkedBlockingDeque链表结构的双向阻塞队列,优势在于多线程入队时,减少一半的竞争。

如何使用阻塞队列来实现生产者-消费者模型?

通知模式实现:所谓通知模式,就是当生产者往满的队列里添加元素时会阻塞住生产者,当消费者消费了一个队列中的元素后,会通知生产者当前队列可用。

使用BlockingQueue解决生产者消费者问题

为什么BlockingQueue适合解决生产者消费者问题

任何有效的生产者-消费者问题解决方案都是通过控制生产者put()方法(生产资源)和消费者take()方法(消费资源)的调用来实现的,一旦你实现了对方法的阻塞控制,那么你将解决该问题。

Java通过BlockingQueue提供了开箱即用的支持来控制这些方法的调用(一个线程创建资源,另一个消费资源)。java.util.concurrent包下的BlockingQueue接口是一个线程安全的可用于存取对象的队列。

BlockingQueue是一种数据结构,支持一个线程往里存资源,另一个线程从里取资源。这正是解决生产者消费者问题所需要的,那么让我们开始解决该问题吧。

生产者

以下代码用于生产者线程

package io.ymq.example.thread;



 



import java.util.concurrent.BlockingQueue;



 



/**



 * 描述:生产者



 *



 * @author yanpenglei



 * @create 2018-03-14 15:52



 **/



class Producer implements Runnable {



 



    protected BlockingQueue<Object> queue;



 



    Producer(BlockingQueue<Object> theQueue) {



        this.queue = theQueue;



    }



 



    public void run() {



        try {



            while (true) {



                Object justProduced = getResource();



                queue.put(justProduced);



                System.out.println("生产者资源队列大小= " + queue.size());



            }



        } catch (InterruptedException ex) {



            System.out.println("生产者 中断");



        }



    }



 



    Object getResource() {



        try {



            Thread.sleep(100);



        } catch (InterruptedException ex) {



            System.out.println("生产者 读 中断");



        }



        return new Object();



    }



}

消费者

以下代码用于消费者线程

package io.ymq.example.thread;



 



import java.util.concurrent.BlockingQueue;



 



/**



 * 描述: 消费者



 *



 * @author yanpenglei



 * @create 2018-03-14 15:54



 **/



class Consumer implements Runnable {



    protected BlockingQueue<Object> queue;



 



    Consumer(BlockingQueue<Object> theQueue) {



        this.queue = theQueue;



    }



 



    public void run() {



        try {



            while (true) {



                Object obj = queue.take();



                System.out.println("消费者 资源 队列大小 " + queue.size());



                take(obj);



            }



        } catch (InterruptedException ex) {



            System.out.println("消费者 中断");



        }



    }



 



    void take(Object obj) {



        try {



            Thread.sleep(100); // simulate time passing



        } catch (InterruptedException ex) {



            System.out.println("消费者 读 中断");



        }



        System.out.println("消费对象 " + obj);



    }



}

测试该解决方案是否运行正常

package io.ymq.example.thread;



import java.util.concurrent.BlockingQueue;



import java.util.concurrent.LinkedBlockingQueue;



 



/**



 * 描述: 测试



 *



 * @author yanpenglei



 * @create 2018-03-14 15:58



 **/



public class ProducerConsumerExample {



 



    public static void main(String[] args) throws InterruptedException {



 



        int numProducers = 4;



        int numConsumers = 3;



 



        BlockingQueue<Object> myQueue = new LinkedBlockingQueue<Object>(5);



 



        for (int i = 0; i < numProducers; i++) {



            new Thread(new Producer(myQueue)).start();



        }



 



        for (int i = 0; i < numConsumers; i++) {



            new Thread(new Consumer(myQueue)).start();



        }



 



        Thread.sleep(1000);



 



        System.exit(0);



    }



}

运行结果

生产者资源队列大小= 1



生产者资源队列大小= 1



消费者 资源 队列大小 1



生产者资源队列大小= 1



消费者 资源 队列大小 1



消费者 资源 队列大小 1



生产者资源队列大小= 1



生产者资源队列大小= 3



消费对象 java.lang.Object@1e1aa52b



生产者资源队列大小= 2



生产者资源队列大小= 5



消费对象 java.lang.Object@6e740a76



消费对象 java.lang.Object@697853f6



 



......



 



消费对象 java.lang.Object@41a10cbc



消费对象 java.lang.Object@4963c8d1



消费者 资源 队列大小 5



生产者资源队列大小= 5



生产者资源队列大小= 5



消费者 资源 队列大小 4



消费对象 java.lang.Object@3e49c35d



消费者 资源 队列大小 4



生产者资源队列大小= 5

从输出结果中,我们可以发现队列大小永远不会超过5,消费者线程消费了生产者生产的资源

什么是Callable和Future?

Callable 和 Future 是比较有趣的一对组合。当我们需要获取线程的执行结果时,就需要用到它们。Callable用于产生结果,Future用于获取结果

Callable接口使用泛型去定义它的返回类型。Executors类提供了一些有用的方法去在线程池中执行Callable内的任务。由于Callable任务是并行的,必须等待它返回的结果。java.util.concurrent.Future对象解决了这个问题。

在线程池提交Callable任务后返回了一个Future对象,使用它可以知道Callable任务的状态和得到Callable返回的执行结果。Future提供了get()方法,等待Callable结束并获取它的执行结果。

代码示例

Callable 是一个接口,它只包含一个call()方法。Callable是一个返回结果并且可能抛出异常的任务

为了便于理解,我们可以将Callable比作一个Runnable接口,而Callable的call()方法则类似于Runnable的run()方法

public class CallableFutureTest {



 



    public static void main(String[] args) throws InterruptedException, ExecutionException {



 



        System.out.println("start main thread ");



 



        ExecutorService exec = Executors.newFixedThreadPool(2);



 



        //新建一个Callable 任务,并将其提交到一个ExecutorService. 将返回一个描述任务情况的Future.



        Callable<String> call = new Callable<String>() {



 



            @Override



            public String call() throws Exception {



                System.out.println("start new thread ");



                Thread.sleep(5000);



                System.out.println("end new thread ");



                return "我是返回的内容";



            }



        };



 



        Future<String> task = exec.submit(call);



        Thread.sleep(1000);



        String retn = task.get();



        //关闭线程池



        exec.shutdown();



        System.out.println(retn + "--end main thread");



    }



}

控制台打印

start main thread 



start new thread 



end new thread 



我是返回的内容--end main thread

什么是FutureTask?

FutureTask可用于异步获取执行结果或取消执行任务的场景。通过传入Runnable或者Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过FutureTask的get方法异步获取执行结果,因此,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。另外,FutureTask还可以确保即使调用了多次run方法,它都只会执行一次Runnable或者Callable任务,或者通过cancel取消FutureTask的执行等。

1.执行多任务计算

FutureTask执行多任务计算的使用场景

利用FutureTask和ExecutorService,可以用多线程的方式提交计算任务,主线程继续执行其他任务,当主线程需要子线程的计算结果时,在异步获取子线程的执行结果。

import java.util.ArrayList;



import java.util.List;



import java.util.concurrent.*;



 



public class FutureTaskForMultiCompute {



 



    public static void main(String[] args) {



 



        FutureTaskForMultiCompute inst = new FutureTaskForMultiCompute();



        // 创建任务集合



        List<FutureTask<Integer>> taskList = new ArrayList<FutureTask<Integer>>();



        // 创建线程池



        ExecutorService exec = Executors.newFixedThreadPool(5);



        for (int i = 0; i < 10; i++) {



            // 传入Callable对象创建FutureTask对象



            FutureTask<Integer> ft = new FutureTask<Integer>(inst.new ComputeTask(i, "" + i));



            taskList.add(ft);



            // 提交给线程池执行任务,也可以通过exec.invokeAll(taskList)一次性提交所有任务;



            exec.submit(ft);



        }



 



        System.out.println("所有计算任务提交完毕, 主线程接着干其他事情!");



 



        // 开始统计各计算线程计算结果



        Integer totalResult = 0;



        for (FutureTask<Integer> ft : taskList) {



            try {



                //FutureTask的get方法会自动阻塞,直到获取计算结果为止



                totalResult = totalResult + ft.get();



            } catch (InterruptedException e) {



                e.printStackTrace();



            } catch (ExecutionException e) {



                e.printStackTrace();



            }



        }



 



        // 关闭线程池



        exec.shutdown();



        System.out.println("多任务计算后的总结果是:" + totalResult);



 



    }



 



    private class ComputeTask implements Callable<Integer> {



 



        private Integer result = 0;



        private String taskName = "";



 



        public ComputeTask(Integer iniResult, String taskName) {



            result = iniResult;



            this.taskName = taskName;



            System.out.println("生成子线程计算任务: " + taskName);



        }



 



        public String getTaskName() {



            return this.taskName;



        }



 



        @Override



        public Integer call() throws Exception {



            // TODO Auto-generated method stub



 



            for (int i = 0; i < 100; i++) {



                result = +i;



            }



            // 休眠5秒钟,观察主线程行为,预期的结果是主线程会继续执行,到要取得FutureTask的结果是等待直至完成。



            Thread.sleep(5000);



            System.out.println("子线程计算任务: " + taskName + " 执行完成!");



            return result;



        }



    }



}
生成子线程计算任务: 0



生成子线程计算任务: 1



生成子线程计算任务: 2



生成子线程计算任务: 3



生成子线程计算任务: 4



生成子线程计算任务: 5



生成子线程计算任务: 6



生成子线程计算任务: 7



生成子线程计算任务: 8



生成子线程计算任务: 9



所有计算任务提交完毕, 主线程接着干其他事情!



子线程计算任务: 0 执行完成!



子线程计算任务: 2 执行完成!



子线程计算任务: 3 执行完成!



子线程计算任务: 4 执行完成!



子线程计算任务: 1 执行完成!



子线程计算任务: 8 执行完成!



子线程计算任务: 7 执行完成!



子线程计算任务: 6 执行完成!



子线程计算任务: 9 执行完成!



子线程计算任务: 5 执行完成!



多任务计算后的总结果是:990

2.高并发环境下

FutureTask在高并发环境下确保任务只执行一次

在很多高并发的环境下,往往我们只需要某些任务只执行一次。这种使用情景FutureTask的特性恰能胜任。举一个例子,假设有一个带key的连接池,当key存在时,即直接返回key对应的对象;当key不存在时,则创建连接。对于这样的应用场景,通常采用的方法为使用一个Map对象来存储key和连接池对应的对应关系,典型的代码如下面所示:

  private Map<String, Connection> connectionPool = new HashMap<String, Connection>();



    private ReentrantLock lock = new ReentrantLock();



 



    public Connection getConnection(String key) {



        try {



            lock.lock();



            if (connectionPool.containsKey(key)) {



                return connectionPool.get(key);



            } else {



                //创建 Connection  



                Connection conn = createConnection();



                connectionPool.put(key, conn);



                return conn;



            }



        } finally {



            lock.unlock();



        }



    }



 



    //创建Connection  



    private Connection createConnection() {



        return null;



    }



 

在上面的例子中,我们通过加锁确保高并发环境下的线程安全,也确保了connection只创建一次,然而确牺牲了性能。改用ConcurrentHash的情况下,几乎可以避免加锁的操作,性能大大提高,但是在高并发的情况下有可能出现Connection被创建多次的现象。这时最需要解决的问题就是当key不存在时,创建Connection的动作能放在connectionPool之后执行,这正是FutureTask发挥作用的时机,基于ConcurrentHashMap和FutureTask的改造代码如下:

  private ConcurrentHashMap<String, FutureTask<Connection>> connectionPool = new ConcurrentHashMap<String, FutureTask<Connection>>();



 



    public Connection getConnection(String key) throws Exception {



        FutureTask<Connection> connectionTask = connectionPool.get(key);



        if (connectionTask != null) {



            return connectionTask.get();



        } else {



            Callable<Connection> callable = new Callable<Connection>() {



                @Override



                public Connection call() throws Exception {



                    // TODO Auto-generated method stub  



                    return createConnection();



                }



            };



            FutureTask<Connection> newTask = new FutureTask<Connection>(callable);



            connectionTask = connectionPool.putIfAbsent(key, newTask);



            if (connectionTask == null) {



                connectionTask = newTask;



                connectionTask.run();



            }



            return connectionTask.get();



        }



    }



 



    //创建Connection  



    private Connection createConnection() {



        return null;



    }

经过这样的改造,可以避免由于并发带来的多次创建连接及锁的出现。

什么是同步容器和并发容器的实现?

一、同步容器

主要代表有Vector和Hashtable,以及Collections.synchronizedXxx等。锁的粒度为当前对象整体。迭代器是及时失败的,即在迭代的过程中发现被修改,就会抛出ConcurrentModificationException。

二、并发容器

主要代表有ConcurrentHashMap、CopyOnWriteArrayList、ConcurrentSkipListMap、ConcurrentSkipListSet。锁的粒度是分散的、细粒度的,即读和写是使用不同的锁。迭代器具有弱一致性,即可以容忍并发修改,不会抛出ConcurrentModificationException。

JDK 7 ConcurrentHashMap

采用分离锁技术,同步容器中,是一个容器一个锁,但在ConcurrentHashMap中,会将hash表的数组部分分成若干段,每段维护一个锁,以达到高效的并发访问;

JDK 8 ConcurrentHashMap

采用分离锁技术,同步容器中,是一个容器一个锁,但在ConcurrentHashMap中,会将hash表的数组部分分成若干段,每段维护一个锁,以达到高效的并发访问;

三、阻塞队列

主要代表有LinkedBlockingQueue、ArrayBlockingQueue、PriorityBlockingQueue(Comparable,Comparator)、SynchronousQueue。提供了可阻塞的put和take方法,以及支持定时的offer和poll方法。适用于生产者、消费者模式(线程池和工作队列-Executor),同时也是同步容器

四、双端队列

主要代表有ArrayDeque和LinkedBlockingDeque。意义:正如阻塞队列适用于生产者消费者模式,双端队列同样适用与另一种模式,即工作密取。在生产者-消费者设计中,所有消费者共享一个工作队列,而在工作密取中,每个消费者都有各自的双端队列。如果一个消费者完成了自己双端队列中的全部工作,那么他就可以从其他消费者的双端队列末尾秘密的获取工作。具有更好的可伸缩性,这是因为工作者线程不会在单个共享的任务队列上发生竞争。在大多数时候,他们都只是访问自己的双端队列,从而极大的减少了竞争。当工作者线程需要访问另一个队列时,它会从队列的尾部而不是头部获取工作,因此进一步降低了队列上的竞争。适用于:网页爬虫等任务中

五、比较及适用场景

如果不需要阻塞队列,优先选择ConcurrentLinkedQueue;如果需要阻塞队列,队列大小固定优先选择ArrayBlockingQueue,队列大小不固定优先选择LinkedBlockingQueue;如果需要对队列进行排序,选择PriorityBlockingQueue;如果需要一个快速交换的队列,选择SynchronousQueue;如果需要对队列中的元素进行延时操作,则选择DelayQueue。

什么是多线程?优缺点?

什么是多线程?

多线程:是指从软件或者硬件上实现多个线程的并发技术。

多线程的好处:

  1. 使用多线程可以把程序中占据时间长的任务放到后台去处理,如图片、视屏的下载
  2. 发挥多核处理器的优势,并发执行让系统运行的更快、更流畅,用户体验更好

多线程的缺点:

  1. 大量的线程降低代码的可读性;
  2. 更多的线程需要更多的内存空间
  3. 当多个线程对同一个资源出现争夺时候要注意线程安全的问题。

什么是多线程的上下文切换?

即使是单核CPU也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程时同时执行的,时间片一般是几十毫秒(ms)

上下文切换过程中,CPU会停止处理当前运行的程序,并保存当前程序运行的具体位置以便之后继续运行

CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再次加载这个任务的状态

  • 从任务保存到再加载的过程就是一次上下文切换

ThreadLocal的设计理念与作用?

Java中的ThreadLocal类允许我们创建只能被同一个线程读写的变量。因此,如果一段代码含有一个ThreadLocal变量的引用,即使两个线程同时执行这段代码,它们也无法访问到对方的ThreadLocal变量

ThreadLocal

如何创建ThreadLocal变量

以下代码展示了如何创建一个ThreadLocal变量:

private ThreadLocal myThreadLocal = new ThreadLocal();

通过这段代码实例化了一个ThreadLocal对象。我们只需要实例化对象一次,并且也不需要知道它是被哪个线程实例化。虽然所有的线程都能访问到这个ThreadLocal实例,但是每个线程却只能访问到自己通过调用ThreadLocal的set()方法设置的值。即使是两个不同的线程在同一个ThreadLocal对象上设置了不同的值,他们仍然无法访问到对方的值

如何访问ThreadLocal变量

一旦创建了一个ThreadLocal变量,你可以通过如下代码设置某个需要保存的值:

myThreadLocal.set("A thread local value”);

可以通过下面方法读取保存在ThreadLocal变量中的值:

String threadLocalValue = (String) myThreadLocal.get();

get()方法返回一个Object对象,set()对象需要传入一个Object类型的参数。

为ThreadLocal指定泛型类型

public static ThreadLocal<String> myThreadLocal = new ThreadLocal<String>();

我们可以创建一个指定泛型类型的ThreadLocal对象,这样我们就不需要每次对使用get()方法返回的值作强制类型转换了。下面展示了指定泛型类型的ThreadLocal例子:

ThreadLocal的设计理念与作用

http://blog.csdn.net/u011860731/article/details/48733073http://blog.csdn.net/u011860731/article/details/48733073)

InheritableThreadLocal

public static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<Integer>();

InheritableThreadLocal类是ThreadLocal类的子类。ThreadLocal中每个线程拥有它自己的值,与ThreadLocal不同的是,InheritableThreadLocal允许一个线程以及该线程创建的所有子线程都可以访问它保存的值

InheritableThreadLocal 原理

Java 多线程:InheritableThreadLocal 实现原理

blog.csdn.net/ni357103403…

ThreadPool(线程池)用法与优势?

为什么要用线程池:

  1. 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  2. 可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
  3. Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。

new Thread 缺点

  1. 每次new Thread新建对象性能差。
  2. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
  3. 缺乏更多功能,如定时执行、定期执行、线程中断。

ThreadPool 优点

减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务

可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)

  • 减少在创建和销毁线程上所花的时间以及系统资源的开销
  • 如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存

Java提供的四种线程池的好处在于

  1. 重用存在的线程,减少对象创建、销毁的开销,提高性能。
  2. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
  3. 提供定时执行、定期执行、单线程、并发数控制等功能。

比较重要的几个类:

描述
ExecutorService真正的线程池接口。
ScheduledExecutorService能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。
ThreadPoolExecutorExecutorService的默认实现。
ScheduledThreadPoolExecutor继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。

要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。

Executors提供四种线程池

newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

一般都不用Executors提供的线程创建方式

使用ThreadPoolExecutor创建线程池

ThreadPoolExecutor的构造函数

public ThreadPoolExecutor(int corePoolSize,



                              int maximumPoolSize,



                              long keepAliveTime,



                              TimeUnit unit,



                              BlockingQueue<Runnable> workQueue,



                              ThreadFactory threadFactory,



                              RejectedExecutionHandler handler) {



        if (corePoolSize < 0 ||



            maximumPoolSize <= 0 ||



            maximumPoolSize < corePoolSize ||



            keepAliveTime < 0)



            throw new IllegalArgumentException();



        if (workQueue == null || threadFactory == null || handler == null)



            throw new NullPointerException();



        this.corePoolSize = corePoolSize;



        this.maximumPoolSize = maximumPoolSize;



        this.workQueue = workQueue;



        this.keepAliveTime = unit.toNanos(keepAliveTime);



        this.threadFactory = threadFactory;



        this.handler = handler;



    }

参数:

  1. corePoolSize核心线程数大小,当线程数<corePoolSize ,会创建线程执行runnable
  2. maximumPoolSize 最大线程数, 当线程数 >= corePoolSize的时候,会把runnable放入workQueue中
  3. keepAliveTime 保持存活时间,当线程数大于corePoolSize的空闲线程能保持的最大时间。
  4. unit 时间单位
  5. workQueue 保存任务的阻塞队列
  6. threadFactory 创建线程的工厂
  7. handler 拒绝策略

任务执行顺序:

  1. 当线程数小于corePoolSize时,创建线程执行任务。
  2. 当线程数大于等于corePoolSize并且workQueue没有满时,放入workQueue中
  3. 线程数大于等于corePoolSize并且当workQueue满时,新任务新建线程运行,线程总数要小于maximumPoolSize
  4. 当线程总数等于maximumPoolSize并且workQueue满了的时候执行handler的rejectedExecution。也就是拒绝策略。

ThreadPoolExecutor默认有四个拒绝策略:

  1. ThreadPoolExecutor.AbortPolicy() 直接抛出异常RejectedExecutionException
  2. ThreadPoolExecutor.CallerRunsPolicy() 直接调用run方法并且阻塞执行
  3. ThreadPoolExecutor.DiscardPolicy() 直接丢弃后来的任务
  4. ThreadPoolExecutor.DiscardOldestPolicy() 丢弃在队列中队首的任务

当然可以自己继承 RejectedExecutionHandler 来写拒绝策略.

java 四种线程池的使用

juejin.im/post/59df0c…

Concurrent包里的其他东西:ArrayBlockingQueue、CountDownLatch等等。

阻塞队列

1、ArrayBlockingQueue 数组结构组成的有界阻塞队列。

此队列按照先进先出(FIFO)的原则对元素进行排序,但是默认情况下不保证线程公平的访问队列,即如果队列满了,那么被阻塞在外面的线程对队列访问的顺序是不能保证线程公平(即先阻塞,先插入)的。

CountDownLatch

CountDownLatch 允许一个或多个线程等待其他线程完成操作。

应用场景

假如有这样一个需求,当我们需要解析一个Excel里多个sheet的数据时,可以考虑使用多线程,每个线程解析一个sheet里的数据,等到所有的sheet都解析完之后,程序需要提示解析完成。

在这个需求中,要实现主线程等待所有线程完成sheet的解析操作,最简单的做法是使用join。代码如下:

public class JoinCountDownLatchTest {



 



	public static void main(String[] args) throws InterruptedException {



		Thread parser1 = new Thread(new Runnable() {



			@Override



			public void run() {



			}



		});



 



		Thread parser2 = new Thread(new Runnable() {



			@Override



			public void run() {



				System.out.println("parser2 finish");



			}



		});



 



		parser1.start();



		parser2.start();



		parser1.join();



		parser2.join();



		System.out.println("all parser finish");



	}



 



}

join用于让当前执行线程等待join线程执行结束。其实现原理是不停检查join线程是否存活,如果join线程存活则让当前线程永远wait,代码片段如下,wait(0)表示永远等待下去。

while (isAlive()) {



 wait(0);



}
  • 方法isAlive()功能是判断当前线程是否处于活动状态。
  • 活动状态就是线程启动且尚未终止,比如正在运行或准备开始运行。

CountDownLatch用法

public class Test {



     public static void main(String[] args) {   



	 



         final CountDownLatch latch = new CountDownLatch(2);



 



         new Thread(){



             public void run() {



                 try {



                     System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");



                    Thread.sleep(3000);



                    System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");



                    latch.countDown();



                } catch (InterruptedException e) {



                    e.printStackTrace();



                }



             };



         }.start();



 



         new Thread(){



             public void run() {



                 try {



                     System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");



                     Thread.sleep(3000);



                     System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");



                     latch.countDown();



                } catch (InterruptedException e) {



                    e.printStackTrace();



                }



             };



         }.start();



 



         try {



             System.out.println("等待2个子线程执行完毕...");



            latch.await();



            System.out.println("2个子线程已经执行完毕");



            System.out.println("继续执行主线程");



        } catch (InterruptedException e) {



            e.printStackTrace();



        }



     }



 }
线程Thread-0正在执行



线程Thread-1正在执行



等待2个子线程执行完毕...



线程Thread-0执行完毕



线程Thread-1执行完毕



2个子线程已经执行完毕



继续执行主线程

new CountDownLatch(2)的构造函数接收一个int类型的参数作为计数器,如果你想等待N个点完成,这里就传入N。

当我们调用一次CountDownLatch的countDown()方法时,N就会减1,CountDownLatch的await()会阻塞当前线程,直到N变成零。由于countDown方法可以用在任何地方,所以这里说的N个点,可以是N个线程,也可以是1个线程里的N个执行步骤。用在多个线程时,你只需要把这个CountDownLatch的引用传递到线程里。

Java并发编程:CountDownLatch、CyclicBarrier和 Semaphore

www.importnew.com/21889.html

synchronized和ReentrantLock的区别?

java在编写多线程程序时,为了保证线程安全,需要对数据同步,经常用到两种同步方式就是Synchronized和重入锁ReentrantLock。

基础知识

  • 可重入锁。可重入锁是指同一个线程可以多次获取同一把锁。ReentrantLock和synchronized都是可重入锁
  • 可中断锁。可中断锁是指线程尝试获取锁的过程中,是否可以响应中断。synchronized是不可中断锁,而ReentrantLock则提供了中断功能。
  • 公平锁与非公平锁。公平锁是指多个线程同时尝试获取同一把锁时,获取锁的顺序按照线程达到的顺序,而非公平锁则允许线程“插队”。synchronized是非公平锁,而ReentrantLock的默认实现是非公平锁,但是也可以设置为公平锁。
  • CAS操作(CompareAndSwap)。CAS操作简单的说就是比较并交换。CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”

Synchronized

synchronized是java内置的关键字,它提供了一种独占的加锁方式。synchronized的获取和释放锁由JVM实现,用户不需要显示的释放锁,非常方便。然而synchronized也有一定的局限性

例如:

  1. 当线程尝试获取锁的时候,如果获取不到锁会一直阻塞。
  2. 如果获取锁的线程进入休眠或者阻塞,除非当前线程异常,否则其他线程尝试获取锁必须一直等待。

ReentrantLock

ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成。

代码示例

private Lock lock = new ReentrantLock();



public void test(){



 lock.lock();



 try{



 doSomeThing();



 }catch (Exception e){



 // ignored



 }finally {



 lock.unlock();



 }



}
  • **lock()**, 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
  • tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
  • tryLock(long timeout,TimeUnit unit)****,如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
  • lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断

ReentrantLock 一些特性

  1. 等待可中断避免,出现死锁的情况(如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false)
  2. 公平锁与非公平锁多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。

公平锁:线程获取锁的顺序和调用lock的顺序一样,FIFO;

非公平锁:线程获取锁的顺序和调用lock的顺序无关,全凭运气。

Java并发包(java.util.concurrent)中大量使用了CAS操作,涉及到并发的地方都调用了sun.misc.Unsafe类方法进行CAS操作

ReenTrantLock实现的原理:

简单来说,ReenTrantLock的实现是一种自旋锁,通过循环调用CAS操作来实现加锁。它的性能比较好也是因为避免了使线程进入内核态的阻塞状态。想尽办法避免线程进入内核的阻塞状态是我们去分析和理解锁设计的关键钥匙。

总结一下

在Synchronized优化以前,synchronized的性能是比ReenTrantLock差很多的,但是自从Synchronized引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多了,在两种方法都可用的情况下,官方甚至建议使用synchronized,其实synchronized的优化我感觉就借鉴了ReenTrantLock中的CAS技术。都是试图在用户态就把加锁问题解决,避免进入内核态的线程阻塞。

synchronized

在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好。

ReentrantLock:

ReentrantLock用起来会复杂一些。在基本的加锁和解锁上,两者是一样的,所以无特殊情况下,推荐使用synchronized。ReentrantLock的优势在于它更灵活、更强大,增加了轮训、超时、中断等高级功能。

ReentrantLock默认使用非公平锁是基于性能考虑,公平锁为了保证线程规规矩矩地排队,需要增加阻塞和唤醒的时间开销。如果直接插队获取非公平锁,跳过了对队列的处理,速度会更快。

ReentrantLock实现原理

www.cnblogs.com/maypattis/p…

分析ReentrantLock的实现原理(ReentrantLock和同步工具类的实现基础都是AQS)

www.jianshu.com/p/fe027772e…

Semaphore有什么作用?

  1. Semaphore就是一个信号量,它的作用是限制某段代码块的并发数
  2. Semaphore有一个构造函数,可以传入一个int型整数n,表示某段代码最多只有n个线程可以访问
  3. 如果超出了n,那么请等待,等到某个线程执行完毕这段代码块,下一个线程再进入
  4. 由此可以看出如果Semaphore构造函数中传入的int型整数n=1,相当于变成了一个synchronized了。

Semaphore类位于java.util.concurrent包下,它提供了2个构造器:

//参数permits表示许可数目,即同时可以允许多少线程进行访问  



public Semaphore(int permits) {  



    sync = new NonfairSync(permits);  



}  



//这个多了一个参数fair表示是否是公平的,即等待时间越久的越先获取许可  



public Semaphore(int permits, boolean fair) {  



    sync = (fair)? new FairSync(permits) : new NonfairSync(permits);  



}  
  • Semaphore类中比较重要的几个方法,首先是acquire()、release()方法:
  • acquire()用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许可。
  • release()用来释放许可。注意,在释放许可之前,必须先获获得许可。
Semaphore类中比较重要的几个方法,首先是acquire()、release()方法:



acquire()用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许可。



release()用来释放许可。注意,在释放许可之前,必须先获获得许可。

这4个方法都会被阻塞,如果想立即得到执行结果,可以使用下面几个方法:

//尝试获取一个许可,若获取成功,则立即返回true,若获取失败,则立即返回false  



public boolean tryAcquire() { };  



//尝试获取一个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false  



public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException { };   



//尝试获取permits个许可,若获取成功,则立即返回true,若获取失败,则立即返回false  



public boolean tryAcquire(int permits) { };   



//尝试获取permits个许可,若在指定的时间内获取成功,则立即返回true  



public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException { };  



//得到当前可用的许可数目  



public int availablePermits(); 

示例

假若一个工厂有5台机器,但是有8个工人,一台机器同时只能被一个工人使用,只有使用完了,其他工人才能继续使用。那么我们就可以通过Semaphore来实现:

public class Test {  



    public static void main(String[] args) {  



        int N = 8; //工人数  



        Semaphore semaphore = new Semaphore(5); //机器数目  



        for(int i=0;i<N;i++)  



            new Worker(i,semaphore).start();  



    }      



    static class Worker extends Thread{  



        private int num;  



        private Semaphore semaphore;  



        public Worker(int num,Semaphore semaphore){  



            this.num = num;  



            this.semaphore = semaphore;  



        }          



        @Override  



        public void run() {  



            try {  



                semaphore.acquire();  



                System.out.println("工人"+this.num+"占用一个机器在生产...");  



                Thread.sleep(2000);  



                System.out.println("工人"+this.num+"释放出机器");  



                semaphore.release();              



            } catch (InterruptedException e) {  



                e.printStackTrace();  



            }  



        }  



    }  



} 

运行结果:

工人0占用一个机器在生产...  



工人1占用一个机器在生产...  



工人2占用一个机器在生产...  



工人4占用一个机器在生产...  



工人5占用一个机器在生产...  



工人0释放出机器  



工人2释放出机器  



工人3占用一个机器在生产...  



工人7占用一个机器在生产...  



工人4释放出机器  



工人5释放出机器  



工人1释放出机器  



工人6占用一个机器在生产...  



工人3释放出机器  



工人7释放出机器  



工人6释放出机器

Java Concurrency API中的Lock接口(Lock interface)是什么?对比同步它有什么优势?

Lock接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。

它的优势有:

  • 可以使锁更公平
  • 可以使线程在等待锁的时候响应中断
  • 可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间
  • 可以在不同的范围,以不同的顺序获取和释放锁

Hashtable的size()方法中明明只有一条语句”return count”,为什么还要做同步?

同一时间只能有一条线程执行固定类的同步方法,但是对于类的非同步方法,可以多条线程同时访问。所以,这样就有问题了,可能线程A在执行Hashtable的put方法添加数据,线程B则可以正常调用size()方法读取Hashtable中当前元素的个数,那读取到的值可能不是最新的,可能线程A添加了完了数据,但是没有对size++,线程B就已经读取size了,那么对于线程B来说读取到的size一定是不准确的。

而给size()方法加了同步之后,意味着线程B调用size()方法只有在线程A调用put方法完毕之后才可以调用,这样就保证了线程安全性

ConcurrentHashMap的并发度是什么?

ConcurrentHashMap的并发度就是segment的大小,默认为16,这意味着最多同时可以有16条线程操作ConcurrentHashMap,这也是ConcurrentHashMap对Hashtable的最大优势

ReentrantReadWriteLock读写锁的使用

Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象。

读写锁分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁

如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!

ReentrantReadWriteLock会使用两把锁来解决问题,一个读锁,一个写锁

线程进入读锁的前提条件

  • 没有其他线程的写锁
  • 没有写请求或者有写请求,但调用线程和持有锁的线程是同一个

线程进入写锁的前提条件

  • 没有其他线程的读锁
  • 没有其他线程的写锁
  • 读锁的重入是允许多个申请读操作的线程的,而写锁同时只允许单个线程占有,该线程的写操作可以重入。
  • 如果一个线程占有了写锁,在不释放写锁的情况下,它还能占有读锁,即写锁降级为读锁。
  • 对于同时占有读锁和写锁的线程,如果完全释放了写锁,那么它就完全转换成了读锁,以后的写操作无法重入,在写锁未完全释放时写操作是可以重入的。
  • 公平模式下无论读锁还是写锁的申请都必须按照AQS锁等待队列先进先出的顺序。非公平模式下读操作插队的条件是锁等待队列head节点后的下一个节点是SHARED型节点,写锁则无条件插队。
  • 读锁不允许newConditon获取Condition接口,而写锁的newCondition接口实现方法同ReentrantLock。
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-29 08:57:09  更:2021-08-29 08:59:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 13:29:57-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码