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

黑马程序员-java 多线程

2013-04-21 15:35 218 查看
----------- android培训java培训、java学习型技术博客、期待与您交流! ------------

由于以前工作不曾使用过多线程技术,而通过最近一段时间的学习,可能学的东西还很片面。使用起来也很生疏,为了能更好的记忆和理解,我来把我最近一段时间学习的东西做一些整理。

首先,对于多线程技术,我们要明白如下的定义:

1.什么是进程?

进程就是正在进行中的程序,也就是对应一个应用程序在内存中所使用的空间。

2. 什么是线程?

线程就是进程中用于控制程序执行的控制单元(执行路径,执行情景),而一个进程中至少要有一个线程。

3.什么是多线程

多线程就是一个进程中有多个(两个或两个以上)执行路径(控制单元)。

注意:对于JVM,启动时,就有两个线程:

1.jvm的主线程,也就是执行mian函数的线程。

2.jvm的垃圾回收线程。

创建线程的目的:就是开启一条线程,去运行指定的代码(线程要执行的代码),并且和其他代码同时运行,以达到多个线程来实现多任务并发,提高程序的执行效率。

那么,我们怎么来创建线程呢?在Java中提供了两种线程的方式:

一.继承Thread类

注:java提供的对线程描述的类是Thread类, 创建线程对象的方法是构造函数,提供了要被线程执行的代码存储的位置是在run()里面。

步骤:

1.继承Thread类。

2.覆盖run方法,将线程要运行的代码定义其中。(要覆盖run方法的原因是,定义线程要运行的代码存储在run()里面)

3.创建Thread类的子类对象,其实就是在创建线程,调用start方法(其中,start()有两个功能:1.开启线程 2.执行run()方法)。

class ThreadDemo extends Thread// 方法局限性,不能再继承其他父类.
{
private String name;

ThreadDemo(String name){

//super(name);Thread中的构造函数,可以给线程自定义名字。

this.name=name;

}

publicvoid run() {

System.out.println(this.name+"  Thread run...");

}

publicstaticvoid main(String[]args){

ThreadDemo t1=new ThreadDemo("线程一");

ThreadDemo t2=new ThreadDemo("线程二");

t1.start(); //开启线程,调用run方法

t2.start();

}

}

二.实现Runnable接口

注:下面的3种情况,就不可以再使用继承Thread类。

1.自定义的类中有多线程要运行的代码。但是该类有自己的父类。

2.要求程序具有功能的可扩展性。

3.多个功能使用一个共享数据。

那么,程序就提供了一个规则。通过实现Runnable接口。并将多线程要运行的代码存放在实现了Runnable类的子类的run方法中,然后调用对应的线程,进行处理。

步骤:

1,定义了实现Runnable接口。

2,覆盖接口的run方法。将多线程要运行的代码存入其中。

3,创建Thread类的对象(创建线程),并将Runnable接口的子类对象作为参数传递给Thread的构造函数。

为什么要传递?因为线程要运行的代码都在Runnable子类的run方法中存储。所以要将该run方法所属的对象传递给Thread。让Thread线程去使用该对象调用其run方法。

4,调用Thread对象的start方法。开启线程。

class ThreadDemo implements Runnable//实现Runnable接口

{

publicvoid run(){
//Thread.currentThread() 获取当前线程的对象,相当于this
//Thread类的getName()   获取当前线程的名称
System.out.println(Thread.currentThread().getName()+"  Runnablerun...");

}

publicstaticvoid main(String[] args){
ThreadDemo demo=new ThreadDemo();

Thread t1=new Thread(demo);

Thread t2=new Thread(demo);

t1.start(); //开启线程,调用run方法

t2.start();

}

}

通过上面的知识点,我们可以知道:

1. 线程都有自己默认的名称,格式是:Thread-编号,该编号是从0开始。

2. 使用多线程的好处是:解决了多部分同时运行的问题。

3. 继承Thred类和实现Runnable接口这两种方式,都能创建线程,但是综合比较,创建线程建议使用实现Runnable接口这种方式,原因如下:

A:通过Thread继承的类,有局限性,不能再继承其他类.,这是java单继承的特性。

B:实现Runnable接口实现多线程,可以实现数据共享,根据接口可以多个实现,并且可以继承其他类,所以功能的扩展性能好。

线程的生命周期

线程可以划分为5个状态:创建、运行、阻塞(临时状态)、冻结、消亡。线程启动后,并不是一直占用cpu独自运行,而是多个线程在cpu中进行切换运行,所以多线程具备随机性。



临时状态的特点:具备了执行资格,但不具备执行权。

冻结状态的特点:放弃了执行资格。

注:启动线程使用的start方法,而不是run方法,调用start方法启动线程,系统会把该run方法当成线程执行体来处理。如果直接调用线程对象的run方法,就相当在执行普通的方法。

多线程运行有什么问题么?

由于多线程的运行具备随机性,就有可能会产生多线程的安全问题。

问题的产生的原因:

几个关键点:

1,多线程代码中有操作共享数据。

2,多条语句操作该共享数据。

当出现上面的情况时,就有可能出现下面问题:

有一个线程对多条操作共享数据的代码执行的一部分。还没有执行完,另一个线程开始参与执行。就会发生数据错误。

解决的办法:

当一个线程在执行多条操作共享数据代码时,其他线程即使获取了执行权,也不可以参与操作。

根据上面的思路,那么可以使用Java中提供的同步的原理:就是将部分操作功能数据的代码进行加锁,就可以保证在同一时间只允许一个线程进行操作。

使用同步的前提是:

1,必须是两个或者两个以上的线程。

2,必须是多个线程使用同一个锁。

同步可以分为两种表现形式:

一.同步代码块

synchronized(对象) //该对象可以是任何类的对象.如Object类的对象

{

//...操作共享数据的语句

}
二.同步函数
函数类型前+synchronized关键字,如下:

publicstaticsynchronizedvoid func(){

//...方法体

}
它们的区别是:
1,同步代码块使用的锁是任意对象。

2,同步函数使用的锁是this。

注意:对于static的同步函数,使用的锁不是this。是 类名.class 是该类的字节码文件对象。

同步的优点和缺点:

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

缺点:要判断同步锁,较为消耗资源;并且同步嵌套后,容易死锁。

死锁:当两个线程相互等待对方释放同步监视器时就会发生死锁。一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

注:同步中可以有多条线程,但是只有一个线程在执行,谁拿锁谁执行

class Ticket implements Runnable
{
private  inttick = 100;
Object obj = new Object();
publicvoid run()
{
//show(); //同步方法
while(tick<=100)
{
synchronized(obj) //同步代码块
{
if(tick>0)
{
System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);

}
}
}
}

publicsynchronizedvoid show(){//同步方法
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"....同步函数_"+i);
}
}
}
class  TicketDemo2
{
publicstaticvoid main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.start();
t2.start();
t3.start();
}
}

线程间通讯:其实就是多个线程在操作同一个资源,但是操作的动作不同。

为了让线程协调运行,Object类提供了如下三个方法:

wait():导致当前线程处于冻结状态,被wait的线程,会被存储到线程池中,可以通过其他线程使用notify()方法或notifyAll()方法来唤醒该线程。

notify():唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则会选择唤醒其中一个线程。选择是任意性的。只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程。

notifAll():唤醒在此同步监视器上等待的所有线程。只有当前线程放弃对该同步监视器的锁定后,才可能执行被唤醒的线程。

注意:wait()和notify()等待和唤醒的锁,必须是同一个锁。

为什么这些操作线程的方法要定义Object类中呢?

上面的方法只能在持有监视器(锁)的线程中操作,而监视器(锁)可以是任意对象,所以可以被任意对象调用的方法,要定义在Object类中。

其中上面的三个方法都使用在同步中,因为要在持有监视器(锁)的线程操作,但是同步才具有锁,所以只能使用在同步中。

/*
对于多个生产者和消费者。
*/
class Resource{
private String name;
privateintcount = 1;
privatebooleanflag = false;
publicsynchronizedvoid set(String name){

/*为什么要定义while判断标记。
原因:让被唤醒的线程再一次判断标记。*/
while(flag)
try{this.wait();}catch(Exception e){}
this.name = name+"--"+count++;

System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
flag = true;
/*为什么定义notifyAll,
因为需要唤醒对方线程。
因为只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。*/
this.notifyAll();
}
publicsynchronizedvoid out(){
while(!flag)
try{wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);
flag = false;
this.notifyAll();
}
}
class Producer implements Runnable{
private Resource res;
Producer(Resource res)
{
this.res = res;
}
publicvoid run()
{
while(true)
{
res.set("+商品+");
}
}
}

class Consumer implements Runnable{
private Resource res;
Consumer(Resource res){
this.res = res;
}
publicvoid run(){
while(true){
res.out();
}
}
}
class ProducerConsumerDemo {
publicstaticvoid main(String[] args) {
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con= new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
t1.start();
t2.start();

}
}

其他常用方法:

interrupt():强制让线程恢复到运行状态中,清除冻结状态。

setDaemon():当正在运行的线程都是守护线程时,虚拟机退出,如果所有的前台线程都结束,后台线程无论处于何种状态都会自动结束。

join():哪个线程执行到join方法,哪个线程会释放执行资格,处于冻结状态。

setPriority(级别):设置线程的优先级,默认优先级是5。其中:线程优先级在1到10之间10个数。优先级越大被执行到的几率越大。

yield():可以使线程暂时释放执行权。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: