线程的基础知识:
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
创建一个线程有两种方法,一种是继续Thread类(重写run方法),另外一种是实现Runable接口(还是重写run方法);
线程的转换状态如下:
1:start之后不是立即开始执行thread,而是变成可执行状态,等待cpu翻牌子;
2:阻塞:出于某个原因放弃cpu使用权,暂停运行,直到进入就绪状态才有机会继续执行。
三种情况:(1)运行的线程调用了wait()方法 jvm将其放入等待队列,同时释放持有的锁;
(2)线程没有抢到锁 进入等待队列;
(3)调用了sleep()方法(并不会释放锁)或join()方法或发出I/O请求,jvm将其状态设为阻塞状态,当 sleep()超时或join()等待线程超时或者结束或者I/O请求处理完毕,线程重新转入就绪状态。
//join用法如下:运行try的线程要等待mTh1结束才能结束。
- try {
- mTh1.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
$$$$$$$$线程池:$$$$$$$
先看一下线程池构造方法:
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
int corePoolSize:核心线程个数;
int maximumPoolSize:线程最多好多个;
long keepAliveTime:每个线程最多可以闲多少个时间单位;
TimeUnit unit:时间单位;
BlockingQueue<Runnable> workQueue:线程池中的任务队列.
常用的有三种队列,SynchronousQueue
,LinkedBlockingDeque
,ArrayBlockingQueue
。复不复用。
第一个线程池:可缓存线程池CachedThreadPool
我们可以看出:没有核心线程;线程数量没有限制;空闲60秒之后被kill掉;在创建任务时 有空闲的线程则服用空闲线程 没有则新建;
怎么用:1创建:mCachedThreadPool = Executors.newCachedThreadPool();
2执行:mCachedThreadPool.execute(new Runnable(){.........});
第二个线程池:FixedThreadPool定长线程池
全是核心线程,所以在默认情况下,该线程池的线程不会因为闲置状态超时而被销毁(核心线程 得永生(相对而言的永生));
在创建任务时 当线程数未到核心线程数,即最大线程数,即使有空闲的线程也不复用,当大于核心线程数之后,多的部分进入队列等候,等有闲置线程的时候再来处理。
用法同上;
第三个线程池:SingleThreadPool 单线程池
池如其名
用法同上;
第四个线程池:ScheduledThreadPool
可延时执行 也可以周期执行的线程池;
怎么用:
延时执行
1:mScheduledThreadPool = Executors.newScheduledThreadPool(3);//3个核心线程
2:mScheduledThreadPool.scheduled(new Runnable(){.....},4,TimeUnit.SECONDS);//4秒后执行
周期执行
1:mScheduledThreadPool = Executors.newScheduledThreadPool(3);//3个核心线程
2:mScheduledThreadPool.scheduledAtFixedRate(new Runnable(){.....},4,7,TimeUnit.SECONDS);//4秒后执行,执行完任务7秒后再次执行,执行完任务7秒后再次执行.....
$$$$$$$$线程同步:$$$$$$$$
在JDK1.5中,synchronized是性能低效的。因为这是一个重量级操作,它对性能最大的影响是阻塞的是实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性带来了很大的压力。相比之下使用Java提供的Lock对象,性能更高一些。Brian Goetz对这两种锁在JDK1.5、单核处理器及双Xeon处理器环境下做了一组吞吐量对比的实验,发现多线程环境下,synchronized的吞吐量下降的非常严重,而ReentrankLock则能基本保持在同一个比较稳定的水平上。但与其说ReetrantLock性能好,倒不如说synchronized还有非常大的优化余地,于是到了JDK1.6,发生了变化,对synchronize加入了很多优化措施,有自适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在JDK1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地,所以还是提倡在synchronized能实现需求的情况下,优先考虑使用synchronized来进行同步。
什么时候考虑线程同步呢?是否有竞争,资源被同时改动的情况。
关键字:synchronized。
!!!synchronized是给对象上锁而不是代码!!!
举例项目里用到的情况。
1:唯一 一个不是修饰方法的:单例模式
1 public static HttpUtils getInstance(){ 2 if(null==sInstance){ 3 synchronized(HttpUtils.class){ 4 if(null ==sInstance){ 6 sInstance = new HttpUtils(); 7 } 8 } 9 return sInstance; 10 } 11 12
synchronized给网络请求对象上锁:
什么时候考虑线程同步呢?是否有竞争,资源被同时改动的情况。
有多个线程网络请求的情况 而且我们并不希望同时多次网络请求 希望始终只有唯一的一个sInstance实例。
//至于这里为什么要写两个 if(null==sInstance),这是一个非常好的问题。
假设有 ab两个线程同时要执行synchronized里面的代码,a抢先一步,获得了sInstance的锁,可以执行同步的代码,b就只能在同步代码外面等着。当a执行完,sInstance 已经不为空了,b线程可以执行synchronized里面的代码了,为了避免再实例一个sInstance,所以同步代码里面有一个 if(null==sInstance) .。
为什么外面也有一if(null==sInstance) .呢?因为synchronized,同步是一个非常耗时的过程,耗时意味着 队列排队排很久 对cpu开销非常大.。所以没有sInstance实例的时候再来进行同步 new一个实例的操作,所以 如果有实例的时候,同步之前判断 ,直接返回该实例。避免了同步。
对 这就是Java 中的双重检查(Double-Check) 详情可以点击链接哦。
http://blog.csdn.net/dl88250/article/details/5439024
2:synchronic修饰方法:public synchronized void A(..){...}给调用A方法的对象上锁
关键词:lock
synchronized与lock都可以保证同步,可是两个是不一样的。
区别:
synchronized 是一个修饰符 是 java语言内置的。而lock是一个类。通过利用lock对象实现代码同步。
之前提到 synchronized的锁的释放有三种(正常termination,异常,唤醒机制里的wait())取决于于jvm,而lock,我们可以控制它的释放。要保证锁定一定会被释放,就必须将unLock()放到finally{}中;
简单代码如下 (在as里实现)
public class LockSellTicket implements Runnable { private int ticket = 100; private Lock lock = new ReentrantLock(); @Override public void run() { while (true){ try { lock.lock(); if (ticket>0){ try{ Thread.sleep(100); }catch (InterruptedException e){ e.printStackTrace(); } Log.i("$$$$$$$",Thread.currentThread().getName()+"正在熟手第"+(ticket--)+"张票"); } }finally { lock.unlock(); } } } }
public class RunnableDemo { LockSellTicket str = new LockSellTicket(); public void sell (){ Thread str1 = new Thread(str ,"1"); Thread str2 = new Thread(str ,"2"); Thread str3 = new Thread(str ,"3"); str1.start(); str2.start(); str3.start(); } }
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); RunnableDemo runnableDemo = new RunnableDemo(); runnableDemo.sell(); } }
关键词:volatile
volatile修饰共享变量:一旦volatile修饰一个变量之后会花生什么呢?
下面这段话摘自《深入理解Java虚拟机》:
“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”
lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
2)它会强制将对缓存的修改操作立即写入主存;
3)如果是写操作,它会导致其他CPU中对应的缓存行无效。
从上面一段话中 我们可以分析出一下结论:
1:volatile保证 一定的程度 有序性,保证volatile先后的指令相对于它的位置是不变的,但是它前面(后面)的一系列指令,其中没有依赖关系的部分 还是有可能实现指令重排序。这也是为什么只能保证一定程度上的有序性。
2:volatile 保证 可见性。一旦 volatile修饰的共享变量 被修改时!!!即马上进行写的操作,写的操作会导致其他线程里的缓存无效,其他内存缓存无效会去cpu读取共享变量值,volatile修饰的变量只有当写的操作完成时才能进行读操作。(即jvm happens-end8条原则中的volatile原则)
3:volatile 并不能保证原子性。2中强调了时volatile修饰变量x被修改时才会发生,若:public
volatile
int x
=
0
;多个线程都会执行
x++;自增不是一个原子操作,它包含了 读,加一,写三个子操作。假设当线程1进行从内存中取出x(x=100)的值,1被阻塞了,线程2也从内存中取出x(x=100);加一,x=101;写入内存,x=101;1运行 加一,x=101;写入,先101;。两次自增 实际上只增加了1;所以 volatile 并不能保证原子性。
volatile与synchronized有什么使用区别呢?
根据以上我们可以知道 在保证了 原子性之后 ,我们才能让volatile保证我们程序在并发是正确执行。
补充内容:(copy大神的:)
happens-before原则(先行发生原则):
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
- 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
- volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
- 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
- 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
- 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
这8条原则摘自《深入理解Java虚拟机》。
这8条规则中,前4条规则是比较重要的,后4条规则都是显而易见的。
下面我们来解释一下前4条规则:
对于程序次序规则来说,我的理解就是一段程序代码的执行在单个线程中看起来是有序的。注意,虽然这条规则中提到“书写在前面的操作先行发生于书写在后面的操作”,这个应该是程序看起来执行的顺序是按照代码顺序执行的,因为虚拟机可能会对程序代码进行指令重排序。虽然进行重排序,但是最终执行的结果是与程序顺序执行的结果一致的,它只会对不存在数据依赖性的指令进行重排序。因此,在单个线程中,程序执行看起来是有序执行的,这一点要注意理解。事实上,这个规则是用来保证程序在单线程中执行结果的正确性,但无法保证程序在多线程中执行的正确性。
第二条规则也比较容易理解,也就是说无论在单线程中还是多线程中,同一个锁如果出于被锁定的状态,那么必须先对锁进行了释放操作,后面才能继续进行lock操作。
第三条规则是一条比较重要的规则,也是后文将要重点讲述的内容。直观地解释就是,如果一个线程先去写一个变量,然后一个线程去进行读取,那么写入操作肯定会先行发生于读操作。
第四条规则实际上就是体现happens-before原则具备传递性。
内存模型:
每一个线程都会配有一定的高速cpu缓存=》Cache。
线程间通信-异步消息处理机制:
就这样先将就看把。。有心情了再补充补充