---------------------- <a href="http://edu.csdn.net"target="blank">ASP.Net+Android+IOS开发</a>、<a href="http://edu.csdn.net"target="blank">.Net培训</a>、期待与您交流! ----------------------
1. 什么是线程:
要理解线程,首先必须明白什么是进程。
进程是指内存中正在运行的程序。而每一个进程的执行都有一定的执行顺序,该顺序是一个执行路径,或者叫一个控制单元。这个控制单元就叫做线程。
线程是进程中的一个执行流程,一个进程中可以运行多个线程,多个线程同时执行。线程总是属于某个进程,进程中至少有一个线程,进程中的多个线程共享进程的内存。
注意:对于单核操作系统来说并没有真正意义上的多线程同时执行,而是cpu在不同线程之间进行着快速地切换;多核操作系统才能实现真正意义上的多线程。
2. Java中的多线程:
每个Java程序都有一个默认的主线程。Java程序总是从主类的main方法开始执行。当JVM加载代码,发现main方法后就启动一个线程,这个线程就称作"主线程",该线程负责执行main方法。在main方法中创建的线程就是其他线程。
如果main方法中没有创建其他线程,那么当main方法运行至结尾时JVM就会结束Java应用程序。但如果main方法中创建了其他线程,那么JVM就要在主线程和其他线程之间轮流切换,保证每个线程都有机会使用CPU资源。正常情况下,这时即使main方法返回(主线程结束),JVM也不会结束,而是要一直等到该程序所有线程全部结束才结束Java程序。
3. Java中多线程的创建和启动:
Java中创建多线程的方式有两种:
a) 创建一个继承自Thread类的子类:
继承Thread 类时,必须复写其中的run() 方法,把作为单独线程要执行的代码放在run() 中。创建一个该子类对象就是创建一个线程。
b) 创建一个类实现Runnable 接口:
Runnable接口中同样有一个run() 方法,所以当创建一个类实现Runnable接口时必须复写该方法,该方法中的代码就是作为单独线程要执行的代码。与Thread子类不同,Runnable 接口的子类创建的对象不是线程,这时要创建线程,必须把Runnable 接口的子类创建的对象作为参数传入Thread类或其子类的构造方法中,通过创建Thread类或其子类的对象来创建线程。
注意:无论是通过Thread类还是Runnable接口建立线程,都必须建立Thread类或它的子类的实例。只有Thread及其子类创建的对象才是线程,Runnable 子类创建的对象不是线程;
线程的启动:线程的启动是通过Thread类或其子类对象调用start()方法,注意这里不是调用其中的run() 方法,直接调用run() 方法并不能开启线程;
下面通过创建两个类ThreadDemo1 和ThreadDemo2来分别演示两种创建并开启多线程的方式。代码示例如下:
public class ThreadDemo {
publicstatic void main(String[] args) {
//通过ThreadDemo1创建两个线程;
Threadt1=new ThreadDemo1("线程1");
Threadt2=new Thread("线程2");
//通过ThreadDemo2创建两个线程;
Threadt3=new Thread(new ThreadDemo2(),"线程3");
Threadt4=new Thread(new ThreadDemo2(),"线程4");
//开启四个线程;
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//创建Thread类的子类,并复写其中的run()方法;
class ThreadDemo1 extends Thread{
ThreadDemo1(Stringname){
super(name);
}
publicvoid run(){
for(int i=0;i<50;i++)
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
//创建实现Runnable接口的子类,并复写其中的run()方法;
class ThreadDemo2 implements Runnable{
publicvoid run(){
for(int i=0;i<50;i++)
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
4. 线程的状态与生命周期:
线程在它的一个完整生命周期中通常会经历四种状态:创建、运行、冻结(中断/挂起/阻塞)、消亡。
a) 线程的创建:该过程之前已介绍过,无论是通过Thread类还是Runnable接口建立线程,都必须建立Thread类或它的子类的实例。只有Thread及其子类创建的对象才是线程。
b) 线程的运行:线程创建后仅仅是占有了内存资源,在JVM管理的线程中还没有这个线程。这时线程必须通过调用start()方法才能开启线程,开启后线程才会拥有执行的资格并等待享用CPU资源。
c) 线程的冻结:线程被冻结的原因有四种情况:
1) JVM将CPU资源从当前线程切换给其他线程,使本线程让出CPU的使用权,并处于挂起状态。
2) 线程使用CPU资源期间,执行了sleep(int millsecond)方法,使当前线程进入休眠状态。sleep(int millsecond)方法是Thread类中的一个类方法,线程执行该方法就立刻让出CPU使用权,进入挂起状态。经过参数millsecond指定的毫秒数之后,该线程就重新进到线程队列中排队等待CPU资源,然后从中断处继续运行。
3) 线程使用CPU资源期间,执行了wait()方法,使得当前线程进入等待状态。等待状态的线程不会主动进入线程队列等待CPU资源,必须由其他线程调用notify()方法通知它,才能让该线程从新进入到线程队列中排队等待CPU资源,以便从中断处继续运行。
4) 线程使用CPU资源期间,执行某个操作进入阻塞状态,如执行读/写操作引起阻塞。进入阻塞状态时线程不能进入线程队列,只有引起阻塞的原因消除时,线程才能进入到线程队列排队等待CPU资源,以便从中断处继续运行。
d) 线程的消亡:该状态是线程释放了实体,即释放了分配给线程对象的内存。
线程消亡的原因有两个:
1) 正常运行的线程完成了它的全部工作,即执行完run()方法中的全部语句,结束了run()方法。
2) 线程被提前强制性终止,即强制run()方法结束。
下面将对sleep(), wait(),notify() 方法的使用进行举例。代码在main线程中开启了一个名称为线程1的线程,线程1开启后使用sleep(3000)的方法休眠的3秒钟;同时main线程进入for循环,当i==10时,main线程使用wait()方法进入等待状态;线程1休眠结束后进入for循环,当循环结束后使用notify() 方法唤醒main线程,main线程继续剩下的循环。代码示例如下:
public class SleepDemo {
publicstatic void main(String[] args) {
//创建并开启线程1;
newThread(new ThreadDemo3(),"线程1").start();
synchronized(SleepDemo.class){ //同步代码块;
//main线程进入for循环;
for(int i=0;i<50;i++){
System.out.println("main线程:"+i);
if(i==10){
try{
System.out.println("main线程进入wait状态*****");
//main线程进入wait 状态;
SleepDemo.class.wait();
System.out.println("main线程被唤醒***********");
}catch (InterruptedException e) {
//TODO Auto-generated catch block
}
}
}
}
}
}
class ThreadDemo3 implements Runnable{
publicvoid run(){
try{
System.out.println(Thread.currentThread().getName()+"休眠3s");
Thread.sleep(3000); //线程1 开始进入休眠状态;
}catch (InterruptedException e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(SleepDemo.class){
for(int i=0;i<50;i++)
System.out.println(Thread.currentThread().getName()+":"+i);
SleepDemo.class.notify(); //线程1 将main 线程唤醒;
}
}
}
5. Java多线程中的安全问题:
假设有一个线程在执行多条语句,且这些语句中运算同一个数据。若在执行过程中,有其他线程参与进来,并操作了这个数据,那么将会导致线程安全问题的产生。
由此可见线程安全问题的产生涉及到两个因素:
a) 线程中有多条语句对共享数据进行运算。
b) 存在别的线程也在操作该共享数据。
解决线程安全问题的方法就是采用代码同步。实现代码同步的方式有两种:
a) 同步代码块:具体的做法是在需要同步的程序代码前加上synchronized标记,并把代码放置在synchronized标记后的大括号中。
b) 同步函数:具体做法是在需要同步的函数语句的返回值类型前加上synchronized标记。
实现代码同步又叫对代码进行加锁,synchronized(this)语句中的this就是一个锁,它代表执行该段代码的对象。同步函数的锁也是对象,谁调用该函数,谁就是锁。
对代码进行加锁以实现同步的原理就是当程序执行到同步语句时,会先判断锁,这时有两种情况:
a) 如果这个锁已经被其它的线程占用,JVM就会把这个线程放到本对象的锁池中,本线程进入阻塞状态。锁池中可能有很多的线程,等到其他的线程释放了锁,JVM就会从锁池中随机取出一个线程,使这个线程拥有锁,并且转到就绪状态。
b) 假如这个锁没有被其他线程占用,本线程会获得这把锁,开始执行同步代码块。
注意:一般情况下在执行同步代码块时不会释放同步锁,但也有特殊情况会释放对象锁,例如:在执行同步代码块时,遇到异常而导致线程终止,这时锁会被释放;或者在执行代码块时,执行了锁所属对象的wait()方法,这时这个线程会释放对象锁,并进入对象的等待池中。
下面将通过同步代码块演示多线程对一个共享变量进行操作,代码示例如下:
public class TicketDemo {
publicstatic void main(String[] args) {
Tickett=new Ticket();
//创建并开启四个线程;
newThread(t,"线程1").start();
newThread(t,"线程2").start();
newThread(t,"线程3").start();
newThread(t,"线程4").start();
}
}
class Ticket implements Runnable{
privateint tic=1000; //共享变量;
publicvoid run() {
while(true){
//同步代码块;
synchronized(this){
if(tic>0){
System.out.println(Thread.currentThread().getName()+"票数剩余::"+(tic--));
}
}
}
}
}
6. 死锁问题:
一旦有多个进程,且它们都要争用对多个锁的独占访问,那么就有可能发生死锁。如果有一组进程或线程,其中每个都在等待一个只有其它进程或线程才可以执行的操作,那么就称它们被死锁了。
最常见的死锁形式是当线程 1 持有对象 A 上的锁,而且正在等待对象B上的锁;而线程 2 持有对象 B 上的锁,却正在等待对象 A 上的锁。这两个线程永远都不会获得第二个锁,或是释放第一个锁,所以它们只会永远等待下去。
因此死锁出现的条件是:同步中嵌套同步,两个同步使用不同的锁;例如a锁嵌套b锁,且b锁嵌套a锁;
要避免死锁,应该确保在获取多个锁时,在所有的线程中都以相同的顺序获取锁。例如,如果有四个资源A、B、C 和 D,并且一个线程可能要获取四个资源中任何一个资源的锁,则请确保在获取对 B 的锁之前首先获取对 A 的锁,依此类推。如果“线程 1”希望获取对 B 和 C 的锁,而“线程 2”获取了 A、C 和 D 的锁,则这一技术可能导致阻塞,但它永远不会在这四个锁上造成死锁。
根据死锁出现的条件,死锁代码示例如下:
//创建一个类用于产生对象以代表锁;
class llock{
publicstatic llock locka=new llock();
publicstatic llock lockb=new llock();
}
public class DeadLockDemo {
publicstatic void main(String[] args){
Threadt1=new Thread(new lock(true));
Threadt2=new Thread(new lock(false));
t1.start();
t2.start();
}
}
class lock implements Runnable{
booleanflag=true;
lock(booleanf){
flag=f;
}
publicvoid run() {
if(flag){
while(true){
//同步中嵌套同步,a锁中嵌套b锁;
synchronized(llock.locka){
System.out.println("a");
synchronized(llock.lockb){
System.out.println("b");
}
}
}
}
else
show();
}
publicvoid show(){
while(true){
//同步中嵌套同步,b锁中嵌套a锁;
synchronized(llock.lockb){
System.out.println("aa");
synchronized(llock.locka){
System.out.println("bb");
}
}
}
}
}
---------------------- <a href="http://edu.csdn.net"target="blank">ASP.Net+Android+IOS开发</a>、<a href="http://edu.csdn.net"target="blank">.Net培训</a>、期待与您交流! ----------------------
详细请查看:<a href="http://edu.csdn.net" target="blank">http://edu.csdn.net</a>