黑马程序员_多线程

来源:互联网 发布:ubuntu 14.04 iso 编辑:程序博客网 时间:2024/06/16 00:02

一、多线程

在说到多线程,我先简单说一下线程的概念,线程就是在进程中一个负责程序执行的控制单元,也叫执行路径。那么多线程就是一个进程中有多个执行路径称之为多线程。

为什么会有多线程,就是因为我们想让多个部分代码同时在运行,打个很简单的比方就是,射击游戏中子弹在运行着,人物也同时可以跑动,这就是线程在同时运行着他们,每一个线程都有自己的内容,这个内容就称为线程要执行的任务.

但是多线程有优点,也有弊端.它解决了多部分同时运行的问题.但是线程太多对导致效率降低.就比如电脑开了一个软件运行起来很流畅,我要是开十几个软件就卡的动不了.应用程序的执行由cpu做着快速切换完成的(这个切换是随机的),也就是说cpu其实就是一个"单线程",但是做着快速的切换去完成多个应用同时在运行,这个切换都是以毫秒计算,所以看不出来,但是应用程序多了,自然也就变慢了.哦了.

JVM启动时就会启动多个线程,只是有两个我们可以分析的出来.

1.执行main函数的线程;2.负责垃圾回收的线程.finalize()当垃圾回收器确定不存在对该对象更多引用时,由对象垃圾回收器调用此方法).

二、多线程详细图解



cpu的执行资格:可以被cpu处理,在处理队列中排队.

cpu的执行权:正在被cpu处理

创建线程的两种方式:1.定义类继承Thread类,覆盖run方法,直接创建Thread的子类对象创建线程,调用start方法开启;

     JVM创建的主线程的任务都定义在主函数中,自定义的线程需要封装在run方法中,通过start开启进入run方法自定义的的线程.

     2.定义类实现Runnable接口,覆盖接口中的run方法,将线程代码封装在run方法中,通过Thread类创建线程对象,并将Runnable接口子类对象作为Thread类的构造函数的参数传递.调用线程对象的start方法开启.

class Demo implements Runnable{     public void run()     {          show();      }     public void show()     {          //方法内容...      }}public static void main(String[] agrs){         Demo d = new Demo();Thread t = new Thread(d);        t.start();}

第二种方式细节:Runnable他的出现仅仅是将线程的任务进行了对象封装,第一种方式继承Thread类,是说我们是线程的子类,任务只是我们对象中的一部分内容.而第二种方式是单独将线程的任务用对象封装起来,类型:Runnable

好处:(实现Runnable接口的好处也是区别)

1.将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象.

2.避免了java单继承的局限性.所以第二种方式较为常用.

三、线程安全问题

原因:既然是多线程,肯定涉及到1.多个线程在操作共享的数据;2.操作共享数据的线程代码有多余.

当一个线程在执行操作共享的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生.这时就出现了一个同步的概念.它是多个线程使用一个锁,当这个锁被一个线程拿着时,别的线程是无法继续执行的,解决了线程安全问题,但同时同步外的线程都会判断同步锁,效率也相对降低了.对于这个锁,可以是任意的对象.

//方式一:同步代码块synchronized(对象/相当与锁){       // 需要同步的代码;}//方式二:同步函数public synchronized void show(){      }

在这里同步函数的锁是this.同步代码快的锁是任意对象(开发建议)

在函数是静态的时候,new的对象不可能成为同步的锁,静态随类的加载而加载,在new对象之前,但是在字节码进入内存要先封装对象,这个对象就是当前class文件所属的对象.写法:当前类名.class,所以静态同步函数使用的锁是该函数所属字节码文件对象。

在这里说一下单例设计模式的多线程问题(面试题)

延迟加载单例设计模式(懒汉式)

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()}                 }            }       }}
根据程序分析,如果有多个线程进入,其中一个线程抢到Single.class的锁的情况下,这时s==null,会new一个对象,当下一个线程进来依然要判断是否为空,为了提高效率,我们将是否为空的判断在获得锁之前就先判断,这样就避免了锁的和对象是否为空的重复判断。

四、死锁

常见的情景之一:同步的嵌套

package com.qyc.dao;class Ticket implements Runnable {boolean flag = true;Ticket(boolean flag) {this.flag = flag;}/* * 分析: * 假设a先线程进入了if(),打开了locka锁,输出了...if....a * 这时b也进入了else,打开了lockb锁.输出了...if....b * 这时a想打开lockb锁,可是else中b持有着,所以a还打不开,就在这里等着b走完else将lockb锁释放 * 这时b需要打开locka锁,走出else才能释放lockb锁,但是a在if中持有着locka锁,b也打不开,也在这等待着a走完if将locka锁释放 * 最终都停在这里出不去,这就是死锁 * 如何解决: * 首先找到问题的所在,让他们共用的锁在释放后,才允许下一个线程持有这个锁,这个还是主要分析具体的情况,这代码就是一个死锁的代码示例 * */public void run() {if (flag) {synchronized (Mylock.locka) { System.out.println(Thread.currentThread().getName()+ "...if....a");synchronized (Mylock.lockb) {System.out.println(Thread.currentThread().getName()+ "...if....b");}}} else {synchronized (Mylock.lockb) {System.out.println(Thread.currentThread().getName()+ "...else....a");synchronized (Mylock.locka) {System.out.println(Thread.currentThread().getName()+ "...else....b");}}}}}class Mylock {public static final Object locka = new Object(); //locka用于线程a的锁public static final Object lockb = new Object(); //lockb用于线程b的锁}public class Test1 {public static void main(String[] args) throws InterruptedException {Ticket a = new Ticket(true);  //让a走if语句Ticket b = new Ticket(false);//让b走else语句Thread t1 = new Thread(a);Thread t2 = new Thread(b);t1.start();  //开启线程t2.start();}}

 五、JDK1.5版本后出现的新特性

Lock lock = new Reetrantlock();

原来同步代码块,对于锁的操作是隐式的,JDK1.5后将同步和锁封装了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。

lock替换了synchronized方法和语句使用;Condtion替代了Object监视器方法使用。它对Object监视器对象,自己单独封装了一组对象,并可以将多个Condition对象使用一个锁

优点分析:以前一个锁上只有一组监视器,这个监视器既监视着生产者又监视着消费者,也就是有可能将生产者或消费者全部wait()或notify();具有着不确定性,而有了Condition监视器对象,可以来分组分别监视生产者和消费者.

package com.qyc.dao;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;class Product {private String water;private int count = 0;// 创建一个锁对象Lock lock = new ReentrantLock();// 创建两个监视器,一个监视生产者,一个监视消费者Condition con1 = lock.newCondition();Condition con2 = lock.newCondition();private boolean flag = false;public void production(String water) {lock.lock();try {while (flag) {try {con1.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}this.water = water;count++;System.out.println(Thread.currentThread().getName() + "生产第" + count+ "瓶" + water);flag = true;con2.signal();} finally {lock.unlock();}}public void consumption() {lock.lock();try {while (!flag) {try {con2.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}System.out.println(Thread.currentThread().getName() + "消费第" + count+ "瓶" + water);flag = false;con1.signal();} finally {lock.unlock();}}}class Production implements Runnable {private Product p;Production(Product p) {this.p = p;}public void run() {while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}p.production("矿泉水");}}}class Consumption implements Runnable {private Product p;Consumption(Product p) {this.p = p;}public void run() {while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}p.consumption();}}}public class SynchronizedTest1 {public static void main(String[] args) {Product pro = new Product();Production p = new Production(pro);Consumption c = new Consumption(pro);Thread t0 = new Thread(p);Thread t1 = new Thread(p);Thread t2 = new Thread(c);Thread t3 = new Thread(c);t0.start();t1.start();t2.start();t3.start();}}


 六、其他

停止线程(Interrupt()):对于停止有一种方法stop(),但是这种方法已经过时。还有一种方法是:run方法的结束.任务中都有循环,要控制循环就要结束循环.但是如果线程处于了冻结状态,无法读取标记,如何结束?可以使用 Interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备CPU的执行资格.强制会发生InterruptedException异常,记得处理。

守护线程(setDaermon):可以理解为后台线程,又称用户线程.注意:1.该方法必须在启动线程之前调用;2.当运行的线程都是守护线程,Java虚拟机是不管的,直接退出。

等待线程终止(join()方法):临时加入一个线程去运算时可使用join方法。

返回线程的字符串表示形式(toString()):包括线程名称、优先级(1~10)、线程组。更改线程优先级:setPriority(int newPriority)

线程组(ThreadGroup):可封装多个线程,对其操作.

暂停当前正在执行的线程(yield()):暂时释放一下执行权.

0 0