黑马程序员_java多线程总结

来源:互联网 发布:军事题材电视剧知乎 编辑:程序博客网 时间:2024/05/18 10:52

                                                        --------------android培训java培训、期待与您交流! --------------

一、多线程概述

现实生活中许多事情并不是按照顺序发生的,很多时候都是同时发生,例如人体中可以同时进行呼吸、血液流动、思考问题这些活动,用户操作电脑可以同时听音乐、玩游戏和打印文件。这些问题投射在java中就引出了并发执行的问题,而java就靠多线程机制来解决这些问题。

(1)进程:
是一个正在执行中的程序。
每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。

(2)线程:
就是进程中的一个独立的控制单元。
线程在控制着进程的执行。一个进程中至少有一个线程。

java中体现:java JVM启动的时候,会有一个进程java.exe。该进程中至少有一个线程在负责java程序的执行。

                     而且这个线程运行的代码存在于main方法中。该线程称为主线程。

扩展知识:jvm启动不止一个线程,因为还有负责垃圾回收机制的线程。


二、线程类及线程实现

1、Thread(线程类)

java体系中,任何机制都是基于类的,线程机制也是基于Thread类。

Thread类的对象实例就表示java程序中的线程,通过它可以对线程采取多种操作。

Thread类常见方法:

yield();引起当前执行线程暂停,允许其他线程执行。

sleep(long millis);

start();使线程由新建状态变成可运行,并自动调用线程中的run方法

run();线程在启动时执行的功能

stop();停止当前线程

wait();使当前线程等待直到被唤醒或者超过指定的等待时间

isAlive();测试线程是否是活动的

setPriority(int new);设置线程优先级

notify();唤起一个等待的线程

notifyAll();唤起所有等待的线程

线程运行状态示例图:


2、实现线程的两种方式

(1)继承Thread类

Thread类位于java.lang包中,且java.lang包自动的被导入每个java程序中,省去了建立线程还要进行导入类的操作,说明java对线程进行了彻底支持。

继承Thread类实现线程步骤及代码实例:

①建立一个类使其继承Thread类。

②在类中复写Thread类的run方法,就是将希望运行的代码写进run中,让其在线程启动的时候执行。

③新建一个该类的对象,即一个线程。

④使用该对象调用start方法启动线程。

实例:

//Demo继承Thread类class Demo extends Thread{//复写了Thread类的run方法public void run(){for(int i=0;i<10;i++){System.out.println("Demo run  "+i);}}}public class ThreadDemo {public static void main(String[] args) {Demo d =new Demo();//创建Demo的对象,即新建了一个线程d.start();//调用start方法,此线程进入可运行状态for(int i=0;i<10;i++){System.out.println("main run  "+i);}//main函数的执行代码,其实主程序也是线程,而且一般都是主线程。}}

将代码运行多次发现每一次的结果都不相同。

原因:因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。

在上述实例中,Demo的线程和主程序的主线程在抢夺cpu执行权,

由于每次运行时的cpu运行环境不同,就造成了每次运行时它们之间抢夺cpu执行权的结果也不同,就造成了程序运行结果不同。

明确一点,在某一个时刻,只能有一个程序在运行。(多核除外)
cpu在做着快速的切换,以达到看上去是同时运行的效果。
我们可以形象把多线程的运行行为在互相抢夺cpu的执行权。
这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,cpu说的算。


(2)实现Runnable接口

通过继承Thread类来实现线程确实很方便,但又出现了一个问题,如果类已经继承了一个类,由于java不允许多继承,类想要实现线程就无法继承Thread类,这时候又不想创建别的类,这时候就应该使该类通过实现Runnable接口来实现线程。

实现Runnable接口实现线程步骤及代码实例:

①建立一个类使其实现Runnable接口

②在类中实现Runnable接口的run方法,就是将希望运行的代码写进run中,让其在线程启动的时候执行。

③新建一个该类的对象,并使用参数为该Runnable对象的构造方法创建Thread实例

④使用Thread实例调用start方法启动线程。

实例:

//Demo类实现Runnable接口class Demo implements Runnable{//实现了Runnable接口的run方法public void run(){for(int i=0;i<10;i++){System.out.println("Demo run  "+i);}}}public class ThreadDemo {public static void main(String[] args) {Demo d =new Demo();//创建Demo的对象,即新建了一个Runnable对象Thread t = new Thread(d);/*调用Thread类中的Thread(Runnable target)构造方法建立线程实例, *就是将实现Runnable接口的类对象传递给Thread类的对象即传递给新线程,为新线程提供方法和数据。*/t.start();//Thread类实例调用start方法,此线程进入可运行状态for(int i=0;i<10;i++){System.out.println("main run  "+i);}//main函数的执行代码,其实主程序也是线程,而且一般都是主线程。}}

继承Thread和实现Runnable的区别:
    继承Thread:线程代码存放在Thread子类run方法中。无法多继承。
    实现Runnable:线程代码存放在接口的子类的run方法中。可以多实现。

    实现方式好处:避免了单继承的局限性。
    在定义线程时,建立使用实现方式。


3、run和start特点

为什么要覆盖run方法呢?
Thread类用于描述线程。
该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法。也就是说Thread类中的run方法,用于存储线程要运行的代码。

class  ThreadA implements Runnable{//实现了Runnable接口的run方法public void run(){for(int i=0;i<10;i++){System.out.println("ThreadA run "+i);}}}class  ThreadB implements Runnable{//实现了Runnable接口的run方法public void run(){for(int i=0;i<10;i++){System.out.println("ThreadB run "+i);}}}public class ThreadDemo {public static void main(String[] args) {Thread t1= new Thread(new ThreadA());Thread t2= new Thread(new ThreadB());t1.start();t2.start();//t1.run();//t2.run();for(int i=0;i<10;i++){System.out.println("main run  "+i);}}}
通过实例发现:

start可以实现多线程,run就不行。

run():是Thread类和Runnable接口中的方法,对其进行复写或实现后,其中存储的是线程启动时要执行的代码。

直接调用run方法只是执行了其中的代码,并不能启动线程。

start():此方法并没有执行代码,它的作用就是使一个新建线程开启并处于就绪状态,等待cpu执行权获得后自动调用run方法。


run方法可以看成只要一运行就获得了cpu执行权,没有其他方法与之争抢。

例如A线程的run()和B线程的run()。先让A线程的run方法开始执行,它会一直执行下去直到完毕,接着才执行B线程的run方法。

start方法可以看成多个线程在争抢cpu执行权,获得了cpu执行权的方法才能执行。

例如A线程的start()和B线程的start()。两个线程开启后,它们会开始争抢cpu执行权,A线程的run方法和B线程的run方法会根据哪个线程抢到了cpu的执行权而交替执行。

4、获取线程对象和名称

currentThread() 返回对当前正在执行的线程对象的引用。
setName() 设置线程名称。
getName():获取线程名称。
System.currentTimeMillis(); 获取当前毫秒数。
System.nanoTime(); 获取当前毫微秒(1毫秒=1000微秒=1000000毫微秒)

(1)怎么获取主线程的名称?
Thread类有一个静态方法currentThread(),直接类名调用得到主线程的对象,该对象调用getName()获得名称。
String mainThreadName = Thread.currentThread().getName(); 

(2)为什么在主函数中无法用this调用getName()方法获取线程名?
因为主函数是静态的,类一加载,函数就存在了,但还没有对象,所以不能使用this。

class Test extends Thread{//构造函数,设置线程名称Test(String name){super(name);//访问父类的设置名称构造函数}public void run(){for(int x=0; x<10; x++){System.out.println(this.getName()+" run..."+x+" 当前毫秒"+System.currentTimeMillis());//getName可以获取当前线程名称,这里的this就相当于Thread.currentThread()//Thread.currentThread()返回的是当前执行的线程对象引用}}}class ThreadDemo{public static void main(String[] args) {Test t1 = new Test("线程A");Test t2 = new Test("线程B");t1.start();t2.start();Thread.currentThread().setName("主线程");//将当前线程的名称设为主线程for(int x=0; x<10; x++){System.out.println(Thread.currentThread().getName()+"run"+x);//主线程中不能用this来调用getName获取名称//因为主函数是静态的,其随着类的加载而加载而加载,不存在对象,所以不能使用this}}}
5、多线程实例

需求:简单的卖票程序,多个窗口同时卖票。

class TicketWindow implements Runnable{private int ticket_num=100;//设置总票数public void run(){while(true){if(ticket_num>0){try{Thread.sleep(10);}catch(Exception e){}//让该线程休眠10毫秒,目的:为了显现出线程中的安全问题System.out.println(Thread.currentThread().getName()+"....sale : "+ ticket_num--);}}}}class TicketDemo{public static void main(String[] args) {//新建4个线程并调用start方法启动线程TicketWindow t = new TicketWindow();Thread t1=new Thread(t);Thread t2=new Thread(t);Thread t3=new Thread(t);Thread t4=new Thread(t);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t4.setName("窗口4");t1.start();t2.start();t3.start();t4.start();}}



通过分析,发现程序可能会打印出0,-1,-2等错票,多线程的运行出现了安全问题。

问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,
另一个线程参与进来执行。导致共享数据的错误。
解决办法:对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。

Java对于多线程的安全问题提供了专业的解决方式,这就引入了线程的同步。


三、线程的同步

由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。

Java语言为解决这种冲突提供了专门的机制,有效避免了同一个数据对象被多个线程同时访问,这就是线程同步。

1、同步代码块

(1)格式:

synchronized(对象)
{
需要被同步的代码

}

对象:如同锁
持有锁的线程可以在同步中执行。
没有锁的线程即使获取CPU的执行权,也进不去,因为没有获取锁。

(2)同步的前提:
1:必须要有两个或者两个以上的线程。
2:必须是多个线程使用同一个锁。
3:必须保证同步中只能有一个线程在运行。
(3)好处:解决了多线程的安全问题。

         弊端:多个线程需要判断锁,较为消耗资源。

例如公共卫生间问题:

公共卫生间的门关上并锁死,表示里面有人使用,外面的人进不来,只能等里面的人使用完出来才能进入;

公共卫生间的门开着,表示里面没有人,外面的人可以进来使用,进来一个人把门关上锁死后,外面的人就又进不来了。

实例:

class TicketWindow implements Runnable{private int ticket_num=100;//设置总票数Object obj = new Object();public void run(){while(true){//给访问共享数据的代码加锁,当有进程正在访问共享数据的时候,其他进程不能访问,保证了数据安全性。synchronized (obj)//obj对象标志位为0,线程不能运行同步块代码,为1,则可以运行。{if(ticket_num>0){try{Thread.sleep(10);}catch(Exception e){}//让该线程休眠10毫秒System.out.println(Thread.currentThread().getName()+"....sale : "+ ticket_num--);}}}}}class TicketDemo{public static void main(String[] args) {//新建4个线程并调用start方法启动线程TicketWindow t = new TicketWindow();Thread t1=new Thread(t);Thread t2=new Thread(t);Thread t3=new Thread(t);Thread t4=new Thread(t);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t4.setName("窗口4");t1.start();t2.start();t3.start();t4.start();}}

2、同步函数

同步函数就是在方法前面修饰synchronized关键字的方法。

当某个对象调用了同步方法时,该对象上的其他同步方法必须等待该同步方法执行完毕后才能执行,必须将每个能访问共享资源的方法修饰为同步方法,否则会出错。

需求:
银行有一个金库。
有两个储户分别存300员,每次存100,存3次。
目的:该程序是否有安全问题,如果有,如何解决?

如何在程序中解决同步问题:
(1)明确哪些代码是多线程运行代码。
(2)明确共享数据。
(3)明确多线程运行代码中哪些语句是操作共享数据的。

class Bank{private int sum;//银行储金总数public synchronized void add(int n)//同步方法{sum = sum + n;try{Thread.sleep(10);}catch(Exception e){}System.out.println("sum="+sum);}}class Custom implements Runnable{private Bank b = new Bank();public void run(){for(int x=0; x<3; x++){b.add(100);//调用同步方法,其他线程必须等待该线程的同步方法执行完毕才能调用。}}}class  BankDemo{public static void main(String[] args) {Custom c = new Custom();//建立两个线程开始运行,即两个顾客开始存钱Thread t1 = new Thread(c);Thread t2 = new Thread(c);t1.start();t2.start();}}

3、同步函数的锁是this

同步代码块中都有一个对象作为锁,可是同步函数没有指明的对象也能进行锁的操作。

这是因为同步函数都将当前所属的对象作为锁,即this。

class TicketWindow implements Runnable{private int ticket_num=100;//设置总票数Object obj = new Object();public void run(){while(true){sell();}}public synchronized void sell(){  //同步函数的锁是thisif(ticket_num>0){try{Thread.sleep(10);}catch(Exception e){}//让该线程休眠10毫秒System.out.println(Thread.currentThread().getName()+"....sale : "+ ticket_num--);}    }     }class TicketDemo{public static void main(String[] args) {//新建4个线程并调用start方法启动线程TicketWindow t = new TicketWindow();Thread t1=new Thread(t);Thread t2=new Thread(t);Thread t3=new Thread(t);Thread t4=new Thread(t);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t4.setName("窗口4");t1.start();t2.start();t3.start();t4.start();}}

4、静态同步函数的锁是Class对象

如果同步函数被静态修饰后,使用的锁是什么呢?
通过实例验证,发现不再是this。因为静态方法中也不可以定义this。
静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象:类名.class  该对象的类型是Class
静态的同步方法,使用的锁是该方法所在类的字节码文件对象。 类名.class。

实例验证:

class Ticket implements Runnable{private static  int tick = 100;boolean flag = true;public  void run(){if(flag)//如果标记flag为真,则线程运行同步代码块{while(true){synchronized(Ticket.class){if(tick>0){try{Thread.sleep(10);}catch(Exception e){}System.out.println(Thread.currentThread().getName()+"....code : "+ tick--);}}}}else//如果标记flag为真,则线程运行同步函数while(true)sell();}public static synchronized void sell()//静态同步函数使用的锁是Class对象{if(tick>0){try{Thread.sleep(10);}catch(Exception e){}System.out.println(Thread.currentThread().getName()+"....show.... : "+ tick--);}}}class  TicketDemo{public static void main(String[] args) {Ticket t = new Ticket();Thread t1 = new Thread(t);Thread t2 = new Thread(t);t1.start();try{Thread.sleep(10);}catch(Exception e){}//让主线程在开启t1线程后休眠10毫秒,目的是让t1有运行的时间,防止两个线程只运行同步函数t.flag = false;//在t1线程运行同步代码块后,设置标记为flase,用来让线程访问同步函数t2.start();}}
从实例得出:

两个线程同时在卖票,且run方法中使用了两个同步机制:同步代码块和静态同步函数。

若让同步代码块中使用一个对象锁,则代码运行结果中会出现卖出0票,表明不安全,就是线程同步中用的锁不一样。

若让同步代码块中使用当前class对象锁,则代码运行结果中不会出现卖出0票,表明安全,就是说静态同步函数使用的锁就是class对象锁。


5、单例设计模式-懒汉式

单例设计模式-懒汉式:对象是方法被调用时才初始化,也称为对象的延迟加载。
例:Single类进内存,对象还没有存在,只有调用了getInstance方法时,才建立对象。
class Single{private Single(){}private static Single s=null;public static  Single getInstance(){if(s==null){s=new Single();return s;}}//向外提供本类对象。}
懒汉式会出现问题:
例如A程序和B程序,A程序判断完s==null,cpu切换到另一程序,A程序挂起;接着cpu开始执行B程序,B程序判断完s==null,cpu又切换到另一程序,B程序挂起。等到A程序恢复执行,则建立一个对象s,接下来当B程序开始执行,B又会进行创建对象s,由于这是单例设计,所以这里两个对象会冲突报错,产生安全问题。
这个问题可以用synchronized关键字(同步)来对程序加锁解决
class Single{private Single(){}private static Single s=null;public static  Single getInstance(){//或者在函数上修饰synchronized关键字//判断对象是否为空if(s==null){//判断锁,无锁,则执行该同步代码块中代码,建立对象synchronized(Single.class)//用到的是class对象锁{//再次判断对象是否为空if(s==null){s=new Single();//新建Single类对象return s;//返回该对象}}}}//向外提供本类对象。}


6、死锁

死锁是指发生在线程之间相互阻塞的现象,这种现象导致同步线程相互等待,以致每个线程都不能往下执行。

例如:两个人各持有一根筷子,想要吃饭,但吃饭需要两根筷子,所以两个人都想要对方的筷子,这时候就产生了死锁,谁也得不到所需的资源。

死锁:同步中嵌套同步。


死锁实例1:多窗口同时售票,这个程序会造成死锁。

死锁问题发生:

若线程1开始访问同步代码块,并把obj锁锁死,准备访问同步函数;

与此同时,线程2开始访问同步函数,并把class锁锁死,准备访问同步代码块;

紧接着,线程1发现同步函数被线程2锁死,不能运行,线程2发现同步代码块被线程1锁死,也不能运行;

同时两个线程都想访问对方所占用的资源,且都不放弃自己的资源,就发生了阻塞,产生了死锁现象。

class Ticket implements Runnable{private  int tick = 1000;Object obj = new Object();boolean flag = true;public  void run(){if(flag){while(true){synchronized(obj){sell();}}}elsewhile(true)sell();}public synchronized void sell()//this{synchronized(obj){if(tick>0){try{Thread.sleep(10);}catch(Exception e){}System.out.println(Thread.currentThread().getName()+"....code : "+ tick--);}}}}class  DeadLockDemo{public static void main(String[] args) {Ticket t = new Ticket();Thread t1 = new Thread(t);Thread t2 = new Thread(t);t1.start();try{Thread.sleep(10);}catch(Exception e){}t.flag = false;t2.start();}}
死锁实例2:

class Test implements Runnable{private boolean flag;Test(boolean flag){this.flag = flag;}public void run(){if(flag){while(true){synchronized(MyLock.locka){System.out.println(Thread.currentThread().getName()+"...if locka ");synchronized(MyLock.lockb){System.out.println(Thread.currentThread().getName()+"..if lockb");}}}}else{while(true){synchronized(MyLock.lockb){System.out.println(Thread.currentThread().getName()+"..else lockb");synchronized(MyLock.locka){System.out.println(Thread.currentThread().getName()+".....else locka");}}}}}}//定义锁class MyLock{static Object locka = new Object();static Object lockb = new Object();}class  DeadLockDemo{public static void main(String[] args) {Thread t1 = new Thread(new Test(true));Thread t2 = new Thread(new Test(false));t1.start();t2.start();}}

死锁问题发生:

线程1有a锁,线程2有b锁;线程1想要访问b锁,线程2想要访问a锁,双方还不放自己的锁,于是产生死锁。


四、线程操作

1、线程通信

(1)线程通信

多线程一个重要特点就是它们之间可以相互通信,线程通信是线程之间可以相互交流和等待,可以通过经常共享的数据使线程相互交流,也可以通过线程控制方法使线程之间相互等待。

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

(2)等待唤醒机制

线程通信可以通过线程控制方法使线程互相等待。

Object类提供了3个方法:wait()、notify()和notifyAll()。

都使用在同步中,因为要对持有监视器(锁)的线程操作。
所以要使用在同步中,因为只有同步才具有锁。


为什么这些操作线程的方法要定义Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁,
只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。
不可以对不同锁中的线程进行唤醒。
也就是说,等待和唤醒必须是同一个锁。
而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。


wait()和sleep()的区别:wait() 释放资源,释放锁;sleep() 释放资源,不释放锁。

线程通信实例:

//资源class Res{String name;String sex;boolean flag = false;//默认资源标识为false,没有资源}//输入class Input implements Runnable{private Res r ;//资源锁Input(Res r){this.r = r;}public void run(){int x = 0;while(true){synchronized(r)//同步锁{if(r.flag)//若有资源,则调用wait等待输出者取走资源try{r.wait();}catch(Exception e){}//放入资源if(x==0){r.name="小明";r.sex="男";}else{r.name="小丽";r.sex = "女";}x = (x+1)%2;//对输入的资源进行切换r.flag = true;//将资源标识设为true,表明资源有了r.notify();//唤醒输出线程,让其取走资源}}}}//输出class Output implements Runnable{private Res r ;//资源锁Output(Res r){this.r = r;}public void run(){while(true){synchronized(r)//同步锁{if(!r.flag)//若没有资源,则调用wait等待输入者放入资源try{r.wait();}catch(Exception e){}System.out.println(r.name+"...."+r.sex);//输出资源r.flag = false;//将资源标识设为flase,表明资源没有了r.notify();//唤醒输入线程,让其放入资源}}}}class  InputOutputDemo{public static void main(String[] args) {Res r = new Res();//建立资源对象                Input in = new Input(r);Output out = new Output(r);                //建立两个线程并开启线程,一个输入线程,一个输出线程new Thread(in).start();new Thread(out).start();}}


(3)生产者与消费者

生产者和消费者问题:是一个典型的线程通信模型。

该问题和上文的输入输出同步问题相似,并对其进行优化,将同步代码块封装成同步函数。

代码:

//商品仓库class Res{private String name;private int num=1;//仓库商品编号private boolean flag = false;//仓库中是否有商品的标识,初始化为没有商品。public synchronized void put()//生产函数,同步函数{          if(flag)//若有商品,则调用wait等待消费者消费商品            try{this.wait();}catch (Exception e){}          //生产产品        this.name="商品"+num++;        System.out.println(Thread.currentThread().getName()+"生产者生产:"+name);          flag = true;//将商品标识设为true,表明仓库中已有商品        this.notify();//通知消费者消费商品    } public synchronized void get()//消费函数,同步函数{  if(!flag)//若没有商品,则调用wait等待生产者生产商品try{this.wait();}catch(Exception e){}//消费产品System.out.println(Thread.currentThread().getName()+"消费者消费:"+name);  flag = false;//将商品标识设为false,表明仓库中没商品this.notify();//通知生产者生产商品    }}//生产者class Producer implements Runnable{private Res r ;Producer(Res r){this.r = r;}public void run(){for(int i=0;i<100;i++){r.put();}}}//消费者class Customer implements Runnable{private Res r ;Customer(Res r){this.r = r;}public void run(){for(int i=0;i<100;i++){r.get();}}}class  ProducerCustomerDemo{public static void main(String[] args) {Res r = new Res();new Thread(new Producer(r)).start();new Thread(new Customer(r)).start();}}

上述是一个生产者线程和一个消费者线程的代码,如果使其开启两个生产者线程和两个消费者线程,则可能会出现异常情况。

出现的情况:
序号为线程执行顺序,F、T指商品标识
生产一次,消费两次的异常情况:

  

                                       F                                  T                       F                        F                                      T                                                 F
生产者1       (1)生产商品186,等待   
生产者2                                                                                               (4)生产商品187,等待
消费者1                                           (2)消费商品186,等待                                                    (5)消费商品187,等待
消费者2                                                                                  (3)等待                                                                                 (6)不再判断标识,仍然消费商品187,问题出现


生产两次,消费一次的异常情况:


                                        F                      T                     T                          F                         T                                                                        F
生产者1    (1)生产商品169,等待                                                                  (5)生产商品170,等待
生产者2                                           (2)等待                                                                                              (6)不再判断标识,继续生产商品171,导致商品170没有被消费           
消费者1                                                        (3)消费商品169,等待                 
消费者2                                                                                                (4)等待                         


下面解决这个问题并说明原因。

下面是两个生产者线程和两个消费者线程的代码:

//商品仓库class Res{private String name;private int num=1;//仓库商品编号private boolean flag = false;//仓库中是否有商品的标识,初始化为没有商品。public synchronized void put()//生产函数,同步函数{          while(flag)//若多次判断商品标识            try{this.wait();}catch (Exception e){}          //生产产品        this.name="商品"+num++;        System.out.println(Thread.currentThread().getName()+"生产者生产:"+name);          flag = true;//将商品标识设为true,表明仓库中已有商品        this.notifyAll();//唤醒全部线程    } public synchronized void get()//消费函数,同步函数{  while(!flag)//若没有商品,则调用wait等待生产者生产商品try{this.wait();}catch(Exception e){}//消费产品System.out.println(Thread.currentThread().getName()+"消费者消费:"+name);  flag = false;//将商品标识设为false,表明仓库中没商品this.notifyAll();    }}//生产者class Producer implements Runnable{private Res r ;Producer(Res r){this.r = r;}public void run(){for(int i=0;i<100;i++){r.put();}}}//消费者class Customer implements Runnable{private Res r ;Customer(Res r){this.r = r;}public void run(){for(int i=0;i<100;i++){r.get();}}}class  ProducerCustomerDemo{public static void main(String[] args) {Res r = new Res();//两个生产线程生产new Thread(new Producer(r)).start();new Thread(new Producer(r)).start();//两个消费线程消费new Thread(new Customer(r)).start();new Thread(new Customer(r)).start();}}
对于多个生产者和消费者:
为什么要定义while判断标记?
原因:让被唤醒的线程再一次判断标记。

为什么定义notifyAll?
因为需要唤醒对方线程。只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。


生产者消费者问题中怎么只唤醒对方线程呢?

JDK1.5 中提供了多线程升级解决方案。
将同步Synchronized替换成现实Lock操作。
将Object中的wait,notify notifyAll,替换了Condition对象。
该对象可以Lock锁 进行获取。
该示例中,实现了本方只唤醒对方操作。
Lock:替代了Synchronized
lock 
unlock
newCondition()

Condition:替代了Object wait notify notifyAll
await();
signal();
signalAll();

import java.util.concurrent.locks.*;  //商品仓库class Res{private String name;private int num=1;//仓库商品编号private boolean flag = false;//仓库中是否有商品的标识,初始化为没有商品。 private Lock lock = new ReentrantLock();   private Condition condition_put = lock.newCondition();    private Condition condition_get= lock.newCondition(); public void put()throws InterruptedException//生产函数{  lock.lock();//获取锁        try{            while(flag)//若多次判断商品标识          condition_put.await();            //生产产品          this.name="商品"+num++;          System.out.println(Thread.currentThread().getName()+"生产者生产:"+name);            flag = true;//将商品标识设为true,表明仓库中已有商品          condition_get.signalAll();//唤醒消费线程      }                finally{            lock.unlock();//一定要释放锁        }  } public void get()throws InterruptedException//消费函数{  lock.lock();//获取锁        try{            while(!flag)//若没有商品,则调用wait等待生产者生产商品          condition_get.await();             //消费产品    System.out.println(Thread.currentThread().getName()+"消费者消费:"+name);            flag = false;//将商品标识设为false,表明仓库中没商品          condition_put.signalAll();//唤醒生产线程      }                finally{            lock.unlock();//一定要释放锁        }  }}//生产者class Producer implements Runnable{private Res r ;Producer(Res r){this.r = r;}public void run(){for(int i=0;i<100;i++){try{                  r.put();            }catch (InterruptedException e){} }}}//消费者class Customer implements Runnable{private Res r ;Customer(Res r){this.r = r;}public void run(){for(int i=0;i<100;i++){try{                r.get();          }catch (InterruptedException e){} }}}class  ProducerCustomerDemo{public static void main(String[] args) {Res r = new Res();//两个生产线程生产new Thread(new Producer(r)).start();new Thread(new Producer(r)).start();//两个消费线程消费new Thread(new Customer(r)).start();new Thread(new Customer(r)).start();}}
2、线程的控制

(1)停止线程

stop方法已经过时
如何停止线程?
只有一种方法,run方法结束。开启多线线程,运行代码通常是循环结构。
只要控制住循环,就可以让run方法结束,也就是线程结束。

特殊情况:
当线程处于冻结状态,就不会读取到标记,那么线程就不会结束。
当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。
强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。

一个例子:一个人(线程)正处于冻结状态(wait),当没有指定的方式(notify)让其苏醒,可以对这个人糊过去一个砖头( interrupt),这个人就醒了,恢复了活动(运行)。

Thread类提供该方法 interrupt();

class StopThread implements Runnable{private boolean flag = true;public synchronized void run(){while(flag){try{wait();}catch(InterruptedException e){System.out.println(Thread.currentThread().getName()+"....Exception");flag = false;}System.out.println(Thread.currentThread().getName()+"....run");}}public void changeFlag(){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.start();int num = 0;while(true){if(num++ ==60){t1.interrupt();t2.interrupt();break;}System.out.println(Thread.currentThread().getName()+"...."+num);}System.out.println("over");}}

(2)守护线程

守护线程 setDaemon
特点:开启后和前台线程共同抢劫CPU的执行权运行

当所有的前台线程都结束后,后台线程会自动结束。

public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。

当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。

该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。

class StopThread implements Runnable{private boolean flag =true;public  void run(){while(flag){System.out.println(Thread.currentThread().getName()+"....run");}}public void changeFlag(){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.setDaemon(true);t2.setDaemon(true);t1.start();t2.start();int num = 0;while(true){if(num++ == 60){break;}System.out.println(Thread.currentThread().getName()+"......."+num);}System.out.println("over");}}

(3)加入线程(join)

public final void join()
throws InterruptedException等待该线程终止。

当A线程执行到了B线程的join()方法时,A就会等待,等B线程都执行完后,A线程才执行。
join可以用来临时加入线程执行。

class Demo implements Runnable{public void run(){for (int x=0;x<=10 ;x++ ){System.out.println(Thread.currentThread().getName()+"...."+x);}}}class JoinDemo{public static void main(String[] arsg) throws Exception{Demo d = new Demo();Thread t1 = new Thread(d);Thread t2 = new Thread(d);t1.start();t1.join();//主线程执行到t1线程的join方法后,主线程停止,等到t1线程run方法执行结束,主线程才会运行。也就是t1线程加入了主线程。t2.start();for (int x=0;x<=10 ;x++ ){System.out.println("main...."+x);}System.out.println("over");}}

3.、线程的优先级

yield() 暂停当前正在执行的线程对象,并执行其他线程。

setPriority(int newPriority) 更改线程的优先级。newPriority用1-10表示

java已经给出了常用的三个等级:
    MAX_PRIORITY 线程可以具有的最高优先级。最高是10
    MIN_PRIORITY 线程可以具有的最低优先级。最低是1
    NORM_PRIORITY 分配给线程的默认优先级。默认是5

toString():
返回该线程的字符串表示形式,包括线程名称、优先级和线程组。

class Demo implements Runnable{public void run(){for(int x=0; x<10; x++){System.out.println(Thread.currentThread().toString()+"....."+x);//Thread.yield();//暂停当前正在执行的线程对象,并执行其他线程}}}class  JoinDemo{public static void main(String[] args) throws Exception{Demo d = new Demo();Thread t1 = new Thread(d);Thread t2 = new Thread(d);t1.start();t1.setPriority(Thread.MAX_PRIORITY);//使t1线程拥有最高的优先级,但t1并不一定先运行。t2.start();for(int x=0; x<10; x++){System.out.println("main....."+x);}}}




0 0
原创粉丝点击