一:线程基础

1:进程与线程
2: 线程状态
3: 创建线程
4: 线程中断
5: 安全地终止线程

二:同步

2.1:重入锁与条件对象
2.2: 同步方法
2.3: 同步代码块
2.4:volatile关键字

一:线程基础

1.1.1:什么是进程

进程是操作系统结构的基础,是程序在一个数据集合上运行的过程,是系统进行资源分配和调度的基本单位。进程可以被看作程序的实体,同样它也是线程的容器。

1.2.1:什么是线程

线程是操作系统调度的最小单元,也叫轻量级进程,在一个进程中可以创建多个线程,这些线程都拥有自己的计数器,堆栈和局部变量等属性,并且能够访问共享的内存。比如QQ的进程,它里面运行了很多子任务,这些子任务有加载图片的、也有处理缓存的,还可能有在后台下载的,这些子任务就是线程。

这时候,==为什么要使用多线程==?一个进程上面运行一个线程不可以吗?
当然是可以的,但是单线程的应用程序处理多任务会显得乏力,比如下载是个耗时的操作,当我们聊QQ的时候,好友发来一张照片,而我们的网络又不能在很短的时间内价值完,只有一个线程的话,就需要等待这张图片价值完成,我们才能进行其它操作,这样的沟通效率就会比较低。使用多线程可以减少程序的响应时间。让加载图片的任务在单独线程执行,这样我们就可以继续操作其挺的任务。

与进程相比,线程创建和切换开销更小,同时多线程在数据共享方面也非常高。

使用多线程能简化程序的结构,使程序便于维护和理解。

1.2:线程的状态

线程的生命周期可能会处于6中不同的状态。分别如下:

  • a:New:新创建还没有调用start方法的时候,在线程运行之前可以处理一些基础工作。
  • b:Runnable:可运行状态,调用了start方法之后,线程就处于Runnable状态。一个可运行状态的线程,可能正在运行,也可能没有运行,这取决于操作系统给线程分配的运行时间。
  • c:Blocked:阻塞状态。表示线程被锁阻塞,暂时不活动。
  • d:Waiting:等待状态。线程暂时不活动,这个状态消耗最少,且等待线程调度器激活它。
  • e:Timed waiting超时等待状态。指超过一定的时间,自行返回。
  • f:Terminated:终止状态。线程执行完毕。导致线程终止的,有两种可能,一是run方法执行结束正常退出。二是抛出没有捕获的异常,导致线程进入终止状态。

    线程的状态如图所示:

1.3创建线程

创建线程一般有三种方法,其中有两种方法比较常用,下面看看这些方法都是怎么创建的:

1.3.1. 继承Thread类,重写run()方法

Thread的本质是实现了Runnable接口的一个实列。继承Thread创建线程主要有三步。
- 定义Thread的子类,并重写该类的run()方法,run方法是线程的执行体。
- 创建Thread子类的实列,就创建了线程对象。
- 调用线程对象的start()方法启动线程。

代码如下

public class OneThread extends Thread{
    private String TAG = "MyThread";
    public void run(){//线程主体
        Log.i(TAG , "Thread One");
    }

    public static void main(String [] args){
        Thread thread = new OneThread();
        thread.start();
    }
}
1.3.2.实现Runnable接口,并实现run()方法

三部曲

  • 自定义实现Runnable接口类,实现run()方法。
  • 创建Thread类实列,用Runnable接口子类的对象作为参数实列该Thread对象。
  • 调用Thread的start()方法启动线程。
public class TwoThread implements Runnable{
    private String TAG = "MyThread";
    public void run(){//线程主体
        Log.i(TAG , "Thread Two");
    }
}
//
public class Test1{
    public static void main(String [] args){
        TwoThread runnableThread = new TwoThread();
        Thread thread = new Thread(runnableThread);
        thread.start();
    }
}

1.3.3. 实现Callable接口,重写call()方法

public class TestCallable {  
    //创建线程类
    public static class MyTestCallable  implements Callable {  
        public String call() throws Exception {  
             retun "Hello World";
            }  
        }  
public static void main(String[] args) {  
        MyTestCallable mMyTestCallable= new MyTestCallable();  
        ExecutorService mExecutorService = Executors.newSingleThreadPool();  
        Future mfuture = mExecutorService.submit(mMyTestCallable);  
        try { 
        //等待线程结束,并返回结果
            System.out.println(mfuture.get());  
        } catch (Exception e) {  
           e.printStackTrace();
        } 
    }  
}

1.4.1: 线程中断

在Java早期版本中,可以调用Thread的stop方法来终止线程,其它线程也可以调用该方法,但是现在这个方法已经弃用了。
interrupt方法可以用来请求终止线程,当一个线程调用interrupt方法时,线程的中断状态将被置位。这是每个线程都具有的boolean标志,每个线程都应该不时的检查这个标志,来判断线程是否被中断。

要想弄清线程是否被置位,可以调用Thread.currentThread().isInterrupted():

但是如果一个线程被阻塞,就无法检测中断状态。这是产生InterruptedException的地方。当一个被阻塞的线程(调用sleep或者wait)上调用interrupt方法。阻塞调用将会被InterruptedException中断。

如果每次迭代之后都调用sleep方法(或者其他可中断的方法),isInterrupted检测就没必要也没用处了,如果在中断状态被置位时调用sleep方法,它不会休眠反而会清除这一状态并抛出InterruptedException。所以如果在循环中调用sleep,不要去检测中断状态,只需捕获InterruptedException。

1.5.1: 安全地终止线程

终止线程有两种方式
5.1:用中断来终止线程,代码如下:

public class StopThread{
    public static void main(String []args){
        TestRunnable runnable = new TestRunnable();
        Thread thread = new Thread(rannable,"TestRunnable");
        thread.start();
        TimeUnit.MILLISECONDS.sleep(10);//让线程睡眠10ms,给TestRunnable线程时间来感知中断
        thread.interrup();
    }
    public static class TestRunnable implements Runnable{
        private long i;
        private final String TAG = "MyThread";
        @Override
        public void run(){
            while(!Thread.currentThread().isInterrupted()){
                i++;
                Log.i(TAG , "i="+i);
            }
                Log.i(TAG , "Stop");
        }
    }
}

还有一种是通过一个boolean变量控制run()方法执行结束,从而回收线程。
代码如下:

public class StopThread{
    public static void main(String []args){
        TestRunnable runnable = new TestRunnable();
        Thread thread = new Thread(rannable,"TestRunnable");
        thread.start();
        TimeUnit.MILLISECONDS.sleep(10);//
        runnable.cancel();
    }
    public static class TestRunnable implements Runnable{
        private long i;
        private volatile boolean on = true;//volatile是线程同步关键字
        private final String TAG = "MyThread";

        @Override
        public void run(){
            while(on){
                i++;
                Log.i(TAG , "i="+i);
            }
                Log.i(TAG , "Stop");
        }
        public void cancel(){
            on = false;
        }
    }
}

二:同步

2.1:重入锁与条件对象
2.2: 同步方法
2.3: 同步代码块
2.4:volatile关键字

2.1:重入锁与条件对象

2.1.1:ReentrantLock锁

private Lock mLock =new ReentrantLock();

mLock.lock();
try{
    ...//锁执行的区域
}
finally{
    mLock.unlock();//执行结束,释放锁
}

2.2:Synchronized关键字定义同步方法

如果一个方法用synchronied关键字声明,那么对象的锁将保护这个方法。也就是说调用该方法,线程必须获得内部的对象锁。

方法定义如下:

public synchronied void testMethod(){
    //
}

相当于

public void testMethod(){
    this.lock.lock();
    try{
        //
    }finally{
        this.lock.unlock();
    }
}

2.3:同步阻塞/也叫同步代码块

synchronized(obj){
    //执行需要同步的代码
}

2.4:volatile关键字

volatile关键字可以保证变量的可见性,这是java内存管理的特性之一。当一个变量被多个线程使用时,线程A改变了变量的值,为了让其它线程及时更新变量值,可用volatile修饰该变量,这样就可以保证线程之间的可见。