首页 > 代码库 > 线程的全面讲解

线程的全面讲解

 
     进程:
          是一个正在执行的程序。
          每一个进程执行都有一个执行顺序,该顺是一个执行
     线程: 就是进程中的一个独立的控制单元。线程在控制着进程的执行。

jvm 启动时会有一个进程java.exe
该进程中至少一个线程负责java程序的执行。
而且这个线程运行的代码存在与main方法中。该线程称之为主线程

扩展:其实更细节的说明jvm。其实至少有两个线程:主线程 负责垃圾回收机制的线程。
class Test
{
     public static void main(String[] args)
     {
          MyThread thread = new MyThread();//创建好了一个线程
          thread.start();
         // thread.start();
         // thread.start();
     }
}

class MyThread extends Thread
{
     public int sum = 100;
/*
     为什么要覆盖run方法?
*/
     public void run(){
          while(true){
               System.out.println(Thread.currentThread().getName()+"----"+sum--);
          }
     }
}


 上面的代码thread.start()方法被就、调用多次会出现IllegalThreadStateException异常。
eg:就像一个运动员在起跑线上等待着发号命令,当裁判发号了命令运动员起跑了
    但是裁判过了一会儿又发了命令这样运动员肯定会疯了。所以肯会抛异常。


假如在调用方法是d.run()方法那仅仅是对象调用方法(和普通调用一样),而线程创键了,但并没有被开启。
但是d.start()方法是一是通过底层代码开启一个新的子线程,并且调用子线程所要执行的代码块。
run方法和main方法是一样的效果:都是封装了线程要执行的代码块。只是被主次线程调用的区别。
为什么要覆盖run方法?
     Thread类用于描述线程。
          该类就是定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法
          也就是说Thread类中的run方法,用于存储线程要运行的代码
     目的:将自定义代码存储run方法,让线程运行。

线程的几种状态:
    

第二种创建线程方式:
     在创建线程就应该明确要执行那段代码块

public class TestThread2 {
    public static void main(String[] args) {
         MyRunner runner = new MyRunner();//创建要被执行的代码对象
         Thread thread = new Thread(runner);//创建线程时要明确要执行的代码
         thread.start();
         thread.start();
         thread.start();
    }
}
class MyRunner implements Runnable {
    public int sum = 100;
    public void run() {
          sychronized(对象){//实现同步效果
          if (sum > 0) {
              while (true ) {
                 System. out.println(Thread.currentThread().getName() + "----"
                          + sum++);
             }
          }
         }
    }
}


总结:两种创建线程的区别。

     实现的方式:避免了单继承的局限性,而且线程代码存在接口的子类的run方法。
     继承的方式:线程代码存在thread的子类中的run方法中。
为什么要将runnnable接口的子类对象传递给thread的构造函数。
     因为:自定义的run方法所属的对象是Runnable接口的子类对象。
          所以要让线程去指定指定对象的run方法。就必须明确该run方法所属的对象。
在实际的开发当中使用第二种方式比较多。
     因为:定义Runnerable的子类不仅仅可以实现他,
          而且可以继承其他类。避免了第一种的局限性,单一性。

线程安全问题:
     问题原因是:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分
               还没有执行完,另外一个线程参与进来的执行,导致共享数据的错误
     解决方法:
               对于多条操作共享数据的语句,只能让一个线程都执行完,在执行的过程中
               其它线程不可以参与执行。
          synchronizied(对象){需要同步的代码}
               怎样知道要同步哪些代码呢?  参与操作共享数据的据应该放入同步代码块中去。
     对象如同锁,持有锁的线程可以在同步中执行。没有持有锁的即使占有cpu也不能执行同步中的代码。
          
eg : 火车上的卫生间-----经典

     同步的前提:
          1. 必须有两个或两个以上的线程。
          2. 必须多个线程使用同一个锁。
     必须保证同步中只有一个线程在运行。
  
     好处:避免了多线程的安全问题
     弊端:多线程需要判断锁,较为消耗资源。
     
     同步函数用的是哪一个锁呢?
          函数线程要被对象调用,那么函数都有一个所属对象引用:this
          所以同步函数的锁是this
          
package xyxysjxy.thread;
public class TestThread3 {
    public static void main(String args[]) {
         MyRunner2 mrs1 = new MyRunner2();
         Thread t1 = new Thread(mrs1);
         t1.start();
          try {
             Thread. sleep(10);
         } catch (Exception e) {
         }
         mrs1. flag = true ;
         Thread t2 = new Thread(mrs1);
         t2.start();
    }
}
class MyRunner2 implements Runnable {
    public int ticket = 100;
    public Object o = new Object();
    public boolean flag = false;
    @Override
    public void run() {
          if (ticket > 0) {
              if (flag ) {
                  while (true ) {
                       if (ticket > 0)
                           synchronized (o ) {//this才可以同步
                              try{Thread}catch(Exception e){}
                              System. out.println(Thread.currentThread().getName()
                                       + "*****" + ticket--);
                          }
                 }
             } else {
                if(ticket > 0)
                 show();
             }
         }
    }
    public synchronized void show() {
        System. out.println(Thread.currentThread().getName() + "***" + ticket --);
    }
}


通过上面的结果你会发现他们两个并没有达到同步的效果:
     同步的前提是: 1.至少有两个以上的线程同步 2. 必须是同一把锁。
     上面的锁不是同意把锁所以不能达到同步
     同一把锁不一定必须去用到同一段代码。

静态同步函数的的锁是:class对象 因为静态方法中也不能定义this

静态进内存时,内存中没有本类对象,但一定有该类对应的字节码文件对象
     类名.class 该对象类型是class
     静态的同步方法中使用的锁是字节码文件对象。

单例设计模式---懒汉式。饿汉式
     静态方法没有this super 因为他的调用不需要对象。是封装在字节码文件当中的。

package xyxysjxy.thread;
public class TestThread4 {
    public static void main(String[] args) {
         Thread t1 = new Thread() {
              @Override
              public void run() {
                 Demo D = Demo.getTestThread ();
             }
         };
         t1. start();
         Thread t2 = new Thread() {
              @Override
              public void run() {
                 Demo D = Demo.getTestThread ();
             }
         };
         t2. start();
    }
}
class Demo {
    public static int flag = 0;
    private static Demo t = null;
    private Demo() {
    }
    public static Demo getTestThread() {
          if (t == null) {
              try {
                 Thread. sleep(100);
             } catch (Exception e) {
             }
              flag++;
             System. out.println("flag = " + flag);
              t = new Demo();
              return t ;
         }
          return t ;
    }
}


输出的结果是:flag = 2, flag =2.
所以出现了线程的不安全问题,本来是单例的但是出现不只是一个对象
解决方法:加上一个静态锁:
package xyxysjxy.thread;
public class TestThread4 {
    public static void main(String[] args) {
         Thread t1 = new Thread() {
              @Override
              public void run() {
                 Demo D = Demo.getTestThread ();
             }
         };
         t1.start();
         Thread t2 = new Thread() {
              @Override
              public void run() {
                 Demo D = Demo.getTestThread ();
             }
         };
         t2.start();
    }
}
class Demo {
    public static int flag = 0;
    private static Demo t = null;
    private Demo() {
    }
    public static Demo getTestThread() {
          if (t == null) {//为了高效点要进行两重判断,线程越安全,效率越低
              synchronized (Demo.class) {// 不能用this,因为静态中不存在thi
                  if (t == null) {
                       try {
                          Thread. sleep(100);
                      } catch (Exception e) {
                      }
                       flag++;
                      System. out.println("flag = " + flag);
                       t = new Demo();
                       return t ;
                 }
             }
         }
          return t ;
    }
}
线程死锁:持有对方的锁
package xyxysjxy.thread;
public class TestThread5 {
    public static void main(String[] args) {
         MyRunner3 mr3 = new MyRunner3(false);
         MyRunner3 mr4 = new MyRunner3( true);
         Thread t1 = new Thread(mr3);
         t1.start();
         Thread t2 = new Thread(mr4);
         t2.start();
    }
}
class MyRunner3 implements Runnable {
    private boolean b ;
    public MyRunner3( boolean b) {
          this.b = b;
    }
    @Override
    public void run() {
          if (b ) {
              synchronized (Suo.s1 ) {
                 System. out.println("if suo si" );
                  synchronized (Suo.s2 ) {
                      System. out.println("if suo s2");
                 }
             }
         } else {
              synchronized (Suo.s2 ) {
                 System. out.println("else suo s2" );
                  synchronized (Suo.s1 ) {
                      System. out.println("else suo s1");
                 }
             }
         }
    }
}
class Suo {
    static Suo s1 = new Suo();
    static Suo s2 = new Suo();
}


     线程间通讯:
          其实就是多线程在操作同一个资源,但是操作的动作不同
    
wait:
notify:
notifyAll:
都使用在同步中,因为要对持有监视器(锁)的线程操作。
所以要使用同步中,因为只有同步才具有锁。

为什么这些操作线程的方法要定义在object中呢?
因为这些方法在操作同步中的线程时,都必须要标识他们所有操作线程持有的锁。
只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒。
不可以对不同的锁中的线程进行唤醒。

也就是说,等待和唤醒必须是同一个锁。
而锁可以是任意的对象,所以可以被任意对像调用的方法定义在object类中。


生产者消费者问题:
     在该代码中注意一下几点:
          1. 必须在判断flag时用while进行判断,if不能进行循环判断对于多个消费者和生产者是不行的
          2. 必须在唤醒对方时是用notifyAll方法进行唤醒,因为在多个消费者和生产者中notify有可
               只是唤醒了生产者方的线程导致线程全部都挂起。notifyAll则同时把对方也唤醒了。
package xyxysjxy.thread;
public class ProducerConsumerDemo {
    public static void main(String args[]) {
         Resource r = new Resource();
         Producer p1 = new Producer(r);
         Thread t1 = new Thread(p1);
          //Producer p2 = new Producer(r);
         Thread t3 = new Thread(p1);
          Consumer c1 = new Consumer (r);
         Thread t2 = new Thread(c1);
          //Consumer c2 = new Consumer(r);
         Thread t4 = new Thread(c1);
         t1.start();
         t2.start();
         t3.start();
         t4.start();
    }
}
class Resource {
    private int count = 1;
    private String name;
    private boolean flag = false;
    public synchronized void set(String name) {
          while (true ) {//if不能达到判断多线程效果
              while (flag )
                  try {
                      wait();
                 } catch (Exception e) {
                 }
              this.name = name + "----" + count++;
             System. out.println("producer生产---" + this.name );
              flag = true ;
             notifyAll();//notify不一定是唤醒对方的可能是自己这边的线程
         }
    }
    public synchronized void out() {
          while (true ) {
              while (!flag )
                  try {
                      wait();
                 } catch (Exception e) {
                 }
             System. out.println("consumer消费-------" + name);
              flag = false ;
             notifyAll();
         }
    }
}
class Producer implements Runnable {
    private Resource r;
    public Producer(Resource r) {
          this.r = r;
    }
    public void run() {
          r.set( "商品");
    }
}
class Consumer implements Runnable {
    private Resource r;
    public Consumer(Resource r) {
          this.r = r;
    }
    public void run() {
          r.out();
    }
}

在JDK1.5中提供了多线程升级解决方案。
将同步synchronizied替换成了显示的lock操作。
将object中的wait,notify,notifyAll,替换了condition对象
该对象可以从lock所长获取,该示例中,实现了本方只唤醒对方线程。
class Resource2 {
    private int count = 1;
    private String name;
    private boolean flag = false;
    private Lock lock = new ReentrantLock();
    private Condition producer_c = lock.newCondition();
    private Condition consumer_c = lock.newCondition();
    public void set(String name) {
          lock.lock();
          while (true ) {
              while (flag )
                  try {
                       producer_c.await();
                       this.name = name + "----" + count++;
                      System. out.println("producer生产---" + this. name);
                       flag = true ;
                 } catch (Exception e) {
                 } finally {
                       consumer_c.signal();
                 }
         }
    }
    public void out() {
          lock.lock();
          while (true ) {
              while (!flag )
                  try {
                      wait();
                      System. out.println("consumer消费-------" + name);
                       flag = false ;
                 } catch (Exception e) {
                 }
              producer_c.signal();
         }
    }
}




停止线程:该如何停止多线程stop()方法已经过时。所以只有一种方式:让run方法结束
          但是开启多线程运行,运行代码通常是在循环结构中。
          所以只有控制循环,就可以让run方法结束,也就能让线程结束。】
          
特殊情况:当线程处于冻结状态,就不会读到为循环所设置的标记,那么就不能控制线程。
     当没有指定的方式让冻结的线程恢复到运行状态,这时需要对冻结进行清除。
     强制线程恢复到运行状态中来,这样就可以操作标记让线程结束。
示例代码如下:

     package xyxysjxy.thread;
public class TestThread6 {
    public static void main(String[] args) throws InterruptedException {
         MyRunner6 m = new MyRunner6();
         Thread t1 = new Thread(m);
         t1.start();
         Thread. sleep(100);
         t1.interrupt(); //当调用该方法时,会抛出interruptedException
    }
}
class MyRunner6 implements Runnable {
    @Override
    public synchronized void run() {
          boolean flag = true;
         System. out.println(flag);
          while (flag) {//设置标志位,目的是让运行的线程能得到控制
              try {
                 wait();
             } catch (InterruptedException e) {
                 System. out.println(e);
                 flag = false;//把标志位至为false就可以中断线程
             }
             System. out.println(Thread.currentThread().getName() + "------");
         }
    }
}
   
setDaemon
守护线程:后台线程
/*
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。
该方法首先调用该线程的 checkAccess 方法,且不带任何参数。
这可能抛出 SecurityException(在当前线程中)。
 * */
    
    join():
          当A线程执行到了B线程的.join()时,A就会等待(释放执行权,
          并不代表他把执行权赋予给B了而是释放执行权利而已),等待B线程都执行完了,A才会执行
          join可以用来临时加入线程执行。