一、基本概念
1、并发与并行:
1)并发:多个任务交替执行。
2)并行:多个任务同时执行。如果系统只有一个CPU,那真实环境中不可能是真实并行,因为一个CPU一次只能执行一个指令。多核CPU有可能出现并行。
2、临界区:
一种公共资源或共享数据,每一次只能有一个线程使用它。一旦临界区资源被占用,其他线程要想使用这个资源,就必须等待。
3、阻塞(Blocking)和 非阻塞(Non-Blocking):
阻塞:用来形容多线程间的相互影响。一个线程占用了临界区,其他需要使用这个临界区的线程就必须等待,等待会导致线程挂起,这种情况就是阻塞。
非阻塞:没有一个线程可以防碍其它线程执行。
4、死锁(DeadLock)、饥饿(Starvation)和活锁(LiveLock):
死锁: 各线程彼此阻塞。
饥饿:某个或多个线程由于某种原因(线程优先级太低,或某个线程一直占用资源)无法获得需要的资源,导致一直无法执行。
活锁:两个线程相互谦让资源,导致没有一个线程可以同时拿到所有资源而正常执行。
5、并发级别:
根据控制并发的策略,可以发并发级别分为: 阻塞、无饥饿、无障碍、无锁、无等待。
6、多线程的原子性、可见性、有序性:
a、 原子性( Atomicity ):指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
如: 对于32位系统,Long (64位)型数据的读写不是原子性的,
b、可见性:当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改。
如: 多核CPU中,CPU1 将变量 t 缓存在Cache或寄存器, 如果CPU2上的某线程修改了t 的值,则CPU1 不会知道,这就是可见性的问题。
可见性问题有多种情况: 缓存优化,硬件优化,指令重排,编辑器优化。
c、有序性:指令重排后,代码执行顺序会变化。
7、线程中断的三种方法:
1) Thread.interrupt(); // 中断线程
实例方法,通知目标线程中断,即设置中断标志位。中断标志位表示当前线程已经被中断了。
2) Thread.isInterrupted(); // 判断是否被中断
实例方法,判断当前线程是否有被中断(通过检查中断标志位)。
3) Thread.interrupted(); // 判断是否被中断,并清除当前中断状态。
也用来判断当前线程的中断状态,但同时会清除当前线程的中断标识位状态。
8、Thread.sleep()方法:
当线程在 sleep()休眠时,如果被中断,则会抛出InterruptException中断异常
该异常不是运行时异常,程序必须捕获并处理它。
9、Object.wait() 方法:
当在一个对象实例上调用 wait()方法后,当前线程就会在这个对象上等待,直到其它线程调用了obj.notify()方法为止。
obj对象成为多个线程之间的通信手段。
Object.wait()方法必须包含在对应的 synchronized语句中。
执行前需要获得目标对象的监视器 - 用synchronized 语句
wait() 和 sleep()方法的区别:
1)、相同点: 都可以让线程等待,
2)、不同点: wati 可以被唤醒,会释放目标对象的锁,slee()方法不会释放任何资源。
10、Object.notify()方法:随机唤醒(不公平)等待的线程。
Object.notifyAll()方法:唤醒全部等待队列中的线程。
执行前需要获得目标对象的监视器
11、suspend( 挂起)和 resume (继续执行)线程:
这是两个相反的操作,已经不建议使用。
因为: suspend()在挂起线程时,不释放任何的锁资源,直至对应的线程执行了resume()操作。
但如果 resume()操作先与 suspend()执行,则被挂起的线程在很难有机会被继续执行。
严重时它占用的锁不被释放,会导致整个系统工作不正常。
12、Join (等待线程结束) 和 yield ( 谦让 ) 方法:
Join 方法有两个:
a、 public final void join(): 无限等待,一直阻塞当前线程,直到目标线程执行完毕。
b、 public final synchronized void join( long millis ):给定一个最大等待时间,如超过
等待时间目标线程还在执行,当前线程会不再等待而继续往下执行。
join()的核心代码是 wait(0);
注:不要在应用程序中,在Thread对象实例上使用类似 wait()或 notify()等方法,
因为这很可能会影响系统API的工作,或者被系统API所影响。(为什么? 没有理解)
yield()方法定义: public static native void yield();
静态方法,一旦执行,它会使当前线程让出CPU。但后续还会进行CPU资源的争夺。
如果一个线程不太重要或者优先级低,可以在适当的时候调用 Thread.yield().
13、Java内存模型:原子性、有序性、可见性。
14、volatile: 当用该关键词声明变量后,虚拟机会特别小心地处理,保证该变量的可见性。
但 volatile 不能代替锁,也无法保证一些复合操作的原子性。
15、线程组:
ThreadGroup tg = new ThreadGroup("TGroup");
Thread t1 = new Thread(tg, new ThreadGroupName(), "T1");
Thread t2 = new Thread(tg, new ThreadGroupName(), "T2");
tg.activeCount()
tg.list();
16、守护线程( Daemon),与之对应的是用户线程。
Thread t = new DaemonT();
t.setDaemon(true); //必须在 start()方法前设置。
t.start();
17、线程优先级:
java中,线程优先级从1到10。
高优先级的线程竞争资源时会更有优势,但并不绝对。
18、线程安全的概念:
1)、synchronized 的基本用法:
a、指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
b、直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
c、直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。
问题: 这三种用法是锁的粒度范围不同?a和b 有什么区别?
二、线程池
1、工作方式:
有一个队列,任务被提交到这个队列中(也可有多个队列)。一定数量的线程会从该队列中取任务,
然后执行。执行完任务后,线程会从任务队列中取下一个任务并执行,如果没有任务要执行,则等待。
2、线程池有最小线程数和最大线程数,池中有最小数目的线程随时待命,等待任务指派给他们。
最小线程数也叫核心池大小。
最大线程数是一个限流阀,防止一次执行太多线程。最大线程数的大小取决于负载特性以及底层硬件。
最优线程数还与每个任务的阻塞频率有关。
3、线程池Executor框架:
ThreadPoolExecutor
Executors 为线程池工厂,包含如下构造线程池的方法:
public staticExecutorServicenewFixedThreadPool() :
返回一个固定线程数量的线程池。
public staticExecutorService newSingleThreadExecutor:
返回只有一个线程的线程池。
public static ExecutorService newCachedThreadPool
返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不固定。
public staticScheduledExecutorServicenewSingleThreadScheduledExecutor
返回一个 ScheduledExecutorService 对象,线程池大小为一。
ScheduledExecutorService接口在ExecutorService 接口只上扩展了在给定时间执行某任务的功能。
如在某个固定时间执行某任务的功能,如在某个固定的延迟之后执行,或者周期性的执行某个任务。
public staticScheduledExecutorService newScheduledThreadPool
该方法也返回一个ScheduledExecutorService对象,但该线程可以指定线程数量。
public staticExecutorService unconfigurableExecutorService
public static ScheduledExecutorService unconfigurableScheduledExecutorService
20、JDK 并发容器:
CuncurrentHashMap:
CopyOnWriteArrayList:
ConcurrentLinkedQueue:
BlockingQueue:
ConcurrentSkipListMap:
问题
1、ActomicLong 类?
2、JUC(包括重入锁(Lock)、线程池/连接池(commom-pool)、Timer、Future模式、AQS、CAS等)
3、代理,反射