- 线程
1线程的定义和实现方式
1.1资源分配的基本单位是进程,而调度的基本单位是线程。进程就是一段程序的执行(进程是程序的一部分),程序运行的时候会产生一个或多个进程。线程是进程的一部分,是应用程序中的执行流。
2线程状态:
七个状态:
初始化状态---------就绪状态-------运行中状态(抢占到cpu资源)--------死亡状态
三种 阻塞状态:(01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
(02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
(03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
3 线程之间的通信:
3.1.wait()和notify()方法都需要在同步代码块中执行,用当前锁对象执行
调用wait()方法会释放锁,notify()会随机叫醒一个处于等待状态的线程,调用notify会拿到锁
例子:
3.2.生产和消费者模型:
public class Tmall { private int count; public final int MAX_COUNT = 10; public synchronized void push () { while(count >= MAX_COUNT) { try { System.out.println(Thread.currentThread().getName() + " 库存数量达到上限,生产者停止生产。"); wait(); } catch (InterruptedException e) { e.printStackTrace(); } } count ++; System.out.println(Thread.currentThread().getName() + " 生产者生产,当前库存为:" + count); notifyAll(); } public synchronized void take () { while(count <= 0) { try { System.out.println(Thread.currentThread().getName() + " 库存数量为零,消费者等待。"); wait(); } catch (InterruptedException e) { e.printStackTrace(); } } count --; System.out.println(Thread.currentThread().getName() + " 消费者消费,当前库存为:" + count); notifyAll(); }
} |
3.3.join方法:加塞线程
public class Demo {
public void a(Thread joinThread) {
System.out.println("方法a执行了...");
joinThread.start();
try {
joinThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("a方法执行完毕...");
}
public void b() {
System.out.println("加塞线程开始执行....");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("加塞线程执行完毕...");
}
public static void main(String[] args) {
Demo demo = new Demo();
Thread joinThread = new Thread(new Runnable() {
@Override
public void run() {
demo.b();
}
});
new Thread(new Runnable() {
@Override
public void run() {
demo.a(joinThread);
}
}).start();
}
}
4 线程安全性问题:
4.1出现前提:
(1)在多线程的环境下
(2)必须有共享资源
(3)对共享资源进行非原子性操作
4.2活跃性:
死锁、饥饿、活锁,饥饿指的是某个现场永远无法获取到锁
4.3例子:
public class sequence{ private int value; public int getNext(){ return value++; } public static void main(String [] args){ sequence s=new sequence(); new Thread(new Runnable(){ System.out.println(s.getNext()); }).start();
new Thread(new Runnable(){ System.out.println(s.getNext()); }).start();
new Thread(new Runnable(){ System.out.println(s.getNext()); }).start(); } }
|
4.4单例模式的线程安全:
一.饿汉模式: public class Singleton{ //私有化构造方法 private Singleton(){} //饿汉模式,不会出现线程安全问题 private static Singleton instance = new Singleton(); public static Singleton getInstance(){ return instance;
}
}
二.懒汉模式: public static Singleton getInstance(){ if(instance==null){ instance = new Singleton(); } return instance; } 三.线程安全的懒汉模式: 3.1 同步方法的方式:由于轻量级锁的自旋的特性,每次自旋都会消耗cpu资源,这个代码会成为单线程执行,性能很低 public static synchronized Singleton getInstance(){ if(instance==null){ instance = new Singleton(); } return instance; } 3.2双重锁模式 public static Singleton getInstance(){ if(instance==null){ synchronized(Singleton.class){ if(instance==null){ instance = new Singleton();//指令重排序的原因 } } } return instance; } 指令重排序: 1.申请一块内存空间 2.在这块空间里实例化对象 3.instance引用指向这块空间地址 但是执行顺序会改变,因此采取双重锁,并且给 给成员变量volatile修饰。private static volatile Singleton instance;
|
- 锁
自旋锁:自旋是指某线程需要获取锁,但该锁已经被其他线程占用时,该线程不会被挂起,而是在不断的消耗CPU的时间,不停的试图获取锁。虽然CPU的时间被消耗了,但是比线程下文切换时间要少。这个时候使用自旋是划算的。
偏向锁:每次获取锁和释放锁会浪费资源、很多情况下,竞争锁不是由多个线程,而是由一个线程。
它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下,就会给线程加一个偏向锁。
如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。
偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁。
轻量级锁:
轻量级锁是由偏向锁升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁;
重量级锁:
当竞争线程尝试占用轻量级锁失败多次之后(使用自旋)轻量级锁就会膨胀为重量级锁,重量级线程指针指向竞争线程,竞争线程也会阻塞,等待轻量级线程释放锁后唤醒他。
重量级锁的加锁、解锁过程和轻量级锁差不多,区别是:竞争失败后,线程阻塞,释放锁后,唤醒阻塞的线程,不使用自旋锁,不会那么消耗CPU,所以重量级锁适合用在同步块执行时间长的情况下。
1synchronized:内置锁
synchronized 放在普通方法上,内置锁就是当前实例,保证方法体执行的原子性
synchronized修饰静态方法,内置锁是当前的clas字节码对象
synchronized同步代码块
2 ReentrentLock:
与synchronized比较:可重入、非阻塞的获取锁、可公平、可中断、可超时获取锁()。代码灵活
synchronized:可重入、可中断、非公平。简单
2.1例子:
public class Demo { private int signal; Lock lock = new ReentrantLock(); Condition a = lock.newCondition(); Condition b = lock.newCondition(); Condition c = lock.newCondition(); public void a() { lock.lock(); while(signal != 0 ) { try { a.await(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("a"); signal ++; b.signal(); lock.unlock(); } public void b() { lock.lock(); while(signal != 1) { try { b.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("b"); signal ++; c.signal(); lock.unlock(); } public void c () { lock.lock(); while(signal != 2) { try { c.await(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("c"); signal = 0; a.signal(); lock.unlock(); } public static void main(String[] args) { Demo d = new Demo(); A a = new A(d); B b = new B(d); C c = new C(d); new Thread(a).start(); new Thread(b).start(); new Thread(c).start(); } }
class A implements Runnable { private Demo demo; public A(Demo demo) { this.demo = demo; }
@Override public void run() { while(true) { demo.a(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } class B implements Runnable { private Demo demo; public B(Demo demo) { this.demo = demo; } @Override public void run() { while(true) { demo.b(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } class C implements Runnable { private Demo demo; public C(Demo demo) { this.demo = demo; } @Override public void run() { while(true) { demo.c(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
|
2.2实现一个有界队列:
public class MyQueue<E> {
private Object[] obj;
private int addIndex; private int removeIndex; private int queueSize;
private Lock lock = new ReentrantLock(); Condition addCondition = lock.newCondition(); Condition removeCondition = lock.newCondition();
public MyQueue(int count) { obj = new Object[count]; }
public void add(E e) { lock.lock(); while (queueSize == obj.length) { try { addCondition.await(); } catch (InterruptedException e1) { e1.printStackTrace(); } } obj[addIndex] = e;
if (++addIndex == obj.length) { addIndex = 0; }
queueSize++; removeCondition.signal(); lock.unlock(); }
public void remove() { lock.lock();
while (queueSize == 0) { try { removeCondition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } obj[removeIndex] = null;
if (++removeIndex == obj.length) { removeIndex = 0; }
queueSize--;
addCondition.signal();
lock.unlock(); } }
|
- 使用synchronized实现一个重入锁:
public class Test implements Lock { private boolean isLocked = false; //标志位,是否已经上锁 @Override public synchronized void lock() { while(isLocked){//自旋 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } finally { } } isLocked = true; } @Override public synchronized void unlock() { isLocked = false; notify();
} } //这个锁不可重入 改造 private boolean isLocked = false; //标志位,是否已经上锁 private Thread lockby; int lockCount; //记录重入次数 @Override public synchronized void lock() { Thread currentLock = Thread.currentThread(); while(isLocked && currentLock != lockby){//自旋 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } finally { } } isLocked = true; lockby = currentLock; lockCount++; } @Override public synchronized void unlock() { //判断当前线程 if(lockby==Thread.currentThread()){ lockCount--; if(lockCount == 0){ isLocked = false; notify(); } } }
|
- 读写锁:
ReentrantReadWriteLock
读写锁需要保存的状态:
写锁重入的次数
读锁的个数
每个读锁重入的次数,只有当读锁重入次数为0了,写锁才能进入
16位写锁数量低八位,读锁数量高八位
锁降级:写锁降级为读锁,在写锁没有释放的时候,获取到读锁,在释放写锁。一个操作既有读又有写
public void readWrite() {
r.lock(); // 为了保证isUpdate能够拿到最新的值
if (isUpdate) {
r.unlock();
w.lock();
map.put("xxx", "xxx");
r.lock();//需要在读锁释放之前再加一把读锁
w.unlock();
}
Object obj = map.get("xxx");
System.out.println(obj);
r.unlock();
}
- 死锁:线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
private Object obj1 = new Object();
private Object obj2 = new Object();
public void a(){
synchronized (obj1){
synchronized (obj2){
System.out.print("a");
}
}
}
public void b(){
synchronized (obj2){
synchronized (obj1){
System.out.print("b");
}
}
}
- 重入锁源码分析:
- Jdk中的原子类操作
- Aqs详解
- Volatile和Threadlocal关键字
- 并发工具包
1CountDownLatch:同步功能的辅助类,效果是给定一个计数,当使用这个latch类的线程判断计数不为0,则呈现wait状态:
例子:
|
2 cyclicBarrier
3 semaphore
4 exchanger
5 fork-join框架
6线程池原理(源码分析):线程池主要用来解决线程生命周期开销问题和资源不足问题,通过对多个任务重用线程,线程创建的开销被分摊到多个任务上了,而且由于在请求到达时线程已经存在,所以消除了创建所带来的延迟
* corePoolSize 线程池维护线程的最少数量
* maximumPoolSize 线程池维护线程的最大数量
* keepAliveTime 线程池维护线程所允许的空闲时间
* workQueue 任务队列,用来存放我们所定义的任务处理线程
三种提交策略:
1.直接提交。直接提交策略表示线程池不对任务进行缓存。新进任务直接提交给线程池,当线程池中没有空闲线程时,创建一个新的线程处理此任务。这种策略需要线程池具有无限增长的可能性。实现为:SynchronousQueue
2.有界队列。当线程池中线程达到corePoolSize时,新进任务被放在队列里排队等待处理。有界队列(如ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
3.无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
拒绝策略:当任务源源不断的过来,而我们的系统又处理不过来的时候,我们要采取的策略是拒绝服务。RejectedExecutionHandler接口提供了拒绝任务处理的自定义方法的机会。在ThreadPoolExecutor中已经包含四种处理策略。
1.CallerRunsPolicy:线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
这个策略显然不想放弃执行任务。但是由于池中已经没有任何资源了,那么就直接使用调用该execute的线程本身来执行。(开始我总不想丢弃任务的执行,但是对某些应用场景来讲,很有可能造成当前线程也被阻塞。如果所有线程都是不能执行的,很可能导致程序没法继续跑了。需要视业务情景而定吧。)
2.AbortPolicy:处理程序遭到拒绝将抛出运行时 RejectedExecutionException
这种策略直接抛出异常,丢弃任务。(jdk默认策略,队列满并线程满时直接拒绝添加新任务,并抛出异常,所以说有时候放弃也是一种勇气,为了保证后续任务的正常进行,丢弃一些也是可以接收的,记得做好记录)
3.DiscardPolicy:不能执行的任务将被删除
这种策略和AbortPolicy几乎一样,也是丢弃任务,只不过他不抛出异常。
4.DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程) 也就是丢弃最早的任务
七.并发容器:
同步容器:vector和hashtable或者调用collections来对集合进行同步
1.CopyOnWriteArrayList解决ArrayList的并发问题
2.ConcurrentHashMap 解决hashmap的并发问题
3.ConcurrentLinkedQueue
4.阻塞队列BlockingQueue:
concurrentHashMap源码分析:
java重排序():编译器和处理器为了提高程序的运行性能,对指令进行重新排序
原则:as if serial数据依赖性:写后读、读后写、写后写。对于没有数据依赖性的代码cpu会进行指令重排序
分类:编译器重排序和处理器重排序
影响:单线程情况下,会提高性能,但是多线程情况下
Happen-Before:
在java内存模型中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必然存在Happen-Before关系
《深入理解Java内存模型(二)——重排序》
《happens-before俗解》