`

Java多线程详解

阅读更多
多线程的一些问题:
1. 线程和进程的联系和区别是什么?
2. 什么是前台线程,什么是后台线程?
3. 创建线程有几种方法,他们之间的区别是什么?
4. 线程的生命周期有哪些状态,哪些方法可以改变这些状态?
5. 什么是线程安全,为什么会产生线程安全问题,如何解决线程安全问题?
6. 什么是线程的同步通信,同步通信又是如何实现的?
7. 什么是死锁?


看我下面的介绍,这些问题便也不是问题了......




进程的定义:
进程(process)是程序的一次执行过程,是系统运行程序的基本单元,一个进程就是一个执行中的程序,而每个进程都有自己独立的一块内存空间、一组系统资源,每一个进程的内部数据和状态都是完全独立的。



线程的定义:
java程序执行中的单个顺序的流控制称为线程。


多线程的定义:
多线程则指一个进程中可以同时运行多个不同的线程,执行不同的任务。
多线程意味着一个程序的多行语句片段看上去几乎是同时运行的。


线程与进程的区别:
同类的多个线程共享一块内存空间和一组系统资源,而线程本身的数据通常只有微处理器的寄存器数据,以及一个供程序执行时的使用的堆栈。所以系统在产生一个线程,或者在各个线程之间切换时,负担要比进程小得多,正因为如此,线程称为轻负荷进程(light-weight-process)


实现多线程的第一种方法:继承 Thread类,覆盖public void run()方法,在main函数中调用Thread的start()方法
public class ThreadTest {

public static void main(String[] args) {

ThreadA a = new ThreadA();
a.start();

for(int i = 0; i < 100; i++ )
	System.out.println("main");
	try {
		Thread.sleep(4000);
	} catch(InterruptedException ie) {
		System.out.println(ie.getMessage());
	}
}

}

class ThreadA extends Thread {

public void run() {

for(int i = 0; i < 100; i++ ) {

	
	try {
		Thread.sleep(4000);
	} catch(InterruptedException ie) {
		System.out.println(ie.getMessage());
	}
	System.out.println("run");
}
	

}


}



实现多线程的第二种方法:实现Runnable接口,覆盖public void run()方法,在main()函数中new Thread(),并把实现Runnable的传入给new Thread(xx);,调用线程的start()方法
public class ThreadTest3 {

public static void main(String[] args) {

ThreadC c = new ThreadC(100);

Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();

}

}


class ThreadC implements Runnable{

public int i;

public ThreadC(int i) {

this.i = i;
}

public void run() {

while(true) {

if (i > 0) {
i--;
System.out.println(Thread.currentThread().getName() + " " + i);
} else {

break;
}

}

}


}


线程的生命周期详解:
同进程一样,一个线程也有从创建,运行到消亡的过程,这称为线程的生命周期。
线程的生命周期分为五个状态:
     创建状态,就绪状态,运行中状态,阻塞状态,死亡状态;
通过线程的控制与调度可以使线程在这五个状态间进行转化。

创建状态:
   使用new运算符创建一个线程后,该线程仅仅是一个对象,系统并没有分配活动线程的资源给他,这种状态称为创建状态;

就绪(可运行)状态:
   当线程调用start方法启动一个线程后,系统为该线程分配了除CPU以外的所有资源,使该线程处于可运行状态(runnable);

运行中状态:
   Java运行系统通过调度选中一个处于就绪状态的线程,使其占有CPU并转为运行中状态,此时才真正的运行线程体;

阻塞状态:
   一个真在执行的线程在某些特殊的情况下,如执行join(等待)、sleep(睡眠)、wait(等待)方法,将让出CPU并暂时中止自己的执行,进入阻塞状态。阻塞时他不能进入就绪队列,只有当引起阻塞的原因被消除后,线程才可以转入就绪状态,重新进到线程队列中排队,等待CPU的资源分配,以便从原来终止处开始继续执行;

死亡状态:
    一个线程一旦调用了start方法就注定会走上死亡的道路。
    线程结束后就是死亡状态,一个线程在下列状态会结束线程的生命:
         a> 线程到达其run()方法的末尾;
         b> 线程抛出一个未捕获的Exception或Error;
         c> 线程调用stop()方法,突然杀死真在运行中的线程。
  


线程状态的改变:
1.线程睡眠 sleep()
public static void sleep(long millis) throws InterruptedException
当前线程睡眠(停止执行)若干毫秒,线程由运行中状态进入阻塞状态,睡眠时间过后线程进入就绪状态。

2.暂停线程 yield()
public static void yield()
yield()暂停当前线程的执行,允许其他线程执行,该线程仍处于就绪(可运行)状态,不转为阻塞状态,此时系统选择其他同优先级线程执行,若无其他同优先级线程,则选择该线程继续执行。

3.连接线程 join()
join方法使当前线程暂停执行,等待其他线程执行,有一些三种用法:
   a> 等待其他线程运行结束,再执行该线程:
        public final void join() throws InterruptedException
   b> 最多为某个线程等待millis毫秒,再执行该线程:
        public final void join(long millis) throws InterruptedException
   c> 最多为某个线程等待millis毫秒 + nanos纳秒,再执行该线程:
        publci final void join(long millis, int nanos) throws  InterruptedException


前台线程与后台线程的区别
后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。

前台线程:使用Thread建立的线程默认情况下是前台线程,在进程中,只要有一个前台线程未退出,进程就不会终止。主线程就是一个前台线程。而后台线程不管线程是否结束,只要所有的前台线程都退出(包括正常退出和异常退出)后,进程就会自动终止

可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。



线程调度与优先级
同一时刻如果有多个线程处于可运行状态,而运行的机器只有一个CPU时,虚连接系统需要引入线程调度机制来决定哪个线程应该执行。
线程产生时,每个线程自动获得一个线程的优先级(priority),优先级的高低反映线程的重要或紧急程度。线程优先级用1~10之间的整数表示,1优先级最高,默认值是5,每个优先级对应一个Thread的公用静态常来。

线程调度机制:
    线程调度管理器根据调度算法负责线程排队和CPU在线程见到分配,当线程调度管理器选中某个线程时,该线程将获得CPU资源并由可运行状态转入运行中状态。调度算法首先遵从优先数法,其次为轮转调度。也就是说多个处于所运行中状态的线程如果优先级一样,每个线程轮流获得一个时间片去执行,时间片到时,即使没有执行完毕也要让出CPU,重新进入可运行状态,等待下一个时间片段的到来,直到所有线程执行完毕。但是,如果多个处于可运行状态的线程中有高优先级的线程,则优先级高的线程立即执行,即使比他优先级低的线程正处于运行中状态,也要转为可运行状态,让出CPU资源让优先级高的线程优先执行。
从而可见,高优先级线程执行时,将采用独占方式调度,除非他执行完毕,或是进入阻塞状态,低优先级的线程才获得执行权。、



class ThreadE implements Runnable {

volatile public int i;

public ThreadE(int i ) {

this.i = i;
}

public void run() {

while(true) {

if (i > 0) {

i--;
System.out.println(Thread.currentThread().getName() + " " + i);

} else {

System.out.println(Thread.currentThread().getName());
break;


}

}

} 

}



public class ThreadTest5 {

	public static void main(String[] args) {

		ThreadE e = new ThreadE(100);

		Thread t1 = new Thread(e);


		Thread t2 = new Thread(e);

		t2.setPriority(Thread.MAX_PRIORITY);
		t1.start();

		try {

 			Thread.sleep(5);

		} catch(Exception ex){
		
		}

		t2.start();
	}

}


volatile修饰符:
在JVM1.2之前,Java总是从主存读取变量,但随着JVM的优化,线程可以把变量保存在机器的寄存器中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中变量值的副本,造成数据的不一致,要解决这个问题,就需要把该变量声明为volatile(不稳定的),它指示JVM这个变量是不稳定的,每次使用它都要到主存中进行读取,因此多线程环境下volatile关键字就变得非常重要。


线程安全问题:多个线程同时访问同一个资源,导致线程安全问题的产生,这就是非线程安全,一下这个例子就是非线程安全问题
class Resource implements Runnable {

	volatile public int i;

	public Resource(int i) {
		this.i = i;
	}

	public void run(){
		
		while(true){
		 if (i > 0) {
			 try{
				Thread.sleep(400);		
			 } catch(Exception ex){}
			 i--;
			 System.out.println(Thread.currentThread().getName() + " " + i);
		 } else {
			System.out.println(Thread.currentThread().getName());
			break;
			
		 }
		}
	}
}



public class ThreadTest6 {

	public static void main(String[] args) {
		Resource rs = new Resource(100);
		Thread t1 = new Thread(rs);
		Thread t2 = new Thread(rs);

		t1.start();
		t2.start();
	}




}


为了解决线程安全的问题,Java语言提出了线程同步的概念,即在同一时刻只能让一个线程访问共享资源。
为了达到线程同步,Java语言引入了监视器(monitor)这个概念,来保证共享数据操作的同步性。每个对象都可以作为一个监视器,这个监视器用来保证在人一个时刻,只能有一个线程访问该对象。关键字synchronized修饰某个对象后,该对象就成了监视器。

以下代码解决了线程安全问题,实现了线程同步操作:
class Resource implements Runnable {

	volatile public int i;
	volatile public Integer it;

	public Resource(int i) {
		this.i = i;
		it = new Integer(this.i);
	}

	public void run(){
		
		while(true){
		 
		synchronized(it) {
		 if (i > 0) {
			  try{
				Thread.sleep(400);		
			  } catch(Exception ex){}
			  i--;
			  System.out.println(Thread.currentThread().getName() + " " + i);
		  } else {
		 	System.out.println(Thread.currentThread().getName());
			break;
			
		  }
		}
		}
	}
}



public class ThreadTest7 {

	public static void main(String[] args) {
		Resource rs = new Resource(100);
		Thread t1 = new Thread(rs);
		Thread t2 = new Thread(rs);

		t1.start();
		t2.start();
	}




}





class Resource implements Runnable {

	volatile public int i;
	volatile public Integer it;

	public Resource(int i) {
		this.i = i;
		it = new Integer(this.i);
	}

	public synchronized void run(){
		
		while(true){
		 
		//synchronized(it) {
		 if (i > 0) {
			  try{
				Thread.sleep(400);		
			  } catch(Exception ex){}
			  i--;
			  System.out.println(Thread.currentThread().getName() + " " + i);
		  } else {
		 	System.out.println(Thread.currentThread().getName());
			break;
			
		  }
		//}
		}
	}
}



public class ThreadTest8 {

	public static void main(String[] args) {
		Resource rs = new Resource(100);
		Thread t1 = new Thread(rs);
		Thread t2 = new Thread(rs);

		t1.start();
		t2.start();
	}
}



synchronized的作用和三种用法:

作用:多线程对共享资源操作容易引起冲突,这些容易引起冲突的代码称为临界区,临界区通过引入监视器,并用synchronized使多个线程在临界区同步起来,就能避免可能引起的冲突。

用法:
a> synchronized代码块: 监视器就是指定的对象
b> synchronized方法: 监视器就是this对象
c> synchronized静态方法: 监视器就是相应的类


死锁问题:
多线程的在进行同步时,存在“死锁”的潜在危险。如果多个线程处于等待状态,彼此需要对方所占用的监视器所有权,就构成了死锁(deallock),Java既不能发现死锁,也不能避免死锁。所以程序员编程时应该注意死锁问题,尽量避免
方法一:
{
    synchronized(A) {
       System.out.println("abc");
       synchronized(B) {
            System.out.println("def");
       }
    }
}

方法二:
{
    synchronized(B) {
       System.out.println("abc");
       synchronized(A) {
            System.out.println("def");
       }
    }
}


同步通信的问题提出与解决
问题提出:
class Account {
	
	volatile private int value;
	void put(int i) {
		value = value + i;
		System.out.println("存入" + i + " 账上金额为: " + value);
	}
	int get(int i) {
		if (value >= i) {
			value = value - i;
		} else {
			i = value;
			value = 0;
		}
		System.out.println("取走" + i +  " 账上金额为: " + value);
		return i;
	}
}

class Save implements Runnable {
	private Account account;

	public Save(Account account) {
		this.account = account;
	}

	public void run() {
		while(true) {
			account.put(100);
		}
	}
	
}


class Fetch implements Runnable {
	private Account account;

	public Fetch(Account account) {
		this.account = account;
	}

	public void run() {
		while(true) {
			account.get(100);
		}
	}
};



public class ThreadTest9 {
	
	public static void main(String[] args) {
		Account account = new Account();
		new Thread(new Save(account)).start();
		new Thread(new Fetch(account)).start();
	
	}
};


两个线程间的同步通信解决方案:
class Account {
	
	volatile private int value;
	volatile private boolean isMoney;

	synchronized void put(int i) {
		if (isMoney) {
			try {
				wait();
			}
			catch (Exception ex) {}
		}
		value = value + i;
		System.out.println("存入" + i + " 账上金额为: " + value);
		isMoney = true;
		notify();
	}
	synchronized int get(int i) {
		
		if (!isMoney) {
			try {
				wait();
			}
			catch (Exception ex) {}
		}

		if (value >= i) {
			value = value - i;
		} else {
			i = value;
			value = 0;
		}
		System.out.println("取走" + i +  " 账上金额为: " + value);
		isMoney = false;
		notify();

		return i;
	}
}

class Save implements Runnable {
	private Account account;

	public Save(Account account) {
		this.account = account;
	}

	public void run() {
		while(true) {
			account.put(100);
		}
	}
	
}


class Fetch implements Runnable {
	private Account account;

	public Fetch(Account account) {
		this.account = account;
	}

	public void run() {
		while(true) {
			account.get(100);
		}
	}
};



public class ThreadTest10 {
	
	public static void main(String[] args) {
		Account account = new Account();
		new Thread(new Save(account)).start();
		new Thread(new Fetch(account)).start();
	
	}
};



总结两个线程间的同步通信:
a> wait(), notify(), notifyAll()是object的方法
b> 执行Save的线程调用了put方法时,由于put方法前存在的synchronized关键字,他拥有了Account对象的监视权,如果发现有资金,将执行对象的wait方法,使线程暂停并释放该对象的监视权(sleep方法不会释放对象的监视器),进入一个以此对象监视器为标志的队列当中,这样执行Fetch的线程就可以获得该对象监视权而执行get方法,当get方法执行完毕后,调用notify方法释放该对象监视权并唤醒队列中和他拥有同类型标签的等待线程(注意:并不唤醒哪些和他的对象监视器标志不同的线程),于是执行Save的线程就被唤醒得以继续执行;
c> 两个线程之间的同步通信,需要方法前的synchronized修饰,方法中wait和notify,一个布尔型的变量指示器之间的配合得以实现。


解决多个线程的同步通信:
class Account {
	
	volatile private int value;
	volatile private boolean isMoney;

	synchronized void put(int i) {
		 while (isMoney) {
			try {
				wait();
			}
			catch (Exception ex) {}
		}
		value = value + i;
		System.out.println("存入" + i + " 账上金额为: " + value);
		isMoney = true;
		notifyAll();
	}
	synchronized int get(int i) {
		
		while (!isMoney) {
			try {
				wait();
			}
			catch (Exception ex) {}
		}

		if (value >= i) {
			value = value - i;
		} else {
			i = value;
			value = 0;
		}
		System.out.println("取走" + i +  " 账上金额为: " + value);
		isMoney = false;
		notifyAll();

		return i;
	}
}

class Save implements Runnable {
	private Account account;

	public Save(Account account) {
		this.account = account;
	}

	public void run() {
		while(true) {
			account.put(100);
		}
	}
	
}


class Fetch implements Runnable {
	private Account account;

	public Fetch(Account account) {
		this.account = account;
	}

	public void run() {
		while(true) {
			account.get(100);
		}
	}
};



public class ThreadTest11 {
	
	public static void main(String[] args) {
		Account account = new Account();
		new Thread(new Save(account)).start();
		new Thread(new Save(account)).start();
		new Thread(new Fetch(account)).start();
	
	}
};



总结多个(大于2个)线程间同步通信:
a> 当两个存钱线程处于等待状态时,一个取钱线程使用notifyAll将他们都唤醒,这两个存钱线程通过竞争获得监视权,获得监视权的线程继续执行。
b> 相对于上面的两个线程的通信我们用if判断,而多个线程通信我们用while判断,为什么?其原因是:当两个存钱线程中的一个执行完毕调用notifyAll时,可能同时会激活取钱线程和另一个存钱线程,如果存钱线程获得了监视权,则会在while代码块中重新wait,从而将监视权让给了取钱线程。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics