java多线程理解 以及java实现的简单的死锁

     多线程,三大机制中的一个,编程问题的一个难点,在前两天的面试中,就被问到了这个问题,尴尬的是当时居然没回答上来,最后还被刷了,想起来还真是尴尬。

     说到多线程,先来理解一下线程吧。

     线程,也被称为“轻量级进程”,是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己几乎不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪阻塞运行。

   线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制

从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

在java中实现多线程有两种方式:继承Runnable接口和继承Thread方法,无论哪种方式实现多线程都需要建立Thread类或它子类的实例,例如一个简单的线程。

第一种方式:

public class Car1 implements Runnable {

    @Override
    public void run() {
        System.out.println("我是第一种方式创建的线程!");
    }
    
    public static void main(String[] args) {
        Runnable r = new Car1();
        Thread t = new Thread(r);
        t.start();
    }

}

第二种方式:

public class Car2 extends Thread {
    @Override
    public void run(){
        System.out.println("我是第二种方式创建的线程!");
    }
    
    public static void main(String[] args) {
        Thread t = new Car2();
        t.start();
    }
}

 两种方式都需要重写run()方法,以实现自己的方法。其次启动线程都需要用start()方法,若直接调用run方法时,则只会有一个线程启动,不能实现多线程。

实现多线程:

package mhc.learn.Thread;

public class Car1 implements Runnable {

    @Override
    public void run() {
        //Thread.currentThread().getName()获取当前线程的名字
        System.out.println("我是小汽车线程==="+Thread.currentThread().getName()+"===我在跑。o 0");
    }
    
    public static void main(String[] args) {
        Runnable r = new Car1();
//        Thread t = new Thread(r);
//        t.start();
        //尝试启动多个线程
        Thread t[] = new Thread[100];
        for(int i=0;i<100;i++){
            t[i] = new Thread(r,"线程"+i);//给每个线程不同的名字,加以区别
        }
        
        for(Thread thread:t){
            thread.start();
        }
        
    }

}
该程序的运行结果:

从运行的程序结果可以看出,运行的线程都是抢占式的,可以说他们的顺序都是随机的,所以再这种情况下就会出现一些问题,例如对公共变量的操作。


就像下面这样

public class Car1 implements Runnable {
	static int sum=0;//创建一个统计线程总数的变量,这个变量是该类对象共享的,每个线程都可以对这个数字进行操作
	@Override
	public void run() {
		//Thread.currentThread().getName()获取当前线程的名字
		sum++;
		System.out.println("我是小汽车线程==="+Thread.currentThread().getName()+"===我在跑。o 0");
	}
	
	public static void main(String[] args) {
		Runnable r = new Car1();
//		Thread t = new Thread(r);
//		t.start();
		//尝试启动多个线程
		Thread t[] = new Thread[100];
		for(int i=0;i<100;i++){
			t[i] = new Thread(r,"线程"+i);//给每个线程不同的名字,加以区别
		}
		
		for(Thread thread:t){
			thread.start();
		}
		System.out.println("线程的总数是:"+Car1.sum);	
	}
}
//结果获得的线程的总数是90,99,93等等,这个数字肯定是比真实的线程总数100少的,这个是可以解释的,当第一个线程取得该值的时候假如sum=1,第二个线程也取得了sum=1,然后第一个线程保存了sum++的结果2,但第二个线程此时也写入了sum=2,所以两个线程操作完sum之后还是2,在这种情况之下就出现了差错。所以加锁就很容易想得到了。

Synchronized关键字,就是同步锁,关于同步锁的上锁方式的介绍,我没写博文,我看到有一篇写的很详细的推荐一下:http://www.blogjava.net/konhon/archive/2005/08/17/10296.html,谢谢仁兄的博文,


按博文中的第4个将run方法修改为下

public void run() {
        synchronized(Car1.class){
            //Thread.currentThread().getName()获取当前线程的名字
            sum++;
            System.out.println("我是小汽车线程==="+Thread.currentThread().getName()+"===我在跑。o 0");
        }
    }

然后在主线程中将主线程睡眠一小段时间,防止主线程和其他线程一起抢占CPU资源,实现的代码为

try {
            Thread.currentThread().sleep(500);
            //Thread.currentThread().join();//当前的主线程将等待其他线程全部执行完之后才执行
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        System.out.println("线程的总数是:"+Car1.sum);
        这样一来,可以统计出当前的所有总线程数为100.

在想让主线程最后执行的过程中,在网上搜索了很多资料,发现很多都是推荐使用当前线程的join()方法来实现,

但是有的却说,是使用主线程的join,这样可以保证主线程等待其他线程执行完毕之后再执行,还有的说要在每个线程都得加上join方法,Thread最后不加,这样才能保证要求。到底如何呢?我也打算自己来试一试,第一种:主线程中加入join()方法后,join方法后面的一句话未输出,而程序一直结束不了,不知道是什么原因。

第二种:程序运行结果显示,主线程最后一句输出并不是最后执行的,没有达到效果。那到底是怎么回事??

第二天我查找了相关的资料,找到了,器其实第一种是错误的,第二种是正确的,join方法应该是用在线程启动之后,在启动之前使用join方法是没用的。






来说说java实现的死锁吧,synchronized这个关键字肯定是会用到的,那次面试中我也很诧异,当时还没反应过来,给面试官写的java简单的死锁还没用到synchronzied这个关键字,我也是醉了,回来之后,看了一些网上的参考程序,过段时间又快忘记了。还是总结下来的好。

老规矩,上程序,解释已经注释在程序中。



着下面是第一次的

package com.mhc.learn;



/**
 * 
 * @author mhc
 *练习java做一个死锁,上一次面试的时候居然没写出来
 */
public class deadLock implements Runnable{


public int flag=0; //设置变量使多个线程的锁定静态变量的顺序不一样

static Object a = new Object(),b=new Object();//****需要静态的类的变量


@Override
public void run() {

System.out.println("flag = " + flag);
if(flag==1){//当为1时先锁0.5sa对象,再锁b对象
synchronized (a) {
try {
// System.out.println("a当前的线程:"+Thread.currentThread().getName());
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

synchronized (b) {
System.out.println("      1     ");
// System.out.println("b当前的线程:"+Thread.currentThread().getName());
}
}


if(flag==0){//锁对象的顺序与1相反
synchronized (b) {
try {
// System.out.println("b当前的线程:"+Thread.currentThread().getName());
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

synchronized (a) {
System.out.println("   0   ");
// System.out.println("a当前的线程:"+Thread.currentThread().getName());
}
}

}

public static void main(String[] args) {
deadLock d1 = new deadLock();
deadLock d2 = new deadLock();
d1.flag = 0;
d2.flag = 1;
for (int i = 0; i < 10; i++) {
new Thread(d1).start();
new Thread(d2).start();
}

}


}



上面的代码,死锁的效果不明显,最后又参考了些资料。发现另外一种更明显的死锁写法,就是在锁a的函数里面锁b,改成嵌套的,而不是顺序的,就更容易锁住

代码:

package com.mhc.learn;


import java.util.concurrent.atomic.AtomicInteger;


/**
 * 
 * @author mhc
 *练习java做一个死锁,上一次面试的时候居然没写出来
 */
public class deadLock implements Runnable{


public int flag=0; //设置变量使多个线程的锁定静态变量的顺序不一样

static Object a = new Object(),b=new Object();//需要静态的类的变量




@Override
public void run() {

System.out.println("flag = " + flag);
if(flag==1){
synchronized (a) {
try {
// System.out.println("a当前的线程:"+Thread.currentThread().getName());
Thread.currentThread().sleep(500);
synchronized (b) {
System.out.println("      1     ");
// System.out.println("b当前的线程:"+Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}


}


if(flag==0){
synchronized (b) {
try {
// System.out.println("b当前的线程:"+Thread.currentThread().getName());
Thread.currentThread().sleep(500);
synchronized (a) {
System.out.println("   0   ");
// System.out.println("a当前的线程:"+Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}


}

}

public static void main(String[] args) {
deadLock d1 = new deadLock();
deadLock d2 = new deadLock();
d1.flag = 0;
d2.flag = 1;
for (int i = 0; i < 10; i++) {
new Thread(d1).start();
new Thread(d2).start();
}

}


}




可发现程序一直未结束。成功的实现了死锁!


总结一下,实现死锁的几个关键点,1、需要类静态成员变量 2、需要区分进入不同对象锁函数的标志(比如本程序中的flag),3、创建多个线程更容易看到效果 4、需要加入线程睡眠时间.  5,还有就是对对象加锁的顺序。(基本数据类型不让加锁)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章