java 多线程机制简单总结

Java实现多线程有两种方法:
1,继承Thread
2 , 实现 Runnable接口

举例:

public  hello  extends   Thread{
    public  hello();
    public  hello("name"){
         this.name=name;
    }
    public void  run(){
       for(int i=0;i<3;i++){
        System.out.println(name+"   "+i);
         }
}
public static void main(String args[]){
hello  h1  =  new hello("a");      //自己可以实例化自己
hello  h2  =  new hello("b");
h1.run();
h2.run();
}
 private  String name;
}

运行后发现结果为:
a 0
a 1
a 2
b 0
b 1
b 2
我们会发现这些都是顺序执行的,说明我们的调用方法不对,应该调用的是start()方法。
将h1.run()与h2.run();换成h1.start() h2.start();
输出的结果每次都不一样。因为需要用到CPU的资源
注意:虽然我们在这里调用的是start()方法,但是实际上调用的还是run()方法的主体。
2.实现Runnable类:
通过实现Runnable接口类作为一个线程的目标对象。此方法用Runnable目标对象初始化Thread类
new Thread(Runnable target);然后通过此实例调用start(),只有Thread类才有start()方法
调用的是Runnable接口的run,但启动是包装在Thread类中启动

public hello implements Runnable{
     public hello(String name){
     this.name=name;
     }
     public void run(){
     for(int i=0;i<3;i++)
     System.out.println(name+" "+i);
     }
     public static void main(String argu[]){
     hello h1 = new hello("a");
     hello h2 = new hello("b");
     Thread demo = new Thread(h1);
     Thread demo1 = new Thread(h1);
     demo.start();
     demo1.start();
     }
   private  String name;
}

注意:一个类实现了Runnable并不是说明它一定是在多线程中运行,只有Thread类才是在多线程中运行,故把其作为参数传递给Thread才可以启动多线程来执行。

继承Thread类时,修改线程的名称。通过调用setName()或者构造方法传递,获得线程的名字通过getName()
而针对实现Runnable接口的这种方式,没有办法获取名称(当然通过Thread包装之后就可以调用setName了),所以,Java就提供了一个静态方法,获取当前正在执行的线程对象。
public static Thread currentThread()

//关于选择继承Thread还是实现Runnable接口?
//其实Thread也是实现Runnable接口的:
class Thread implements Runnable {
    //…
public void run() {
        if (target != null) {
             target.run();
        }
        }
}

其实Thread中的run方法调用的是Runnable接口的run方法。不知道大家发现没有,Thread和Runnable都实现了run方法,这种操作模式其实就是代理模式。

问题1:使用Thread子类创建线程的优点是?
可以在子类中增加新的成员变量,使线程具有某种属性;可以在子类中新增加方法,使线程具有某种功能
这里写图片描述

public class  Test_Thread  implements Runnable {   
  public void run(){
         for(int i =0;i <10;i ++){
               if (ticket > 0) {
                   System.out.println(Thread.currentThread().getName() + "正在卖"+ this .ticket --+"号票" );
           }
          }
     }
     public static void main(String args[]){
         Test_Thread tt= new Test_Thread();
         //Thread第一个参数是Runnable, 第二个参数线程名称
         Thread demo= new Thread(tt ,"1窗口" );
         Thread demo1= new Thread(tt ,"2窗口" );
         Thread demo2= new Thread(tt ,"3窗口" );
         demo.start();
         demo1.start();
         demo2.start();
     }
     private int   ticket =5;
}

程序的问题:
A:相同的票卖了多次。

  1. CPU的每一次执行,必须是一个原子性的操作。这个操作是不能在分割的。
  2. int i =10;其实是两个原子性的操作(定义和赋值) 这样的话,tickets–其实也将不是一个原子性的操作。
  3. 可能在操作的中间部分,被其他的线程给执行了。这样就会有相同的票执行了多次。

B:出现了负数票的情况。

1. 因为线程的随机性。

问题:怎么样改进上面的问题呢???
只需要把多线程环境中,操作共享数据的操作给变成单线程的就没有问题了。
* Java针对这种情况,就提供了同步技术:同步代码块。
* 格式:
* synchronized(对象) {
* 需要被同步的代码。
* }
问题:
* A:对象?
* 如果不知道用哪个对象,就用Object对象。
* B:需要被同步的代码?
* 哪些代码导致出现了问题,就把哪些代码给同步起来。
哪些代码会出问题呢?
* 有共享数据。
* 针对共享数据有多条语句操作。
加入同步后,居然还有问题,为什么呢?

 public void run() {
  while (true) {
   synchronized (new Object()) {
    if (piao > 0) {
     try {
      Thread.sleep(1000);
     } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
     System.out.println(Thread.currentThread().getName()
       + "号窗口正在卖" + (piao--) + "号票");
    }
   }
  }
 }
  • 同步代码块中的对象针对多个线程必须是同一个。
  • 其实这个对象被称为同步锁对象。
private static int piao = 10;
//创建同步锁对象
 private Object obj = new Object();
 public void run() {
  while (true) {
// 看到obj就知道这是锁对象。假设t1进去前,obj这个锁对象表示开的状态。
   synchronized (obj) {
// t1进来,就把锁对象的状态改为关的状态。多个线程必须使用同一个锁对象
    if (piao > 0) {
     try {
      Thread.sleep(1000);
     } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
     System.out.println(Thread.currentThread().getName()
       + "号窗口正在卖" + (piao--) + "号票");
    }
   }
  }
 }

注意同步方法(内有一个隐含的this,也就是说锁对象是this)
静态方法的锁对象是谁呢?
* 当前类的字节码文件对象。
同步代码块的锁对象可以是任意对象.
JDK5后的新特性:

Lock接口:实现类ReentrantLock

 // 定义一个锁对象
 private Lock lock = new ReentrantLock();
 @Override
 public void run() {
  while (true) {
   // 加锁
   lock.lock();
   if (tickets > 0) {
    try {
     Thread.sleep(10);
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "正在出售第"
      + (tickets--) + "张票");
   }
   // 释放锁
   lock.unlock();
  }
 }

这里写图片描述

一个完整的多线程的输入输出问题:(Student类省掉):

public class setStudent implements Runnable {
 private Student s;
 private int x = 0;
 // 通过构造方法传递值
 public setStudent(Student s) {
  this.s = s;
 }
 public void run() {
  while (true) {
   // 同步锁,setStudent()与getStudent()的锁对象保持一致,都是测试类中的s
   synchronized (s) {
    // 这个才是重点,多线程的 等待唤醒机制
    if (s.Judge()) {
     // s.Judge为student自定义的方法,flase表示没有值,此处判断当没有值的时候就不进入if而去等待
     try {
      s.wait(); //在这里等待了,释放锁对象,将来它获得执行权的时候,哪里睡的,哪里醒来
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
    }
    // 没有值,设置值
    if (x % 2 == 0) {
     s.setName("wang");
     s.setAge(17);
    } else {
     s.setName("xi");
     s.setAge(19);
    }
    x++;
    // 当设置完值后,s.judge()就成此true
    s.setJudegTrue();
    // 唤醒等待线程
    s.notify(); // 唤醒等待的线程,唤醒其他的线程,不代表其他的线程能够立即执行。
   }
//然后重新强执行器
  }
 }
}

public class getStudent implements Runnable {
 private Student s;
 // 通过构造方法传递值
 getStudent(Student s) {
  this.s = s;
 }
 @Override
 public void run() {
  while (true) {
   // 同步锁,setStudent()与setStudent()的锁对象保持一致,都是测试类中的s
   synchronized (s) {
    // 这个才是重点,多线程的 等待唤醒机制
    if (!s.Judge()) {
     // s.Judge为flase表示没有值,此处判断当有值的时候就不进入if而去等待
     try {
      s.wait(); //在这里等待了,释放锁对象,将来它获得执行权的时候,哪里睡的,哪里醒来
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
    }
    // 有值,边打印值
    System.out.println(s.getAge() + "+++" + s.getName());
    // 打印完成后,自然又没有值了,便设置为false,表示没有值
    s.setJudegFalse();
    // 唤醒线程
    s.notify();
   }
//然后重新强执行器
  }
 }
}
public class test {
 public static void main(String[] args) {
  // 创建学生类
  Student s = new Student();
  // setStudent与getStudent分别继承了Runnable接口,并且可以通过构造方法传递值
  setStudent set = new setStudent(s);
  // 这种传递值的方法以后会常见到,保证了两个对象内的对象值保持一致
  getStudent get = new getStudent(s);
  // 创建Thread类来包装Rubbable接口的实现类的实例
  Thread t1 = new Thread(set);
  Thread t2 = new Thread(get);
  // 启动线程
  t2.start();
  t1.start();
 }
}

让线程休眠的两种方法sleep和wait:
sleep()必须制定休眠时间,而且不会释放锁对象。
wait()可以制定时间,也可以不制定,会释放锁对象。
线程醒来之后并不会立即执行,而是加入队列中等待CPU的调度。

线程的死锁:

public class DieLock extends Thread {
 private boolean judge;
 DieLock(boolean judge) {
  this.judge = judge;
 }
 public void run() {
  if (judge) {
   while (true) {
    //拿到objA锁
    synchronized (MyLock.objA) {
     System.out.println("if objA");
     //此时需要objB锁。但objB锁没有释放,线程死锁
     synchronized (MyLock.objB) {
      System.out.println("if objB");
     }
    }
   }
  } else {
   while (true) {
    //拿到objB锁
    synchronized (MyLock.objB) {
     System.out.println(" else objB");
     //此时需要objA锁。但objA锁没有释放,线程死锁
     synchronized (MyLock.objA) {
      System.out.println("else objA");
     }
    }
   }
  }
 }
}

public class MyLock {
  static final Object objA  =  new Object();
  static final Object objB  =  new Object();
}


public class Test {
   public static void main(String[] args) {
    DieLock d1 = new DieLock(true);
    DieLock d2 = new DieLock(false);
    d1.start();
    d2.start();
}
}

线程死锁的另一种情况:

这里写图片描述

分析上面的线程死锁的原因:
main线程访问Thread_Died.str的值。此时str尚未被初始化。则main线程开始对该类执行初始化。初始化包括以下两个步骤:
1)为静态变量分配空间
2)调用静态初始化块的代码为静态变量赋值
程序为str分配空间后执行初始化,即执行静态代码块的内容,此时str的值为null。。而静态代码块创建并启动了一条新线程,并调用了线程的join(),这意味着main线程必须等待新线程执行结束后才能向下执行。而在新线程中程序试图使用str的值。。。这时问题出现了:
Thread_Died类正由main线程执行初始化,因此新线程会等待main线程对Thread_Died类执行完初始化后才能使用str的值。
则main线程由于join()方法等待新线程的结束,而新线程由于输出str的值要等待main线程的介绍。则发生死锁。

避免上述代码的死锁:由于上述代码死锁的原因是调用了join()方法。把join代码注释掉即可避免死锁。
当把join()方法注释掉之后,通过上面的分析则先输出main线程的内容,然后为新线程的内容

如果我们希望先输出新线程的内容,则添加sleep()方法让main线程休眠即可。

不管是注释掉join()方法还是sleep()两个都不会输出新线程中被赋值的值,因为mian线程的执行初始化后没有调用join()方法,新线程处于就绪状态,还未进入运行状态,如果此时新线程进入了运行状态,当在操作str的值的时候新线程还是会放弃资源来让main执行初始化。main线程会继续执行初始化。故输出都不是新线程中的复制语句.当输出完成后新线程的赋值没有了意义。
而执行sleep()让新线程立即启动后输出的依然不是新线程中的赋值语句:因为当新线程要操作str的值时,该值还没有被初始化。因此新线程不得不放弃执行来等待main线程执行初始化,则输出依然不是新线程中的赋值。。。当然把新线程中的赋值放在输出之前则程序会不同,但在join()时依然会死锁。。。当输出完成后新线程的赋值没有了意义
优先级和暂停yield(): setDaemon(); join();

实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源,当然通过继承Thread然后定义static变量也能实现而我们一般避免使用static,因为它的生命周期太长了
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。
main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。
在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM就是在操作系统中启动了一个进程。
实现Runnable接口要获取当前线程对象,必须Thread.currentThread().getName()
而继承了Thread类的只需要this即可this. getName()

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值