黑马程序员-多线程

来源:互联网 发布:js跨域请求 get错误 编辑:程序博客网 时间:2024/06/07 01:42
----------------------- android培训java培训、java学习型技术博客、期待与您交流! ----------------------

1.进程与线程进程:正在进行中的程序。其实进程就是一个应用程序运行时的内存分配空间。线程:其实就是进程中一个程序执行控制单元,一条执行路径。进程负责的是应用程序的空间的标示。线程负责的是应用程序的执行顺序。 区别:进程有独立的进程空间,进程中的数据存放空间(堆内存和栈内存)都是独立的       线程的堆内存空间是共享的,栈内存空间才是独立的(堆共享,栈独立)      线程消耗的资源比进程小,相互之间可以影响。 java中可以进行多线程的程序编程  其实线程是由操作系统开启,依靠java虚拟机来实现  线程也是对象,有了线程对象的描述类 java.lang.ThreadJavaVM  启动的时候会有一个进程java.exe.该进程中至少一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称之为主线程2.创建线程的两种方式及区别 2.1创建线程方式一 继承Thread类(extends1. 继承Thread类,自定义的子类就是一个线程类2. 复写Thread类中的run方法   -----用于存储线程要运行的代码       java工程师们,不知道其他人要运行什么代码   规定了run方法,不管线程运行什么程序,代码必须写进run方法3. 建立Thread类的子类对象4. 调用线程的start方法       start()方法是Thread类的方法,子类继承的,直接使用  start方法,开启线程,JVM会自动调用该线程的run方法2.2创建线程方式二: 定义类实现Runnable接口(implements)1,定义类实现Runnable接口。2,覆盖接口中的run方法(用于封装线程要运行的代码)。3通过Thread类创建线程对象,构造方法,并传递Runnable接口的实现类对象   Thread(Runnable target)接口不能建立对象,只能建立子类对象 Runnable r = new Ticket();   接口引用指向子类对象(多态)运行对象中的run方法4,调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的run方法。     class Ticket implements Runnable       Ticket t = new Ticket();   //tRunnable接口的实现类对象直接创建Ticket对象,并不是创建线程对象。Thread t1 = new Thread(t); //创建线程创建Thread类的对象, Thread(Runnable target) 分配新的thread对象new Thread(Runnable接口的实现类对象) //多态的应用只要将t作为Thread类的构造函数的实际参数传入即可完成线程对象和t之间的关联为什么要将t传给Thread类的构造函数呢?其实就是为了明确线程要运行的代码run方法。t1.start();2.3两种方式的区别a. 线程代码   继承Thread:    线程代码存放Thread子类run方法中。   实现Runnable:  线程代码存在接口的子类的run方法。b. 继承有局限性,java中只能单继承--------接口可以多实现 【 1.避免了单继承的局限性。2.将线程运行的代码都单独封装到Runnable接口类型的对象中,这样就实现了线程对象和任务对象的解耦(降低耦合性).】c.  继承Thread类的方式,数据是线程独享的------Runnable接口方式,数据是程共享的3.获取线程对象以及名称线程名称:线程都有默认的名称,格式:Thread-编号,该编号从0开始   |-- 可以使用Thread类的setName方法,自己设置线程的名字   |-- 还可以使用Thread类的构造方法,自己设置线程的名字    |-- String getName()方法获得线程的名字,使用在Thread类,或者是子类中   |-- 如果不是Thread类,也不是子类。则用 Thread.currentThread().getName()   |-- main是规定好的,主线程,程序都是从主线程开始,分别由主线程开启其他线程 4.线程运行状态
5.线程安全  同步 重点多线程售票案例
class Ticket implements Runnable{private int tickets = 100;public void run(){    //接口中的方法固定权限是publicwhile(true){   synchronized(this){     if(tickets > 0){        //假使剩一张票 线程都挂在这里,再执行时导致错票,我们用sleep停止模拟下             引出线程安全   try{               //接口里没异常 这里不能抛 只能try   Thread.sleep(10);     //   }catch(Exception e){} //  System.out.println(Thread.currentThread().getName()+" 出售第"+tickets--);  //前一种直接getName(),是子类继承父类方法 这里没有继承,必须对象调用}   }}}}class TicketDemo {public static void main(String[] args) {Ticket t = new Ticket();Thread t1 = new Thread(t);  // Thread t1 = new Thread();创建线程Thread t2 = new Thread(t);  // Runnable r = new Ticket() ;接口型引用指向自己的子类对象 Thread t3 = new Thread(t);Thread t4 = new Thread(t);t1.setName("一号窗口");t2.setName("二号窗口");t3.setName("三号窗口");t4.setName("四号窗口");t1.start();        t2.start();        t3.start();        t4.start();}}


5.1原因:
多线程同时操作共享数据。
多线程的任务代码中操作共享数据的语句不止一条。
5.2解决思想:
让一个线程在执行多条操作共享数据的运算过程中其他线程不要参与共享数据的操作。
 
如何进行多句操作共享数据代码的封装呢?
解决方案:同步 synchronized
  将需要同步的代码封装到了指定同步语句块当中。
同步代码块体现:
synchronized(对象)
{
需要被同步的代码
}
理解:火车卫生间例子。
 
5.3同步的好处、前提、弊端
    好处:解决了线程的安全问题,为什么能解决线程的安全问题?因为当一个线程在操作共同数据时,通过同步锁(对象监视器)的机制,使得其他的线程不能去操作共享的数据,不管这条操作语句在执行过程中处于什么状态,消亡除外。比如sleep或者wait,都要等到它重新获取了执行权,把执行语句执行完毕后才出去。这样的话就保证了其他线程在有线程在操作共享数据的时候不能再操作共享数据。
前提:
1. 同步中如只有一个线程在执行,就没有必要去同步。
2. 如果有多个线程同步,必须要保证用的是同一个锁。
这个前提的好处是:如果在多线程中加入了同步后,还是出现了安全问题的话,这时就可以用这个前提来对程序进行分析。
弊端:
对程序的性能有一些影响,会降低一些效率。 多个线程需要判断锁,较为消耗资源
 
5.4同步函数
如果一个函数中,所有的代码,都是线程操作的共享数据,函数修饰成同步的。synchronized写在函数上
    |-- 非静态函数的锁是this,非静态函数中,写同步代码块,锁也是this
    |-- 静态函数的锁是本类名.class 写同步代码块锁也是本类名.class
5.5线程安全问题 如何查找
1,明确哪些代码是多线程运行代码。 
2,明确共享数据。 
3,明确多线程运行代码中哪些语句是操作共享数据的。
5.6懒汉式在加载时的安全问题解决方法
单例设计模式。饿汉式。  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()   {         //如果线程第一次建立好对象后,s不在是空,既然不是null//第二个线程,还有必要进同步吗,直接返回                   if(s==null)   {                              synchronized(Single.class)  {                                       if(s==null)                                                 //--->A;                                        s = new Single();                              }                     }                     return s;           }  }


线程间通信 -等待唤醒机制
6.1等待唤醒机制:涉及的方法:
wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。
notify:唤醒线程池中某一个等待线程。
notifyAll:唤醒的是线程池中的所有线程。
注意:
1:这些方法都需要定义在同步中。 
   因为这些方法必须要标示所属的锁。A锁上的线程被wait,那这个线程就相当于处于A锁的线程池中,只能A锁的notify唤醒
2wait notify控制线程的,不写在Thread类,写在Object
需要锁的支持,而锁是任意的对象,对象是由类创建的出来的
所以任何类都是Object的子类,new 出来的任意对象,都具有线程控制的三个方法
6.2 sleep wait 区别是什么
    sleepThread类的一个静态方法 ,运行不需要锁
    waitObject类的一个非静态方法,运行必须有锁的支持
 
sleep 指定时间,到时间线程自己醒来
wait 必须被其他的线程唤醒才可以
 
sleep方法,线程睡眠的过程中,不放锁
wait方法,线程等待的过程中,释放锁,使得其他线程可以使用同步控制块或者方法。
一旦被其他线程唤醒(notify),线程从哪里等待的,还从哪里被唤醒,进入获取锁后,才能继续执行。
 
6.3生产者 消费者的例子
   多线程不同方向操作数据
   1. 线程只要被唤醒,就必须判断标记 讲if换成while
   2. 唤醒全部的线程   notifyAll()
    只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。 
 
//定义产品class Rescourc{private String name ;  //产品的名字private int count = 0 ;  //产品的计数器private boolean flag = false; //定义生产的方法public synchronized void set(String name){   while(flag==true){//这里也可以写成while(flag)     try{ this.wait();}catch(Exception e){} //大括号可以省略 因为try catch是整体   }   this.name = name + count;   count++;   System.out.println(Thread.currentThread().getName()+ "生产---"+this.name);   //标记改成true   flag = true;   this.notifyAll();  } //定义消费的方法public synchronized void get(){  while(flag==false){  ////这里也可以写成while(!flag)     try{ this.wait();}catch(Exception e){}  }  System.out.println(Thread.currentThread().getName()+"消费======="+this.name);  flag = false;  this.notifyAll();}} //生产者线程class Pro implements Runnable{    private Rescourc r ;Pro(Rescourc r){ this.r = r;}  //确保生产 消费的是同一个Rescourc r = new Rescourc()public void run(){   while(true){       r.set("鼠标");   }}}//消费者线程class Cus implements Runnable{private Rescourc r ;Cus(Rescourc r){ this.r = r;}public void run(){      while(true){      r.get();   }}}class ProCusDemo {public static void main(String[] args) {Rescourc r = new Rescourc();//资源产品Pro p = new Pro(r);        Cus c = new Cus(r);         Thread t1 = new Thread(p);Thread t2 = new Thread(c);Thread t3 = new Thread(p);Thread t4 = new Thread(c);t1.start();t2.start();t3.start();t4.start();}}   


JDK1.5 中提供了多线程升级解决方案----Lock
唤醒自己本方线程是没有意义,唤醒全部是浪费资源的
唤醒对方中的一个
Java.util.concurrent.locks
Lock锁,替换了同步关键字 synchronized{}
    import java.util.concurrent.locks.*; 
lock()获取锁 unlock()释放锁
ReentrantLock Lock接口的实现类对象 //定义锁Lock lock= new ReentrantLock();
 
Condition对象中的方法,await()、signal()、signalAll()替代了Object中的 wait notify notifyAll
Lock接口中的方法 Condition newCondition() 返回绑定到此 Lock 实例的新 Condition 实例。 
之前一个锁只有一个wait notify ,需要再建锁建同步 容易嵌套死锁
现在一个锁可以对应好几个wait notify ,将wait notify其封装成了condition对象
 
JDK1.5 新特性 Lock来练习多个生产者和消费者 
import java.util.concurrent.locks.*;  //导入子包class Rescourc{private String name ;private int count = 0 ;private boolean flag = false;//Lock替代了同步机制//定义锁Lock lock= new ReentrantLock();    //创建绑定锁的Condition对象  //lock锁,可以绑定多个Condition对象,实现线程的分组控制Condition pro = lock.newCondition();Condition cus = lock.newCondition();public  void set(String name){lock.lock();   while(flag==true){     try{ pro.await();}catch(Exception e){}   }   this.name = name + count;   count++;   System.out.println(Thread.currentThread().getName()+   "生产---"+this.name);      flag = true;   cus.signal();   lock.unlock();   }public  void get(){  lock.lock();  while(flag==false){     try{ cus.await();}catch(Exception e){}  }  System.out.println(Thread.currentThread().getName()+  "消费======="+this.name);  flag = false;  pro.signal();  lock.unlock();}}  class Pro implements Runnable{    private Rescourc r ;Pro(Rescourc r){ this.r = r;}public void run(){   while(true){       r.set("鼠标");   }}} class Cus implements Runnable{private Rescourc r ;Cus(Rescourc r){ this.r = r;}public void run(){      while(true){      r.get();   }}}class ProCusDemo2 {public static void main(String[] args) {Rescourc r = new Rescourc();//资源产品Pro p = new Pro(r);//生产者Cus c = new Cus(r);//消费者 Thread t1 = new Thread(p);Thread t2 = new Thread(c);Thread t3 = new Thread(p);Thread t4 = new Thread(c); t1.start();t2.start();t3.start();t4.start();}}


8.死锁
当两个线程相互等待对方释放同步监视器时就会放生死锁。(同步里面嵌套同步)
class Dead implements Runnable{String s1 = "abc";String s2 = "cde";char flag; Dead(char flag){this.flag = flag;}public void run(){while(true){if(flag == 'a'){synchronized(s1){System.out.println("if...s1");synchronized(s2){System.out.println("if...s2");}}}else if(flag == 'b'){synchronized(s2){System.out.println("else...s2");synchronized(s1){System.out.println("else...s2");}}}}}}class DeadLock{public static void main(String[] args){Dead d1 = new Dead('a');Dead d2 = new Dead('b');Thread t1 = new Thread(d1);Thread t2 = new Thread(d2);t1.start();t2.start();}}


9.停止线程
stop方法已经过时。
线程运行的代码,都是写在了run方法中,如果可以停止run方法的运行,线程就已经停止。
第一种方式:定义循环的结束标记。
第二种方式:如果线程处于了冻结状态,是不可能读到标记的,这时就需要通过Thread类中的interrupt方法,将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到标记,并结束。
 
public void run(){   while(flag){       特殊情况:     synchronized(this){ //当线程处于了冻结状态,就不会读取到标记。那么线程就不会结束。  try{this.wait();}catch(Exception e){ }      System.out.println("run...");  }   }  }public void set(boolean flag){  this.flag = flag;}        当没有指定的方式让冻结的线程恢复到运行状态,这时需要对冻结进行清除。强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。Thread类提供该方法 interrupt();    利用Thread类的void interrupt() 中断线程,会抛异常new Thread(new Demo()).interrupt() class Demo implements Runnable{private boolean flag = true;public void run(){   while(flag){  synchronized(this){  try{this.wait();}catch(Exception e){flag = false;  }      System.out.println("run...");  }   }}public void set(boolean flag){  this.flag = flag;}} class StopThreadDemo {public static void main(String[] args) {//new Thread(new Demo()).start();Demo d = new Demo();Thread t = new Thread(d);t.start();for(int x = 0; x < 50 ; x++){  if(x==40){    t.interrupt();  }System.out.println("main..."+x);}}}


10.守护线程   又叫做后台线程 
   当一个程序中所有的线程都是守护线程的时候,JVM退出
 
   main主线程,开启了t这个线程,t线程设置为守护线程,保护主线程
   main主线程死了,保护主线程的t线程,还有存在的意义吗?跟着主线程一起死
void setDaemon(boolean on)   //t.setDaemon(true); 将该线程标记为守护线程或用户线程。
11.线程中的其他方法
setDaemon(boolean on)   后台 
将该线程标记为守护线程或用户线程。  前台线程结束 后台守护自动退出
 
join() 等待该线程终止。   //t1.join();
 
yield() 暂停当前正在执行的线程对象,并执行其他线程。//Thread.yield();
 
toString() 返回该线程的字符串表示形式,包括线程名称、优先级和线程组
 
setPriority(int newPriority) 更改线程的优先级。 // t1.setPriority(Thread.MAX_PRIORITY );
MAX_PRIORITY
         线程可以具有的最高优先级。
MIN_PRIORITY
         线程可以具有的最低优先级。
NORM_PRIORITY
         分配给线程的默认优先级。

 
----------------------- android培训java培训、java学习型技术博客、期待与您交流! ----------------------

 
 
 
 
0 0
原创粉丝点击