写作原因:对于并发编程一块,是编程中的一道坎,对于它的理解有一定难度。所以本文作者只能略作皮毛分享,对于深层学习仍在途中。希望读者能够多多交流
线程初识
先了解一下线程、进程、并发、并行的概念。进程顾名思义,就是进行中的程序,线程是进程中可以独立并发执行的基本单元。进程中包括内存和线程两块,内存是存储资源,线程是进程内部各个功能的分载体,进程内所有线程都享有进程的共享内存,此外线程有各自的独立工作内存。这些细节在下面的锁机制中进行分析。
创建
java中创建一个线程并不难,有下面两种方式:直接使用Thread创建,实现Runnable接口然后注入Thread中创建。由于相对简单,这里不多加阐述。
生命周期
一个线程包括下面几个生命周期:新建、可运行(启动线程)、运行(系统调度)、阻塞/等待、消亡(退出run)。新建一个线程就是指我们写完一个线程并将它实例化对象的过程,可运行其实就是使用start()方法的过程,注意调用start()方法后不一定开始运行,因为线程的运行受到系统的调度。运行过程是线程真正运行的过程,当调用sleep()、suspend()、wait()时或者线程执行IO操作时会让出CPU中止执行,进入阻塞和等待过程;当run()方法结束后进入死亡状态。
常见API
这里介绍几个常用的API:
类方法:currentThread()获取当前类对象、yield()线程礼让、sleep()线程休眠
setDaemon():设置守护线程(幽灵)。守护线程的特点:当其它线程中止时,守护线程自动消亡。
线程数据共享问题
下面开始阐述线程同步的问题。线程同步很重要,因为一个进程内多个线程的数据经常需要共享使用。然而如果在不知道任何关于线程同步的操作的情况下直接进行线程间数据的共享,那样是很危险的。为什么不能直接进行线程之间的数据共享呢?我们都知道每个线程有它们独立的工作内存,这说明线程操作共享内存是间接的,也就是说某个线程对主工作内存刷新后另一个工作内存可能不知道,那么你直接操作拿到的数据可能就过时了,这样共享就失败了。此外,由于多个线程可能同时进行,内部操作某个共有的变量时顺序可能会出现多种不同的顺序,即指令重排序问题。比如说A线程,B线程,如果A线程和B线程内部多次操作C变量,这时就会出现混乱的排序问题。如果解决了以上两个问题就解决了线程数据共享问题。下面引入synchronized关键字。
解决方案
java提供了锁机制(mutex),每次只允许一个线程访问一块共享内存。对于加锁有两种形式,一种是使用代码块对某个共享的变量进行加锁,另一种是直接对方法进行加锁,对方法进行加锁实际上是以成员方法所在的对象本身作为对象锁。下面通过一个例子来认识synchronized关键字:
Data类:
public class Data {
static int i;
public static synchronized void add(){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
System.out.println("i:"+i);
}
}
ThreadA:
public class ThreadA extends Thread{
private Data data;
public ThreadA(String name,Data data){
super(name);
this.data = data;
}
public void run(){
while(true){
data.add();
}
}
}
Main.java:
public class Main {
public static void main(String[] args){
for(int i=0;i<10;i++){
Data data = new Data();
new ThreadA("Thread"+i,data).start();
}
Data data = new Data();
while(true){
data.add();
}
}
}
Main中新建了十个子线程,每个子线程中都可以执行add()方法使i变量增大,我们使用synchronized关键字修饰add()静态方法,就是表明Data类的类实例被用作对象锁了。也就是data被锁住了,其它线程操作data时只能一次一个操作。所以如果你将synchronized关键字加在run()方法上就是无效的,因为这时锁住的对象是线程对象,线程对象是多变的,而非共享的区域。使用synchronized后线程执行互斥代码时大致过程是这样的:先获得互斥锁,然后清空工作内存,从主内存中拷贝变量的最新副本,执行代码,然后将更改后的共享变量的值刷新到主内存中,最后释放互斥锁。这样就实现了线程间共享数据逻辑。
总结
关于多线程这一大块是硬骨头,很难真正把它啃下来。文中还有许多地方没有涉及,而且由于是个人理解可能存在疏漏,后续在进行Android线程剖析时再做补充。