第十六章.多线程

线程的创建和启动:

  1.定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体

  2.创建Thread子类的实例,即创建了线程的对象

  3.调用线程对象的start()方法来启动该线程

 1 public class FirstThread extends Thread{
 2     private int i;
 3     //重写run()方法,run()方法的方法体就是线程的执行体
 4     public void run(){
 5         for(; i < 100; i++){
 6             //当线程类继承Thread类时,直接使用this即可获取当前线程
 7             //Thread对象的getName9)返回当前线程的名字
 8             //因此可以直接调用getName()方法返回当前线程的名字
 9             System.out.println(getName() + " " + i);
10         }
11     }
12     
13     public static void main(String[] args){
14         for(int i = 0; i < 100; i++){
15             //调用Thread的currentThread()方法获取当前线程
16             System.out.println(Thread.currentThread().getName() + " " + i);
17             if(i == 20){
18                 //创建并启动第一个线程
19                 new FirstThread().start();
20                 //创建并启动第二个线程
21                 new FirstThread().start();
22             }
23         }
24     }
25 }
View Code

  使用继承Thread类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量。

实现Runnable接口创建线程类:

  1.定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法体同样是该线程的线程执行体。

  2.创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象

  //创建Runnable实现类的对象

  SecondThread st = new SecondThread();

  //以Runnable实现类的对象作为Thread的target来创建Thread对象,即线程对象

  new Thread(st);

  也可以在创建Thread线程的时候为该Thread对象指定一个名字:

  new Thread(st, "新线程1");

  Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其

   target的run()方法。

  3.调用线程对象的start()方法来启动该线程

 1 public class SecondThread implements Runnable{
 2     private int i;
 3     //run()方法同样是线程执行体
 4     public void run(){
 5         for(; i < 100; i++){
 6             //当线程类实现Runnable接口时
 7             //若想获取当前线程,只能用Thread.currentThread()方法
 8             //因此可以直接调用getName()方法返回当前线程的名字
 9             System.out.println(Thread.currentThread().getName() + " " + i);
10         }
11     }
12     
13     public static void main(String[] args){
14         for(int i = 0; i < 100; i++){
15             //调用Thread的currentThread()方法获取当前线程
16             System.out.println(Thread.currentThread().getName() + " " + i);
17             if(i == 20){
18                 SecondThread st = new SecondThread();
19                 //通过new Thread(target, name)方法创建新线程
20                 new Thread(st, "新线程1").start();
21                 new Thread(st, "新线程2").start();
22             }
23         }
24     }
25 }
View Code

    通过实现Runnable接口来获得当前线程对象,必须使用Thread.currentThread()方法。

    Runnable接口只包含一个抽象方法,所以是函数式接口;Callable接口也是函数式接口。

    从上面运行结果可以看出,新线程1和新线程2的两个i值没有重复,这说明采用Runnable接口的方式创建的多线程可以共享线程类的实例变量。这是因为在这种方式下,程

     序所创建额Runnable对象只是线程的target,而多个线程可以共享同一个target,所以多线程可以共享同一个线程的target类的实例变量。

使用Callable和Future创建线程:

  Callable接口提供了一个call()方法,可以作为线程执行体,但call()方法比run()方法更强大:

    1.call()方法有返回值

    2.call()方法可以声明抛出异常

  可以完全提供一个Callable对象作为Thread的target,而该线程的线程执行体就是该Callable对象的call()方法

  Java5提供了Future接口来代表Callable接口里call()方法的返回值,并为接口Future提供了一个FutureTask实现类,该实现类实现了Future接口,并实现了Runnable接口——可

   以作为Thread类的target。

  Callable接口有泛型限制,Callable接口里的泛型形参类型与call()方法返回值类型相同。且Callable接口是函数式接口。

  创建并启动有返回值的线程的步骤如下:

    1.创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法由返回值,在创建Callable实现类的实例。

    2.使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值

    3.使用FutureTask对象作为Thread对象的target创建并启动新线程

    4.调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

 1 import java.util.concurrent.FutureTask;
 2 import java.util.concurrent.Callable;
 3 
 4 public class ThirdThread{
 5     public static void main(String[] args){
 6         //先使用Lambda表达式创建Callable<Integer>对象
 7         //使用FutureTask来包装Callable对象
 8         FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>) () -> {
 9             int i = 0;
10             for( ; i < 100; i++){
11                 System.out.println(Thread.currentThread().getName() + " 的循环变量i的值:" + i);
12             }
13             //call()方法可以有返回值
14             return i;
15         });
16         for(int i = 0; i < 100; i++){
17             System.out.println(Thread.currentThread().getName() + " 的循环变量i的值:" + i);
18             if(i == 20){
19                 //实质还是以Callable对象来创建并启动线程
20                 new Thread(task, "有返回值的线程").start();
21             }
22         }
23         
24         try{
25             //获取线程返回值
26             System.out.println("子线程的返回值:" + task.get());
27         }catch(Exception ex){
28             ex.printStackTrace();
29         }
30     }
31 }
View Code

 1 public class InvokeRun extends Thread{
 2     private int i;
 3     //重写run()方法,run()方法的方法体就是线程执行体
 4     public void run(){
 5         for( ; i < 100; i++){
 6             //直接调用run()方法时,Thread的this.getName()返回的是该对象的名字
 7             //而不是当前线程的名字
 8             //使用Thread.currentThread().getName()总是获取当前线程的名字
 9             System.out.println(Thread.currentThread().getName() + " " + i);
10         }
11     }
12     
13     public static void main(String[] args){
14         for(int i = 0; i < 100; i++){
15             //调用Thread的currentThread()方法获取当前线程
16             System.out.println(Thread.currentThread().getName() + " " + i);
17             if( i == 20){
18                 //直接调用线程对象的run()方法
19                 //系统会把线程对象当成普通对象,把run()方法当成普通方法
20                 //所以下面两行代码并不会启动两个线程,而是一次执行两个run()方法
21                 new InvokeRun().run();
22                 new InvokeRun().run();
23             }
24         }
25     }
26 }
View Code

 1 public class StartDead extends Thread{
 2     private int i;
 3     //重写run()方法,run()方法的方法体就是线程执行体
 4     public void run(){
 5         for( ; i < 100; i++){
 6             System.out.println(getName() + " " + i);
 7         }
 8     }
 9     
10     public static void main(String[] args){
11         //创建线程对象
12         StartDead sd = new StartDead();
13         for(int i = 0; i < 300; i++){
14             //调用Thread的currentThread()方法获取当前线程
15             System.out.println(Thread.currentThread().getName() + " " + i);
16             if( i == 20){
17                 //启动线程
18                 sd.start();
19                 //判断启动后线程的isAlive()值,输出true
20                 System.out.println(sd.isAlive());
21             }
22             //当线程处于新建、死亡两种状态时,isAlive()方法返回false
23             //当i > 20时,该线程肯定已经启动过了,若sd.isAlive()为假时
24             //那么就是死亡状态了
25             if(i > 20 && !sd.isAlive()){
26                 //试图再次启动该线程
27                 sd.start();
28             }
29         }
30     }
31 }
View Code

    不要对处于死亡状态的线程调用start()方法,程序只能对新建状态的线程调用start()方法,对新建线程调用两次start()方法也是错误的。

控制线程:

  join线程:

    Thread提供了让一个线程等待另一个线程完成的方法——join()方法。

    当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的join线程执行完为止。

    join()方法通常由使用线程的程序调用,以将大问题划分成许多小问题,每个小问题分配一个线程。当所有小问题得到处理后,再调用主线程进一步操作:

 1 public class JoinThread extends Thread{
 2     //提供一个有参数的构造器,用于设置该线程的名字
 3     public JoinThread(String name){
 4         super(name);
 5     }
 6     //重写run()方法,定义线程执行体
 7     public void run(){
 8         for(int i = 0; i < 100; i++){
 9             System.out.println(getName() + " " + i);
10         }
11     }
12     
13     public static void main(String[] args) throws Exception{
14         //启动子线程
15         new JoinThread("新线程").start();
16         for(int i = 0; i < 100; i++){
17             
18             if(i == 20){
19                 JoinThread jt = new JoinThread("被Join的线程");
20                 jt.start();
21                 //main线程调用了jt线程的join()方法,main线程必须等jt执行结束才会向下执行
22                 jt.join();
23             }
24             System.out.println(Thread.currentThread().getName() + " " + i);
25         }
26     }
27 }
View Code

    当主线程的循环变量i等于20时,启动了名为“被Join的线程”的线程,该线程不会和main线程并发执行,main必须等该线程执行结束后才可以向下执行。

后台线程:

  在后台运行,它的任务是为其他的线程提供服务,这种线程被称为“后台线程”(Daemon Thread),JVM的垃圾回收线程就是典型的后台线程。

  后台线程有个特征:若所有的前台线程都死亡,后台线程会自动死亡。

  调用Thread对象的setDaemon(true)方法可将指定线程设置成后台线程。

 1 public class DaemonThread extends Thread{
 2     //定义后台线程的线程执行体与普通线程没有任何区别
 3     public void run(){
 4         for(int i = 0; i < 1000; i++){
 5             System.out.println(getName() + " " + i);
 6         }
 7     }
 8     
 9     public static void main(String[] args){
10         DaemonThread t = new DaemonThread();
11         //将此线程设置成后台线程
12         t.setDaemon(true);
13         //启动后台线程
14         t.start();
15         for(int i = 0; i < 10; i++){
16             System.out.println(Thread.currentThread().getName() + " " + i);
17         }
18         //------程序执行到此处,前台线程(main线程)结束------
19         //后台线程也应该随之结束
20     }
21 }
View Code

    从程序中可以看出主线程默认是前台线程,前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程。

    setDaemon(true)必须在start()方法之前调用。

线程睡眠:

  让当前正在执行的线程暂停一段时间,并进入阻塞状态,可通过调用Thread类的静态sleep()方法来实现。

 1 import java.util.Date;
 2 
 3 public class SleepTest{
 4     public static void main(String[] args) throws Exception{
 5         for(int i = 0; i < 10; i++){
 6             System.out.println("当前时间:" + new Date());
 7             //调用sleep()方法让当前线程暂停1s
 8             Thread.sleep(1000);
 9         }
10     }
11 }
View Code

    让线程睡眠1s。

线程让步:

  yield()方法是一个和sleep()方法有点相似的方法,yield()也是Thread类提供的一个静态方法。可以让当前正在执行的线程暂停,但不会阻塞该线程,只是将该线程转入就绪状态

  实际上,当某个线程调用了yield()方法暂停之后,只有优先级和当前线程相同,或优先级比当前线程更高的处于就绪状态的线程才会获得执行的机会。

 1 public class YieldTest extends Thread{
 2     public YieldTest(String name){
 3         super(name);
 4     }
 5     //定义run()方法作为线程执行体
 6     public void run(){
 7         for(int i = 0; i < 50; i++){
 8             System.out.println(getName() + " " + i);
 9             //当i等于20时,使用yield()方法让当前进程让步
10             if(i == 20){
11                 Thread.yield();
12             }
13         }
14     }
15     
16     public static void main(String[] args){
17         //启动两个并发线程
18         YieldTest yt1 = new YieldTest("高级");
19         //将yt1线程设置成最高优先级
20         yt1.setPriority(Thread.MAX_PRIORITY);
21         yt1.start();
22         YieldTest yt2 = new YieldTest("低级");
23         //将yt2线程设置成最低级优先级
24         yt2.setPriority(Thread.MIN_PRIORITY);
25         yt2.start();
26     }
27 }
View Code

    由于电脑是多CPU,所以yield()方法不明显。

改变线程的优先级:

  setPriority()方法用来改变线程的优先级:

 1 public class PriorityTest extends Thread{
 2     //定义一个有参数的构造器,用于创建线程时指定name
 3     public PriorityTest(String name){
 4         super(name);
 5     }
 6     public void run(){
 7         for(int i = 0; i < 50; i++){
 8             System.out.println(getName() + ",其优先级是:" + getPriority() + ",循环变量的值为:" + i);
 9         }
10     }
11     
12     public static void main(String[] args){
13         //改变主线程的优先级
14         Thread.currentThread().setPriority(6);
15         for(int i = 0; i < 30; i++){
16             if(i == 10){
17                 PriorityTest low = new PriorityTest("低级");
18                 low.start();
19                 System.out.println("创建之初的优先级:" + low.getPriority());
20                 //设置该线程为最低优先级
21                 low.setPriority(Thread.MIN_PRIORITY);
22             }
23             if(i == 20){
24                 PriorityTest high = new PriorityTest("高级");
25                 high.start();
26                 System.out.println("创建之初的优先级:" + high.getPriority());
27                 //设置该线程为最高优先级
28                 high.setPriority(Thread.MAX_PRIORITY);
29             }
30         }
31     }
32 }
View Code

    改变了主线程的优先级为6,所以主线程所创建的子线程的优先级默认都是6。

线程同步:

  线程安全问题:

  有一个经典的问题——银行取钱问题:

    1.用户输入账号、密码,系统判断用户的账号、密码是否匹配。

    2.用户输入取款金额

    3.系统判断账户余额是否大于取款金额

    4.若余额大于取款金额,则取款成功;小于取款金额,则取款失败

 1 public class Account
 2 {
 3     // 封装账户编号、账户余额的两个成员变量
 4     private String accountNo;
 5     private double balance;
 6     public Account(){}
 7     // 构造器
 8     public Account(String accountNo , double balance)
 9     {
10         this.accountNo = accountNo;
11         this.balance = balance;
12     }
13     // 此处省略了accountNo和balance的setter和getter方法
14 
15     // accountNo的setter和getter方法
16     public void setAccountNo(String accountNo)
17     {
18         this.accountNo = accountNo;
19     }
20     public String getAccountNo()
21     {
22         return this.accountNo;
23     }
24 
25     // balance的setter和getter方法
26     public void setBalance(double balance)
27     {
28         this.balance = balance;
29     }
30     public double getBalance()
31     {
32         return this.balance;
33     }
34 
35     // 下面两个方法根据accountNo来重写hashCode()和equals()方法
36     public int hashCode()
37     {
38         return accountNo.hashCode();
39     }
40     public boolean equals(Object obj)
41     {
42         if(this == obj)
43             return true;
44         if (obj != null && obj.getClass() == Account.class)
45         {
46             Account target = (Account)obj;
47             return target.getAccountNo().equals(accountNo);
48         }
49         return false;
50     }
51 }
View Code
 1 public class DrawThread extends Thread
 2 {
 3     // 模拟用户账户
 4     private Account account;
 5     // 当前取钱线程所希望取的钱数
 6     private double drawAmount;
 7     public DrawThread(String name , Account account
 8         , double drawAmount)
 9     {
10         super(name);
11         this.account = account;
12         this.drawAmount = drawAmount;
13     }
14     // 当多条线程修改同一个共享数据时,将涉及数据安全问题。
15     public void run()
16     {
17         // 账户余额大于取钱数目
18         if (account.getBalance() >= drawAmount)
19         {
20             // 吐出钞票
21             System.out.println(getName()
22                 + "取钱成功!吐出钞票:" + drawAmount);
23             
24             try
25             {
26                 Thread.sleep(1);
27             }
28             catch (InterruptedException ex)
29             {
30                 ex.printStackTrace();
31             }
32             
33             // 修改余额
34             account.setBalance(account.getBalance() - drawAmount);
35             System.out.println("\t余额为: " + account.getBalance());
36         }
37         else
38         {
39             System.out.println(getName() + "取钱失败!余额不足!");
40         }
41     }
42 }
View Code
 1 public class DrawTest
 2 {
 3     public static void main(String[] args)
 4     {
 5         // 创建一个账户
 6         Account acct = new Account("1234567" , 1000);
 7         // 模拟两个线程对同一个账户取钱
 8         new DrawThread("甲" , acct , 800).start();
 9         new DrawThread("乙" , acct , 800).start();
10     }
11 }
View Code

  上面结果都是错误的,所以这就是线程的安全问题。

同步代码块:

  上面代码出错的主要原因是在执行run()方法的时候同一个线程没有一次执行完毕,导致余额总是大于800。

  为了解决上面出现的问题,Java的多线程支持引入同步监视器,使用同步件事器的通用方法就是同步代码块。格式如下:

    synchronized(obj){

      ...

      //此处的代码就是同步代码块

    }

  上面格式中synchronized括号中的obj就是同步监视器,含义是:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。

  任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。

  同步监视器的目的:阻止两个线程对同一个共享资源进行并发访问,因此通常推荐使用可能被并发访问的共享资源充当同步监视器,对于上面的取钱模拟程序,应考虑账户作为

   同步监视器:

 1 public class DrawThread extends Thread
 2 {
 3     // 模拟用户账户
 4     private Account account;
 5     // 当前取钱线程所希望取的钱数
 6     private double drawAmount;
 7     public DrawThread(String name , Account account
 8         , double drawAmount)
 9     {
10         super(name);
11         this.account = account;
12         this.drawAmount = drawAmount;
13     }
14     // 当多条线程修改同一个共享数据时,将涉及数据安全问题。
15     public void run()
16     {
17         // 使用account作为同步监视器,任何线程进入下面同步代码块之前,
18         // 必须先获得对account账户的锁定——其他线程无法获得锁,也就无法修改它
19         // 这种做法符合:“加锁 → 修改 → 释放锁”的逻辑
20         synchronized (account)
21         {
22             // 账户余额大于取钱数目
23             if (account.getBalance() >= drawAmount)
24             {
25                 // 吐出钞票
26                 System.out.println(getName()
27                     + "取钱成功!吐出钞票:" + drawAmount);
28                 try
29                 {
30                     Thread.sleep(1);
31                 }
32                 catch (InterruptedException ex)
33                 {
34                     ex.printStackTrace();
35                 }
36                 // 修改余额
37                 account.setBalance(account.getBalance() - drawAmount);
38                 System.out.println("\t余额为: " + account.getBalance());
39             }
40             else
41             {
42                 System.out.println(getName() + "取钱失败!余额不足!");
43             }
44         }
45         // 同步代码块结束,该线程释放同步锁
46     }
47 }
View Code

    上面程序使用synchronized将run()方法体修改成同步代码块,该同步代码块的同步监视器是account对象。

同步方法:

  就是使用synchronized关键字修饰某个方法。对于synchronized修饰的实例方法(非static方法)而言,无需显式指定同步监视器,同步方法的同步监视器是this,也就是调用该

   方法的对象。

 1 public class Account
 2 {
 3     // 封装账户编号、账户余额两个成员变量
 4     private String accountNo;
 5     private double balance;
 6     public Account(){}
 7     // 构造器
 8     public Account(String accountNo , double balance)
 9     {
10         this.accountNo = accountNo;
11         this.balance = balance;
12     }
13 
14     // accountNo的setter和getter方法
15     public void setAccountNo(String accountNo)
16     {
17         this.accountNo = accountNo;
18     }
19     public String getAccountNo()
20     {
21         return this.accountNo;
22     }
23     // 因此账户余额不允许随便修改,所以只为balance提供getter方法,
24     public double getBalance()
25     {
26         return this.balance;
27     }
28 
29     // 提供一个线程安全draw()方法来完成取钱操作
30     public synchronized void draw(double drawAmount)
31     {
32         // 账户余额大于取钱数目
33         if (balance >= drawAmount)
34         {
35             // 吐出钞票
36             System.out.println(Thread.currentThread().getName()
37                 + "取钱成功!吐出钞票:" + drawAmount);
38             try
39             {
40                 Thread.sleep(1);
41             }
42             catch (InterruptedException ex)
43             {
44                 ex.printStackTrace();
45             }
46             // 修改余额
47             balance -= drawAmount;
48             System.out.println("\t余额为: " + balance);
49         }
50         else
51         {
52             System.out.println(Thread.currentThread().getName()
53                 + "取钱失败!余额不足!");
54         }
55     }
56 
57     // 下面两个方法根据accountNo来重写hashCode()和equals()方法
58     public int hashCode()
59     {
60         return accountNo.hashCode();
61     }
62     public boolean equals(Object obj)
63     {
64         if(this == obj)
65             return true;
66         if (obj !=null
67             && obj.getClass() == Account.class)
68         {
69             Account target = (Account)obj;
70             return target.getAccountNo().equals(accountNo);
71         }
72         return false;
73     }
74 }
View Code
 1 public class DrawThread extends Thread
 2 {
 3     // 模拟用户账户
 4     private Account account;
 5     // 当前取钱线程所希望取的钱数
 6     private double drawAmount;
 7     public DrawThread(String name , Account account
 8         , double drawAmount)
 9     {
10         super(name);
11         this.account = account;
12         this.drawAmount = drawAmount;
13     }
14     // 当多条线程修改同一个共享数据时,将涉及数据安全问题。
15     public void run()
16     {
17         // 直接调用account对象的draw方法来执行取钱
18         // 同步方法的同步监视器是this,this代表调用draw()方法的对象。
19         // 也就是说:线程进入draw()方法之前,必须先对account对象的加锁。
20         account.draw(drawAmount);
21     }
22 }
View Code
 1 public class DrawTest
 2 {
 3     public static void main(String[] args)
 4     {
 5         // 创建一个账户
 6         Account acct = new Account("1234567" , 1000);
 7         // 模拟两个线程对同一个账户取钱
 8         new DrawThread("甲" , acct , 800).start();
 9         new DrawThread("乙" , acct , 800).start();
10     }
11 }
View Code

同步锁:

  java5提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。

 1 import java.util.concurrent.locks.*;
 2 
 3 public class Account
 4 {
 5     // 定义锁对象
 6     private final ReentrantLock lock = new ReentrantLock();
 7     // 封装账户编号、账户余额的两个成员变量
 8     private String accountNo;
 9     private double balance;
10     public Account(){}
11     // 构造器
12     public Account(String accountNo , double balance)
13     {
14         this.accountNo = accountNo;
15         this.balance = balance;
16     }
17 
18     // accountNo的setter和getter方法
19     public void setAccountNo(String accountNo)
20     {
21         this.accountNo = accountNo;
22     }
23     public String getAccountNo()
24     {
25         return this.accountNo;
26     }
27     // 因此账户余额不允许随便修改,所以只为balance提供getter方法,
28     public double getBalance()
29     {
30         return this.balance;
31     }
32 
33     // 提供一个线程安全draw()方法来完成取钱操作
34     public void draw(double drawAmount)
35     {
36         // 加锁
37         lock.lock();
38         try
39         {
40             // 账户余额大于取钱数目
41             if (balance >= drawAmount)
42             {
43                 // 吐出钞票
44                 System.out.println(Thread.currentThread().getName()
45                     + "取钱成功!吐出钞票:" + drawAmount);
46                 try
47                 {
48                     Thread.sleep(1);
49                 }
50                 catch (InterruptedException ex)
51                 {
52                     ex.printStackTrace();
53                 }
54                 // 修改余额
55                 balance -= drawAmount;
56                 System.out.println("\t余额为: " + balance);
57             }
58             else
59             {
60                 System.out.println(Thread.currentThread().getName()
61                     + "取钱失败!余额不足!");
62             }
63         }
64         finally
65         {
66             // 修改完成,释放锁
67             lock.unlock();
68         }
69     }
70 
71     // 下面两个方法根据accountNo来重写hashCode()和equals()方法
72     public int hashCode()
73     {
74         return accountNo.hashCode();
75     }
76     public boolean equals(Object obj)
77     {
78         if(this == obj)
79             return true;
80         if (obj !=null
81             && obj.getClass() == Account.class)
82         {
83             Account target = (Account)obj;
84             return target.getAccountNo().equals(accountNo);
85         }
86         return false;
87     }
88 }
View Code

  在Account类中定义ReentrantLock对象,在draw()方法中上锁,执行draw()方法后释放锁。

死锁:

  死锁的情况很容易发生,如在系统中出现多个同步监听器:

 1 class A
 2 {
 3     public synchronized void foo( B b )
 4     {
 5         System.out.println("当前线程名: " + Thread.currentThread().getName()
 6             + " 进入了A实例的foo()方法" );     //
 7         try
 8         {
 9             Thread.sleep(200);
10         }
11         catch (InterruptedException ex)
12         {
13             ex.printStackTrace();
14         }
15         System.out.println("当前线程名: " + Thread.currentThread().getName()
16             + " 企图调用B实例的last()方法");    //
17         b.last();
18     }
19     public synchronized void last()
20     {
21         System.out.println("进入了A类的last()方法内部");
22     }
23 }
24 class B
25 {
26     public synchronized void bar( A a )
27     {
28         System.out.println("当前线程名: " + Thread.currentThread().getName()
29             + " 进入了B实例的bar()方法" );   //
30         try
31         {
32             Thread.sleep(200);
33         }
34         catch (InterruptedException ex)
35         {
36             ex.printStackTrace();
37         }
38         System.out.println("当前线程名: " + Thread.currentThread().getName()
39             + " 企图调用A实例的last()方法");  //
40         a.last();
41     }
42     public synchronized void last()
43     {
44         System.out.println("进入了B类的last()方法内部");
45     }
46 }
47 public class DeadLock implements Runnable
48 {
49     A a = new A();
50     B b = new B();
51     public void init()
52     {
53         Thread.currentThread().setName("主线程");
54         // 调用a对象的foo方法
55         a.foo(b);
56         System.out.println("进入了主线程之后");
57     }
58     public void run()
59     {
60         Thread.currentThread().setName("副线程");
61         // 调用b对象的bar方法
62         b.bar(a);
63         System.out.println("进入了副线程之后");
64     }
65     public static void main(String[] args)
66     {
67         DeadLock dl = new DeadLock();
68         // 以dl为target启动新线程
69         new Thread(dl).start();
70         // 调用init()方法
71         dl.init();
72     }
73 }
View Code

线程通信:

  1 public class Account
  2 {
  3     // 封装账户编号、账户余额的两个成员变量
  4     private String accountNo;
  5     private double balance;
  6     // 标识账户中是否已有存款的旗标
  7     private boolean flag = false;
  8 
  9     public Account(){}
 10     // 构造器
 11     public Account(String accountNo , double balance)
 12     {
 13         this.accountNo = accountNo;
 14         this.balance = balance;
 15     }
 16 
 17     // accountNo的setter和getter方法
 18     public void setAccountNo(String accountNo)
 19     {
 20         this.accountNo = accountNo;
 21     }
 22     public String getAccountNo()
 23     {
 24         return this.accountNo;
 25     }
 26     // 因此账户余额不允许随便修改,所以只为balance提供getter方法,
 27     public double getBalance()
 28     {
 29         return this.balance;
 30     }
 31 
 32     public synchronized void draw(double drawAmount)
 33     {
 34         try
 35         {
 36             // 如果flag为假,表明账户中还没有人存钱进去,取钱方法阻塞
 37             if (!flag)
 38             {
 39                 wait();
 40             }
 41             else
 42             {
 43                 // 执行取钱
 44                 System.out.println(Thread.currentThread().getName()
 45                     + " 取钱:" +  drawAmount);
 46                 balance -= drawAmount;
 47                 System.out.println("账户余额为:" + balance);
 48                 // 将标识账户是否已有存款的旗标设为false。
 49                 flag = false;
 50                 // 唤醒其他线程
 51                 notifyAll();
 52             }
 53         }
 54         catch (InterruptedException ex)
 55         {
 56             ex.printStackTrace();
 57         }
 58     }
 59     public synchronized void deposit(double depositAmount)
 60     {
 61         try
 62         {
 63             // 如果flag为真,表明账户中已有人存钱进去,则存钱方法阻塞
 64             if (flag)             //
 65             {
 66                 wait();
 67             }
 68             else
 69             {
 70                 // 执行存款
 71                 System.out.println(Thread.currentThread().getName()
 72                     + " 存款:" +  depositAmount);
 73                 balance += depositAmount;
 74                 System.out.println("账户余额为:" + balance);
 75                 // 将表示账户是否已有存款的旗标设为true
 76                 flag = true;
 77                 // 唤醒其他线程
 78                 notifyAll();
 79             }
 80         }
 81         catch (InterruptedException ex)
 82         {
 83             ex.printStackTrace();
 84         }
 85     }
 86 
 87     // 下面两个方法根据accountNo来重写hashCode()和equals()方法
 88     public int hashCode()
 89     {
 90         return accountNo.hashCode();
 91     }
 92     public boolean equals(Object obj)
 93     {
 94         if(this == obj)
 95             return true;
 96         if (obj !=null
 97             && obj.getClass() == Account.class)
 98         {
 99             Account target = (Account)obj;
100             return target.getAccountNo().equals(accountNo);
101         }
102         return false;
103     }
104 }
View Code
 1 public class DrawThread extends Thread
 2 {
 3     // 模拟用户账户
 4     private Account account;
 5     // 当前取钱线程所希望取的钱数
 6     private double drawAmount;
 7     public DrawThread(String name , Account account
 8         , double drawAmount)
 9     {
10         super(name);
11         this.account = account;
12         this.drawAmount = drawAmount;
13     }
14     // 重复100次执行取钱操作
15     public void run()
16     {
17         for (int i = 0 ; i < 100 ; i++ )
18         {
19             account.draw(drawAmount);
20         }
21     }
22 }
View Code
 1 public class DepositThread extends Thread
 2 {
 3     // 模拟用户账户
 4     private Account account;
 5     // 当前取钱线程所希望存款的钱数
 6     private double depositAmount;
 7     public DepositThread(String name , Account account
 8         , double depositAmount)
 9     {
10         super(name);
11         this.account = account;
12         this.depositAmount = depositAmount;
13     }
14     // 重复100次执行存款操作
15     public void run()
16     {
17         for (int i = 0 ; i < 100 ; i++ )
18         {
19             account.deposit(depositAmount);
20         }
21     }
22 }
View Code
 1 public class DrawTest
 2 {
 3     public static void main(String[] args)
 4     {
 5         // 创建一个账户
 6         Account acct = new Account("1234567" , 0);
 7         new DrawThread("取钱者" , acct , 800).start();
 8         new DepositThread("存款者甲" , acct , 800).start();
 9         new DepositThread("存款者乙" , acct , 800).start();
10         new DepositThread("存款者丙" , acct , 800).start();
11     }
12 }
View Code

    上面程序代表存款者和取款者,存款者将钱存入账户唤醒取款者,取款者从账户取出钱后唤醒所有线程。取款者是随机的。程序使用synchronized方法,flag标志位,wait()

     方法、notify()方法、notifyAll()方法进行同步操作。

使用Condition控制线程通信:  

  Condition和Lock对象协作,实现上面程序的功能。这时候Lock替代了同步方法或同步代码块,Condition替代了同步监视器的功能。

  Condition实例被绑定在一个Lock对象上。要获得特定Lock实例的Condition实例,调用Lock对象的newCondition()方法即可。

修改上面程序的Account代码:

  1 import java.util.concurrent.*;
  2 import java.util.concurrent.locks.*;
  3 
  4 public class Account
  5 {
  6     // 显式定义Lock对象
  7     private final Lock lock = new ReentrantLock();
  8     // 获得指定Lock对象对应的Condition
  9     private final Condition cond  = lock.newCondition();
 10     // 封装账户编号、账户余额的两个成员变量
 11     private String accountNo;
 12     private double balance;
 13     // 标识账户中是否已有存款的旗标
 14     private boolean flag = false;
 15 
 16     public Account(){}
 17     // 构造器
 18     public Account(String accountNo , double balance)
 19     {
 20         this.accountNo = accountNo;
 21         this.balance = balance;
 22     }
 23 
 24     // accountNo的setter和getter方法
 25     public void setAccountNo(String accountNo)
 26     {
 27         this.accountNo = accountNo;
 28     }
 29     public String getAccountNo()
 30     {
 31         return this.accountNo;
 32     }
 33     // 因此账户余额不允许随便修改,所以只为balance提供getter方法,
 34     public double getBalance()
 35     {
 36         return this.balance;
 37     }
 38 
 39     public void draw(double drawAmount)
 40     {
 41         // 加锁
 42         lock.lock();
 43         try
 44         {
 45             // 如果flag为假,表明账户中还没有人存钱进去,取钱方法阻塞
 46             if (!flag)
 47             {
 48                 cond.await();
 49             }
 50             else
 51             {
 52                 // 执行取钱
 53                 System.out.println(Thread.currentThread().getName()
 54                     + " 取钱:" +  drawAmount);
 55                 balance -= drawAmount;
 56                 System.out.println("账户余额为:" + balance);
 57                 // 将标识账户是否已有存款的旗标设为false。
 58                 flag = false;
 59                 // 唤醒其他线程
 60                 cond.signalAll();
 61             }
 62         }
 63         catch (InterruptedException ex)
 64         {
 65             ex.printStackTrace();
 66         }
 67         // 使用finally块来释放锁
 68         finally
 69         {
 70             lock.unlock();
 71         }
 72     }
 73     public void deposit(double depositAmount)
 74     {
 75         lock.lock();
 76         try
 77         {
 78             // 如果flag为真,表明账户中已有人存钱进去,则存钱方法阻塞
 79             if (flag)             //
 80             {
 81                 cond.await();
 82             }
 83             else
 84             {
 85                 // 执行存款
 86                 System.out.println(Thread.currentThread().getName()
 87                     + " 存款:" +  depositAmount);
 88                 balance += depositAmount;
 89                 System.out.println("账户余额为:" + balance);
 90                 // 将表示账户是否已有存款的旗标设为true
 91                 flag = true;
 92                 // 唤醒其他线程
 93                 cond.signalAll();
 94             }
 95         }
 96         catch (InterruptedException ex)
 97         {
 98             ex.printStackTrace();
 99         }
100         // 使用finally块来释放锁
101         finally
102         {
103             lock.unlock();
104         }
105     }
106 
107     // 下面两个方法根据accountNo来重写hashCode()和equals()方法
108     public int hashCode()
109     {
110         return accountNo.hashCode();
111     }
112     public boolean equals(Object obj)
113     {
114         if(this == obj)
115             return true;
116         if (obj !=null
117             && obj.getClass() == Account.class)
118         {
119             Account target = (Account)obj;
120             return target.getAccountNo().equals(accountNo);
121         }
122         return false;
123     }
124 }
View Code

    程序通过上锁和Condition控制实现存款和取款

使用阻塞队列(BlockingQueue)控制线程通信:

  BlockingQueue是Queue的子接口,是线程同步的工具。BlockingQueue具有一个特征:当生产者线程试图向BlockingQueue中放入元素时,若该队列已满,则该线程被阻塞;

   当消费者线程试图从BlockingQueue中取出元素时,若该队列已空,则该线程被阻塞。

 1 import java.util.concurrent.*;
 2 
 3 class Producer extends Thread
 4 {
 5     private BlockingQueue<String> bq;
 6     public Producer(BlockingQueue<String> bq)
 7     {
 8         this.bq = bq;
 9     }
10     public void run()
11     {
12         String[] strArr = new String[]
13         {
14             "Java",
15             "Struts",
16             "Spring"
17         };
18         for (int i = 0 ; i < 99 ; i++ )
19         {
20             System.out.println(getName() + "生产者准备生产集合元素!");
21             try
22             {
23                 Thread.sleep(200);
24                 // 尝试放入元素,如果队列已满,线程被阻塞
25                 bq.put(strArr[i % 3]);
26             }
27             catch (Exception ex){ex.printStackTrace();}
28             System.out.println(getName() + "生产完成:" + bq);
29         }
30     }
31 }
32 class Consumer extends Thread
33 {
34     private BlockingQueue<String> bq;
35     public Consumer(BlockingQueue<String> bq)
36     {
37         this.bq = bq;
38     }
39     public void run()
40     {
41         while(true)
42         {
43             System.out.println(getName() + "消费者准备消费集合元素!");
44             try
45             {
46                 Thread.sleep(200);
47                 // 尝试取出元素,如果队列已空,线程被阻塞
48                 bq.take();
49             }
50             catch (Exception ex){ex.printStackTrace();}
51             System.out.println(getName() + "消费完成:" + bq);
52         }
53     }
54 }
55 public class BlockingQueueTest2
56 {
57     public static void main(String[] args)
58     {
59         // 创建一个容量为1的BlockingQueue
60         BlockingQueue<String> bq = new ArrayBlockingQueue<>(1);
61         // 启动3条生产者线程
62         new Producer(bq).start();
63         new Producer(bq).start();
64         new Producer(bq).start();
65         // 启动一条消费者线程
66         new Consumer(bq).start();
67     }
68 }
View Code

线程组和未处理的异常:

  Java使用ThreadGroup来表示线程组,可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。

 1 class MyThread extends Thread
 2 {
 3     // 提供指定线程名的构造器
 4     public MyThread(String name)
 5     {
 6         super(name);
 7     }
 8     // 提供指定线程名、线程组的构造器
 9     public MyThread(ThreadGroup group , String name)
10     {
11         super(group, name);
12     }
13     public void run()
14     {
15         for (int i = 0; i < 20 ; i++ )
16         {
17             System.out.println(getName() + " 线程的i变量" + i);
18         }
19     }
20 }
21 public class ThreadGroupTest
22 {
23     public static void main(String[] args)
24     {
25         // 获取主线程所在的线程组,这是所有线程默认的线程组
26         ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
27         System.out.println("主线程组的名字:"
28             + mainGroup.getName());
29         System.out.println("主线程组是否是后台线程组:"
30             + mainGroup.isDaemon());
31         new MyThread("主线程组的线程").start();
32         ThreadGroup tg = new ThreadGroup("新线程组");
33         tg.setDaemon(true);
34         System.out.println("tg线程组是否是后台线程组:"
35             + tg.isDaemon());
36         MyThread tt = new MyThread(tg , "tg组的线程甲");
37         tt.start();
38         new MyThread(tg , "tg组的线程乙").start();
39     }
40 }
View Code

 1 // 定义自己的异常处理器
 2 class MyExHandler implements Thread.UncaughtExceptionHandler
 3 {
 4     // 实现uncaughtException方法,该方法将处理线程的未处理异常
 5     public void uncaughtException(Thread t, Throwable e)
 6     {
 7         System.out.println(t + " 线程出现了异常:" + e);
 8     }
 9 }
10 public class ExHandler
11 {
12     public static void main(String[] args)
13     {
14         // 设置主线程的异常处理器
15         Thread.currentThread().setUncaughtExceptionHandler
16             (new MyExHandler());
17         int a = 5 / 0;     //
18         System.out.println("程序正常结束!");
19     }
20 }
View Code

  

posted @ 2017-08-25 00:31  lanshanxiao  阅读(331)  评论(0编辑  收藏  举报