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

黑马程序员---------笔记整理(java基础八-----多线程)

2012-05-22 00:55 716 查看
---------------------- android培训java培训、期待与您交流! ----------------------

今天有点晕晕的,说不清的感觉,总觉得时间有点紧,不大够用。不知道究竟是怎么了。慢慢来吧,饭总得一口口吃。尽力!!!来吧,让暴风雨来得更猛烈些吧。

1.1 线程的优势

1.2线程的弊端

1.3 线程和进程的区别

1.4多线程的目的

1.5计算机执行任务的原理

1.6 创建线程的两种方式

1.7 线程的安全问题产生的原因

1.9 线程状态图解

2.0 锁

2.1 死锁

2.2 延迟加载同步代码块儿

2.3 线程间通讯

2.4 同步

2.5 其他

多线程

进程:正在运行中的程序。

线程:就是进程中一个负责程序执行的控制单元(执行路径)

1.1 线程的优势

多线程好处:解决了多部分同时运行的问题

1.2线程的弊端

多线程的弊端:线程太多回到效率的降低,因为线程的执行依靠的是CPU的来回切换。 1.3 线程和进程的区别

一个进程中可以多执行路径,称为多线程。

一个进程中至少要有一个线程。
了解:

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。如果有兴趣深入的话,我建议你们看看《现代操作系统》或者《操作系统的设计与实现》。对就个问题说得比较清楚。

1.4多线程的目的

开启多个线程是为了同时运行多部分代码,每一个线程都有自己运行的
内容,这个内容称为线程要执行的任务

1.5计算机执行任务的原理

应用程序的执行都是cpu在做着快速的切换完成的,而且切换是随机的 1.6 创建线程的两种方式

1.继承Thread。

继承Thread类;

覆盖Thread类中的run方法;

直接创建Threadde子类对象创建线程;

调用start方法开启线程并调用线程的任务run方法。

另外可以通过Thread的getName()获取线程的名称。

主要代码:

class ThreadDemo extends Thread

{

public void run()

{

for (int i=0;i<10;i++)

{

System.out.println("线程"+getName()+"正在运行:"+i);

}

}

}

class Demo

{

public static void main(String args[])

{

ThreadDemo a=new ThreadDemo();

ThreadDemo b=new ThreadDemo();

a.start();

b.start();

}

}

2.实现runnable接口

定义类实现Runnable接口;

覆盖接口的run的方法,将线程的任务代码封装到run方法中;

通过Thread类创建线程对象,将Runnabl接口的子类对象作为Thread类的构造函数的参数进行传递

(原因:线程的任务都封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时就必须明确要运行的任务)。

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

主要代码:

class RunnableDemo implements Runnable //实现Runnable

{

public void run()

{

show();

}

private void show()

{

for (int i=1;i<10 ;i++ )

{

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

}

}

}

class Demo

{

public static void main(String[] args)

{

RunnableDemo a=new RunnableDemo();

Thread b=new Thread(a); //将任务封装成对象,将其传入线程中

Thread b1=new Thread(a);

b.start();

b1.start();

}
}

总结:两种方法的比较:实现Runnable接口,将线程的任务从线程的子类中分离出来的,进行了单独的封装,按照面向对象的思想将任务的封装成对象,避免了java中单继承的局限性。(创建线程第二种方式较好)

1.7 线程的安全问题产生的原因

当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。

解决思路:就是将多条操作共享的线程代码封装起来,当有线程在执行这些代码的时候,其他线程就不可以参与运算,执行完以后,

其他的线程才 可以执行。

java中的体现:

(同步代码块,解决线程的安全问题,但是降低了效率。因为同步外的线程 都会判断同步锁,同步的前提:同步中必须有有多

个线程并使用用一个锁。)

synchronized(对象)



需要被同步的代码;



主要代码:

class Test implements Runnable

{

private int num=50;

public void run()

{

while(num>0)//线程每循环一次,释放cpu的执行权,进来再次判断。

{

synchronized(this)//一次只能有一个线程进来售票

{

if(num>0)

System.out.println(Thread.currentThread().getName()+"正在出售:"+num--);

}

}

}

}

class TestDemo

{

public static void main(String[] args)

{

Test a=new Test();

Thread t1=new Thread(a);

Thread t2=new Thread(a);

t1.start();

t2.start();

}

}

2.4 同步

同步的好处:解决了线程的安全问题

同步的弊端:相对降低了效率,因为同步外的线程的都会判断同步锁

同步的前提:同步中必须有多个线程并使用同一个锁。

同步的表现:多个线程运行到同步时,谁具备执行权,其他线程就不参与共享数据的操作。

同步代码块儿和同步函数区别:
同步函数的锁是固定的this。

同步代码块的锁是任意的对象。

建议使用同步代码块。

静态的同步函数使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前 类名.class表示。
1.9 线程状态图解



分析:

状态一,创建,使用start()开启线程。

状态二,运行(具备着执行资格,具备着执行权)

状态三,冻结(释放执行权,同时释放执行资格)

从运行到冻结的方式:

sleep(time),sleep(time)时间到,进入临时阻塞状态(具备着执行资格,但是不具备执行权,正在等待执行权)

wait()线程等待,notify()线程唤醒,进入临时阻塞状态。

状态四,消亡

从运行到消亡的方式:

stop()中止线程;

run()方法结束,线程的任务结束。

2.0 锁

锁可以方便的实现资源的封锁,用来控制对竞争资源并发访问的控制

2.1 死锁

同步代码块之间的嵌套。就是有两把锁,你那我需要的那把,我持有你需要的那把,而我们谁也不释放该锁,这样

我们都不能进去,在那等待。

主要代码:

class DeadLock implements Runnable

{

public boolean flag=false;

public void run()

{

if (!flag)

{ while(true)

{

synchronized(this)

{

synchronized(DeadLock.class)

{

System.out.println("true");

}

}

}

}

else

{

while(true)

{

synchronized(DeadLock.class)

{

synchronized(this)

{

System.out.println("false");

}

}

}

}

}

}

class Test

{

public static void main(String[] args)

{

DeadLock a=new DeadLock ();

Thread t1=new Thread(a);

Thread t2=new Thread(a);

t1.start();

try{ Thread.sleep(10);}catch(Exception e){}

a.flag=true;

t2.start();

}

}

2.2 延迟加载同步代码块儿

/*单例设计模式:

//饿汉式。

/*

class Single

{

private
static final Single s = new Single();
private
Single(){}
public
static Single getInstance()
{

return
s;

}

}

//懒汉式,请给我写个延迟加载的单例设计模式。

/*

区别:

懒汉式特点是实例的延迟加载。多线程访问时回出现安全问题。

可以加同步来解决。可以用同步代码块或者同步函数。有些低效,

用双重判断可以效率问题。同步的时候,用的锁是,该类所属的字节码文件对象。

*/

class Single

{

private static Single s = null;

private Single(){}

public static Single getInstance()

{

if(s==null)

{

synchronized(Single.class)

{

if (s==null)

{

s = new Single();

}

}

}

return s;

}

}

class SingleDmeo

{

public static void main(String[] args)

{

System.out.println("Hello World!");

}

}

2.3 线程间通讯

多线程在处理同一资源,但是任务却不同。

1.1 等待唤醒机制:

涉及的方法:

1, wait(); 是为了让线程处于冻结状态,释放CPU执行权和执行资格,被wait的线程会被存储到线程池中。

2, notify(); 是用于唤醒线程池中的一个线程(任意的线程)。

3, notifyAll(); 是用于唤醒线程池中的所有线程。

这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法。必须要明确到底操作的是哪个锁上的线程。

为什么需要唤醒机制:

有时候我们多个线程在操作同一数据时,需要有一个先后顺序,当其中一个线程在操作该数据时,其他线程不能操作该数据,

这时候可能就会涉及到冻结和唤醒机制。

1.2 为什么操作线程的方法wait
notify notifyAll 定义在Object类中?

因为这些方法是监视器的方法,监视器其实就是锁,锁可以是任意的对象,任意的对象调用的方法一定定义在Object类当中。

1 .3 线程间通讯最重要的机制:

等待唤醒机制。

wait():让当前线程处于冻结状态,当前线程就被存储到了线程池中。

notify():唤醒线程池中的任意一个线程。让该线程恢复到运行状态,会具备cpu的执行资格。

notifyAll(): 唤醒线程池中的所有等待的线程,具备cpu的执行资格。

1.4 等待唤醒机制中,最常见的体现就是生产者消费者问题:



发现两个问题:

1,出现了错误的数据。是因为多生产和多消费的时候,被唤醒的线程没有再次判断标记就执行了。

解决是将if判断变成while判断。

2,发现有了while判断后,死锁了。

因为本方线程唤醒的有可能还是本方线程。所以导致了死锁。

解决:本方必须唤醒对方才有效。notify只能唤醒一个,还不确定。所以干脆唤醒全部,肯定包含对方,

至于被唤醒的本方,会判断标记是否继续等待。

示例代码:

class ThreadDemo4

{

public static void main(String [] args)

{

Resource r = new Resource();

Shengchan s = new Shengchan(r);

Xiaofei x = new Xiaofei(r);

Thread t1 = new Thread(s);

Thread t2 = new Thread(s);

Thread t3 = new Thread(x);

Thread t4 = new Thread(x);

t1.start();

t2.start();

t3.start();

t4.start();

}

}

class Resource

{

private String name;

private int count = 1;

private boolean flag = false;

public synchronized void set(String name)

{

if(flag)

try{this.wait();}catch(InterruptedException e){}

this.name = name+count;

count++;

System.out.println(Thread.currentThread().getName()+"...生产者...."+this.name);

flag = true;

notify();

}

public synchronized void out()

{

if(!flag)

try{this.wait();}catch(InterruptedException e){}

System.out.println(Thread.currentThread().getName()+".........消费者........."+this.name);

flag = false;

notify();

}

}

class Shengchan implements Runnable

{

Resource r;

Shengchan(Resource r)

{

this.r = r;

}

public void run()

{

while(true)

r.set("烤鸭");

}

}

class Xiaofei implements Runnable

{

Resource r;

Xiaofei(Resource r)

{

this.r = r;

}

public void run()

{

while(true)

r.out();

}

}

编译后:



造成了线程的不安全的问题。

notify唤醒的是不确定的一其中的一个线程,造成了生产出的烤鸭没有被消费。

解决多生产多消费的线程安全问题:

示例代码:

class ThreadDemo4

{

public static void main(String [] args)

{

Resource r = new Resource();

Shengchan s = new Shengchan(r);

Xiaofei x = new Xiaofei(r);

Thread t1 = new Thread(s);

Thread t2 = new Thread(s);

Thread t3 = new Thread(x);

Thread t4 = new Thread(x);

t1.start();

t2.start();

t3.start();

t4.start();

}

}

class Resource

{

private String name;

private int count = 1;

private boolean flag = false;

public synchronized void set(String name)

{

while(flag) //这里写while循环是为了当线程每次醒后再判断一次标记。

try{this.wait();}catch(InterruptedException e){}

this.name = name+count;

count++;

System.out.println(Thread.currentThread().getName()+"...生产者...."+this.name);

flag = true;

notifyAll();

}

public synchronized void out()

{

while(!flag)

try{this.wait();}catch(InterruptedException e){}

System.out.println(Thread.currentThread().getName()+".........消费者........."+this.name);

flag = false;

notifyAll();

}

}

class Shengchan implements Runnable

{

Resource r;

Shengchan(Resource r)

{

this.r = r;

}

public void run()

{

while(true)

r.set("烤鸭");

}

}

class Xiaofei implements Runnable

{

Resource r;

Xiaofei(Resource r)

{

this.r = r;

}

public void run()

{

while(true)

r.out();

}

}

1.5 同步代码块儿

同步代码块,对于锁的操作是隐式的

JDK1.5以后将同步和锁封装成了对象。并将操作锁的隐式方式定义到了对象中,将隐式动作编程了显示动作。



1.6 Lock接口

出现替代了同步代码块或者同步函数。将同步的隐式锁操作编程了显示锁操作。 同事更为灵活。可以一个锁上加上多组监视器。

lock(); 获取锁。

unlock();释放锁,通常需要定义finally代码块中。

JDK1.5版本后,对多线程中的内部细节进行了升级改良。

在java.util.concurrent.locks包中提供了一个Lock接口。

Lock接口中提供了 lock()获取锁 unlock释放锁的操作。

Lock接口更符合面向对象的思想,将锁这种事物封装成了对象。

public void run()

{

synchronized(obj)

{//获取锁。

code...

//释放锁。

}

}

但是对于释放和获取锁的操作,都是隐式的。

JDK1.5后,就有了新的方法,将锁封装成了对象。因为释放锁和获取锁动作,锁自己最清楚。

锁对象的类型就是Lock接口。

并提供了,显示的对锁的获取和释放的操作方法。

Lock lock;

public void run()

{

try

{

lock.lock();//获取锁。

code...throw ...

}

finally

{

lock.unlock();//释放锁.

}

}

Lock接口替代了synchronized .

Condition替代了Object类中监视器方法 wait
notify notifyAll。、

将监视器方法单独封装成了Condition对象。而且一个锁上可以组合多组监视器对象。

实现了多生产者多消费者时,本方只唤醒对方中一个的操作,提高效率。

1.7Condition接口

出现替代了Object中的wait
notify notifyAll方法。

await(),signal(); signalAll();

将这些监视器方法单独进行封装,变成了Condition监视器对象。

可以任意锁进行组合。

await(); 睡眠

signal();signalAll(); 唤醒

使用为一般是生产者是被消费者唤醒,消费者是被生产者唤醒。

1.8 停止线程:

1,stop方法

2,run方法结束。

stop方法已经过时。 所以只剩下一种办法:线程执行的代码结束,线程会自动终止。

1,run方法中通常都有循环语句,所以只要让循环结束即可,所以只要控制住循环的条件,最简单的方式就是定义标记。

怎么控制线程的任务结束呢?

任务中都会有循环结构,只要控制住循环就可以结束任务。

控制循环通常就用定义标记来完成。

但是如果线程处于了冻结状态,无法读取标记,如何结束呢?

可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备CPU的执行资格。但是强制动作会发

terruptedException,记得要处理。

示例代码:

class StopThread implements Runnable

{

private boolean flag = true;

public synchronized void run(){

while(flag){

try {

wait();//t0 t1

}catch (InterruptedException
e){

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

flag = false;

}

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

}

}

public void setFlag(){

flag = false;

}

}

class StopThreadDemo

{

public static void main(String[] args) {

StopThread st = new StopThread();

Thread t1 = new Thread(st);

Thread t2 = new Thread(st);

t1.start();

t2.setDaemon(true);

t2.start();

int num = 1;

for(;;){

if(++num==50){

//st.setFlag();

t1.interrupt();

//t2.interrupt();

break;

}

System.out.println("main...."+num);

}

System.out.println("over");

}

}

临时加入一个线程运算时可以使用join方法。

面试题

class Test implements Runnable

{

public void run(Thread t)

{}

}

//如果错误 错误发生在哪一行?错误在第一行,应该被abstract修饰

class ThreadTest

{

public static void main(String[] args)

{

new Thread(new Runnable(){

public void run(){

System.out.println("runnable run");

}}){

public void run(){

System.out.println("subThread run");
}

}.start();

打印的是subThread run 运行以子类的为主,如果子类里面没有这个run()方法的话,就以任务线程的run为主,如果都没有的话,以父类自己的为主。

总结:

wait和sleep的区别:

wait可以指定时间也可以不指定.

sleep必须指定时间。

在同步中时,对于CPU的执行权和锁的处理不同。

wait:释放执行权,释放锁。

sleep:释放执行权,不释放锁。

wait方法,必须定义在同步中,sleep不一定。

-

--------------------- android培训java培训、期待与您交流! ----------------------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: