线程与进程的差别
(1)程序是一段静态的代码,进程是程序的一次动态执行过程。它是操作系统资源调度的基本单位。线程是比进程更小的执行单位。一个进程在其执行过程中,能够产生多个线程。所以又称线程为“轻型进程”。
尽管说能够并发执行多个线程,但在不论什么时刻cpu仅仅执行一个线程,仅仅是宏观上看好像是同一时候执行,事实上微观上它们仅仅是高速交替执行的。
这就是java中的多线程机制。
(2)不同进程的代码、内部数据和状态都是全然独立的。而一个程序内的多线程是共享同一块内存空间和同一组系统资源的,有可能互相影响。
(3)线程切换比进程切换的负担要小。
线程的创建
java提供了类java.lang.Thread来支持多线程编程,创建线程主要有两种方法:
(1)继承Thread类
Thread类中的run 方法是空的,直接通过 Thread类实例化的线程对象不能完毕不论什么事,所以能够通过继承Thread 类,重写run 方法,实现具有各种不同功能的线程类。
run()又称为线程体。不能直接调用run(),而是通过调用start(),让线程自己主动调用run(),由于start()会首先进行与多线程相关的初始化(即让start()做准备工作)。
class ThreadType extends Thread{
public void run(){ //重写Thread类中的run 方法
……
}
}
(2)实现Runnable接口
java仅仅同意单继承,假设类已经继承了其它类。就不能再继承Thread类了,所以提供了实现Runnable接口来创建线程的方式。
该接口仅仅定义了一个run方法,在新类中实现它就可以。
Runnable接口并没有不论什么对线程的支持,还必须通过创建Thread类的实例。将Rnnable接口对象作为Thread类构造方法的參数传递进去,从而创建一个线程。
如:
class ThreadDemo3 implements Runnable {
// 重载run函数
public void run() {
for (int count = 1, row = 1; row < 10; row++, count++){ // 循环计算输出的*数目
for (int i = 0; i < count; i++){ // 循环输出指定的count数目的*
System.out.print('*');
}
System.out.println();
}
}
public static void main(String argv[]) {
Runnable rb = new ThreadDemo3(); // 创建,并初始化ThreadDemo3对象rb
Thread td = new Thread(rb); // 通过Thread创建线程
td.start(); // 启动线程td
}
}
注意:假设当前线程是通过继承Thread类创建的,则訪问当前线程能够直接使用this,假设当前线程是通过实现Runnable接口创建的,则通过调用Thread.currentThread()方法来获取当前线程。
线程的生命周期
依照线程体在计算机系统内存中状态的不同。能够将线程分为以下5种状态:
(1)创建状态
新建一个线程对象,仅仅作为一个实例存在,JVM没有为其分配执行资源。
(2)就绪状态
创建状态的线程调用start方法后。转换为就绪状态,此时线程已得到除CPU时间之外的其它系统资源。一旦获得CPU,就进入执行状态。注意的是,线程没有结束run()方法之前。不能再调用start()方法。否则将发生IllegalThreadStateException异常,即启动的线程不能再启动。
(3)执行状态
就绪状态的线程获取了CPU,执行程序代码。
(4)堵塞状态
堵塞状态是线程由于某种原因放弃CPU使用权。暂时停止执行。直到线程进入就绪状态。才有机会转到执行状态。堵塞的情况分三种:(一)、等待堵塞:执行的线程执行wait()方法,JVM会把该线程放入等待池中。
(二)、同步堵塞:执行的线程在获取对象的同步锁时,若该同步锁被别的线程占用。则JVM会把该线程放入锁池中。
(三)、其它堵塞:执行的线程执行sleep()或join()方法。或者发出了I/O请求时,JVM会把该线程置为堵塞状态。
当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程又一次转入就绪状态。
(5)死亡状态
线程死亡的原因有两个:一是执行完了线程体(run方法),二是由于异常run方法被强制性终止。
假设线程进入死亡状态。JVM会收回线程占用的资源(释放分配给线程对象的内存)。
注意:调用stop()能够使线程马上进入死亡状态,只是该方法如今已经不推荐使用了。线程的退出通常採用自然终止的方法,不建议人工强制停止,easy引起“死锁”。
转换图例如以下:
从图中,能够看出。比較复杂的是就绪状态和堵塞状态转换的过程,java提供了大量的方法来支持堵塞,以下一 一说明:
sleep():能够以毫秒为单位,指定休眠一段时间(作为參数)。时间一过,又进入就绪状态。
wait()和notify():wait使得线程进入堵塞状态。它有两种形式,一种是同意指定以毫秒为单位的一段时间作为參数的,还有一种是无參数的。前者当相应的notify方法被调用或超出指定时间时线程又一次进入就绪状态,后者则必须调用notify方法才干又一次进入就绪状态。
注意:此外。还有suspend方法(相应的恢复则用resume方法)也能使线程进入堵塞状态。只是这种方法如今已经不提倡使用了,会引起“死锁”。由于调用该方法会释放占用的全部资源,由JVM调度转入暂时存储空间。
线程调度和优先级
java採用抢占式调度,即优先级高线程的先执行。优先级同样的交替执行
java将线程的优先级分为10个等级。1-10,数字越大表明线程的级别超高。能够通过setPriority方法设置线程优先级。
在java中有一个比較特殊的线程称为守护线程。它具有最低的优先级。用于为系统中的其它线程对象提供服务。典型的就是JVM中的系统资源自己主动回收线程。
线程相互排斥(银行取款问题)
线程相互排斥是什么?什么时候要用到线程相互排斥呢?
发现问题
举个样例,假设你的银行账户有100元,而且你和你的妻子两人都知道账户password。假设某一天,你去取100元。银行系统会先查看你的账户够不够100元。明显你是满足条件的,可是。假设此时你的妻子也须要去取100元。而且你的取钱线程刚好由于某些状况被打断了(这时系统还来不及改动你的账户剩余金额)。所以你的妻子去取钱时也满足条件,所以她完毕了取钱动作。而你取钱线程恢复之后,你也将完毕取钱动作。大家能够发现共享数据(账户剩余金额)的完整性被破坏了,两人都从银行里取出了一百元,而账户明明仅仅有一百元。假设现实中真发生这样的情况,预计银行就要哭晕在厕所了。代码及执行结果例如以下:
//Account.java
public class Acount{
double balance;
public Acount(double money){
balance = money;
System.out.println("Totle Money: "+balance);
}
}
//AccountThread.java
class Account
{
double balance;
public Account(double money)
{
balance = money;
System.out.println("Totle Money: " + balance);
}
}
public class AccountThread extends Thread
{
Account Account;
int delay;
public AccountThread(Account Account, int delay)
{
this.Account = Account;
this.delay = delay;
}
public void run()
{
if (Account.balance >= 100) {
try {
sleep(delay);
Account.balance = Account.balance - 100;
System.out.println("withdraw 100 successful!");
} catch (InterruptedException e) {
}
} else
System.out.println("withdraw failed!");
}
public static void main(String[] args)
{
Account Account = new Account(100);
AccountThread AccountThread1 = new AccountThread(Account, 1000);
AccountThread AccountThread2 = new AccountThread(Account, 0);
AccountThread1.start();
AccountThread2.start();
}
}
解决这个问题
为了解决这个问题,java提供了线程相互排斥,通过synchronized关键字为共享的资源或数据加锁,避免在该线程没有完毕操作之前,被其它线程的调用。从而保证了该变量的唯一性和准确性。
在java语言中。每一个对象都有一把内置锁。线程进入同步代码块或方法的时候会通过synchronized关键字自己主动获取该对象上的内置锁,其它须要获取该锁的线程。必须等待当前拥有该锁的线程将其释放。从而保证任一时刻。仅仅有一个线程訪问共享资源。
为了接下来更好地理解synchronized使用方法的一些差别,我们先引入两个概念:对象锁和类锁
java的对象锁和类锁在锁的概念上基本上和内置锁是一致的。可是,两个锁实际是有非常大的差别的,对象锁是用于对象实例方法。或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例能够有非常多个。可是每一个类仅仅有一个class对象,所以不同对象实例的对象锁是互不干扰的,可是每一个类仅仅有一个类锁。 可是有一点必须注意的是,事实上类锁仅仅是一个概念上的东西。并非真实存在的。它仅仅是用来帮助我们理解锁定实例方法和静态方法的差别的
synchronized具体解释
synchronized的使用方法:修饰方法和修饰代码块。
以下分析synchronized这两种使用方法在对象锁和类锁上有什么差别
(1)对象锁——synchronized修饰方法和代码块
public class TestSynchronized
{
public void test1()
{
/*
synchronized修饰代码块。传入的对象实例是this,表明是当前对象,当然,假设须要同步其它对象实例。也可传入其它对象的实例
*/
synchronized(this)
{
int i = 5;
while( i-- > 0)
{
System.out.println(Thread.currentThread().getName() + " : " + i);
try
{
Thread.sleep(500);
}
catch (InterruptedException ie)
{
}
}
}
}
/*
synchronized修饰方法。由于前面同步代码块中传入參数是this。所以两个公共资源码所须要获得的对象锁都是同一个对象锁
*/
public synchronized void test2()
{
int i = 5;
while( i-- > 0)
{
System.out.println(Thread.currentThread().getName() + " : " + i);
try
{
Thread.sleep(500);
}
catch (InterruptedException ie)
{
}
}
}
public static void main(String[] args)
{
final TestSynchronized myt2 = new TestSynchronized();
/*
main方法中分别开启两个线程(这两个线程的run()方法分别调用test1和test2方法)。由于两个公共资源码所须要获得的对象锁都是同一个对象锁。所以当有一个线程获得锁时,还有一个线程必须等待。上面也给出了执行的结果能够看到:直到test1线程执行完毕。释放掉锁,test2线程才開始执行。
*/
Thread test1 = new Thread( new Runnable() { public void run() { myt2.test1(); } }, "test1" );
Thread test2 = new Thread( new Runnable() { public void run() { myt2.test2(); } }, "test2" );
test1.start();;
test2.start();
// TestRunnable tr=new TestRunnable();
// Thread test3=new Thread(tr);
// test3.start();
}
}
执行结果:
假设我们把test2方法的synchronized关键字去掉,执行结果会怎样呢?
我们能够看到,结果输出是交替着进行输出的。这是由于,尽管某个线程得到了对象的内置锁(即能够訪问同步的方法或代码),可是还有一个线程还是能够訪问该对象的,即訪问没有进行加锁的方法或者代码,所以加锁方法和没加锁方法之间是互不影响的。
(这里说一个题外话,代码里面明明是先开启test1线程,为什么先执行的是test2呢?这是由于java编译器在编译成字节码的时候。会依据实际情况对代码进行一个重排序,编译前代码写在前面,在编译后的字节码不一定排在前面,所以这样的执行结果是正常的)
(2)类锁——synchronized修饰(静态)方法和代码块:
public class TestSynchronized
{
public void test1()
{
synchronized(TestSynchronized.class)
{
int i = 5;
while( i-- > 0)
{
System.out.println(Thread.currentThread().getName() + " : " + i);
try
{
Thread.sleep(500);
}
catch (InterruptedException ie)
{
}
}
}
}
public static synchronized void test2()
{
int i = 5;
while( i-- > 0)
{
System.out.println(Thread.currentThread().getName() + " : " + i);
try
{
Thread.sleep(500);
}
catch (InterruptedException ie)
{
}
}
}
public static void main(String[] args)
{
final TestSynchronized myt2 = new TestSynchronized();
Thread test1 = new Thread( new Runnable() { public void run() { myt2.test1(); } }, "test1" );
Thread test2 = new Thread( new Runnable() { public void run() { TestSynchronized.test2(); } }, "test2" );
test1.start();
test2.start();
// TestRunnable tr=new TestRunnable();
// Thread test3=new Thread(tr);
// test3.start();
}
}
执行结果例如以下:
从中能够看出,两个同步代码所须要获得的对象锁都是同一个对象锁,即synchronized修饰静态方法所相应的锁为类锁(即TestSynchronized.class),注意喔,类锁仅仅是我们为了方便差别静态方法的特点而抽象出来的一个概念。由于静态方法是全部对象实例共用的,所以相应着synchronized修饰的静态方法的锁也是唯一的,所以抽象出来个类锁。
为了更好地这证明类锁和对象锁是两个不一样的锁。我们同一时候用synchronized修饰静态方法和普通的方法,看看执行结果怎样
public class TestSynchronized
{
public synchronized void test1() //修饰普通方法
{
int i = 5;
while( i-- > 0)
{
System.out.println(Thread.currentThread().getName() + " : " + i);
try
{
Thread.sleep(500);
}
catch (InterruptedException ie)
{
}
}
}
public static synchronized void test2() //修饰静态方法
{
int i = 5;
while( i-- > 0)
{
System.out.println(Thread.currentThread().getName() + " : " + i);
try
{
Thread.sleep(500);
}
catch (InterruptedException ie)
{
}
}
}
public static void main(String[] args)
{
final TestSynchronized myt2 = new TestSynchronized();
Thread test1 = new Thread( new Runnable() { public void run() { myt2.test1(); } }, "test1" );
Thread test2 = new Thread( new Runnable() { public void run() { TestSynchronized.test2(); } }, "test2" );
test1.start();
test2.start();
// TestRunnable tr=new TestRunnable();
// Thread test3=new Thread(tr);
// test3.start();
}
}
执行结果:
可见。线程是交替执行的。这就验证了类锁和对象锁是两个不一样的锁。控制着不同的区域。它们是互不干扰的。而且。线程获得对象锁的同一时候,也能够获得该类锁,即同一时候获得两个锁,这是同意的。
总结:
1、不管是同步代码块还是同步方法。必须获得对象锁才干够进入同步代码块或者同步方法进行操作。
2、同步是一种高开销的操作,因此应该尽量降低同步的内容。 通常没有必要同步整个方法。使用synchronized代码块同步关键代码就可以。
3、假设採用方法级别的同步,对象锁为方法所在的对象;假设是静态同步方法,对象锁为方法所在的类(唯一)。
4、对于代码块。对象锁即指synchronized(object)中的object。
此处參考了博客:http://langgufu.iteye.com/blog/2152608
线程同步(生产-消费者模型)
线程相互排斥和线程同步都是指。某一资源同一时候仅仅同意一个訪问者对其进行訪问,具有唯一性和排它性。不同的是,同步是指在相互排斥的基础上(大多数情况),通过其它机制实现訪问者对资源的有序訪问(有序交替执行),而线程相互排斥无法限制訪问者对资源的訪问顺序。即訪问是无序的(一个线程释放锁之后,不能保证什么时候再次获得锁)。
一言蔽之,同步是一种更复杂的相互排斥。
一个典型的线程同步的应用是生产-消费者模型。其约束条件为:
(1)生产者生产产品,并将其保存到仓库中。
(2)消费者从仓库中取得产品。
(3)由于库房容量有限,因此仅仅有当库房还有空间时,生产者才干够将产品放入库房;否则仅仅能等待。
(4)仅仅有库房中存在满足数量的产品时,消费者才干取走产品,否则仅仅能等待。
实际应用中。非常多样例都能够归结为该模型。这里举个样例,还是之前存款和取款的问题。假设存在一个账户对象(仓库)及两个线程:存款线程(生产者)和取款线程(消费者)。并对其进行例如以下的限制:
- 仅仅有当账户上的剩余金额balance=0时。存款线程才干够存进100元;否则仅仅能等待。
仅仅有当账户上的剩余金额balance=100时,取款线程才干够取走100元。否则仅仅能等待。
依据生产-消费者模型,应该得到一个交替执行的执行序列:存款100元、取款100元、存款100元、取款100元……非常明显,使用前面的相互排斥对象是无法完毕这两个线程的同步问题的。为了实现线程同步,java为相互排斥对象提供了两个方法:一个是wait()。还有一个是notify()。(可见,同步确实是在相互排斥的基础上加上某些机制实现次序訪问的)
要注意的是,这两个方法是作为相互排斥对象的方法来实现的,而不是作为Thread类的方法实现。而且,必须将这两个方法放在临界代码段中(synchronized修饰的代码)。也就是说执行该方法的线程必须已获得了相互排斥对象的相互排斥锁,由于这两个方法实际上也是在操作相互排斥对象的相互排斥锁。
wait():堵塞线程,释放相互排斥对象的相互排斥锁。(而sleep方法堵塞线程后,并不释放相互排斥锁)
notify():当还有一个线程调用相互排斥对象的notify()方法时。该相互排斥对象等待队列中的第一个线程才干进入就绪状态。
样例代码及执行结果例如以下:
//Account4.java
public class Account4 {
double balance;
public Account4(){
balance = 0;
System.out.println("Totle Money: "+balance);
}
/*
取款
*/
public synchronized void withdraw(double money){
if(balance == 0)
try{
wait(); //使取款线程进入堵塞状态,并释放相互排斥对象的相互排斥锁
}catch(InterruptedException e){
}
balance = balance - money;
System.out.println("withdraw 100 success");
notify(); //使存款线程进入就绪状态
}
/*
存款
*/
public synchronized void deposite(double money){
if (balance != 0)
try {
wait(); //使存款线程进入堵塞状态,并释放相互排斥对象的相互排斥锁
}
catch (InterruptedException e) {
}
balance = balance + money;
System.out.println("deposite 100 success");
notify(); //使取款线程进入就绪状态
}
}
//WithdrawThread.java
public class WithdrawThread extends Thread
{
Account4 account;
public WithdrawThread(Account4 acount)
{
this.account = acount;
}
public void run()
{
for (int i = 0; i < 5; i++)
account.withdraw(100);
}
}
//DepositeThread.java
class DepositeThread extends Thread {
Account4 acount;
public DepositeThread(Account4 acount) {
this.acount = acount;
}
public void run(){
for(int i=0;i<5;i++)
acount.deposite(100);
}
}
//TestProCon.java
public class TestProCon
{
public static void main(String[] args)
{
Account4 acount = new Account4();
WithdrawThread withdraw = new WithdrawThread(acount);
DepositeThread deposite = new DepositeThread(acount);
withdraw.start();
deposite.start();
}
}
执行结果:
线程通信
线程通信是指线程之间相互传递信息。线程之间有好几种通信方式,如数据共享、管道等。这里,我们主要解说线程间通过管道来进行通信的方式。管道通信具有例如以下特点:
(1)管道是单向的。假设须要建立双向通信。能够通过建立多个管道来解决。
(2)管道通信是面向连接的。发送线程建立管道的发送端,接收线程建立与发送管道的连接。
(3)管道中的信息是严格依照发送的顺序进行传送的。
收到的数据和发送方在顺序上全然一致。
java语言管道看作是一种特殊的I/O流。并提供了两对相应的基本类来支持管道通信。
这些类都位于java.io包中。一对是PipedOutStream和PipedInputStream,用于建立基于字节的通信;还有一对是PipedWriter和PipedReader。用于建立基于字符的管道通信。
以下这个样例建立的就是字符管道。
//SenderThread.java
import java.io.*;
class SenderThread extends Thread{
PipedWriter pipedWriter;
public SenderThread( ){
pipedWriter = new PipedWriter( );
}
public PipedWriter getPipedWriter( ){
return pipedWriter;
}
public void run( ){
for (int i =0; i<5;i++){
try{
pipedWriter.write(i);
}catch(IOException e){
}
System.out.println("Send: "+i);
}
}
}
//ReceiverThread.java
import java.io.*;
class ReceiverThread extends Thread{
PipedReader pipedReader;
public ReceiverThread( SenderThread senderThread) throws IOException{
pipedReader = new PipedReader(senderThread.getPipedWriter( ));
}
public void run( ){
int i=0;
while(true){
try{
i = pipedReader.read();
System.out.println("Received: "+i);
}catch(IOException e){
}
if(i == 4)
break;
}
}
}
//ThreadComm.java
import java.io.*;
public class ThreadComm
{
public static void main(String[] args) throws Exception
{
SenderThread sender = new SenderThread();
ReceiverThread receiver = new ReceiverThread(sender);
sender.start();
receiver.start();
}
}
执行结果:
线程死锁(哲学家用餐问题)
线程死锁是并发程序设计中可能遇到的问题之中的一个。它是指程序执行中,多个线程竞争共享资源时可能出现的一种系统状态。该问题能够形象地描写叙述为哲学家用餐问题(此处对其进行了简化):5个哲学家围坐在一圆桌旁,每人的两边放着一筷子,共5支筷子。并规定例如以下条件:
(1)每一个人仅仅有拿起位于自己两边的筷子,合成一双才干够用餐。
(2)用餐后每人必须将两仅仅筷子放回原处。
假设每一个哲学家都彬彬有礼。轮流吃饭。则这样的融洽的气氛能够长久地保持下去,可是假设每一个人都拿起自己左手边的筷子。并想要去拿自己右手边的筷子(这支在还有一个哲学家手中),这样就会处于僵持状态,这就是相当于线程死锁。
要注意的是,死锁不是一定会发生的,相反它出现的可能性非常小,简单的測试往往无法发现,仅仅有在程序设计中尽量避免这样的情况的发生。
演示样例代码例如以下:
//ChopStick.java
public class ChopStick
{
private String name;
public ChopStick(String name)
{
this.name = name;
}
public String getNumber()
{
return name;
}
}
//Philosopher.java
import java.util.*;
public class Philosopher extends Thread
{
private ChopStick leftChopStick;
private ChopStick rightChopStick;
private String name;
private static Random random = new Random();
public Philosopher(String name, ChopStick leftChopStick,
ChopStick rightChopStick)
{
this.name = name;
this.leftChopStick = leftChopStick;
this.rightChopStick = rightChopStick;
}
public String getNumber()
{
return name;
}
public void run()
{
try {
sleep(random.nextInt(10));
} catch (InterruptedException e) {
}
synchronized (leftChopStick) {
System.out.println(this.getNumber() + " has "
+ leftChopStick.getNumber() + " and wait for "
+ rightChopStick.getNumber());
synchronized (rightChopStick) {
System.out.println(this.getNumber() + " eating");
}
}
}
public static void main(String args[])
{
// 建立三个筷子对象
ChopStick chopStick1 = new ChopStick("ChopStick1");
ChopStick chopStick2 = new ChopStick("ChopStick2");
ChopStick chopStick3 = new ChopStick("ChopStick3");
// 建立哲学家对象。并在其两边摆放筷子。
Philosopher philosopher1 = new Philosopher("philosopher1", chopStick1,
chopStick2);
Philosopher philosopher2 = new Philosopher("philosopher2", chopStick2,
chopStick3);
Philosopher philosopher3 = new Philosopher("philosopher3", chopStick3,
chopStick2);
// 启动三个线程
philosopher1.start();
philosopher2.start();
philosopher3.start();
}
}
执行结果一:
执行结果二:
执行结果一发生了死锁,结果二没发生死锁。可见,线程死锁存在偶然性,不是一定会发生的,而且发生概率一般比較小,只是我们还是要尽可能地避免它。这样才算是优雅的代码。
线程池
创建和清除线程垃圾都会大量占用CPU等系统资源,所以java中用线程池来解决这一问题。
基本思想是:在系统中开辟一块区域。用来存放一些待命的线程。这个区域就叫线程池。假设须要执行任务,则从线程池中取一个待命的线程来执行指定的任务,到任务结束再将其放回,这样能够避免反复创建线程。
经常使用的两种线程池为:
固定尺寸线程池,待命线程数量一定;
可变尺寸线程池。待命线程数量是依据任务负载的须要动态变化的。
之前在探索资料的时候,发现有一篇具体介绍线程池的博客,讲得挺好的,能够学习下:http://blog.csdn.net/hsuxu/article/details/8985931