黑马程序员—多线程

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

定义线程
一.继承Thread类
1.步骤:
  1).定义类继承Thread类
  2).覆写Thread类中的run()方法
     目的:将自定义的代码存储在run方法中,让线程运行。
  3).调用线程的start方法,该方法启动线程,并且调用run方法


  eg:
  class Demo extends Thead{
public void run(){
System.out.println("demo run");
}
  }


  class ThreadDemo{
public static void main(String[] args){
Demo d = new Demo();
d.start();
}
  }
  结果发现每一次的结果都不同
  因为多个线程都获取CPU的执行权,cpu执行到谁,谁就运行
  明确一点,在某一个时刻,只能有一个程序在运行(多核除外)
  cpu在做着快速的切换,已达到看上去是同时运行的效果。
  我们可以形象的把多线程的运行行为在互相抢夺cpu的执行权


  这就是多线程的一个特征:随机性。谁抢到谁执行,至于执行多长时间,cpu说的算。


2.为什么要覆写run方法?
  Thread类用于描述线程。
  该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法。
  也就是Tread类中的run方法,用于存储线程要运行的代码。
  public static void main(String[] args){
Demo d = new Demo();
d.run(); // 仅仅是对象调用run方法,线程创建了,但没运行
d.start(); // 启动线程,并且调用run方法
  }


3.线程生命周期
  如图:


4.线程中常见方法:
  1).static Thread currentThread() 获取当前线程对象
  2).getName():获取线程名称
  3).设置线程名称:setName或者构造函数
  eg:
  class Test extends Thread{  
Test(String name){
super(name);
}
public void run(){
System.out.println((Thread.currentThread()==this)+"..."+this.getName()+"...run...");
}
  }
  class ThreadDemo{
public static void main(String[] args){
Test t1 = new Test("one");
Test t2 = new Test("two");
t1.start();
t2.setName("TWO");
t2.start();
}
  }
  结果:
true...one...run...
true...TWO...run...
     
5.举例:简单的卖票程序,多个窗口同时卖票
  class Ticket extends Thread{
private int tick = 3;
public void run(){
while(tick > 0){
System.out.println(Thread.currentThread().getName()+"..."+tick--);
}
}
  }
  class ThreadDemo{
public static void main(String[] args){
Ticket t1 = new Ticket();
Ticket t2 = new Ticket();
Ticket t3 = new Ticket();


t1.start();
t2.start();
t3.start();
}
  }
  结果:
Thread-0...3
Thread-2...3
Thread-1...3
Thread-2...2
Thread-0...2
Thread-2...1
Thread-1...2
Thread-1...1
Thread-0...1
   发现每个窗口都卖了三张票
   解决方法:tick定义为静态
               private static int tick = 3;
   但是因为static的生命周期太长,占用资源,所以通过线程的另一种创建方式解决,接口。


二.实现runnable接口
1.步骤:
  1).定义类实现Runnable接口
  2).覆盖Runnable接口中的run方法
     将线程要运行的代码存放在run方法中
  3).通过Thread类建立线程对象
  4).将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
     为什么要将Runnable接口的子类对象传递给Thread的构造方法?
     因为自定义的run方法所属的对象时Runnable接口的子类对象,所以要让线程取指定对象的run方法,就必须明确该run方法所属的对象。
  5).通过Thread类的start方法开启线程并调用Runnable接口子类的run方法


2.实现接口方式和继承方式有什么区别?
  1).避免了单继承的局限性,如Student类需要继承Person,并且类中部分代码还需要多线程处理
  2).继承Thread类:线程代码存放在Thread子类run方法中
     实现Runnable接口:线程代码存放在接口子类的run方法中


3.解决卖票问题
  class Ticket implements Runnable{
private int tick = 6;
public void run(){
while(tick>0){
System.out.println(Thread.currentThread().getName()+"..."+tick--);
}
}
  }
  public class ThreadDemo02 {
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);
t1.start();
t2.start();
t3.start();
}
  }
  结果:
Thread-1...5
Thread-2...4
Thread-0...5
Thread-0...3
Thread-0...2
Thread-2...1
  以上结果不稳定,每次的结果可能都不一样。但是足以说明问题。


  原因?
  当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误


  解决办法:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中其他线程不可以参与执行。


  java对于多线程安全问题提供了同步代码块解决方式。
  synchronized(对象){
需要被同步的代码;
  }


  class Ticket implements Runnable{
private int tick = 6;
Object obj = new Object();
public void run(){
while(tick>0){
   synchronized(obj){
System.out.println(Thread.currentThread().getName()+"..."+tick--);
   }
}
}
  }
  相当于锁的功能
  经典实例:火车上卫生间,一个乘客进去上锁、出来开锁,另一个人乘客才能进去,上锁。。。


  同步的前提:
  1).必须要有两个或者两个以上的线程
  2).必须是多个线程使用同一个锁


  弊端:多个线程需要判断锁,较为消耗资源,但是在可接受范围内


  开发中代码结果出现问题,需要同步,如何判断同步位置
  1).明确哪些代码是多线程运行代码
  2).明确共享数据
  3).明确多线程运行代码中哪些语句是操作共享数据的


  函数是用来装一些需要全部执行的代码,而同步代码块也是用来装一些需要全部执行的代码,只不过同步代码块拥有了同步的特性,如果函数也赋予同步的特性,那么它也可以实现同步的功能,即同步函数


  eg:public void run(){
while(tick>0){
show();
}
     }
     public synchronized void show(){
System.out.println(Thread.currentThread().getName()+"..."+tick--);
     }
   
  同步函数用的是哪一个锁呢?
  函数需要被对象调用,那么函数都有一个所属对象引用,就是this
  所以同步函数使用的锁是this.


  验证:使用两个线程来卖票,一个线程在同步代码块中,一个线程在同步函数中,都在执行卖票动作。
  class Ticket implements Runnable{
private int tick = 10;
Object obj = new Object();
boolean flag = true;
public void run(){
   if (flag) {
       while(true){
   synchronized (obj) {
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(){
   if(tick>0){
try{Thread.sleep(10);}catch(Exception e){};
System.out.println(Thread.currentThread().getName()+"...show"+tick--);
   }
}
  }
  public class ThreadDemo02 {
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(10);}catch(Exception e){};
   t.flag = false;
   t2.start();
}
  }
  结果:
Thread-0...code10
Thread-0...code9
Thread-1...show8
Thread-1...show7
Thread-0...code6
Thread-1...show5
Thread-0...code4
Thread-0...code3
Thread-1...show2
Thread-1...show1
Thread-0...code0
  最后出现0票的问题
  此时run方法用的锁是obj,如果,把obj的锁改成this,结果如下:
Thread-0...code10
Thread-1...show9
Thread-1...show8
Thread-1...show7
Thread-1...show6
Thread-1...show5
Thread-1...show4
Thread-1...show3
Thread-1...show2
Thread-1...show1


  此时发现另一个现象,如果把show方法定义为静态的,执行结果又出现0票的问题,结果显示:略。
  说明,如果同步函数被静态修饰后,使用的锁不再是this了,同时静态方法中也不可以定义使用this,那么此时的锁是什么呢?


  类进入内存首先要变成字节码文件对象,类.class文件
  静态进入内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象,所以此时的锁就是Ticket.class。


  所以静态的同步方法没使用的锁是该方法所在类的字节码文件对象,即类名.class。


三.多线程死锁
   偶尔面试的时候,会要求写一段死锁程序
   eg:
   class Test implements Runnable{
private boolean flag;
Test(boolean flag){
this.flag = flag;
}
public void run(){
   if(flag){
       synchronized(MyLock.Locka){
   System.out.println("if locka");
   synchronized(MyLock.Lockb){
System.out.println("if lockb");
   }
}
   }else{
synchronized(MyLock.Lockb){
   System.out.println("else lockb");
   synchronized(MyLock.Locka){
System.out.println("else locka");
   }
}
   }
}
   }
   class MyLock{
static Object Locka = new Object();
static Object Lockb = 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();
}
   }
   结果:
if locka
else lockb


四.线程间通信-等待唤醒机制
   wait();
   notify();
   notifyAll();
   此三种方法都使用在同步中,因为要对持有监视器(锁)的线程操作。
   所以要使用在同步中,因为只有同步才具有锁。


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


   而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。


   案例:生产者消费者
        class Resource{
private String name;
private int count = 1;
private boolean flag = false;

public synchronized void producer(String name){
while (flag) {
try{wait();}catch(Exception e){}
}
this.name = name+"---"+count++;
System.out.println(Thread.currentThread().getName()+"+++++生产者+++++"+this.name);
notifyAll();
flag = true;
}


public synchronized void consumer(){
while (!flag) {
try{wait();}catch(Exception e){}
}
System.out.println(Thread.currentThread().getName()+"-----消费者------------"+this.name);
notifyAll();
flag = false;
}
}


   对于多个生产者和消费者,为什么要定义while判断标记?
   原因:让被唤醒的线程再一次判断标记


   为什么定义notifyAll?
   因为需要唤醒对方线程,只用notity,容易出现只唤醒本方线程的情况,导致程序中的所有线程都在等待。


   生产者消费者jdk1.5升级版
   Lock接口和Condition接口
   显式lock操作替代synchronized,lock()拿锁,unlock()解锁
   将object中的wait,notify,notifyAll替换成了condition中await,signal,signalAll
   
   升级后:
   class Resource{
private String name;
private int count = 1;
private boolean flag = false;
Lock lock = new ReentrantLock();
Condition condition_pro = lock.newCondition();
Condition condition_con = lock.newCondition();

public void producer(String name) throws InterruptedException {
lock.lock();
try{
while (flag) {
condition_pro.await();
}
this.name = name+"---"+count++;
System.out.println(Thread.currentThread().getName()+"+++++生产者+++++"+this.name);
flag = true;
condition_con.signal();
}finally{
lock.unlock();// 释放锁的动作一定要执行
}


}


public void consumer(){
lock.lock();
try{
while (!flag) {
condition_con.await();
}
System.out.println(Thread.currentThread().getName()+"-----消费者------------"+this.name);
flag = false;
condition_pro.signal();
}finally{
lock.unlock();
}
}
   }


五.多线程-停止线程
   stop()方法已经过时
   
   如何停止线程?
   只有一种,run()方法结束
   开启多线程运行,运行代码通常是循环结构
   只要控制住循环,就可以让run方法结束,也就是线程结束
   添加标记,判断.


   当线程处于冻结状态,就不会读取到标记,那么线程就不会结束
   当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结状态进行清除,强制让线程恢复到运行状态中,就可以操作标记,让线程结束


   Thread中提供了该方法,interrupt().


六.守护线程
   线程.setDaemon(true);
   当正在运行的线程都是守护线程时,jvm退出。
   如当前运行的线程都是守护线程,并且全部wait了,则虚拟机退出。
   该方法必须在启动线程前调用


七.多线程-join方法
   join可以用来临时加入线程执行
   当A线程执行到了B线程的join()方法,A线程就会等待,等B线程都执行完,A才会执行


八.优先级和yield方法
   线程覆写object的toString()方法
   此方法返回该线程的字符串表现形式,包括线程名称,优先级和线程组。


   setPriority()更改线程优先级,默认等级是5
   MAX_PRIORITY:10
   MIN_PRIORITY:1
   NORM_PRIORITY:5


   yield()可以减缓线程执行频率


九.多线程创建示例
   三个for循环,顺序执行效率低,封装成线程提高效率
   class ThreadTest{
new Thread(){
public void run(){
for(int i = 0; i<=50; i++){
System.out.println("Hello World!!");
}
}
}.start();


for(int i = 0; i<=50; i++){
System.out.println("Hello World!!");
}


Runnable r = new Runnable(){
public void run(){
for(int i = 0; i<=50; i++){
System.out.println("Hello World!!");
}
}
};


new Thread(r).start();
   }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值