黑马程序员_Java多线程基础

----------- Android培训Java培训Java学习型技术博客、期待与您交流! ------------

学习到多线程,就必须对多线程中的概念理解透彻.

程序:

程序是一段静态的代码,它是应用程序执行的蓝本.

进程:

进程是程序的一次动态执行过程,它对应了从代码加载、执行至执行完毕的一个完整过程,这个过程也是进程本身从产生、发展至消亡的过程.

线程:

线程是比进程更小的单位,一个进程执行过程中可以产生多个线程,每个线程有自身的产生、存在和消亡的过程,也是一个动态的概念.

每个进程都有一段专用的内存区域,而线程间可以共享相同的内存区域(包括代码和数据),并利用这些共享单元来实现数据交换、实时通信与必要的同步操作.

主线程:

每个Java程序都有一个默认的主线程。Java程序总是从主类的main方法开始执行。当JVM加载代码,发现main方法后就启动一个线程,这个线程就称作"主线程",该线程负责执行main方法.

在main方法中再创建的线程就是其他线程。

 

在Java中,创建线程的方式有两种:

1、继承Thread类,覆盖run()方法:使用Thread子类创建线程的优点是可以在子类中增加新的成员变量或方法,使线程具有某种属性或功能。但Java不支持多继承,Thread类的子类不能再扩展其他的类.

2、实现Runnable接口:用Thread类直接创建线程对象,使用构造函数Thread(Runnable target)(参数target是一个Runnable接口),创建线程对象时必须向构造方法参数传递一个实现Runnable接口类的实例,该实例对 象称作所创线程 的目标对象。当线程调用start()方法,一旦轮到它使用CPU资源,目标对象自动调用接口中的run()方法(接口回调).

线程间可以共享相同的内存单元(包括代码和数据),并利用这些共享单元来实现数据交换、实时通信与必要的同步操作。对于Thread(Runnable target)创建的使用同一目标对象的线程,可以共享该目标对象的成员变量和方法.

另外,创建目标对象类在必要时还可以是某个特定类的子类,因此,使用Runnable接口比使用Thread的子类更具有灵活性.


线程的常用方法:

1.start():线程调用该方法将启动线程,从新建态进入就绪队列,一旦享用CPU资源就可以脱离创建它的线程,独立开始自己的生命周期.

2.run():Thread类的run()方法与Runnable接口中的run()方法功能和作用相同,都用来定义线程对象被调度后所进行的操作,都是系统自动调用而用户不得引用的方法。run()方法执行完毕,线程就成死亡状态,即线程释放了分配给它的内存(死亡态线程不能再调用start()方法)。在线程没有结束run()方法前,不能让线程再调用start()方法,否则将发生IllegalThreadStateException异常.

sleep(int millsecond):有时,优先级高的线程需要优先级低的线程做一些工作来配合它,此时为让优先级高的线程让出CPU资源,使得优先级低的线程有机会运行,可以使用sleep(int millsecond)方法。线程在休眠时被打断,JVM就抛出InterruptedException异常。因此,必须在try-catch语句块中调用sleep方法.

3. isAlive():当线程调用start()方法并占有CPU资源后该线程的run()方法开始运行,在run()方法没有结束之前调用isAlive()返回true,当线程处于新建态或死亡态时调用isAlive()返回false.

4. currentThread():是Thread类的类方法,可以用类名调用,返回当前正在使用CPU资源的线程。

5.interrupt():当线程调用sleep()方法处于休眠状态,一个占有CPU资源的线程可以让休眠的线程调用interrupt()方法"吵醒"自己,即导致线程发生IllegalThreadStateException异常,从而结束休眠,重新排队等待CPU资源。

 

线程的几种状态:

线程在一定条件下,状态会发生变化。线程变化的状态转换图如下:

  1、新建状态(New):新创建了一个线程对象。

  2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

  3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

  4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

  (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。

  (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。

  (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

  5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

 

创建线程的具体方式:

1.继承Thread类,覆盖run()方法

大致结构是:

 

class 类名 extends Thread{ 

 属性; 
 … 
 public void run(){ 
.....
 } //必须覆写run()方法

方法;
 … 
   
 } 
 

 

简单的示例:

 

 
 public class ThreadDemo
 {
 	public static void main(String[] args)
 	{
 		Demo demo = new Demo();
 		
 		demo.start();
 		
 		for(int i = 1; i <= 100; i++)
 		{
 			System.out.println("main Run(主)" + i);
 		}
 	}
 }
 
 class Demo extends Thread
 {
 	public void run()
 	{
 		for(int i = 1; i <= 100; i++)
 		{
 			System.out.println("Demo Run(副)" + i);
 		}
 	}
 }
 
 

运行结果是main与Demo方法的交替出现..

由于循环次数较多,只截取其中的部分片段

 

...................
Demo Run(副)93
 Demo Run(副)94
 Demo Run(副)95
 Demo Run(副)96
 Demo Run(副)97
 Demo Run(副)98
 Demo Run(副)99
 Demo Run(副)100
 main Run(主)48
 main Run(主)49
 main Run(主)50
 main Run(主)51
 main Run(主)52
 main Run(主)53
 main Run(主)54
...................
 

每次运行结果都不相同:因为多个线程获取cpu的执行权,cpu执行到谁,谁就运行.多线程一个特性:随机性.

 

如果将上述代码中的demo.start()换成demo.run().此时就类似于单线程了,先执行完Demo Run(副),然后再执行main Run(主).

结果分析:

如果是直接调用run()方法,此时并没有使用新建的线程执行,还在主线程的内部进行执行,而调用start()表明开启新的进程,并在新的进程中执行该段方法.

直接调用run()方法相当于在主方法中对象直接调用本类方法.

2.实现Runnable接口

具体步骤:

 

--定义类实现Runnable接口

--覆盖Runnable接口中的run方法,将线程要有运行的代码放在run方法中

--通过Thread类建立线程对象

--Runnable接口的子类对象作为实际对象传给Thread类的构造函数,自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去明确指定对象的run方法

--调用Thread类的start方法开启线程.

大致结构是:

 

class 类名 implements Runnable{ 
 属性;
 … 
 public void run(){ 
.....
 } //实现接口中run()方法
 方法; 
   ....
 } 
 

  简单的示例:

 

 class TicketRun implements Runnable
 {
 	private int ticket = 100;
 
 	public void run()
 	{
 		while (true)
 		{
 			if (ticket > 0)
 			{
 				System.out.println(Thread.currentThread().getName()
 				+ " Sale : " + ticket--);
 			}			
 		}
 	}
 }
 
 public class TicketRunDemo
 {
 	public static void main(String[] args)
 	{
 		TicketRun rd = new TicketRun();
 
 		Thread t1 = new Thread(rd);// 创建线程对象时,就要明确需要运行的run方法
 		Thread t2 = new Thread(rd);
 	
 
 		t1.start();
 		t2.start();
 	
 
 	}
 }
 

 

运行结果(部分截取)

 

.........................
Thread-3 Sale : 46
 Thread-1 Sale : 47
 Thread-3 Sale : 42
 Thread-0 Sale : 43
 Thread-2 Sale : 44
 Thread-0 Sale : 39
 Thread-3 Sale : 40
 Thread-1 Sale : 41
 Thread-3 Sale : 36
 Thread-3 Sale : 34
 Thread-0 Sale : 37
 Thread-2 Sale : 38
 Thread-0 Sale : 32
 Thread-3 Sale : 33
.............................
 

那么两者有什么区别?

两者线程代码存放的位置不同:实现接口的线程代码放在接口的子类run方法中,继承方式的线程代码存放在Thread子类的run方法中

实现接口方式避免了单继承的局限性,在定义线程时,建议实现接口方式.

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

 

实现Runnable接口比继承Thread类所具有的优势:

1):适合多个相同的程序代码的线程去处理同一个资源

2):可以避免java中的单继承的限制

3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。

 

 

多线程的安全问题(同步与死锁)

 

当多条语句在操作同一个线程共享数据时,一个线程多多条语句只执行了一部分,还没有执行完,另一个线程参与执行,导致共享数据的错误.

解决方式:同步代码块.synchronized.对象如同锁,持有锁的线程可以再同步代码中执行,没有锁的线程,即使获得cpu执行权,也进不去同步代码块.

将操作共享数据的语句放入同步代码块中 原理:设置标志位()

 同步的前提:

1.必须要有多个线程,单线程无需多同步.

2.多个线程必须使用同一个锁.

 必须保证同步中只能有一个线程在运行

 

优点:解决多线程的安全问题

弊端:每次必须进行锁判断,较为消耗资源.

采用同步的话,可以使用同步代码块和同步方法两种来完成。

【同步代码块】:

语法格式:

synchronized(同步对象){

 //需要同步的代码

}

while (true)
 {
 	synchronized (this)
 	{
 		if (ticket > 0)
 		{
 
 			try
 			{
 				Thread.sleep(10);
 			} catch (Exception e)
 			{
 				System.out.println("error");
 			}
 			System.out.println(Thread.currentThread().getName()
 						+ " --code-- : " + ticket--);
 		}
 
 	}
}
 

【同步方法】

 

也可以采用同步方法。

语法格式为synchronized 方法返回类型方法名(参数列表){

    // 其他代码

}

 

public synchronized void show()	
{
 
 	if (ticket > 0)
 	{
 
 		try
 		{
 			Thread.sleep(10);
 		} catch (Exception e)
 		{
 			System.out.println("error");
 		}
 			System.out.println(Thread.currentThread().getName() + " ----show---- : "
 					+ ticket--);
 	}
 
 }
 

需要注意的问题:

 

同步方法使用的锁

方法需要被对象调用,方法都有一个所属对象的引用,就是this.所以同步方法使用的锁是this.

若同步方法被static修饰.锁是Class对象,则使用的锁是 类名.class

静态进内存时,内存中没有本类对象,但有该类的字节码对象

 

提醒一下,当多个线程共享一个资源的时候需要进行同步,但是过多的同步可能导致死锁。

具体示例:

 

 

 public class ThisLockDemo
 {
 	public static void main(String[] args)
 	{
 		TicketRun rd = new TicketRun();
 
 		Thread t1 = new Thread(rd);// 创建线程对象时,就要明确需要运行的run方法
 		Thread t2 = new Thread(rd);
 
 		t1.start();
 		try
 		{
 			Thread.sleep(20);
 		} catch (Exception e)
 		{
 		}
 		rd.flag = false;
 		t2.start();
 	}
 
 }
 
 class TicketRun implements Runnable
 {
 	private int ticket = 100;
 
 	boolean flag = true;
 	//Object o = new Object();
 
 	public void run()
 	{
 		if (flag)
 		{
 			while (true)
 			{
 				synchronized (this)
 				{
 					if (ticket > 0)
 					{
 
 						try
 						{
 							Thread.sleep(10);
 						} catch (Exception e)
 						{
 							System.out.println("error");
 						}
 						System.out.println(Thread.currentThread().getName()
 								+ " --code-- : " + ticket--);
 					}
 
 				}
 			}
 		} else
 			while (true)
 				show();
 	}
 
 	public synchronized void show()	//若同步方法被static修饰.锁是Class对象,则使用的锁是	类名.class
 	{
 
 		if (ticket > 0)
 		{
 
 			try
 			{
 				Thread.sleep(10);
 			} catch (Exception e)
 			{
 				System.out.println("error");
 			}
 			System.out.println(Thread.currentThread().getName() + " ----show---- : "
 					+ ticket--);
 		}
 
 	}
 }
 

 

 

小插曲:单例设计模式

 

饿汉式与懒汉式

饿汉式:

 

class Single
 {	
 	private static final Single s = new Single();
 	
 	private Single(){}
 	
 	public static Single getInstance()
 	{
 		return s;
 	}
 }
 

懒汉式:(实例的延时加载)--->保证不了单例,存在安全问题.

 

class Single
 {
 	private static Single s = null;
 	
 	private Single(){}
 	
 	public static Single getInstance() 
 	{
 		if(s == null)
 			s = new Single();
 		return s;
 	}
 }

 

加强的懒汉式:

 

public class Single
 {
 	private static Single s = null;
 	private Single() {}
 	
 	public static Single getInstance()
 	{
 		if(s == null)
 		{
 			synchronized(Single.class)
 			{
 				if(s == null)
 					s = new Single();
 			}
 		}
 		return null;
 	}
 }

 

 

Done...

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值