Android 线程进阶

 1.线程常用方法:  



1.1.取得当前线程对象: currentThread()  

1.2.判断线程是否启动: isAlive()  

1.3.线程的强行运行:  join()  

1.4.线程的休眠:        sleep()  

1.5.线程的礼让:        yield() 

1.6.取得当前线程对象: currentThread()  

1.7.start:启动线程  

1.8.setPriority:设置线程优先级  

1.9.getPriority:  获得线程优先级  

1.10.取得线程名称:   getName()  

1.11.设置线程名称:   setName()  

1.12.设置线程幽灵状态: setDaemon()  

1.13.取得线程幽灵状态: isDaemon()  



2.线程常用方法比较

2.1.sleep()方法和wait()方法的区别


每个对象都有一个锁来控制同步访问,Synchronized关键字可以和对象的锁交互,来实现同步方法或同步块。

2.1.1.sleep()方法 让正在执行的线程主动让出CPU(然后CPU就可以去执行其他任务),在sleep指定时间后CPU再回到该线程继续往下执行 即sleep方法只让出了CPU,而并不会释放同步资源锁。


2.1.2.wait()方法 让正在执行的线程暂时退让出同步资源锁,以便其他正在等待该资源的线程得到该资源进而运行,只有调用了notify()方法,之前调用wait()的线程才会解除wait状态,可以去参与竞争同步资源锁,进而得到执行。(注意:notify的作用相当于叫醒睡着的人,而并不会给他分配任务,就是说notify只是让之前调用wait的线程有权利重新参与线程的调度);


2.1.3.sleep()方法可以在任何地方使用;wait()方法则只能在同步方法或同步块中使用;


2.1.4.sleep()是线程类(Thread)的方法,调用会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复;


2.1.5.wait()是Object的方法,调用会放弃对象锁,进入等待队列,待调用notify()/notifyAll()唤醒指定的线程或者所有线程,才会进入锁池,不再次获得对象锁才会进入运行状态;


举例

package com.wjn.thread;


public class MyThreadDemo1 {


   public static void main(String[] args) {

      new Thread(new Thread1()).start();//开启线程1

      try{

         Thread.sleep(2000);

      }catch(Exception e){

         e.printStackTrace();

      }


      new Thread(new Thread2()).start();//2秒后开启线程2

}



//线程1

private static class Thread1 implements Runnable {


   @Override

   public void run() {

      synchronized (MyThreadDemo1.class) {

         System.out.println("enter thread1...");

         System.out.println("thread1 is waiting...");

         try {

            //调用wait()方法,线程1会放弃CPU 执行权 也会放弃对象锁,进入等待此对象的等待锁定池

            MyThreadDemo1.class.wait();

         }catch(Exception e){

            e.printStackTrace();

         }

         System.out.println("thread1 is going on ...");
         System.out.println("thread1 is over...");

   }

}

}




//线程2
private static class Thread2 implements Runnable {

   @Override

   public void run() {

      synchronized (MyThreadDemo1.class) {

      System.out.println("enter thread2....");

      System.out.println("thread2 is sleep....");

      //只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
      MyThreadDemo1.class.notify();//如果没有MyThreadDemo1.class.notify() 线程1永远处于挂起状态


      try {

         Thread.sleep(2000);

      }catch(Exception e){

         e.printStackTrace();

      }

      System.out.println("thread2 is going on...");

      System.out.println("thread2 is over...");

    }

}

}


}


结果

enter thread1...
thread1 is waiting...
enter thread2....
thread2 is sleep....
thread2 is going on...
thread2 is over...
thread1 is going on ...
thread1 is over...


注意

synchronized (MyThreadDemo1.class) {


       MyThreadDemo1.class.notify();//如果没有MyThreadDemo1.class.notify() 线程1永远处于挂起状态

       两者要一致......否则报错

}




2.2.Yield()方法

    停止当前线程,让同等优先权的线程运行。如果没有同等优先权的线程,那么Yield()方法将不会起作用。(Thread类方法)



2.3.线程结束的标志

run()方法结束



2.4.Wait()方法和notify()方法

Wait()方法和notify()方法:当一个线程执行到wait()方法时(线程休眠且释放机锁),它就进入到一个和该对象相关的等待池中,同时失去了对象的机锁。当它被一个notify()方法唤醒时,等待池中的线程就被放到了锁池中。该线程从锁池中获得机锁,然后回到wait()前的中断现场。



2.5.join()方法

join()方法使当前线程停下来等待,直至另一个调用join方法的线程终止。


join()方法举例

Android 线程进阶




假设有一个线程,在run方法里面随机睡眠一段时间。因为睡眠时间的不确定性,所以如果想要等到这个线程执行完再去执行另外一个线程的话,用睡眠唤醒另一个线程也不太实际。那么现在join方法就能做到这件事,join方法就是使一个线程等待另外一个线程执行完毕后执行。下面看一下简单的测试方法。

Android 线程进阶


join底层的原理

Android 线程进阶




3.线程同步问题

线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。线程的同步是保证多线程安全访问竞争资源的一种手段。



4.synchronized关键字


  synchronized只能标记非抽象的方法,不能标识成员变量。

4.1.同步代码块

      在代码块上加上'synchronized'关键字 则此代码块就称为同步代码块  


4.2.同步代码块格式:  

      synchronized(同步对象){  

        需要同步的代码块;  

       }  



4.3.同步方法:  

       除了代码块可以同步  方法也可以同步  


4.4.方法同步格式  

       synchronized void 方法名称(){

  
       }  




为了演示同步方法的使用,构建了一个信用卡账户,起初信用额为100w,然后模拟透支、存款等多个操作。显然银行账户User对象是个竞争资源,而多个并发操作的是账户方法oper(int x),当然应该在此方法上加上同步,并将账户的余额设为私有变量,禁止直接访问。


public class Test {

   public static void main(String[] args) {

      User u=new User("张三", 100);

      MyThreadss t1=new MyThreadss("线程A", u, 20);

      MyThreadss t2=new MyThreadss("线程B", u, -60);

      MyThreadss t3=new MyThreadss("线程C", u, -80);

      MyThreadss t4=new MyThreadss("线程D", u, -30);

      MyThreadss t5=new MyThreadss("线程E", u, 32);

      MyThreadss t6=new MyThreadss("线程F", u, 21);

      t1.start();

      t2.start();

      t3.start();

      t4.start();

      t5.start();

      t6.start();

   }

}



class MyThreadss extends Thread {

private User u;

private int y = 0;


MyThreadss(String name, User u, int y) {

   super(name);

   this.u = u;

   this.y = y;

}




public void run() {

   u.oper(y);

}

}




class User {

private String code;

private int cash;

User(String code, int cash) {

   this.code = code;

   this.cash = cash;

}




public String getCode() {

   return code;

}




public void setCode(String code) {

   this.code = code;

}






public  void oper(int x) {

   try{

      Thread.sleep(10L);

      this.cash+=x;

      System.out.println(Thread.currentThread().getName() + "运行结束,增加“"+ x + "”,当前用户账户  余额为:" + cash);

      Thread.sleep(100);

   } catch (InterruptedException e) {

   e.printStackTrace();

   }

}

}




即核心代码块(操作钱数)没加同步


结果1 正确


线程E运行结束,增加“32”,当前用户账户余额为:153
线程D运行结束,增加“-30”,当前用户账户余额为:123
线程F运行结束,增加“21”,当前用户账户余额为:153
线程C运行结束,增加“-80”,当前用户账户余额为:43
线程A运行结束,增加“20”,当前用户账户余额为:63
线程B运行结束,增加“-60”,当前用户账户余额为:3






结果2 错误


线程E运行结束,增加“32”,当前用户账户余额为:72
线程B运行结束,增加“-60”,当前用户账户余额为:72
线程F运行结束,增加“21”,当前用户账户余额为:72
线程D运行结束,增加“-30”,当前用户账户余额为:42
线程C运行结束,增加“-80”,当前用户账户余额为:-38
线程A运行结束,增加“20”,当前用户账户余额为:-18






结果3 错误


线程B运行结束,增加“-60”,当前用户账户余额为:152
线程F运行结束,增加“21”,当前用户账户余额为:173
线程A运行结束,增加“20”,当前用户账户余额为:152
线程E运行结束,增加“32”,当前用户账户余额为:152
线程D运行结束,增加“-30”,当前用户账户余额为:143
线程C运行结束,增加“-80”,当前用户账户余额为:63






结果4 正确


线程B运行结束,增加“-60”,当前用户账户余额为:60
线程A运行结束,增加“20”,当前用户账户余额为:60
线程E运行结束,增加“32”,当前用户账户余额为:92
线程F运行结束,增加“21”,当前用户账户余额为:113
线程D运行结束,增加“-30”,当前用户账户余额为:83
线程C运行结束,增加“-80”,当前用户账户余额为:3




举例 也就是没有加同步的情况下 初始值10 


a +10  


b+20


同时计入代码块


由于同时进入所以初始值都是10 


a +10 ------>20


b +20-------->30



显然结果是不对的






核心代码块


public synchronized void oper(int x) {

   try{

      Thread.sleep(10L);

      this.cash+=x;

      System.out.println(Thread.currentThread().getName() + "运行结束,增加“"+ x + "”,当前用户账户余额为:" + cash);

      Thread.sleep(100);

   } catch (InterruptedException e) {

      e.printStackTrace();

   }

}



结果1 正确


线程A运行结束,增加“20”,当前用户账户余额为:120
线程F运行结束,增加“21”,当前用户账户余额为:141
线程E运行结束,增加“32”,当前用户账户余额为:173
线程D运行结束,增加“-30”,当前用户账户余额为:143
线程C运行结束,增加“-80”,当前用户账户余额为:63
线程B运行结束,增加“-60”,当前用户账户余额为:3






结果2 正确


线程B运行结束,增加“-60”,当前用户账户余额为:40
线程F运行结束,增加“21”,当前用户账户余额为:61
线程E运行结束,增加“32”,当前用户账户余额为:93
线程C运行结束,增加“-80”,当前用户账户余额为:13
线程D运行结束,增加“-30”,当前用户账户余额为:-17
线程A运行结束,增加“20”,当前用户账户余额为:3






结果3 正确


线程A运行结束,增加“20”,当前用户账户余额为:120
线程E运行结束,增加“32”,当前用户账户余额为:152
线程F运行结束,增加“21”,当前用户账户余额为:173
线程D运行结束,增加“-30”,当前用户账户余额为:143
线程B运行结束,增加“-60”,当前用户账户余额为:83
线程C运行结束,增加“-80”,当前用户账户余额为:3



即同一时间只允许一个线程访问 数据就会同步。




5.死锁


死锁就是AB线程分别获得CD对象的锁但A此时要调用D对象  
可是B已经获得D对象的锁,导致A进入阻塞状态 又无法返回C的锁(解锁时可通过检测 若死锁就返回锁),导致B也无法退出返回锁D,进而AB死锁 


A线程——>获得C对象锁   正在访问


B线程——>获取D对象锁  正在访问



如果此时A线程此时要调用D对象锁 ,可是B已经获得D对象的锁,导致A进入阻塞状态 又无法返回C的锁(解锁时可通过检测 若死锁就返回锁),导致B也无法退出返回锁D,进而AB死。




6.锁的原理


Java中每个对象都有一个内置锁。


当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。


当程序运行到synchronized同步方法或代码块时该对象锁才起作用。


一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。




释放锁是指持锁线程退出了synchronized同步方法或代码块。




关于锁和同步,有一下几个要点:

1)、只能同步方法,而不能同步变量和类;

2)、每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步

3)、不必同步类中所有的方法,类可以同时拥有同步和非同步方法。

4)、如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。

5)、如果线程拥有同步和非同步方法,则非同步方法可以被多个线程*访问而不受锁的限制。

6)、线程睡眠时,它所持的任何锁都不会释放。

7)、线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。

8)、同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。

9)、在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。例如:

public int fix(int y) {

      synchronized (this) {

            x = x - y;

      }

      return x;

}




当然,同步方法也可以改写为非同步方法,但功能完全一样的,例如:

public synchronized int getX() {

      return x++;

}


public int getX() {

      synchronized (this) {

            return x++;

      }

}

效果是完全一样的。






7.静态方法同步


要同步静态方法,需要一个用于整个类对象的锁,这个对象就是这个类(XXX.class)。

例如:

public static synchronized int setName(String name){

      Xxx.name = name;

}

等价于

public static int setName(String name){

      synchronized(Xxx.class){

            Xxx.name = name;

      }

}






8.如果线程不能获得锁会怎么样


如果线程试图进入同步方法,而其锁已经被占用,则线程在该对象上被阻塞。实质上,线程进入该对象的的一种池中,必须在哪里等待,直到其锁被释放,该线程再次变为可运行或运行为止。



当考虑阻塞时,一定要注意哪个对象正被用于锁定:

1、调用同一个对象中非静态同步方法的线程将彼此阻塞。如果是不同对象,则每个线程有自己的对象的锁,线程间彼此互不干预。

2、调用同一个类中的静态同步方法的线程将彼此阻塞,它们都是锁定在相同的Class对象上。

3、静态同步方法和非静态同步方法将永远不会彼此阻塞,因为静态方法锁定在Class对象上,非静态方法锁定在该类的对象上。

4、对于同步代码块,要看清楚什么对象已经用于锁定(synchronized后面括号的内容)。在同一个对象上进行同步的线程将彼此阻塞,在不同对象上锁定的线程将永远不会彼此阻塞。





9.何时需要同步


在多个线程同时访问互斥(可交换)数据时,应该同步以保护数据,确保两个线程不会同时修改更改它。

对于非静态字段中可更改的数据,通常使用非静态方法访问。

对于静态字段中可更改的数据,通常使用静态方法访问。

如果需要在非静态方法中使用静态字段,或者在静态字段中调用非静态方法,问题将变得非常复杂。已经超出SJCP考试范围了。







10.线程安全类


当一个类已经很好的同步以保护它的数据时,这个类就称为“线程安全的”。

即使是线程安全类,也应该特别小心,因为操作的线程是间仍然不一定安全。





11.线程同步小结

1、线程同步的目的是为了保护多个线程访问一个资源时对资源的破坏。

2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他同步方法。

3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。

4、对于同步,要时刻清醒在哪个对象上同步,这是关键。

5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。

6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。

7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。