您的位置:首页 > 职场人生

黑马程序员-- Java多线程编程总结

2013-04-14 23:43 513 查看
 -------
android培训、java培训、期待与您交流! ----------

Java线程:概念与原理

一、操作系统中线程和进程的概念

进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。

线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。

二、Java中的线程

在Java中,“线程”指两件不同的事情:

1、java.lang.Thread类的一个实例;
2、线程的执行。

使用java.lang.Thread类或者java.lang.Runnable接口编写代码来定义、实例化和启动新线程。

一个Thread类实例只是一个对象,像Java中的任何其他对象一样,具有变量和方法,生死于堆上。

Java中,每个线程都有一个调用栈,即使不在程序中创建任何新的线程,线程也在后台运行着。

一个Java应用总是从main()方法开始运行,mian()方法运行在一个线程内,它被称为主线程。

一旦创建一个新的线程,就产生一个新的调用栈。

当所有用户线程执行完毕的时候,JVM自动关闭。但是守候线程却不独立于JVM,守候线程一般是由操作系统或者用户自己创建的。

1) 程序 挃令 + 数据的byte序列,如: qq.exe

2) 进程 正在运行的程序, 是程序劢态的执行过程(运行于内存中)

3 ) 线程 在迚程内部,并収运程的过程(Java中的方法可以看做线程)

4) 并发 迚程是并収运行的,OS将时间划分为很多时间片段(时间片),尽可能均匀分配给正在运行的程序,微观上迚程走走停停,宏观上都在运行,这种都运行的现象叫并収,但是不是绝对意义上的“同时収生

java线程:创建与启动

一, 定义线程

1、Thread类。

线程类(Thread)包含一个可以运行的过程(方法):run()方法
public void run();
如果该线程是使用独立的 [code]Runnable
运行对象构造的,则调用该
Runnable
对象的
run
方法;否则,该方法不执行任何操作并返回。
Thread
的子类应该重写该方法。
2、实现Runnable接口。[/code]
void run()

使用实现接口
Runnable
的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的
run
方法。

方法
run
的常规协定是,它可能执行任何所需的操作。

3。使用内部类/匿名类创建线程

package bbs.itheima.com;

public class ThreadIntDemo {

/**
* 线程的创建方法
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
/**使用匿名内部类创建线程*/Thread t1 = new Thread(){//继承Thread类
public void run(){
System.out.println("匿名内部类");
}
};
t1.start();

//使用runnable 接口创建线程
//实现Runnable接口
Runnable runner = new Runnable(){

@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("实现Runnable接口");
}

};
//在创建线程的时候,将Runnerble实例作为构造参数Thread t2 = new Thread(runner);
t2.start();

//使用Runnable接口创建匿名类,创建线程实例Thread t3 = new Thread(new Runnable(){
public void run(){
System.out.println("Hi t3");
}
});
t3.start();

//创建匿名内部类,直接启动线程
new Thread(){
public void run(){
System.out.println("HI Thread");
}
};
//创建匿名类实例,使用Runnable接口
new Thread(new Runnable(){
public void run(){
System.out.println("HI, Runnable");
}
}).start();
}

}


二、实例化线程

1、如果是扩展java.lang.Thread类的线程,则直接new即可。

2、如果是实现了java.lang.Runnable接口的类,则用Thread的构造方法:Thread(Runnable target)
Thread(Runnable target, String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)

三、启动线程

在线程的Thread对象上调用start()方法,而不是run()或者别的方法。

在调用start()方法之前:线程处于新状态中,新状态指有一个Thread对象,但还没有一个真正的线程。

在调用start()方法之后:发生了一系列复杂的事情
启动新的执行线程(具有新的调用栈);
该线程从新状态转移到可运行状态;
当该线程获得机会执行时,其目标run()方法将运行。

注意:对Java来说,run()方法没有任何特别之处。像main()方法一样,它只是新线程知道调用的方法名称(和签名)。因此,在Runnable上或者Thread上调用run方法是合法的。但并不启动新的线程。

案例演示:

package bbs.itheima.com;

public class ThreadDemo {
/**基本基本线程演示*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Person p = new Person();//p线程实例
Person2 p2 = new Person2();//p2线程实例
p.start();
p2.start();
System.out.println("over");
}

}
class Person extends Thread{
public void run(){
for(int i = 0;i<5;i++){
System.out.println("我是谁?");
}
}
}
class Person2 extends Thread{
public void run(){
for(int i = 0;i<5;i++){
System.out.println("张三");
}
}
}


线程的状态及其管理

一,线程的状态

线程的5中状态

1) New 新建状态

当程序使用new关键字创建了一个线程后,该线程就处于新建状态,此时线程还未启动,当线程对象调用start()方法时,线程启劢,迚入Runnable状态

2) Runnable 可运行(就绪)状态

当线程处于Runnable状态时,表示线程准备就绪,等待获取CPU

3) Running 运行(正在运行)状态

假如该线程获叏了CPU,则迚入Running状态,开始执行线程体,即run()方法中的内容

注意:

如果系统只有1个CPU,那么在任意时间点则只有1条线程处于Running状态; 如果是双核系统,那么同一时间点会有2条线程处于Running状态

但是,当线程数大于处理器数时,依然会是多条线程在同一个CPU上轮换执行

 当一条线程开始运行时,如果它不是一瞬间完成,那么它丌可能一直处于Running状态,

线程在执行过程中会被中断,目的是让其它线程获得执行的机会,像这样线程调度的策 略叏决于底层平台。对于抢占式策略的平台而言,系统系统会给每个可执行的线程一小 段时间来处理任务,当该时间段(时间片)用完,系统会剥夺该线程所占资源(CPU), 让其他线程获得运行机会。

 调用yield()方法,可以使线程由Running状态迚入Runnable状态

4) Block 阻塞(挂起)状状态

当如下情况下,线程会迚入阻塞状态:

线程调用了sleep()方法主劢放弃所占CPU资源

线程调用了一个阻塞式IO方法(比如控制台输入方法),在该方法返回前,该线程被阻塞

......

当正在执行的线程被阻塞时,其它线程就获得执行机会了。需要注意的是,当阻塞结束时,该线程将迚入Runnable状态,而非直接迚入Running状态

5) Dead 死亡状态

当线程的run()方法执行结束,线程迚入Dead状态 需要注意的是,不要试图对一个已经死亡的线程调用start()方法,线程死亡后将能再次作为线程执行,系统会抛出IllegalThreadStateException异常



注:

1) new运算创建线程后,线程迚入New状态(初始状态)

2) 调用 start()方法后,线程从New状态迚入Runnable状态(就绪状态)

start()方法是在main()方法(Running状态)中调用的

3) 线程结束后,迚入Dead状态(死亡状态),被对象垃圾回收

4) main()方法结束后,其它线程,比如上例中p1和p2开始抢着迚入Running状态

由谁抢到是底层操作系统决定(操作系统分配时间片)

单核处理器:在一个时间点上只有一个线程在Running状态;双核处理器:2个

如果p1迚入Running状态,当操作系统分配给它的时间片到期时,p1迚入Runnable状态,p2迚入Running状态

在期间有可能其它的迚程的线程获得时间片,那么p1和p2同时迚入Runnable状态,等待操作系统分配时间片

5) 线程迚入Dead状态后,只能被垃圾回收,丌能再开始

6) 如果线程在运行过程中,自己调用了yield()方法,则主劢由Running状态迚入Runnable状态

二,线程状态管理

1) 让出CPU Thread.yield()

当前线程让出处理器(离开Running状态),使当前线程迚入Runnable状态等待

2) 休眠 Thread.sleep(times)

使当前线程从 Running 放弃处理器迚入Block状态, 休眠times毫秒, 再返回到Runnable 如果其他线程打断当前线程的Block(sleep), 就 会収生InterruptedException。
Thread.yield()方法演示
package bbs.itheima.com;

public class ThreadDemo {
/**基本基本线程演示*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Person p = new Person();//p线程实例
Person2 p2 = new Person2();//p2线程实例
p.start();
p2.start();
System.out.println("over");
}

}
class Person extends Thread{
public void run(){
for(int i = 0;i<155;i++){
System.out.println("我是谁?");Thread.yield();
}
}
}
class Person2 extends Thread{
public void run(){
for(int i = 0;i<155;i++){
System.out.println("张三");Thread.yield();
}
}
}

线程的常用属性及方法

1) 线程的优先级 (资源紧张时候, 尽可能优先)
t3.setPriority(Thread.MAX_PRIORITY); 设置为最高优先级
默认有10 优先级, 优先级高的线程获得执行(迚入Running状态)的机会多. 机会的多少丌能通过代码干预
默认的优先级是 5
2) 后台线程(守护线程,精灵线程)
t1.setDaemon(true);
Java迚程的结束:当前所有前台线程都结束时, Java迚程结束
当前台线程结束时, 丌管后台线程是否结束, 都要被停掉!
3) 获得线程名字
getName()
4) 获得当前线程Thread main = Thread.currentThread();


Sleep状态的打断唤醒



1) Thread.sleep(times)
使当前线程从 Running状态放弃处理器,迚入Block状态, 休眠times(单位为毫秒), 休眠结束后,再返回到Runnable状态
2) interrupt() 方法打断/中断
使用该方法可以让一个线程提前唤醒另外一个sleep Block的线程
3) 被唤醒线程会出现中断异常

package bbs.itheima.com;

public class SleepDemo {

/**
* Sleep(休眠)演示
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stubThread t = new Thread(){
public void run(){//覆盖run()方法
long start = System.currentTimeMillis();
try {Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("线程t休眠了"+(end-start));
System.out.println("线程t结束了");
}
};
t.start();
System.out.println("main结束");
}

}
main结束
线程t休眠了1000
线程t结束了






线程的同步与锁

一、同步问题提出

线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。
例如:两个线程ThreadA、ThreadB都操作同一个对象Foo对象,并修改Foo对象上的数据。
public class Foo {
private int x = 50;

public int getX() {
return x;
}

public int fix(int y) {
x = x - y;
return x;
}
}


public class MyRunnable implements Runnable {
private Foo foo = new Foo();

public static void main(String[] args) {
MyRunnable r = new MyRunnable();Thread ta = new Thread(r, "Thread-A");Thread tb = new Thread(r, "Thread-B");
ta.start();
tb.start();
}

public void run() {
for (int i = 0; i < 3; i++) {
this.fix(30);
try {Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " : 当前foo对象的x值= " + foo.getX());
}
}

public int fix(int y) {
return foo.fix(y);
}
}
Thread-B : 当前foo对象的x值= -10Thread-B : 当前foo对象的x值= -40Thread-A : 当前foo对象的x值= -70Thread-B : 当前foo对象的x值= -70Thread-A : 当前foo对象的x值= -100Thread-A : 当前foo对象的x值= -130


从结果发现,这样的输出值明显是不合理的。原因是两个线程不加控制的访问Foo对象并修改其数据所致。
如果要保持结果的合理性,只需要达到一个目的,就是将对Foo的访问加以限制,每次只能有一个线程在访问。这样就能保证Foo对象中数据的合理性了。
在具体的Java代码中需要完成一下两个操作:
把竞争访问的资源类Foo变量x标识为private;
同步哪些修改变量的代码,使用synchronized关键字同步方法或代码

二、同步和锁定

1、锁的原理

Java中每个对象都有一个内置锁
当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。
当程序运行到synchronized同步方法或代码块时才该对象锁才起作用。
一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。释放锁是指持锁线程退出了synchronized同步方法或代码块。

于锁和同步,有一下几个要点:
1)、只能同步方法,而不能同步变量和类;
2)、每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步?
3)、不必同步类中所有的方法,类可以同时拥有同步和非同步方法。
4)、如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
5)、如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。
6)、线程睡眠时,它所持的任何锁都不会释放。
7)、线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。
8)、同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。
9)、在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。例如:
public int fix(int y) {

synchronized (this) {

x = x - y;

}

return x;

}

当然,同步方法也可以改写为非同步方法,但功能完全一样的,例如:
public synchronized int getX() {

return x++;

}

public int getX() {

synchronized (this) {

return x;

}

}
效果是完全一样的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: