并行与并发:
并行:表示两个或多个任务一起执行;
并发:多个任务交替执行。
临界区
临界区表示一种公共资源或者共享数据,可以被多个线程使用,但每次只能有一个线程使用它。临界区一旦被占用,其他线程就必须等待这个资源的释放。
阻塞和非阻塞
阻塞:线程等待资源释放,就是被阻塞了。
非阻塞:没有一个线程可以妨碍其他线程的执行。
只两个或两个以上的线程在执行过程中,因为争夺资源而造成的一种相互等待的现象,无外力作用下,将会一直等下去。
死锁的四个条件:
一个或多个线程因为种种原因无法获得所需资源,导致一致无法执行。比如优先级太低,一直被优先级高的线程占用资源。
线程之间因为互相礼让,主动将资源释放给他人,导致资源不断在线程间跳动,导致没有一个线程可以同时拿到所有资源正常执行。
三大特性
为什么进行指令重排:为了提升程序执行的效率,减少设备中断。
Happen-Before 规则:不能重排的指令
【进程】是系统进行资源分配和调度的基本单位,是操作系统结构的基础。是正在执行的程序,其实程序的实体。
【程序】是指令、数据及其组织形式的描述。
【线程】是轻量级进程,是程序执行的最小单位。
1):继承Thread,重写run()方法自定义线程。
Thread t1 = new Thread(){
@override
public void run(){
//线程启动后的操作
}
t1.start();
}
2)实现Runnable结构,重写run()方法。将实例传入线程Thread中。
public class CreateThread implements Runnable {
public static void main(String[] args){
Thread t1 = new Thread(new CreateThread());
t1.start();
}
@override
public void run(){
//执行线程工作
}
}
3)使用Callable 和 Future创建线程
4)使用线程池创建线程
public void Thread.interrupt() //线程中断
public boolean Thread.isInterrupted() //判断线程是否中断
public static boolean Thread.interrupted() //判断线程是否中断,并清除当前中断状态。
public class Simple{
final static Object object = new Object();
public static class T1 extends Thread{
public void run(){
synchronized(object){
System.out.println(System.currentTimeMillis()+": T1 start!");
try{
System.out.println(System.currentTimeMillis()+"T1 wait for Object");
object.wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
System.out.println(System.currentTimeMillis()+": T1 end!");
}
}
public static class T2 extends Thread{
public void run(){
synchronized(object){
System.out.println(System.currentTimeMillis()+": T2 start! notify one thread");
object.notify();
System.out.println(System.currentTimeMillis()+": T2 end!");
try{
Thread.sleep(2000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
public static void main(String[] args){
Thread t1 = new T1();
Thread t2 = new T2();
t1.start();
t2.start();
}
}
代码执行结果:
由上可知:wait() 方法必须包含在对应的synchronized语句中,且无论是wait还是notify 都必须获得目标对象的一个监视器
public class GoodSuspend{
public static class Object u = new Object();
public static class ChangeObjectThread extends Thread{
volatile boolean suspendme = false;
public void suspendMe(){
suspendme = true;
}
public void resumeMe(){
suspendme = false;
synchronized(this){
notify();
}
}
@override
public void run(){
while(true){
synchronized(this){
while(suspendme){
try{
wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
synchronized(u){
System.out,println("in changeObjectThread");
}
Thread.yield();
}
}
}
public static class ReadObjectThread extends Thread{
@override
public void run(){
while(true){
synchronized(u){
System.out,println("in ReadObjectThread");
}
Thread.yield();
}
}
}
public static void main(String[] args) throws InterruptedException{
ChangeObjectThread t1 = new ChangeObjectThread();
ReadObjectThread t2 = new ReadObjectThread();
t1.start();
t2.start();
Thread.sleep(1000);
t1.suspendMe();
System.out,println("suspend t1 2 sec");
Thread.sleep(2000);
System.out.println("resume t1");
t1.resumeMe();
}
}
public final void join() throws InterruptedException
//表示无线等待,会一直阻塞当前线程,知道目标程序执行完毕。
public final synchronized void join(long millis) throws InterruptedException
//给定一个最大等待时间,如果超过给定时间线程还在执行,当前线程也会因为超时而继续执行下去。
yiled:一旦执行,它会使当前线程让出CPU。但并不是不执行了,而是与其他线程进行CPU资源竞争。
是一个变量修饰符,只能用来就是变量。
可见性:当一个线程修改了声明为volatile变量的值,新值对其他线程来说是立即可见的。
有序性:volatile变量的所谓有序性也就是被声明为volatile的变量的临界区代码的执行是有顺序的,即禁止指令重排序。
受限原子性:它用来修饰变量,对于单个volatile变量的读/写操作都具有原子性,但类似于volatile++这种复合操作不具有原子性。所以volatile的原子性是受限制的
可以将相同功能的线程放置在一个线程组中。
ThreadGroup tg = new ThreadGroup("PrintGroup");
Thread t1 = new Thread(tg,new ThreadGroupName(),"T1");
Thread t2 = new Thread(tg,new ThreadGroupName(),"T2");
t1.start();
t2.start();
//获得活动线程总数
System.out.println(tg.activeCount());
//打印线程组中所有线程信息
tg.list();
//停止所有的线程
tg.stop();
指的是在后台默默完成一些系统性的服务,如垃圾回收线程、JIT线程等。
当Java应用内只有守护线程时,java虚拟机就会自然退出。
优先级高的线程在竞争资源时会更有优势,更可能抢占资源,但这只是一个概率问题。
java有三个静态标量:
MIN_PRIORITY = 1;
NORM_PRIORITY = 5;
MAX_PRIORITY = 10;
数字越大,优先级越高,用户可以通过setPriority()来设置线程的优先级。
作用:实现线程间的同步,对同步代码加锁,是的每一次都只能有一个线程进入同步块,保证线程间的安全性。
用法:
· 指定加锁对象:对给定对象加锁,进入公布代码前要获得给定对象的锁。
· 直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
· 直接作用于静态方法:相当于给当前类加锁,进入同步代码前要获得当前类的锁。
synchronized可以保证线程间的可见性、有序性。
重入锁可以完全代替关键字synchronized。
与synchronized相比,重入锁有显示操作过程,必须手动指定何时加锁,何时释放锁。所以灵活性远优于关键字synchronized。
对于同一个线程,是允许连续多次或得同一把锁。
与synchronized进行对比
//构造函数
public ReentrantLock(boolean fair);
//使用
ReentrantLock fairLock = new ReentrantLock(true);
public static ReentrantLock lock = new ReentrantLock();
public static Condition condition = lock.newCondition();
//Condition提供方法:
await() : 当前线程等待,同时释放当前锁,与Object.wait()类似。等待线程被唤醒则继续执行,或者当前线程被中断时跳出等待。
awaitUninterruptibly() : 类似于await(),但不会在等待过程中响应中断。
singal():唤醒一个在等待中的线程;
singalAll():唤醒所有在等待中的线程。
信号量可以指定多个线程,同时访问某一个资源。
public Semaphore(int permits)
public Semaphore(int permits, boolean fair)
//permits:信号量准入数,即同时能申请多少个许可;
//fair:指定是否公平
相关方法:
public void acquire():尝试获取一个准入许可,若无法获得,则线程等待,直到有线程释放一个许可或者当前线程被中断。
public void acquireUninterruptibly():类似于acquire,但不响应中断。
public boolean tryAcquire():尝试获取一个许可,成功返回true,失败返回false,不会等待。
public boolean tryAcquire(long timeout,TimeUnit unit):会等待一段时间,超时返回false,在时间内获得许可返回ture;
public void release():线程访问资源结束后释放一个许可。
读写锁的访问约束情况:
在系统中,读操作的次数远大于写操作,所以读写锁可以发挥最大的功能,提升系统性能。
ReenTrantReadWriteLock readWriteLock= new ReenTrantReadWriteLock();
Lock readLock = readWriteLock.readLock();
Lock writeLock = readWriteLock.writeLock();
表示当前线程必须得在其他一定数量的线程完成后再执行。
CountDownLatch end = new CountDownLatch(10);
//表示等待10个线程执行完后,执行当前线程。
end.countDown();
//表示执行完一个线程,倒计数器-1.
end.await();
//等待所有其他线程完成。
类似于CountDownLatch。但可以反复使用。
public CyclicBarrier(int parties, Runnable barrierAction);
LockSupport弥补了suspend()方法被挂起而无法继续执行的情况,也弥补了wait()方法必须要先获得某个对象的锁,同时也不会抛出InterruptedException异常。
park() :阻塞;
parkNanos() 、parkUntil() 显示等待;
unpark() :唤醒线程
漏桶算法:利用一个缓存区,当请求进入系统时,无论请求的速度如何,都先在缓存区中保存,再以固定的流速流出缓冲区进行处理。
令牌桶算法:桶中存放的是令牌,处理程序只有在拿到令牌后,才能进行请求处理。如果没有令牌,处理程序要么等待可用令牌,要么丢弃请求。(在每个单位时间内生产一定量的令牌存入桶中)
newFixedThreadPool() : 创建一个固定线程数量的线程池。当有一个新的任务提交时,如果线程池中有空闲线程,则立即执行;如果没有,则新的任务会进入一个任务队列中等待。
newSingleThreadExecutor() : 返回一个只有一个线程的线程池。若有多余的任务提交到线程池,则会进入任务队列中等待线程空闲,按先进先出的顺序执行任务。
newCachedThreadPool() : 返回一个可根据实际情况调整线程量的线程池。线程池的线程数量不确定,但若有空闲线程可复用,则会优先使用可复用的线程。当所有线程都在处理工作,又有新的任务提交,则会创建新的线程来处理任务。所有线程在执行完当前任务后,返回线程池进行复用。
newSingleThreadScheduledExecutor() : 返回一个ScheduledExecutorService对象,线程池大小为1,。可执行定时任务。
newScheduledThreadPool() : 返回一个ScheduledExecutorService对象,但可指定线程池的线程数量。
上述的不同线程池工厂都是ThreadPoolExecutor类的封装。
public ThreadPoolExecutor(int corePoolSize,
int maximunPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数说明:
参数 | 说明 |
---|---|
corePoolSize | 指定了线程池中的线程数量 |
maximunPoolSize | 指定了线程池中的最大线程数 |
keepAliveTime | 当线程池数量超过了corePoolSize,多余的空闲线程的存活时间,超过这个时间,线程将被销毁 |
unit | keepAliveTime的单位 |
workQueue | 任务队列,被提交但尚未被执行的任务 |
threadFactory | 线程工厂,用于创建线程,一般为默认 |
handler | 拒绝策略,当任务不能被及时处理时,如何拒绝任务 |
是一个BlockingQueue接口对象
public ArrayBlockingQueue(int capacity) //指定队列的最大容量。
当有新的任务提交时,如果当前线程池的实际线程数少于corePoolSize,则会优先创建新的线程。如果大于corePoolSize,则会将新任务加入等待队列。若等待队列已满,且当前线程总数不大于maximumPoolSize,则创建新的线程执行任务。如过大于maximumPoolSize,则执行拒绝策略。一般选择是默认线程池创建:Executors.defaultThreadFactory();
但也可以进行自定义:Thread newThread(Runnable r);