黑马程序员---java多线程详解

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------


基础:

进程:一个正在执行中的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元,每个进程
都会有独立的 代码 和 数据空间,进程间切换会有较大的开销,系统运行时 会为每个进程分配不同的内存区域


线程:就是一个进程中独立的控制单元,线程在控制着进程的执行,进程的执行其实就是线程的执行,同一类线程共享代码
和数据空间,每个线程有自己独立运行的栈

     一个进程中至少有一个线程   (从 WORD来看 可以把 拼写检查当做一个线程),JVM启动不止一个线程,
          还有负责垃圾回收机制的线程,进程没了 线程肯定会消失,线程消失了 进程不一定消失,并且所有的
线程都是在进程的基础之上并发同时(同时运行)。


。Thread 类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是 run() 方法

。Thread 中 start() 的功能就是创建一个线程,并自动调用该线程中的 run(); 方法 直接调用 run();  不会创建新线程

。run()  方法中出现的异常只能 try{}  catch(){} 

。执行一个线程实际上就是执行该线程中 run();  的代码,所以要重写run(),想在线程中执行别的方法,必须把该方法放在 run(); 内部

。一个 Thread 对象只能代表一个线程,一个 Thread 对象只能调用一次 start(); 否则会抛出异常 

。X核CPU只能一次处理X个程序,而不是同时处理几个,因为CPU处理速度很快,所以看上去像是在同时处理

。每一条线程的 run() 方法都会在 栈 中开辟一块空间,所以 主线程结束 main() 方法弹栈 并不会影响别的线程的执行,在同一个线程
中的多个方法 会共用一个 栈空间,一个方法先开始 先进栈 运行结束后就会先弹出该栈 ,

。wait()  会释放锁  sleep() 不会释放锁

线程的创建:

方法1---直接继承Thread:


class A extends Thread   // 创建多线程通过 extends Thread
{
   public void run()   //重写 父类 Thread 中的 run 方法,来运行线程,
  {
	// code1
   }

}

public class TestThread
{
   public static void main(String[] args)
   {
       A aa = new A();
       aa.start();      
      /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
       *	调用从 Thread类 中继承来的 start 方法,来创建线程,start 会自动执行 A 中的 run();  然后交替执行 code1 code2 
       *	(假设 code1,code 2 都是循环语句)
       *	主线程执行main()里的代码,code1与code2都有可能被先执行,执行次数也不一定,start 与 run(); 连用
       */

      // code 2			     
    }
}

 

方法2---实现Runnable接口:

<span style="font-size:14px;">class MyThread implements Runnable
{
    public void run()
    {
        // code1
    }
}

public class TestThread2
{
    public static void main(String[] args)
    {
        MyThread mt = new MyThread();
        Thread t = new Thread(mt);	// 直接为 Thread 类创建一个对象,开辟一个线程
	t.start();		// 创建好线程后,调用 Thread 类里的 start() ,开启线程
        //code2
    }

}</span>

以上两种方法对比:

Thread 类 与 Runnable 接口 的使用结论:

实现 Runnable 接口比继承 Thread 类 有如下优点:

。适合多个相同程序代码的线程去处理同一个资源 (如例 Test1:)

。可以避免由于单继承带来的局限性

。增强了程序的健壮性,代码能够被多个线程共享,代码与数据是独立的
所以开发中使用 Runnable 接口更为合适


方法三---通过实现Callable接口来创建Thread线程

Callable接口(也只有一个方法)定义如下:

public interface Callable<V>
{
V call() throws Exception;
}
步骤1:创建实现Callable接口的类SomeCallable<Integer>(略);

步骤2:创建一个类对象:

Callable<Integer> oneCallable = new SomeCallable<Integer>();

步骤3:由Callable<Integer>创建一个FutureTask<Integer>对象:

FutureTask<Integer> oneTask = new FutureTask<Integer>(oneCallable);

注:FutureTask<Integer>是一个包装器,它通过接受Callable<Integer>来创建,它同时实现了Future和Runnable接口。
步骤4:由FutureTask<Integer>创建一个Thread对象:

Thread oneThread = new Thread(oneTask);

步骤5:启动线程:

oneThread.start();

至此,一个线程就创建完成了。



线程的生命周期:

 线程在建立后并不马上执行run方法中的代码,而是处于等待状态。线程处于等待状态时,可以通过Thread类的方法来设置线程不各种属性,如线程的优先级(setPriority)、线程名(setName)和线程的类型(setDaemon)等。

   当调用start方法后,线程开始执行run方法中的代码。线程进入运行状态。可以通过Thread类的isAlive方法来判断线程是否处于运行状态。当线程处于运行状态时,isAlive返回true,当isAlive返回false时,可能线程处于等待状态,也可能处于停止状态。下面的代码演示了线程的创建、运行和停止三个状态之间的切换,并输出了相应的isAlive返回值。

   一但线程开始执行run方法,就会一直到这个run方法执行完成这个线程才退出。但在线程执行的过程中,可以通过两个方法使线程暂时停止执行。这两个方法是suspend和sleep。在使用suspend挂起线程后,可以通过resume方法唤醒线程。而使用sleep使线程休眠后,只能在设定的时间后使线程处于就绪状态(在线程休眠结束后,线程不一定会马上执行,只是进入了就绪状态,等待着系统进行调度)。


生产者消费者:

class Res
{
	String name;
	String sex;
	boolean flag = false;
}

class Input implements Runnable
{
	private Res r; 	// 定义 Res 类的引用,用来接收传入的 Res对象
	
	public Input(Res r)
	{
		this.r = r;
	}
	public void run()	// 【这里是一条线程要执行的代码】
	{
		int i = 0;
		while(true)
		{
			synchronized(r)  //【同步代码块,如果线程没有被wait(),在while(true)时 该代码块会被一直执行】
			{
				if(r.flag)
				{
					try
					{
						r.wait();  
					/* 【这里 wait() 之后,当前线程等待状态(停在这里,下次被notify()
					     时从这里下一条代码处开始执行) ,另一条线程开始执行其 run()】
					*/
					}
					catch(Exception e)
					{
						
					}
				}
				if(i%2==0)
				{
					r.name = "kingowe";
					r.sex = "man";
				}
				else
				{
					r.name = "孔超";
					r.sex = "男";
				}
				i++;
				r.flag = true;
				r.notify();	 
				 /* 
				    【唤醒线程池中的一条线程,如果线程池中没有等待的线程,该代码也不会报错,执行完这里,
				      因为是while(true) 并且为synchronized(r),所以继续执行循环体中的代码,由于r.flag=true,
				      所以在进入上边的if(r.flag)中,执行 r.wait(),当前线程停在r.wait()处,另一条线程开始执行】	
				*/		
			}
		}
	}	
}

class Output implements Runnable
{
	private Res r;
	
	public Output(Res r)
	{
		this.r = r;
	}
	
	public void run()  // 【这里是另一条线程要执行的代码】
	{
			while(true)
			{
				synchronized(r)
				{
					if(!r.flag)
					try
					{
						r.wait();
					}
					catch(Exception e)
					{
					
					}
					System.out.println(r.name+ " " + r.sex);

					r.flag = false;
					r.notify();
				}
			}		
	}
	
}

public class ThreadIO 
{
	public static void main(String[] args)
	{
		Res r = new Res();
		Input in = new Input(r);
		Output out = new Output(r);
		Thread t1 = new Thread(in);
		Thread t2 = new Thread(out);
		t1.start();
		t2.start();
	}
}




线程死锁原理:

线程1:
public void run(){

	synchronized(b){	 // <1> 【先锁 b 再锁 a】

		sleep(1000);
		
		synchronized(a){	//<2>
			...
		}
	}
}

线程2:

public void run(){


	synchronized(a){	//<3> 【先锁 a 再锁 b】

		sleep(1000);
		
		synchronized(b){ // <4>
			...
		}
	}
}




。 当线程1执行到 <2> 时,线程2执行到 <4> ,此时 线程1持有了 b 锁, 等待 a 锁, 线程2 持有了 a 锁,等待 b 锁, 所以会出现死锁


。 对于synchronized持有的对象锁,有可能是根据对象的 equals() 或者 compare() 方法来判断每个 synchronized 持有的对象(锁)是否相同
如果结果相同,那么表示这几个synchronized包含的语句同一时间只能有一条线程来执行(一个synchronized执行完才能执行另一个synchronized)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值