JAVA线程的xiao习:线程池 线程同步 线程间通讯 =》进程间通信

线程的基础知识:

线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。

创建一个线程有两种方法,一种是继续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结束才能结束。

  1. try {  
  2.             mTh1.join();  
  3.         } catch (InterruptedException e) {  
  4.             e.printStackTrace();  
  5.         }  

$$$$$$$$线程池:$$$$$$$

先看一下线程池构造方法:

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。

线程间通信-异步消息处理机制:

就这样先将就看把。。有心情了再补充补充

 

 

原文地址:https://www.cnblogs.com/vitabebeauty/p/7218857.html