Java基础之线程

一.线程和进程:

   几乎所有的操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行的程序就是一个进程。当一个程序运行时,内部可能包含多个顺序执行流,每个执行流就是一个线程。

  进程:当一个程序进入内存运行时,即成为一个进程。进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位。

    进程具有三个特征:

       独立性:进程是系统中独立存在的实体,拥有自己独立的资源,每个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间;

       动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合,在进程中加入了时间的概念,进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的。

       并发性:多个进程可以在单个处理器上并行执行,多个进程之间不会相互影响。

    线程:是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程可以拥有自己的堆栈、程序计数器和局部变量,但不拥有系统资源,它与父进程的其他线程共享进程所拥有的全部资源。运行独立,执行时是抢占式的。

二.线程的创建和启动

      1.继承Thread类创建线程类:

          定义thread类的子类,并重写run()方法,该方法代表了线程需要完成的任务;

          创建thread子类的实例,创建线程对象;

          调用线程对象的start()方法来启动该线程。

public class NewThread extends Thread {
    @Override
    public void run() {
        
        super.run();
    }
}

//创建线程对象,执行start方法
 NewThread newThread=new NewThread();
 newThread.start();
       2.实现Runnable接口创建线程类

          定义Runnable接口的实现类,并重写该接口的run()方法;

          创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。

class RunnThread implements Runnable{

        @Override
        public void run() {

        }
    }

 RunnThread runnThread=new RunnThread();
 new Thread(runnThread);

        3.使用Callable和Future创建线程:

           Callable接口提供了一个call()方法可以作为线程执行体,call()方法可以有返回值,call()方法可以抛出异常。Future接口提供了一个FutureTask实现类,该实现类实现了Future接口,并实现了Runnable接口可以作为Thread类的target。

           创建并启动有返回值的线程的步骤:

               1.创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值,再创建Callable实现类的实例;

               2.使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值;

               3.使用FutureTask对象作为Thread对象的target创建并启动新线程;

               4.调用FutureTask对象的get()方法来获取子线程执行结束后的返回值。

三.线程的生命周期:

      当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态,在线程的生命周期中,它要经过新建(new)、就绪(running)、阻塞(blocked)和死亡(dead)五种状态。


四、线程的控制:

    1.join线程:Thread提供了让一个线程等待另一个线程完成的方法---join()方法,当某个程序执行中调用其他线程的join()方法时,调用线程将会被阻塞,直到被join()方法加入的join线程执行完为止;

    2、后台线程:后台运行,它的任务是为其他的线程提供服务,后台线程(Deamon Thread),JVM的垃圾回收线程就是典型的后台线程。如所有的前台线程都死亡后台线程会自动死亡。调用Tread对象的setDeamon(true)方法可指定线程设置为后台线程。

    3、线程睡眠sleep:如需要当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以使用Thread类的静态sleep()方法来实现。

    4、线程让步yield:让当前执行暂停。但他不会被阻塞该线程,只是将线程转入就绪状态。

    5、改变线程优先级:每个线程执行时都具有一定的优先级,优先级高的线程获得较多的执行机会,而优先级低的线程则获取较少的执行机会。setPriority()、setPriority()来设置和返回优先级。

五、线程同步:

    1.同步代码块:在run()方法的方法体不具备有同步安全性。Java中多线程支持引入同步监视器使用监视器就是同步代码块。

synchronized(obj){
   ……
   //同步代码块
}
    2.同步方法:使用synchronized关键词来修饰的方法。
public synchronized 返回值  方法名(参数){ 
     ……
     //同步方法的代码
}
    3.释放同步监视器的锁定及释放:

       当前线程同步方法、同步代码块执行结束,当前线程释放同步监视器;

       当前线程的同步方法、同步代码块中遇到break、return终止了该代码块、方法的继续执行,当前线程释放同步监视器;

       当前线程在同步代码块、同步方法中出现了未处理的error或exception,导致代码块、方法异常结束时,当前线程释放同步监视器;

       当前线程执行同步代码块或同步方法时,程序执行了不同监视器对象的wait()方法,则线程暂停,当前线程释放同步监视器;

        当前执行同步块、方法时,程序调用Thread.sleep()、Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器;

        当前执行的同步块时,其他线程调用了线程的suspend()方法将线程挂起,该线程不会是方法监视器。

六、同步锁(lock):
   Lock是控制多个线程对共享资源进行访问的工具,通常,锁定提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先Lock对象。某些锁可允许对共享资源并发访问,如ReadWriteLock(读写锁)、ReentrantLock(可重入锁)、ReetrantReadWriteLock。

//定义锁对象
private final ReentrantLock lock=new ReentranLock();
//定义需要保证线程安全的方法
public void m(){
   lock.lock();
   try{
      //需要保证线程安全的代码
      ……
     //使用finally块来保证释放锁
     finally{ 
        lock.lock();
     }
   }
}
七、死锁:

    当两个线程互相等待对方释放同步监视器时就会放生死锁,一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。


八、线程通信:

   1. 传统的线程通信:

        Object类提供的wait()、notify()、notifyAll()三个方法,这三个方法不属于Thread类而是属于Object类,这三个方法必须由同步监视器对象来调用。可分为:

        对于使用synchronized修饰的同步方法,因为该类的默认实例(this)就是同步监视器,所以在同步方法中直接调用这三个方法。

        对于使用synchronized修饰的同步代码块,同步监视器是synchronized后括号里的对象,所以必须使用该对象调用这三个方法。

        wait():导致当前线程等待,直到其他贤臣调用该同步监视器的notify()方法或notifyAll()方法唤醒该线程。

        notify():唤醒在此同步监视器上等待的单个线程,如果所有线程都在此同步监视器上等待,则会选择唤醒其中一个线程。选择是任意的。

       notifyAll():唤醒在此同步监视器上等待的所有线程。只有当前线程放弃对该同步监视器的锁定后,才执行被唤醒的线程。

   2.使用Condition控制线程通信:

       如程序不使用synchronized关键字来保证同步,而是使用Lock对象来保证同步,则系统中不存在隐式的同步监视器,也不能使用wait()、notify()、notifyAll()方法进行线程通信。当使用Lock对象来保证同步时,Java提供一个Condition类来保证协调,使用Codition可以让已经得到Lock对象却无法继续执行的线程释放Lock对象,Codition对象也可以唤醒其他处于等待的线程。

      Condition实例被绑定在一个Lock对象上,要获取特定Lock实例的Codition实例,调用Lock对象的newCondition()方法即可,Condition类提供三种方法:

          await():类似于隐式同步监视器上的wait()方法,导致当前线程等待,直到其他线程调用该Condition的signal()方法或signalAll()方法来唤醒该线程。

          signal():唤醒在此Lock对象上等待的单个线程。

          singalAll():唤醒在此Lock对象上等待的所有线程。

//显示定义Lock对象
private final Lock lock=new ReentrantLock();
//获取定义Lock对象对应的Condition
private final Condition cond=lock.newCondition();

//加锁
lock.lock();
//等待线程
cond.await();
//唤起其他线程
cond.signalAll();

   3.使用阻塞队列(BlockingQueue)控制线程通信:

      当生产者线程试图向BlockingQueue中放入元素时,如该队列已满,则该线程被阻塞;当消费者线程取出元素时,如该队列已空,则该线程被阻塞。程序的两个线程通过交替向BlockingQueue中放入、取出元素,可很好的控制线程。

      BlockingQueue支持两个阻塞的方法:

          1).put(E e):尝试把E元素放入BlockingQueue中,如队列满则阻塞该线程;

          2).take():尝试从BlockingQueue头部取出元素,如队列空则阻塞线程。

      BlockingQueue继承Queue接口,Queue接口有如下方法:

          1).在队列尾部插入元素。包括add(E e)、off(E e)、put(E e)方法,当队列已满时,这三个方法分别抛出异常、返回FALSE、阻塞队列;

          2).在队列头部删除并返回删除的元素。包含remove()、poll()、take()方法,当队列已空,这三个方法分别会抛出异常、返回FALSE、阻塞队列;

          3).在队列头部取出但不删除元素。包括element()、peek()方法,当队列已空,这两个方法分别抛出异常、返回FALSE。

      BlockingQueue有以下五个实现类:

          1).ArrayBlockingQueue:基于数组实现的BlockingQueue队列;

          2).LinkedBlockingQueue:基于链表实现的BlockingQueue队列;

          3).PriorityBlockingQueue:并不是标准的阻塞队列,取出队列最小的元素;

          4).SynchronsQueue:同步队列,对该队列的存取操作必须交替进行;

          5).DelayQueue:底层基于PriorityBlockingQueue,要求集合元素都实现Delay接口,根据集合元素的getDalay()方法的返回值进行排序。


九、线程组:
    Java中使用ThreadGroup来表示线程组,它可对一批线程进行分类管理,Java允许程序直接对线程组进行控制。对线程组的控制相当于同时控制这批线程,用户创建的所有线程都属于指定线程组,如程序没显示指定线程属于哪个线程组,则该线程属于默认线程组。在默认情况下,子线程和创建它的父线程处于同一线程组内。

十、线程池:

      系统启动一个新线程的成本比较高,因为涉及与操作系统交互,在这种情形下,使用线程池可很好提高性能,尤其是当程序需要创建大量生存期很短暂的线程时,更应考虑使用它线程池。线程池在系统启动时创建大量空闲的线程,程序将一个Runnable对象或Callable对象传给线程池,线程池就会启动一个线程来执行它们的run()或call()方法,当run()或call()方法执行结束后,该线程并不会死亡,而是再次返回线程池中称为空闲状态,等待下一个Runnable对象的run()或call()方法。

      下面是自己写的关于线程池的总结:

       Android中线程的相关知识及线程池的管理工具类


十一、ThreadLocal类:

    是Thread Local Variable(线程局部变量)的意思,功用其实就是每个使用该变量的线程都提供一个变量值得副本,使得每个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。

    ThreadLocal从另一个角度来解决多线程的并发访问,ThreadLocal将需要并发访问的资源复制多分,每个线程拥有一份资源,每个线程都拥有自己的资源副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的整个变量封装进ThreadLocal,或者把该对象与线程相关的状态使用ThreadLocal保存。


十二、线程安全的集合类:






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值