黑马程序员:多线程

来源:互联网 发布:手机ssh连接linux 编辑:程序博客网 时间:2024/06/04 00:58

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

一、  进程、线程的概念

1)、进程:每个运行中的程序都是一个进程。

         多个进程可以在单个处理器上并发执行,多个进程之间不会相互影响。注意:并发性和并行性是两个概念,并行性是指在同一时刻,有多条指令在多个处理器上同时执行;并发性是指同一时刻只能有一条指令在单个处理器上执行,但多个进程指令被快速轮换执行,但是在宏观上具有多个进程同时执行的效果。

2)、线程:线程在进程中的地位,如同进程在操作系统中的地位。当一个程序运行时,内部包含了多个顺序执行流,每个顺序执行流就是一个线程。

  总结来说:操作系统可以同时执行多个任务,每个任务都是进程;进程可以执行多个任务,每个任务就是线程。一个进程可以包含多个线程,但至少包含一个线程。

a.单线程:单线程的程序只有一个顺序执行流

b.多线程:多线程程序包含多个顺序执行流,多个顺序流之间互不干扰。

  单线程的程序如同只雇用一个服务员的餐厅,他必须做完一件事之后才能做另外一件事情;多线程的程序如果雇佣了多个服务员的餐厅,他们可以同时做多件事情,并且互不干扰。

二、  创建线程的两种方式:

1、继承Thread类创建线程类

a.定义Thread类的子类,并重写该类run()方法。run()方法内是线程要执行的代码,因此run()方法称为线程的执行体。

b.创建Thread类的子类实例,即创建线程对象。

c.调用线程对象start()方法,启动线程(底层是让Java虚拟机调用线程的run()方法)。

代码实例:

class Demo extends Thread{public void run(){for(int i=0; i<100; i++)System.out.println("Demo run");}}class ThreadDemo {public static void main(String[] args){Demo d = new Demo();//创建一个线程d.start();//开启线程,执行线程的run方法。//d.run();//仅仅是对象调用方法。而线程创建了,并没有运行。for(int i = 0; i < 100; i++)System.out.println("Hello World---"+i);}}

2、实现Runnable接口创建线程类

a.定义Runnable接口的实现类,并重写该接口的run()方法。

b.通过Thread类创建线程对象,将实现Runnable接口的实现类对象作为实际参数传递给Thread类的构造函数。原因:让线程对象明确要运行的run()方法所属的对象。

C.调用Thread对象的start()方法。开启线程,并运行Runnable接口实现类的run()方法。

代码示例:

class Ticket implements Runnable{private int tick = 100;public void run(){while(true){if(tick>0){try{Thread.sleep(10);}catch(Exception e){}//假设0线程在条件tick=1时,进来sleep了一会。1线程进来,也睡一会,2线程进来,也睡一,3,线程进来也睡会。//0线程唤醒后,打印1,线程1,2,3醒后,不需要再判断,向下运行打印出0,-1,-2号票,存在安全问题。System.out.println(Thread.currentThread().getName()+"sale: "+tick--);}}}}class TicketDemo1{public static void main(String[] args){Ticket t = new Ticket();Thread t1 = new Thread(t);Thread t2 = new Thread(t);Thread t3 = new Thread(t);Thread t4 = new Thread(t);t1.start();t2.start();t3.start();t4.start();//new Thread(new Ticket()).start();}}

注意:

1)、使用继承Thread类的方法创建线程时,多线程之间无法共享线程类的实例变量。如果定义为static静态的话,虽然可行但是周期太长,浪费空间。

2)、使用实现Runnable接口的方法创建线程,虽然可以实现资源共享,但是会出现安全问题。比如在上边代码中,假设0线程在条件tick=1时,进来sleep了一会。1线程进来,也睡一会,2线程进来,也睡一,3,线程进来也睡会。0线程唤醒后,打印1,线程1,2,3醒后,判断过不需要再判断,向下运行打印出0,-1,-2号票,存在安全问题。

三、  线程状态

1、新建:用new关键字创建线程对象,该对象个由Java虚拟机为其分配内存,并初始化其成员变量。

2、就绪:线程对象调用start()方法,该线程就进入了就绪状态。就绪状态,线程拥有知执行资格,但是没有执行权(是指线程调度程序没有把该线程设置为当前线程)。线程从等待或者睡眠中回来后,也处于就绪状态。

3、运行:线程具备执行资格,并获取执行权(是指线程调度程序把该线程设置为当前线程),开始运行run()方法中的代码。

4、阻塞:线程释放了执行权,也不具备执行资格。被阻塞的线程只有在合适的时候重新进入就绪状态(不是运行状态),才获取执行资格。等待线程调度器调度它,才有执行权,进而进入运行状态。

1)、运行——>阻塞状态的几种常见情况:
         a.线程调用sleep方法。其实是进入了冻结状态:它占着线程,让线程睡觉,别人无法使用。

         b.线程调用一个阻塞式IO方法,read(),readLine(),等,方法返回之前,该线程被阻塞。

         c.线程视图获取一个synchronized锁,但是这个锁被其他线程持有。

         d.线程调用wait()方法。

2)、阻塞状态——>就绪状态的集合总情况:

         a.过了sleep()方法中定义的时间。

         b.阻塞式IO方法已经返回。

         c.线程成功获取视图获取的同步锁。

         d.线程等到了其他线程的通知,如notify、notifyAll。

5、死亡:

         a.run()方法执行完成,线程结束。

         b.线程抛出一个未捕获的Error,或者抛出Error。

         c.调用stop()方法结束线程,该方法容易导致死锁,已过时。

四、  多线程的安全问题

1、多线程出现安全问题的两个因素

         a.多个线程操作共享数据。

         b.有多条语句对共享数据进行运算。(原因:一个线程对多条语句刚执行了一部分,另一个线程获取执行权,进来执行,导致共享数据错误。)

2、解决多线程的安全问题原理

          只要让某一个线程执行共享数据过程中,不让其他线程参与执行。Java提供了同步这个概念——synchronized。使用同步的前提是:首先,必须有两个或者两个以上的线程;其次,多个线程必须使用同一把锁。

3、同步代码块和同步函数

同步代码块的格式:

synchronized(对象)//任意对象都可以,这个对象就是锁

{

                  需要被同步的代码

}

注意:

1)、如果用多个同步代码块时,他们必须使用同一个锁。

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

3)、当同步函数是static静态的话,它使用的锁是:该方法所在类的字节码文件对象—-类名.class。

同步代码块和同步函数代码示例:

class Ticket implements Runnable{private int tick = 100;Object obj = new Object();boolean flag = true;public void run(){if(flag){while(true){synchronized(this)//当改为obj后,打印出0号错票{if(tick>0){try{Thread.sleep(10);}catch(Exception e){}System.out.println(Thread.currentThread().getName()+" code...: "+tick--);}}}}else while(true)show();}public synchronized void show()//静态同步函数的锁是this{if(tick>0){try{Thread.sleep(10);}catch(Exception e){}System.out.println(Thread.currentThread().getName()+"...show...: "+tick--);}}}class ThisLockDemo{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(100);}catch(Exception e){}t.flag = false;t2.start();}}

4、wait(),sleep()方法在线程中的区别

         1)、线程调用wait()时,该线程释放执行权,其他线程可以获取到执行权。同时释放了锁。wait()及唤醒方法只在同步机制中使用。

         2)、线程调用sleep()时,该线程进入冻结状态,线程睡着,其他线程可以获取到执行权。但是在同步中它不释放锁。

同步中的等待、唤醒机制代码示例:

class Resource{private String name;private int count = 1;private boolean flag = false;public synchronized void set(String name){while(flag)//1.t1跳过等待try{this.wait();}catch(Exception e){}//4.t1等待,t2进来也等待//10.t1首先重新持有锁,重新判断为false,不等待向下执行this.name = (name+"----"+count++);System.out.println(Thread.currentThread().getName()+"生产者..."+this.name);//2.生产1号商品,//11.t1生产2号商品flag = true;//3.t1更改标记//12.t1改变标记,this.notifyAll();//在等待的过程中,线程都释放了锁,当notifyAll时t1和t2都从等待中被唤醒,开始争夺锁的持有权,//只有一个线程能持有锁,}public synchronized void out(){while(!flag)//5.t3跳过等待//9.t3和t4都等待try{this.wait();}catch(Exception e){}System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);//6.正常消费1号商品,flag = false;//7.t3更改标记this.notifyAll();//8.t3把t1和t2都唤醒}}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();}}}class ProduceConsumeDemo_1{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);//t1线程Thread t2 = new Thread(pro);//t2线程Thread t3 = new Thread(con);//t3线程Thread t4 = new Thread(con);//t4线程t1.start();t2.start();t3.start();t4.start();}}

  之所以wait()、notify()、notifyAll()被定义在Object类中,是因为锁是任意对象,而这些方法又是锁锁要操作的方法,可以被任意对象都调用的方法需要定义在Object类中。

六、  死锁

死锁:当在同步中嵌套同步,但是同步使用的锁不同,两个线程相互等待对方释放同步锁,这个时候就发生了死锁。程序一旦出现死锁,整个程序既不会发生任何异常,也不会有任何提示,只是所有线程出于阻塞状态,无法继续。

死锁代码示例:

class Test implements Runnable{private boolean flag;Test(boolean flag){this.flag = flag;}public void run(){if(flag){synchronized(MyLock.lock1){System.out.println("if lock1");synchronized(MyLock.lock2){System.out.println("if lock2");}}}else{synchronized(MyLock.lock2){System.out.println("else lock2");synchronized(MyLock.lock1){System.out.println("else lock1");}}}}}class MyLock{static Object lock1 = new Object();static Object lock2 = new Object();}class DeadLockTest{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、如何避免死锁?

 1)、当出现多个同步代码块(或者同步函数)时,尽量使用同一把锁,就可以在一定程度上避免死锁。以上代码把锁改为一样的,就解决了死锁现象。

 2)、不使用synchronized关键字保证同步,直接用Lock对象保证同步。

2、多线程中的Lock机制:

 1)、Lock接口中的lock()方法加锁,unlock()方法解锁。

 2)、通过Condition接口中的await()和signal()方法,实现等待唤醒机制。

代码示例:
class Resource{private String name;private int count = 1;private boolean flag = false;private Lock lock = new ReentrantLock();//创建一个Lock接口的子类实例,赋给接口引用private Condition condition_pro = lock.newCondition();//lock调用方法返回一个Condition实现类实例,赋给接口引用private Condition condition_con = lock.newCondition();public  void set(String name)throws InterruptedException{lock.lock();//加 锁try{while(flag)condition_pro.await();//不try,抛出this.name = name+"----"+count++;System.out.println(Thread.currentThread().getName()+"生产者..."+this.name);flag = true;condition_con.signal();}finally{lock.unlock();//解 锁————释放锁的动作一定要执行}}public synchronized void out()throws InterruptedException{lock.lock();//加 锁try{while(!flag)condition_con.await();//不try,抛出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){try{res.set("商品");//抛出异常的set();要catch()}catch(InterruptedException e){}}}}class Consumer implements Runnable{private Resource res;Consumer(Resource res){this.res = res;}public void run(){while(true){try{res.out();//抛出异常的out();要catch()}catch(InterruptedException e){}}}}class ProduceConsumeDemo1{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);//0线程Thread t2 = new Thread(pro);//1线程Thread t3 = new Thread(con);//2线程Thread t4 = new Thread(con);//3线程t1.start();t2.start();t3.start();t4.start();}}

七、  控制线程

         1)、join线程:当A线程执行到了该线程的join方法时,B线程和其他线程就没有了执行资格。当A线程执行完后,B线程就会和其他线程就获取了执行资格,等待线程调度器调度其中一个,如果此时B线程调用了join方法,其他线程就要等B线程执行完才能执行。 Join线程通常由使用线程的程序调用,以将大问题划分为许多小问题,每个小问题分配一个线程,当多有小问题都得到处理后,再调用主线程来进一步操作。

         2)、后台线程:有一种线程它是后台运行的,它的任务是为其他线程提供服务,这种线程被称为“后台线程”,或者“守护线程”、“精灵线程”。垃圾回收机制就是典型的后台线程。我们把非后台线程称为前台线程。后台线程的特点是:如果所有前台线程都死亡,后台线程会自动死亡。调用Thread对象的setDaemon(true)方法可以将指定的线程设置为后台线程。而且将一个线程设置为后台前程,必须在开启这个线程之前设置,否则抛出异常。

         3)、线程睡眠:当当前线程调用sleep()静态方法方法进入阻塞状态后,在其失眠的之间段内,该线程不会获得执行额机会,及时系统汇总美誉其他可执行的线程,出于sleep()中的线程也不会执行,因此sleep()方法常用来暂停程序的执行。

         4)、线程让步:yield,和sleep()方法相似,都是Thread提供的静态方法。但是yield()方法让正在执行的线程暂停,但它不会阻塞该线程,它只是让线程转入就绪状态。让系统的线程调度器重新调度一次。所以当A线程调用yield()方法暂停后,线程调度器重新分配线程执行权,分配给A线程是有可能。

八、  线程的优先级

         Thread提供了setPriority(int newPriority)、getPriority()方法来设置和返回线程的优先级,其中setPriority()方法的参数可以是一个整数,范围是1~10之间,也可以使用Thread类的3个静态常量: 

MAX_PRIORITY:其值为:10(最高优先级)

MIN_PRIORITY:其值为:1(最低优先级)

NORM_PRIORITY:其值为:5(中等优先级)

为避免误导初学者,本博客如有错误或者不严谨的地方,请在下方给予评论,以便及时修改!谢谢... ...

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

详细请查看:www.itheima.com

0 0
原创粉丝点击