上篇文章说道在Java中,“流”是抽象的概念,不容易理解。而所谓的进程和线程,同样也是看不到摸不着的,同样属于抽象概念。但是把进程和线程形象化之后,就会发现,其实两者有很大的区别。
简单理解进程和线程,现在的操作系统都是多任务操作系统,可以同时运行很多应用程序,进程就是内存中一个正在运行的应用程序,它有自己独立的内存空间,而且可以启动多条线程。比如现在有一个支持多用户登录的系统,系统启动是一个进程,而多个人登录就是多个线程,这样理解起来就方便多了。
概念
线程是一个程序内部的顺序控制流,Java中的线程是通过java.lang.Thread类来实现的。
创建和启动
线程的创建可以有两种方式,第一种是定义的线程类实现Runnable接口
<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">public class TestThread{
public static void main(String args[]){
Runner r=new Runner();
Thread t=new Thread(r);
t.start(); //启动线程
for(int i=0;i<100;i++){
System.out.println("Main Thread:---------"+i);
}
}
}
class Runner implements Runnable{
public void run(){
for(int i=0;i<100;i++){
System.out.println("Runner:"+i);
}
}
}</span></span></span></span>
第二种是定义一个Thread的子类,并重写其run()方法
<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">public class TestThread{
public static void main(String args[]){
Runner r=new Runner();
r.start(); //启动线程
for(int i=0;i<100;i++){
System.out.println("Main Thread:---------"+i);
}
}
}
class Runner extends Thread{
public void run(){
for(int i=0;i<100;i++){
System.out.println("Runner:"+i);
}
}
}</span></span></span></span>
线程同步
什么是线程同步?在实际应用中,会有这样的情况,两个线程同时对相同数据进行操作,这样就会产生问题,会导致两个线程都的不到自己满意的返回数据,解决这个问题的方法就是线程同步。
线程同步就是给数据加锁,在一个线程访问当前对象时,给当前对象加锁,执行完成后解锁,然后另一个线程才能进行访问。不过加锁之后,并不能解决一切问题,因为这样会引起“死锁”。
死锁是当多个进程需要同时访问多个对象时会引起的问题,下面看一个小例子:
<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">public class TestDeadLock implements Runnable {
public int flag = 1;
static Object o1 = new Object(), o2 = new Object();
public void run() {
System.out.println("flag=" + flag);
if(flag == 1) { //当flag=1时,对象o1加锁,只允许一个线程访问
synchronized(o1) {
try {
Thread.sleep(500); //线程睡眠半秒钟
} catch (Exception e) {
e.printStackTrace();
}
synchronized(o2) { //访问对象o2,并对o2加锁
System.out.println("1");
}
}
}
if(flag == 0) { //当flag=0时,对象o2加锁,只允许一个线程访问
synchronized(o2) {
try {
Thread.sleep(500); //线程睡眠半秒钟
} catch (Exception e) {
e.printStackTrace();
}
synchronized(o1) { //访问对象o1,并对o1加锁
System.out.println("0");
}
}
}
}
public static void main(String[] args) {
TestDeadLock td1 = new TestDeadLock();
TestDeadLock td2 = new TestDeadLock();
td1.flag = 1;
td2.flag = 0;
Thread t1 = new Thread(td1); //线程1,且flag为1
Thread t2 = new Thread(td2); //线程2,且flag为0
t1.start(); //线程1启动,此时flag为1,访问对象o1并加锁
t2.start(); //线程2启动,此时flag为0,访问对象o2并加锁
}
}</span></span></span></span>
线程1和线程2都需要对象o1和o2才能完成,如代码中所示,线程1的flag为1,所以此时正访问对象o1,并且锁定了对象o1,而它再需要访问对象o2就能完成任务。同样线程2也是这样,它的flag为0,此时正访问对象o2,并且锁定了对象o2,再需要访问对象o1就能完成任务。而此时的问题是,两者都没有完成任务,所以都不释放当前锁定的对象,而且都需要对方锁定的对象,这是就产生了死锁。
总结
线程是轻量级的进程,如果一个进程只有一个线程,就好像一个饭店只有一个厨师,在一定情况下是不符合实际的,这也就有了多线程。但是,多线程中的死锁问题是很容易出现的,原因就在于锁的设置不合理,只要在项目中合理的设置锁的位置,死锁问题还是可以避免的。