您的位置:首页 > 编程语言 > Java开发

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

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

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

线程,也被称为“轻量级进程”,是程序执行流的最小单元。一个标准的线程由线程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,还有就是对对象加锁的顺序。(基本数据类型不让加锁)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: