1 线程安全定义:当过个线程同时访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步及在调用方式代码不必做其他的协调,这个类行为仍是正确的,那么称这个类是线程安全的;
2 锁竞争:当线程进行了synchronized 操作 线程获取了synchronized 线程进行Lock 其他线程进行竞争 形成锁竞争问题(尽量避免多线程抢一把锁的问题) cause : 锁竞争会导致cpu使用率增高
多个线程多个锁 (synchronized +static 静态表明的话则会产生先后顺序问题 对象锁之间互补关联 )
1 对象的同步和异步 同步:synchronized 同步的概念就是共享,如果不是共享资源,就没有必要进行同步(多个访问需要排队等待) 异步:asynchronized 异步的概念就是独立,相互间不受任何约束.(并发访问) 同步的目的是为了线程安全,对于线程安全来说,需要满足两个特性: 原子性(同步) myObject.java 原子性:一个个操作是不可中断的,要么全部执行成功要么全部执行失败,有着“同生共死”的感觉,由始至终。
可见性:当访问一个共享的可变的变量时,为什么要求所有线程由同一个锁进行同步,为了保证一个线程对数值进行的写入,其他线程也可见,另一方面,如果一个线程在没有恰当地使用锁的情况下读取了变量,那么这个变量可能是过期数据.
synchronized锁重入:
synchronized拥有锁重入的功能,也就是说一个线程得到了一个对象的锁后,再次请求此对象时是可以再次得到该对象的锁
public syncharonized void method1(){
method2()
}
public syncharonized void method2(){ method3() } public syncharonized void method3(){
}
synchronized遇见异常会直接释放锁
出问题解决方案: 1 有问题进行日志记录 2 进行事件整体提交(原子性) 3 continue 记录日志
storm:分布式计算
synchronized可以对任意object进行加锁
注意: 1不要使用string进行加锁,容易出现死循环,因为只有一个引用 2当锁对象进行改变的时候,要注意对象本身发生改变的时候持有不同的锁.若对象本身不发生改变,那么依然是同步的,即使是对象的属性发生了改变
1.7 volatile关键字 volatile 主要用于使变量在多个线程间可见
特性问题:
public class RunThread extends Thread{
private boolean isRunning = true;
private void setRunning(boolean isRunning){
this.isRunning = isRunning; }
public void run(){ System.out.println("进入run方法");
whille(isRunning == true){ } System.out.println("线程停止");
}
public static void main(String[] args) throws InterruptException{
RunThread rt = new RunThread();
rt.start();
Thread.sleep(3000);
rt.setRunning("false");
sout("idRunning的值已经设置了false");
Thread.sleep(1000); sout(rt.isRunning); } }
当程序进行开始的时候正常应该是rt.setRunning的时候打印线程停止,但实际是 "idRunning的值已经设置了false" 但是线程还是继续在死循环之中,原因是因为jvm虚拟机在启动一个线程的时候会先分配一个空间给对象,但是因为java1.5后对线程进行了优化,开始的时候会去主内存中copy 一分isRunning = true 到自己的线程独立内存中,而当线程执行的时候,执行引擎是直接调用独立内存中的变量进行交互,所以线程一直执行 以上解决方案 使用volatilve关键字 volative只具备可见性 不具备原子性 y要实现原子性: AtomicInteger具备原子性 可以使用jvm原子类 AtomicInteger private static AtomicInteger count = new AtomicInteger(0); count.incrementAndGet(); 但是Atomic类只能保证本方法原子性,不能保证多次方法调用原子性(整数才能保证一个方法原子性) volative 多个访问可以提高性能优化(mina,netty)
2.1 多线程之间的通信 概念:线程是操作系统中独立的个体,但这些个体如果不经过特殊处理就不能成为一个整体,线程间的通信就成为了整体的必用方式之一,当线程存在通信指挥,系统间的交互性更强大,在提高cpu利用率的同时还会使开发人员对线程任务在处理的过程中进行有效的把控和监督
使用 wait /notify方法实现线程间的问题(两个方法都是object类方法,所以所有的java对象都提供了这两个方法) 1 wait和notify必须配合synchronized关键子使用 2 wait 释放锁 notify方法不释放锁 例子 ListAdd2 //CountDownLatch 为java.util.conurrent import java.util.List; import java.util.ArrayList; import java.util.concurrent.CountDownLatch;
public class ListAdd2{
private volatile static List list = new ArrayList();
public void add(){ list.add("bjsxt"); }
public int size(){ return list.size(); }
public static void main(String[] args) { // final ListAdd2 listAdd2 = new ListAdd2(); final Object lock = new Object(); final CountDownLatch countDownLatch = new CountDownLatch(1);//发起一次(括号内容代表发起几次) Thread t1 = new Thread( new Runnable() { @Override public void run() { try { synchronized (lock) { for (int i = 0; i < 10; i++) { listAdd2.add(); System.out.println("当前线程:" + Thread.currentThread().getName() + "添加了一个元素"); Thread.sleep(500); if (listAdd2.size() == 5) { System.out.println("已发出通知"); countDownLatch.countDown(); lock.notify(); } } } } catch (InterruptedException e) { e.printStackTrace(); } } }, "t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { synchronized (lock){ if(listAdd2.size() != 5){ try{ System.out.println("t2进入...."); lock.wait(); }catch (InterruptedException e){e.printStackTrace();} } System.out.println("当前线程:"+Thread.currentThread().getName()+"收到通知停止"); throw new RuntimeException(); } } },"t2"); t2.start(); t1.start(); } }
使用wait/notify 模拟Queue(队列) 模拟一个阻塞队列 有界容器 计数器 制定上下限 put 和 take 方法 代码如下: import java.util.LinkedList; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger;
public class MyQueue { //模拟阻塞队列 //1需要一个承装元素的有限集合 private LinkedList linkedList = new LinkedList<>(); //2需要一个计算器 private AtomicInteger count = new AtomicInteger(0); //3需要制定上限和下限 private final int minsize = 0; private final int maxSize; //4 public MyQueue(int size){ this.maxSize = size; } //初始化一个对象,用于加锁 private final Object lock = new Object(); //使用put 方法往容器放元素 使用take 方法往容器拿走元素 public void put(Object obj){ synchronized (lock){ while (count.get() == this.maxSize){ try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果空间元素没满 linkedList.add(obj); //计数器累加 count.incrementAndGet(); //当加到满的时候 唤醒其他等待的线程 lock.notify(); System.out.println("新加入的元素为" + obj); } } //take拿走容器中排在首位的对象 public Object take(){ Object ret = null; synchronized (lock){ while (count.get() == this.minsize){ try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //移除第一个元素 ret = linkedList.removeFirst(); //计数器递减 count.decrementAndGet(); //加满了的情况唤醒put的线程 lock.notify(); } return ret; } public int getSize(){ return this.count.get(); }
public static void main(String[] args) { // final MyQueue mq = new MyQueue(5); mq.put("a"); mq.put("b"); mq.put("c"); mq.put("d"); mq.put("e"); System.out.println("当前容器的长度:" + mq.getSize()); Thread t1 = new Thread(new Runnable() { @Override public void run() { mq.put("f"); mq.put("g"); } },"t1"); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { Object o1 = mq.take(); System.out.println("移除的元素为"+o1); Object o2 = mq.take(); System.out.println("移除的元素为"+o2); } },"t2"); try { TimeUnit.SECONDS.sleep(2);//线程睡眠两秒 } catch (InterruptedException e) { e.printStackTrace(); } t2.start(); } }
2.3 ThreadLocal(单独变量) 线程局部变量,是一种多线程间并发访问变量的解决方案.与其synchronized等加锁的方式不同,ThreadLocal完全不提供锁,而是使用空间换时间的手段,为每个线程提供变量的独立副本,以保障线程安全. 但从性能上说,ThreadLocal不具有绝对的优势,在并发不是很高的时候,加锁的性能会更好,但作为一套与锁完全无关的线程安全解决方案,在高并发量和竞争激烈的场景,使的ThreadLocal可以在一定程度上减少锁竞争
2.4 单例 & 多线程 饿汉 : 直接实例化对象 懒汉 : 需要的时候才进行对象实例化
饿汉:
public class SingletonDemo3 {
private static SingletonDemo3 instance = new SingletonDemo3();
private SingletonDemo3(){} public static SingletonDemo3 getInstance(){ return instance; } }
懒汉(非线程安全): public class SingletonDemo1 {
private static SingletonDemo1 instance; private SingletonDemo1(){} public static SingletonDemo1 getInstance(){ if (instance == null) { instance = new SingletonDemo1(); } return instance; } }
懒汉(线程安全): public class SingletonDemo2 { private static SingletonDemo2 instance; private SingletonDemo2(){} public static synchronized SingletonDemo2 getInstance(){ if (instance == null) { instance = new SingletonDemo2(); } return instance; } }
static inner class 单例模式在多线程中最好使用静态内部类的模式
public class SingletonDemo5 { private static class SingletonHolder{ private static final SingletonDemo5 instance = new SingletonDemo5(); } private SingletonDemo5(){} public static final SingletonDemo5 getInsatance(){ return SingletonHolder.instance; } }
单例返回的hashcode是相等的 或者多线程双重验证 为什么SingletonDemo7 需要加入双重判断? 多线程时的单例,多线程的程序中,多个线程同时访问该单例,会有可能创建多个实例。这个时候就需要用“锁”将它锁起来。包括锁、死锁、锁之间的通信
public class SingletonDemo7 { private volatile static SingletonDemo7 singletonDemo7; private SingletonDemo7(){} public static SingletonDemo7 getSingletonDemo7(){ if (singletonDemo7 == null) { synchronized (SingletonDemo7.class) { if (singletonDemo7 == null) { singletonDemo7 = new SingletonDemo7(); } } } return singletonDemo7; } }
3.1 同步类容器 同步类容器都是线程安全的,但在某些场景下可能需要加锁来保护复合操作,复合类的操作如:迭代(反复访问元素,或遍历完容器中所有的元素),跳转(根据指定的顺序找到当前元素的下一个元素),以及一些条件运算,在多线程操作并发的修改容器的时候可能会发生以为的行为,最经典的便是ConcurrentModificatonException,原因是当容器在迭代的过程中,被并发修改了内容,早早期的迭代器没有考虑并发修改的问题
同步类容器:如古老的vector` HashTable 低层无非就是用传统的synchronized关键字对每个公用的方法进行同步,使得每次只有一个线程访问的状态,但却并不符合我们现在高并发的需求,而且性能方面也欠缺
3.2 并发类容器 同步类容器虽然实现了线程安全,但是严重降低了并发性,在多线程环境下,严重降低了引用程序的吞吐量. 并发容器是专门针对并发设计的,使用ConcurrenHashMap来替代传统的HashTable,而且在ConcurrentHashMap添加的复合操作支持,以及使用CopyOnWriteArrayList 代替Voctor, 并发的CopyOnWriteArraySet 和并发的Queue,ConcurrentLinkedQueue 和BlockingQueue 前者是高性能的队列,后者是以阻塞形式的队列,具体实现Queue还有很多,例如ArrayBlockingQueue priorityBlockingQueue`SynchronousQueue
ConcurrentMap容器: 使用segment(段) 把一个hashTable分解成n个段,最高支持16段 , 一个段为一个小的hashTable,访问的时候对其中的一段hashTable的时候会加上一把锁.减小锁的粒度,减小锁竞争的一种方案,底层使用volatile关键字,目的第一时间获取修改内容
Copy-On-Write容器 Copy-On-Write容器有两种:CopyOnWriteArraySet 和 CopyOnArrayList Copy-On-Writer容器即写时复制的容器,通俗理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,在将原容器的引用指向新的容器,这样好处是可以对CopyOnWrite容器进行并发的读,而且不需要加锁,因为当前容器不会添加任何元素所以这是一种读写分离的思想,读和写不同的容器 使用场景(读多写少)
ReentrantLock 重入锁 ConcurrentHashMap ConcurrentsxipListtMap (支持并发排序 treeMap)
5.1 并发Queue ConcurrentLinkedQueue 高性能非阻塞队列 适用于报并发场景下的队列,通过无锁的方式,实现高并发状态下的高性能,通常ConcurrentLinkedQueue性能好于BlockingQueue,它基于基本链接节点的无界线程安全队列.该队列遵循现进先出原则.头是最先加入的,尾是最近加入的,并且队列中不允许null元素 ConcurrentLinkedQueue重复方法: add() 和offer()都是加入元素的方法(在ConcurrentLinkedQueue中,这两个方法没有任何区别) poll()和peek()都是取头元素节点,区别在于前者会删除元素,后者不会
BlockingQueue 阻塞队列
ArrayBlockingQueue:基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,其内部没实现读写分离,也就意味这生产和消费不能完全并行,长度是需要定义的,可以制定先进先出,也叫有界队列,在很多场合非常适合使用. add()和offer()中 offer具有阻塞功能;
LinkedBlockingQueue:基于链表的阻塞队列,同ArrayBlockingQueue类似,其内部也维持这一个数据缓冲队列(该队列由链表构成),LinkedBlockingQueue之所以能够高效的处理并发数据,是因为其内部实现采用分离锁,从而实现生产者和消费者操作完全并行运行,他是一个无界队列 .drainTo(list,3);从队列中取出三个元素放在list集合中(批量从队列中去元素)
PriorityBlockingQueue:基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定,也就是说传入对象的对象必须实Comparable接口),在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁,他是一个无界队列
DelayQueue:带有延迟时间的Queue.其中的元素只有当其指定的延迟时间到了,才能够从队列汇总获取该元素.DelayQueue中的元素必须实现Delayed接口,DelayQueue是一个没有大小限制的队列.应用场景很多,比如对缓存超时的数据进行移除,任务超时处理,空闲链接的关闭等;)
SynchronousQueue:一种没有缓冲的队列,生产者产生的数据直接会被消费者获取并消费(不允许加入任何元素)(任务非常少的时候使用) 可以使用元素,但是不是往容器中加元素
MQ 进行数据堆积 主要原因是由于在高并发环境下,由于来不及同步处理,请求往往会发生堵塞,比如说,大量的insert,update之类的请求同时到达MySQL,直接导致无数的行锁表锁,甚至最后请求会堆积过多,从而触发too many connections错误。通过使用消息队列,我们可以异步处理请求,从而缓解系统的压力。
直连通信 tcp : mina netty
线程的安全类封装了任何必要的同步,因此不需要自己提供
@ThreadSafe: 引用于多线程环境
并发危险情况之一: 竞争条件 ,当程序被多线程调用时,getNext 是否能返回不重复的值,正像他预期那样,这取决与运行时运算的交替进行这些操作
--因为线程共享相同的内存地址空间是一样的,且并发运行
远程方法调用(Remote Method Invocation ):RMI 使你能够调用在另外一个 JVM上运行的对象的方法.
一个远程对象必须去维护两种线程安全的风险: 对那些可能会与其他对象共享的状态进行适当调节,应对应正确的对远程对象进行调控(因为相同的对象可能会被多个线程调用).比如servlets RMI 对象应该对同时发生的多个调用有所准备,并且必须提供他们的线程安全
Swing 和 AWT GUI应用程序员具有固有的异步特性 . 但是swing 组件中的JTable ,都不是线程安全的 相反Swing通过限制访问事件线程中的GUI组件,实现了他们的线程安全.
无论何时,只要多于一个的线程访问给定的状态变量,而且其中某个线程会写入该变量,此时必须使用同步来协调堆该变量的作用
一个对象的状态的就是他的数据,存储在状态变量中
共享: 被多个线程访问
可变: 变量在其生命周期可以变化
一个对象是否安全,取决是它是否被多个线程访问
在没有正确的同步下,如果多个线程访问同一个变量,你的程序就会存在隐患,有三种方法修复它: 1.不要垮线程共享变量
2.使状态量为不可变
3.在任何访问状态变量的时候使用同步 但是在程序的设置中,尽量一开始设置的时候就是线程安全的,这比在后期修复它更容易
因为线程访问无状态对象的行为不会影响到其他线程的访问的正确性,所以无状态的访问是线程安全的
对于每个可被多个线程访问的可变状态变量,如果所有访问它的线程在执行时都占有同一个锁,这种情况下,我们称这个变量是由这个锁保护的.