首页 > 代码库 > 黑马程序员---Java多线程
黑马程序员---Java多线程
---------------------- Android开发、java培训、期待与您交流! ----------------------
线程与进程是程序运行中体现的两个名词,它包含这不同的程序域。进程指的是一个正在运行的程序,如:360,QQ运行时就是不同的两个进程。当你打开windows任务管理器时,看到的每个进程就是此时windows中正在运行的程序。线程则就是进程中的一个个独立的控制单元,线程控制着进程的执行,也就是说每个正在运行的程序中就包括着很多的线程。
主线程:Java虚拟机运行时会有一个Java.exe的进程执行,该进程中至少有一个线程负责Java程序的运行,而这个线程运行的代码在main方法中,此时该线程成为主线程。
多线程:一个进程中可以同时运行多个线程,很明显多线程可以提高程序的运行的速度。(虽然说是同时运行,其实cup在同一时刻只能运行一个线程,cup在做着快速的切换动作,由于速度极其的快,看上去就好像是同时运行,但多核除外)。
创建线程有两种方法:
⑴继承Thread类
①定义类继承Thread类。
②重写Thread类的run()方法。
---->将线程要运行的代码定义在run()方法中。
③调用Thread类中的start()方法。
----->启动线程,须调用start()方法,不用手动调用run()方法,start()方法会调用run()方法。
⑵实现Runnable接口
①定义类实现Runnable接口。
------>这个实现Runnable接口的类是需要多线程执行的类
②重写Runnable接口中的run()方法。
------>Runnable接口中只有一个抽象方法run()方法,Thread类也实现了Runnable。
③需要多少个线程就创建多少个Thread类对象。(没考虑主线程)
------>一个Thread类对象就是一个线程
④将Runable接口的子类对象作为实际参数传递给Thread类的构造函数。
----->自定义的run方法所属的对象是Runable接口的子类对象,所以要让线程去指定对象的run方法。
⑤调用Thread类中start()方法启动线程既而run()方法也就被执行了。
----->执行start()方法就会自动执行run()方法。
创建线程的两种方法的区别与优劣:
实现Runnable接口的方法避免了Java单继承的局限性,在创建线程时建议使用实现的方法。
继承Thread:线程代码存在Thread子类run方法中。
实现Runable:线程代码存在接口的子类的run方法。
多线程的安全问题
当多条语句操作同一个线程共享数据时,一个线程对语句只执行了一部分,另一个线程就进来执行了,导致共享数据的错误。
解决方法:
对多条共享数据的语句,只能让一个线程都执行完了其它线程才可以去执行,在执行过程中,其他线程不可以参与执行,java对于多线程的安全问题提供了专业的解决方法,那就是同步代码块。
synchronized(对象)
{
需要被同步的代码
}
同步代码块使用的关键字是synchronized,所传递的对象为任意对象,这个对象就相当于锁,持有锁的线程才可以执行被同步的代码,否则就算拥有cpu执行权,也进不来。当被同步代码执行完释放锁后下一个待执行的线程才可以进去。
同步的前提:
①必须要有两个或两个以上的线程执行共享代码
----->只有这样同步才会有意义。
②必须是多线程使用同一个锁
------>对于多个synchronized(对象){},这个对象必须是同一对象,才可以做到未释放锁之前,只有一个线程在执行同步代码。
同步的优劣
优点:解决了多线程的线程安全问题。
劣点:每执行同步时都要判断锁,耗时,较为消耗资源,使用不当时会造成死锁。
同步方法
public synchronized void function(){}
在非静态方法上加上synchronized关键字,与同步代码块功能相同,只是同步方法的锁是this,指的是当前类对象,而同步代码块的锁可以使任何对象。
public static synchronized void function(){}
当同步方法为静态时,与同步代码块和非静态方法的区别只是锁,此时静态同步方法的锁为该方法所在类的字节码文件对象, 类名.class。静态进入内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。
Thread类中常用的操作线程的方法介绍
① static void sleep(int millis)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
② static Thread currentThread()
返回对当前正在执行的线程对象的引用。通常与getName()使用。
③ String getName()
返回该线程的名称。 通常与currentThread()使用来获取当前正在执行的线程的名称,Thread.currentThread().getName()。
④ void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
⑤ String toString()
返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
⑥ void setPriority(int newPriority)
更改线程的优先级。
⑦ static void yield()
暂停当前正在执行的线程对象,并执行其他线程。
⑧ void interrupt()
中断线程。
⑨ void join()
等待该线程终止。
⑩ void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。
㈡定义在 Object类中的三类操作线程的方法介绍
① void notify()
唤醒在此对象同步上等待的单个线程。
② void notifyAll()
唤醒在此对象同步上等待的所有线程。
③ void wait()
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
为什么这三类操作线程的方法要定义在Object类中呢?
这些方法存在同步中,使用这些方法时必须要标识所属的同步锁,锁可以是任意对象调用的方法一定定义Object类中。
wait()与sleep()的区别:
wait():释放资源,释放锁。
sleep():释放资源,不释放锁。
等待唤醒机制
等待唤醒机制要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才具有锁。必须要用到锁,等待和唤醒都必须定义到锁中。同时必然会用到wait()、notify()和notifyAll()三个方法。
第一个实例:
得到一个人的信息,就打印这个人的信息,不可以得到多人再打印,也不可得到一个人重复打印多次。所以要使用多线程,并且要使用同步才能够解决。
⑴定义一个资源类(Res),描述资源。
⑵定义两个线程类,一个输入(Input)类,一个输出(Output)类,让他们都实现Runnable接口。
⑶定义main方法类,即为主线程。
class Res { private String name; private String sex; private boolean flag = false; public synchronized void set(String name,String sex) { if(flag) try{this.wait();}catch(Exception e){} this.name = name; this.sex = sex; flag = true; this.notify(); } public synchronized void out() { if(!flag) try{this.wait();}catch(Exception e){} System.out.println(name+"...."+sex); flag = false; this.notify(); } } class Input implements Runnable { private Res r; Input(Res r) { this.r = r; } public void run() { int x = 0; while (true) { if(x==0) r.set("Mike","man"); else r.set("丽丽","女女"); x = (x+1)%2; } } } class Output implements Runnable { private Res r; Output(Res r) { this.r = r; } public void run() { while (true) { r.out(); } } } class InputOutputDemo { public static void main(String[] srgs) { Res r = new Res(); new Thread(new Input(r)).start(); new Thread(new Ontput(r)).start(); } }第二个实例
生产者/消费者的程序来演示多个线程对共享资源的竞争。
(1)ProducerConsumerDemo类:提供程序入口main()方法,负责创建生产者和消费者线程,并且启动这些线程。
(2)producer类:生产者线程,不断向堆栈中加入产品。
(3)Consumer类:消费者线程,不断向堆栈中取出产品。
(4)Resource类:是资源类,允许从堆栈中取出或加入产品。
class ProducerConsumerDemo { public static void 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(pro); Thread t3 = new Thread(con); Thread t4 = new Thread(con); 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) try{this.wait();}catch(Exception e){} this.name = name+"--"+count++; System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name); flag = true; this.notifyAll(); } public synchronized void out() { while(!flag) try{this.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; } public void run() { while(true) { res.set("+商品+"); } } } class Consumer implements Runnable { private Resource res; Consumer{Resource res} { this.res = res; } public void run() { while(true) { res.out("+商品+"); } } }JDK1.5中提供了多线程升级解决方案
Condition接口中的await(),signal(),signalAll()将Object中的wait(),notify(), notifyAll()给替换了。synchronized同步被Lock接口给替换了,Lock接口有一个ReentrantLock实现类,用它创建Lock的对象,Lock接口中有一个newCondition()方法,可以创建Condition类的实例对象。Lock接口中的lock()和unlock()方法分别为获取锁和释放锁的操作,可替换synchronized锁。
将以上实例用多线程升级解决方案改写如下:
class ProducerConsumerDemo { public static void 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(pro); Thread t3 = new Thread(con); Thread t4 = new Thread(con); t1.start(); t2.start(); t3.start(); t4.start(); } } class Resource { private String name; private int count = 1; private boolean flag = false; private Lock lock =new ReentrantLock(); private Condition condition_pro = lock.newCondition(); private Condition condition_con = lock.newCondition(); public void set(String name)throws InterruptedException { lock.lock();//获取锁。 try { while(flag) condition_pro.await(); this.name = name+"--"+count++; System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name); flag = true; condition_con.signal(); } finally { lock.unlock();//释放锁的动作一定要执行。 } } public void out()throws InterruptedException { lock.lock(); try { while(!flag) condition_con.await(); System.out.println(Thread.currentThread().getName+"...消费者..."+this.name); flag = false; condition_pro.signal(); } finally { lock.unlock(); } } } class Producer implements Runnable { private Resource res; Producer{Resource res} { this.res = res; } public void run() { while(true) { res.set("+商品+"); } } } class Consumer implements Runnable { private Resource res; Consumer{Resource res} { this.res = res; } public void run() { while(true) { res.out("+商品+"); } } }线程中的常用操作解决方案:
一、停止线程。
最早用Thread类中stop()方法,但由于stop()方法具有不安全性,所以不建议使用了,成为过时方法。然而想让线程停止另有其它好方法。实际上停止线程就是停止run()方法。
(1)更改循环标记(终止循环)
----->因为线程运行代码一般都是循环结构,只要控制了循环即可.
----->但就算是要更改循环标记,如果当再更改循环标记之前wait()了,并不能立即结束线程,那该怎么办?此时线程机制任然还有其它好方法。如(2)可见。
(2)使用interrupt()方法,清除如wait()等方法的冻结状态,就会有机会读到标记,既而有机会终止循环终止线程。
------>该方法是结束线程的冻结状态,使线程回到运行状态中来。
二、守护线程。
守护线程又称用户线程也称后台线程,Java中用setDaemon()方法设置线程是否为守护线程,此方法参数为boolean类型,当传递true时则设置为守护线程否则为前台线程。
注意:
(1)setDaemon(boolean on)方法设置是否为守护线程,传递true则为守护线程,否则为前台线程。
(2)当所有线程都为守护线程时,则jvm就会退出。
(3)当所有的前台线程结束了,则守护线程也就都结束了。
(4)若前台线程没有结束,后台线程可结束也可不结束,全在设定。
(5)垃圾回收线程为守护线程。
三、join()等待线程终止。
此方法是Thread类中的,用来操作线程,意思是让别的线程等待直到当前执行线程执行结束。
Resource r = new Resource(); Producer pro = new Producer(r); Consumer con = new Consumer(r); Thread t1 = new Thread(pro); Thread t2 = new Thread(pro); Thread t3 = new Thread(con); Thread t4 = new Thread(con); t1.start(); //① t2.start(); //② t3.start().join(); //③ t4.start(); //④主线程执行①②代码,启动t1和t2两个线程,此时主线程、t1、t2这三个线程交替执行,当执行③代码时,t3线程被启动,遇到join()方法,此时主线程立即冻结,主线程不会执行④代码,t4线程暂时不会被启动。t1、t2、t3这三个线程交替执行,直到t3线程执行结束,这时主线程被激活当抢到执行权时t4线程才会被启动,如果t1和t2还没执行结束,则此时就是t1、t2、t4、主线程抢夺cpu执行权了。
四、Thread类中toString()方法
它执行结果就是打印"线程名 优先级 线程组",线程名为Thread.currentThread().getName()的结果,优先级也就是线程被执行的频率,抢夺到cpu执行权的频率。线程组按我的理解应该是在哪个线程中启动了当前线程,则就是哪个线程的线程名。
五、线程优先级
线程优先级即为线程执行到的频率,优先级定义为int,是1---10之间的整数,1表示被执行到的频率最低,10表示被执行到的频率最高。它有以下5种操作线程优先级的方法。
(1) public static final int MIN_PRIORITY
----->线程可以具有优先级最低,它的优先级实际上为1.
(2)public static final int NORM_PRIORITY
----->线程可以具有优先级为中等,是线程默认的优先级,它的优先级实际上为5
(3)public static final int MAX_PRIORITY
------>线程可以具有优先级为最高,它的优先级实际上为10.
(4)setPriority(int newPriority)
------>更改优先级,可向方法中传递1----10的整数代表优先级的高低。
(5)int getPriority()
------>返回线程的优先级。
【总结】
在学习多线程的过程中,Java多线程的方方面面都给我们代码带来了灵活性和快捷性,包括创建线程,以及对多个线程进行调度、管理,我们深刻认识到了多线程编程的复杂性,以及线程切换开销带来的多线程程序的低效性,这也促使我们认真地思考一个问题:我们是否需要多线程?何时需要多线程?
多线程的核心在于多个代码块并发执行,本质特点在于各代码块之间的代码是乱序执行的。我们的程序是否需要多线程,就是要看这是否也是它的内在特点。
假如我们的程序根本不要求多个代码块并发执行,那自然不需要使用多线程;假如我们的程序虽然要求多个代码块并发执行,但是却不要求乱序,则我们完全可以用一个循环来简单高效地实现,也不需要使用多线程;只有当它完全符合多线程的特点时,多线程机制对线程间通信和线程管理的强大支持才能有用武之地,这时使用多线程才是值得的。
---------------------- Android开发、java培训、期待与您交流! ----------------------详细请查看:www.itheima.com