黑马程序员_多线程(二)

android培训java培训


 

多线程概述:

单线程如同一个餐厅只雇用一个服务员,服务员必须做完一件事情后才能做下一件事情。多线程如同一个餐厅雇佣很多名服务员,他们可以同时做很多事情,并且相互之间不会干扰。

       一个程序被加载进内存运行时,就是一个进程,多线程使同一个进程可以并发处理多个任务,线程是进程的执行单元。一个进程可以拥有多个进程,一个线程必须有一个父进程。

       每个线程拥有自己的堆栈,自己的程序计数器和自己的局部变量,但不拥有系统资源,多个线程共享父进程所拥有的全部资源。线程的执行时抢占式的,当前的线程在任何时候都可能被挂起,以便CUP运行另外的线程。

       一个程序运行后至少有一个进程,一个进程可以包含多个程序,但至少包含一个线程。

线程的创建和启动:

方法1:创建线程需要继承Thread类,并重写run(),run()代表线程需要完成的任务,run()是线程的执行体,启动线程通过start()。

publicclass FirstThread extends Thread{

       privateinti;

       @Override

       publicvoid run(){

              for (; i < 100; i++) {

                     System.out.println(this.getName()+"\t"+i);

              }

       }

       publicstaticvoid main(String[] args) {

              for (int i = 0; i < 100; i++) {

                     System.out.println(Thread.currentThread().getName()+" "+i);

                     if(i==20){

                            new FirstThread().start();

                            new FirstThread().start();

                     }

              }

       }

}

在main()中当循环变量i=20时,启动两个线程。属性i在两个线程中的打印结果是不连续的,证明了多线程之间运行互不干扰,因为new了两个线程对象。以上程序启动时有3个线程,main()也是一个线程,成为主线程。

方法2:实现Runnable接口并实现run(),fun()方法内是线程的执行体。

publicclass SecondThread implements Runnable {

       privateinti;

       @Override

       publicvoid run() {

              for (; i < 100; i++) {

                     System.out.println(Thread.currentThread().getName() + "\t" + i);

              }

       }

       publicstaticvoid main(String[] args) {

                     SecondThread st = new SecondThread();

                     for (int i = 0; i < 100; i++) {

                            System.out.println(Thread.currentThread().getName()+" "+i);

                            if(i==20){

                                   new Thread(st,"线程-1").start();

                                   new Thread(st,"线程-2").start();

                            }

                     }

       }

}

通过实现Runnable接口在启动多线程时也需要使用Thread类并调用start()。这种方式创建的多个线程可以共享线程类中的属性,因为只创建一个Runnable接口的子类对象,这一个对象被Thread类引用。

方法3:通过实现Callable接口,并且实现call()。

ublicclass ThirdThread implements Callable<Integer> {

       @Override

       public Integer call() throws Exception {

              int i=0;

              for (; i <100; i++) {               System.out.println(Thread.currentThread().getName()+"\t"+i);

              }

              return i;

       }

       publicstaticvoid main(String[] args) throws Exception {

              ThirdThread tt = new ThirdThread();

               FutureTask<Integer> task = new FutureTask<Integer>(tt);

               for(int i=0;i<100;i++){

                      System.out.println(Thread.currentThread().getName());

                      if(i==20){

                             new Thread(task,"有返回值的线程").start();

                      }

               }

               System.out.println(task.get());

       }

}

call()可以作为线程的执行体,call()比run()强大,可以有返回值,可以抛异常,Java5新增的接口。

线程的生命周期:

新建(New)à就绪(Runable)à运行(Running)à阻塞(Blocked)à死亡(Dead)


线程控制:

join():调用线程将被阻塞,直到被join()加入的线程执行完为止。

publicclass JoinThread extends Thread{

              public JoinThread(String name) {

              super(name);

       }

       @Override

       publicvoid run(){

              for(int i=0;i<100;i++){

                     System.out.println(this.getName()+"\t"+i);

              }

       }

       publicstaticvoid main(String[] args) throws Exception {

              new JoinThread("新线程").start();

              for(int i=0;i<100;i++){

                     if(i==20){

                            JoinThread jt = new JoinThread("Join的线程");

                            jt.start();

                            jt.join();

                     }

              System.out.println(Thread.currentThread().getName()+"\t"+i);

              }                   

       }

}

main线程调用了jt线程的join(),mian线程必须等jt线程执行结束后才会继续执行。

setDaemon():后台线程,如果所有前台线程都死亡,JVM会通知后台线程死亡,main线程默认是后台线程,设置一个线程为后台线程,必须在线程start()前设置。

publicclass DaemonThread extends Thread {

       @Override

       publicvoid run() {

              for (int i = 0; i < 1000; i++) {

                     System.out.println(this.getName() + "\t" + i);

              }

       }

       publicstaticvoid main(String[] args) {

              DaemonThread dt = new DaemonThread();

              dt.setDaemon(true);// 设置线程为后台线程

              dt.start();

              for (int i = 0; i < 10; i++) {

                     System.out.println(Thread.currentThread().getName() + " " + i);

              }

              // 程序运行到此处,前台线程(main线程)结束,后台dt线程也随之结束

       }

}

sleep():线程休眠,并进入阻塞状态。

publicclass SleepThread {

       publicstaticvoid main(String[] args) throws Exception {

              for (int i = 0; i < 10; i++) {

                     System.out.println("Current time : "+new Date());

                     Thread.sleep(1000);

              }

       }

}

线程休眠后进入阻塞状态,并且不会获得CPU执行资格。

yield():让当前正在执行的线程暂停,但不会阻塞该线程,只是将线程转入就绪状态,暂停后只有优先级与当前线程相同,或者更高的处于就绪状态的线程财货获得CPU的执行资格。

publicclass YieldTest extends Thread {

       private String name;

       public YieldTest(String name) {

              super(name);

       }

       @Override

       publicvoid run() {

              for (int i = 0; i < 50; i++) {

                     System.out.println(this.getName() + "\t" + i);

                     if (i == 20)

                            Thread.yield();

              }

       }

       publicstaticvoid main(String[] args) {

              YieldTest y1 = new YieldTest("优先级高");

              y1.setPriority(Thread.MAX_PRIORITY);

              YieldTest y2 = new YieldTest("优先级低");

              y2.setPriority(Thread.MIN_PRIORITY);

              y1.start();

              y2.start();

       }

}

改变线程优先级:MAX_PRIOPERTY=10,MIN_PRIOPERTY=0,NORM_PRIOPERTY=5,由于修改优先级需要操作系统底层的支持,Java虽然提供了10个等级的优先级,但还是建议使用常量的方法配置优先级。

sleep()与yield()区别:

1、  sleep()暂停后,会给其它线程执行资格,不会考虑优先级,yield()只会给优先级相同或

更高的线程执行资格。

2、sleep()会暂停当前线程,并且进入阻塞状态,yield()不会,只是强制让线程进入就绪状态,一个线程可能调用yield()后,又立即获得了CPU的执行资格。

3、sleep()抛InterruptedException(),yield()没有异常。

线程同步:

当使用多个线程访问同一个数据时就有可能出现线程安全问题。例如,银行取钱:

定义账户类:

publicclass Account {

       private Account account;//模拟账户

       privatedoubledrawAmount;//取出的钱     

public Account() {

              super();

       }

       public Account(String accountNo, double balance) {

              super();

              this.accountNo = accountNo;

              this.balance = balance;

       }

       @Override

       publicint hashCode() {

              returnaccountNo.hashCode();

       }

       @Override

       publicboolean equals(Object obj) {

              if (obj == null || obj.getClass() != Account.class)

                     returnfalse;

              if (this == obj)

                     returntrue;

              Account a = (Account) obj;

              if (this.accountNo.equals(a.accountNo) && this.balance == a.balance)

                     returntrue;

              returntrue;

       }

       public String getAccountNo() {

              returnaccountNo;

       }

       publicvoid setAccountNo(String accountNo) {

              this.accountNo = accountNo;

       }

       publicdouble getBalance() {

              returnbalance;

       }

       publicvoid setBalance(double balance) {

              this.balance = balance;

       }

}

创建取钱的线程类:实现简单的取钱逻辑,判断账户余额

publicclass DrawThread extends Thread {

       private Account account;

       privatedoubledrawAmount;

       public DrawThread(String name, Account account, double drawAmount) {

              super(name);

              this.account = account;

              this.drawAmount = drawAmount;

       }

       @Override

       publicvoid run() {

              // 账户里有余额

              if (account.getBalance() >= this.drawAmount) {

                     System.out.println(this.getName() + "__取钱成功,吐出钞票:" + drawAmount);

                     account.setBalance(account.getBalance() - drawAmount);// 修改账户余额

                     System.out.println("余额:" + account.getBalance());

              } else {

                     // 账户余额不足

                     System.out.println(this.getName() + "__取钱失败,余额不足!");

              }

       }

}

测试:

publicclass DrawTest {

       publicstaticvoid main(String[] args) {

              Account acct = new Account();

              acct.setBalance(1000);

              new DrawThread("",acct,800).start();

              new DrawThread("",acct,800).start();

       }

}

输出结果:

__取钱成功,吐出钞票:800.0

__取钱成功,吐出钞票:800.0

余额:200.0

余额:-600.0

账户里只有1000K,却取出了1600K,多线程操作同一资源时出现了线程安全。原因时run()不具备安全性,程序中两个线程并发的修改了Account对象,这就问题。

Java中为了解决线程安全问题,加入了同步监视器,任何时刻只能有一个线程可以获得同步监视器的锁,当同步代码执行完后,该线程将会释放对该同步监视器的锁给其它线程使用。

修改run()加入同步代码块:

       @Override

       publicvoid run() {

              synchronized (account) {

                     // 账户里有余额

                     if (account.getBalance() >= this.drawAmount) {

                            System.out

                                          .println(this.getName() + "__取钱成功,吐出钞票:" + drawAmount);

                            account.setBalance(account.getBalance() - drawAmount);// 修改账户余额

                            System.out.println("余额:" + account.getBalance());

                     } else {

                            // 账户余额不足

                            System.out.println(this.getName() + "__取钱失败,余额不足!");

                     }

              }

       }

__取钱成功,吐出钞票:800.0

余额:200.0

__取钱失败,余额不足!

synchronized中需要传入一个需要同步的监视器对象,使用account作为同步的监视器对象,同步后,在操作Account对象时逻辑为:“加锁à操作à释放锁”。任何线程在操作共享资源时首先要加锁,加锁期间其它线程无法修改共享资源,修改完成后释放共享资源的锁。通过这种方法保证并发线程访问、修改共享资源时只能有一个线程对其操作。

使用同步方法:在Account类中添加一个取钱的方法:

       publicsynchronizedvoid draw(double drawAmount) {

              // 账户里有余额

              if (getBalance() >= drawAmount) {

                     System.out.println(Thread.currentThread().getName()

                                   + "__取钱成功,吐出钞票:" + drawAmount);

                     balance -= drawAmount;// 修改账户余额

                     System.out.println("余额:" + balance);

              } else {

                     // 账户余额不足

                     System.out.println(Thread.currentThread().getName()

                                   + "__取钱失败,余额不足!");

              }

       }

修改run():

@Override

       publicvoid run() {

              account.draw(drawAmount);

       }

synchronized关键字修饰方法时,方法就变成同步方法了,同步方法使用的对象监视器是this,调用该方法的对象。sleep()、yield()、suspend()不会释放监视器的锁。

Java1.5可以通过显示定义同步锁的方式来为共享资源加锁,使用Lock对象,每次只能有一个线程对Lock对象加锁。代码格式如下:

publicclass X {

       privatefinal Lock lock = new ReentrantLock();

       publicvoid m() {

              lock.lock();

              try {

 

              } catch (Exception e) {

                    

              } finally {

                     lock.unlock();

              }

       }

}

使用finally块保证锁一定会被释放。修改Account类,使用Lock对象为draw()加锁:

publicclass Account {

       private String accountNo;

       privatedoublebalance;

       private Lock lock = new ReentrantLock();

       public Account() {

              super();

       }

       public Account(String accountNo, double balance) {

              super();

              this.accountNo = accountNo;

              this.balance = balance;

       }

       publicvoid draw(double drawAmount) {

              lock.lock();// 显示加锁

              try {

                     // 账户里有余额

                     if (balance >= drawAmount) {

                            System.out.println(Thread.currentThread().getName()

                                          + "__取钱成功,吐出钞票:" + drawAmount);

                            balance -= drawAmount;// 修改账户余额

                            System.out.println("余额:" + balance);

                     } else {

                            // 账户余额不足

                            System.out.println(Thread.currentThread().getName()

                                          + "__取钱失败,余额不足!");

                     }

              } catch (Exception e) {

                     e.printStackTrace();

              } finally {

                     lock.unlock();// 显示释放锁

              }

       }

       @Override

       publicint hashCode() {

              returnaccountNo.hashCode();

       }

       @Override

       publicboolean equals(Object obj) {

              if (obj == null || obj.getClass() != Account.class)

                     returnfalse;

              if (this == obj)

                     returntrue;

              Account a = (Account) obj;

              if (this.accountNo.equals(a.accountNo) && this.balance == a.balance)

                     returntrue;

              returntrue;

       }

       public String getAccountNo() {

              returnaccountNo;

       }

       publicvoid setAccountNo(String accountNo) {

              this.accountNo = accountNo;

       }

       publicdouble getBalance() {

              returnbalance;

       }

       publicvoid setBalance(double balance) {

              this.balance = balance;

       }

}

一个线程可以对已经被加锁的ReentrantLock锁再次加锁,ReentrantLock对象维持一个计数器来追踪lock()方法的嵌套调用。

当两个线程相互等待对象释放同步监视器时就会发生死锁。

死锁实例1:

publicclass A {

       publicsynchronizedvoid A2B(B b) {

              System.out.println("当前线程:" + Thread.currentThread().getName()

                            + "进入method() in class A");

              try {

                     Thread.sleep(2000);

              } catch (Exception e) {

                     e.printStackTrace();

              }

              System.out.println("当前线程:" + Thread.currentThread().getName()

                            + "准备进入method() in class B");

              b.method();

       }

       publicvoid mehod() {

              System.out.println("method() in class A");

       }

}

publicclass B {

       publicsynchronizedvoid B2A(A a) {

              System.out.println("当前线程:" + Thread.currentThread().getName()

                            + "进入method() in class B");

              try {

                     Thread.sleep(2000);

              } catch (Exception e) {

                     e.printStackTrace();

              }

              System.out.println("当前线程:" + Thread.currentThread().getName()

                            + "准备进入method() in class A");

              a.mehod();

       }

       publicvoid method() {

              System.out.println("method() in class B");

       }

}

publicclass DeadLock implements Runnable {

       A a = new A();

       B b = new B();

       publicvoid init() {

              Thread.currentThread().setName("主线程");

              a.A2B(b);

              System.out.println("进入主线程之后");

       }

       @Override

       publicvoid run() {

              Thread.currentThread().setName("副线程");

              b.B2A(a);

              System.out.println("进入副线程之后");

       }

       publicstaticvoid main(String[] args) {

              DeadLock dead = new DeadLock();

              new Thread(dead).start();

              dead.init();

       }

}

死多实例2:

class Test implements Runnable {

       privatebooleanflag;

       public Test(boolean flag) {

              this.flag = flag;

       }

       @Override

       publicvoid run() {

              if (flag) {

                     synchronized (MyLock.locka) {

                            System.out.println("if locka");

                            synchronized (MyLock.lockb) {

                                   System.out.println("if lockb");

                            }

                     }

              } else {

                     synchronized (MyLock.locka) {

                            System.out.println("if lockb");

                            synchronized (MyLock.lockb) {

                                   System.out.println("if locka");

                            }

                     }

              }

       }

}

class MyLock {

       publicstatic Object locka = new Object();

       publicstatic Object lockb = new Object();

}

/**

 * 多线程的死锁

 * @author RockLee

 *

 */

publicclass DeadLockDemo {

       publicstaticvoid main(String[] args) {

              Test t1 = new Test(true);

              Test t2 = new Test(false);

              new Thread(t1).start();

              new Thread(t2).start();

       }

}

线程间通信:

Object类中有三个方法用于实现线程间通信,wait()、notify()、notifyAll(),这三个方法必须有同步锁的监视器对象调用,synchronized方法使用this调用,synchronized块使用同步监视器对象调用。

wait():当前调用线程等待,直到其它线程使用同步监视器对象调用notify()、notifyAll()后才能唤醒。

notify():唤醒当前同步锁上的单个线程。

notifyAll():唤醒等待当前同步锁上的所有线程。

使用两个线程模拟存钱者和取钱者,不断重复存/取操作,但要求必须存一次,取一次。

定义Account账户类:

publicclass Account {

       private String accountNo;// 账户编号

       privatedoublebalace;// 帐户余额

       privatebooleanflag = false;// 定义存/取钱标记位

       public Account(String accountNo, double balace) {

              super();

              this.accountNo = accountNo;

              this.balace = balace;

       }

       /**

        * 定义存钱方法,flag=true时,表示已经存过钱了,账户里有钱,线程等待

        *

        * @param drawAmount

        */

       publicsynchronizedvoid deposit(double depositAmount) {

              if (flag) {

                     try {

                            this.wait();

                     } catch (InterruptedException e) {

                            e.printStackTrace();

                     }

              } else {

                     System.out.println(Thread.currentThread().getName() + "存款:"

                                   + depositAmount);

                     balace += depositAmount;

                     System.out.println("账户余额:" + balace);

                     flag = true;// 存入钱后改变标记位

                     this.notifyAll();// 唤醒等待线程

              }

       }

       /**

        * 取钱方法,flag=false时,表示已经取过钱了,等待

        *

        * @param drawAmount

        */

       publicsynchronizedvoid draw(double drawAmount) {

              if (!flag) {

                     try {

                            this.wait();

                     } catch (InterruptedException e) {

                            e.printStackTrace();

                     }

              } else {

                     System.out.println(Thread.currentThread().getName() + "取钱:"

                                   + drawAmount);

                     balace -= drawAmount;

                     System.out.println("账户余额:" + balace);

                     flag = false;

                     this.notifyAll();

              }

       }

       public String getAccountNo() {

              returnaccountNo;

       }

       publicvoid setAccountNo(String accountNo) {

              this.accountNo = accountNo;

       }

       publicdouble getBalace() {

              returnbalace;

       }

}

定义一个线程模拟取钱100次:

publicclass DrawThread extends Thread {

       private Account account;

       privatedoubledrawAmount;

       public DrawThread(String name,Account account, double drawAmount) {

              super(name);

              this.account = account;

              this.drawAmount = drawAmount;

       }

       @Override

       publicvoid run() {

              // 模拟取钱100

              for (int i = 0; i < 100; i++) {

                     account.draw(drawAmount);

              }

       }

}

定义一个线程模拟存钱100次:

publicclass DepositThread extends Thread {

       private Account account;

       privatedoubledepositAmount;

       public DepositThread(String name, Account account, double depositAmount) {

              super(name);

              this.account = account;

              this.depositAmount = depositAmount;

       }

       @Override

       publicvoid run() {

              // 模拟存钱100

              for (int i = 0; i < 100; i++) {

                     account.deposit(depositAmount);

              }

       }

}

测试:

publicclass DrwaTest {

       publicstaticvoid main(String[] args) {

              Account account = new Account("110", 0);

              new DrawThread("取钱", account, 800).start();

              new DepositThread("存钱甲", account, 800).start();

       }

}

存钱甲存款:800.0

账户余额:800.0

取钱取钱:800.0

账户余额:0.0

线程池:

系统启动一个线程的成本比较高,尤其需要创建大量生存周期短暂的线程时,就不得不考虑性能,使用线程池可以很好的提高性能。线程池在系统创建启动时即创建了大量空闲线程,程序将线程运行的对象传给线程池,线程池会启动一个线程来执行run(),当run()结束后线程池中的线程不会死亡,将再次返回线程池成为空闲状态,等待执行下一个线程运行的对象。

创建线程池:

1、  Excutors工厂类创建ExecutorService对象,ExecutorService代表线程池类对象;

2、  创建线程类对象

3、     ExecutorService调用submit()执行线程类run();

publicclass ThreadPoolTest {

       publicstaticvoid main(String[] args) {

              ExecutorService pool = Executors.newFixedThreadPool(6);// 创建线程池对象,池中有6个线程

              // 创建并通过线程池启动一个线程

              pool.submit(new Runnable() {

                     @Override

                     publicvoid run() {

                            for (int i = 0; i < 100; i++) {                                  System.out.println(Thread.currentThread().getName()

                                                 + "--->" + i);

                            }

                     }

              });

              // 创建并通过线程池启动一个线程

              pool.submit(new Runnable() {

                     @Override

                     publicvoid run() {

                            for (int i = 0; i < 100; i++) {                                  System.out.println(Thread.currentThread().getName()

                                                 + "--->" + i);

                            }

                     }

              });

              pool.shutdown();// 关闭线程池

       }

}

 

 

 

 


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值