谈谈多线程

前言:

  一直以来,对于多线程的理解总是赶在前一秒翻书时回忆起,后一秒放下书即忘。甚是可恼!今晚对多线程总结一下,也好有个了断~

概念引入:

  首先,我们想了解的是:什么是线程,跟进程有什么关联?

  其实是这样的:线程是程序执行流的最小单元。其一般有3种状态:就绪,执行和阻塞(因本文注重实例,就不对概念作过多的解释~)。在计算机中,一个代码块(block)运行时产生一个或多个进程(process),而每一个进程又对应一至多个线程(thread),每一个线程又可以分为一至多个任务(task).

  因此,以上的关系我们可以通过下面一张图进行理解。

  • 1.创建线程的两种实现方式:

1)继承Thread

2)实现Runnable接口

 1 package com.gdufe.thread;
 2 
 3 public class ThreadTest {
 4 
 5     public static void main(String[] args) {
 6 
 7         Thread thread1 = new Thread(new TaskA(),"thread1");
 8         Thread thread2 = new Thread(new TaskB('a',100),"thread2");
 9         Thread thread3 = new Thread(new TaskB('b',100),"thread3");
10         
11         thread1.start();;
12         thread2.start();
13         thread3.start();
14         System.out.println("--End--");
15         
16         
17     }
18     
19     /*
20      * 任务A通过实现Runnable接口创建任务
21      */
22     private static class TaskA implements Runnable{
23 
24         @Override
25         public void run() {
26             for(int i=0;i<100;i++){
27                 System.out.print(i+" ");
28             }
29         }
30         
31     }
32     /*
33      * 任务B通过继承Thread类并重写run()方法来创建任务
34      */
35     private static class TaskB extends Thread{
36         
37         private char ch;
38         private int times;
39         
40         public TaskB(char ch,int times){
41             this.ch=ch;
42             this.times=times;
43         }
44         @Override
45         public void run() {
46             for(int i=0;i<times;i++){
47                 System.out.print(ch+" ");
48             }
49         }
50     }
51 }

输出结果:

  • 2.线程池thread-pool 

  线程池指的是不需要每个任务用一个线程来存储,而是初始就给出一个“池”。线程池首先会初始池的大小,即可供同时进入的“任务”的数量,这样的话每当来一个新任务就直接往线程池里面扔,不需要再单独创建一个个线程。

  • 3.线程不安全

【不安全实例代码】

 1 package com.gdufe.thread;
 2 
 3 import java.util.concurrent.ExecutorService;
 4 import java.util.concurrent.Executors;
 5 
 6 public class ThreadNotSafe {
 7     static Account account = new Account();
 8 
 9     public static void main(String[] args) {
10 
11         ExecutorService executor = Executors.newCachedThreadPool();
12         for (int i = 0; i < 100; i++) {
13             executor.execute(new Deposit());
14         }
15         executor.shutdown();
16         while (!executor.isTerminated()) {
17         }
18         System.out.println("Finally, the account get the total balance:"
19                 + account.getBalance());
20     }
21     /*
22      * 存钱操作,每次存入1
23      */
24     static class Deposit implements Runnable {
25 
26         @Override
27         public void run() {
28             account.deposit(1);
29         }
30     }
31 
32     static class Account {
33         private int balance = 0;
34 
35         public int getBalance() {
36             return balance;
37         }
38 
39         public void deposit(int value) {
40             int newBalance = balance + value;
41             try {
42                 Thread.sleep(5);    //故意将原有的简单操作拆分两步,并且中间延迟5毫秒
43             } catch (Exception e) {
44             }
45             balance = newBalance;
46         }
47     }
48 
49 }

输出结果:

Finally, the account get the total balance:3

(注意:执行多次的结果不一样)

分析

  上述实例进行100个任务,每个任务都是往账号里面存“1”,正确的结果应该输出“100”,那为什么最终的结果好像“不正确”。原因是,当多个任务同时访问一个资源时,就出现了所谓的资源“竞争”。


解决方法:

1)使用关键字“synchronized”对资源进行封锁,此方式称作“隐式加锁”;

2)采用java内部工具类“Lock”进行“显式加锁”。

【方式1-代码修改部分】:

 1 static class Account {
 2         private int balance = 0;
 3 
 4         public int getBalance() {
 5             return balance;
 6         }
 7         /*
 8          * 增加关键字‘synchronized’,方法未执行完时,仅限一个任务访问该方法
 9          */
10         public synchronized void deposit(int value) {
11             int newBalance = balance + value;
12             try {
13                 Thread.sleep(5);    //故意延迟5毫秒
14             } catch (Exception e) {
15             }
16             balance = newBalance;
17         }
18     }

【方式2-代码修改部分】:

 1 static class Account {
 2         private int balance = 0;
 3         private static Lock lock = new ReentrantLock();
 4 
 5         public int getBalance() {
 6             return balance;
 7         }
 8         /*
 9          * 采用显示加锁,方法开始执行时加锁,执行结束前解锁
10          */
11         public void deposit(int value) {
12             lock.lock();    //加锁
13             int newBalance = balance + value;
14             try {
15                 Thread.sleep(5);    //故意延迟5毫秒
16                 balance = newBalance;
17             } catch (Exception e) {
18             
19             }finally{
20                 lock.unlock();     //解锁
21             }
22         }
23     }

输出结果:

Finally, the account get the total balance:100

(多次执行,输出结果总是100)

  • 4.线程协作实例:

  实例情境:

  假如现在要对一个银行账号(Account)进行存(Deposit)取(Withdraw)钱操作。故确定了2个线程,这里的协作需要注意一点的是当银行账号的余额不足时,取钱操作必须等待。因此,除了前面设定的锁之外,我们还得加一个信号量signal。信号量的作用是当取钱的数量大于当前账号余额时,停止该操作,发出等待的信号量signal;存钱时,有多少即存多少就是了。不过,每进行一次存钱操作,都必须发出信号提醒还在等待的取钱操作,不然取钱操作将一直等下去...

代码实现:

 1 package com.gdufe.thread;
 2 
 3 import java.util.concurrent.ExecutorService;
 4 import java.util.concurrent.Executors;
 5 import java.util.concurrent.locks.Condition;
 6 import java.util.concurrent.locks.Lock;
 7 import java.util.concurrent.locks.ReentrantLock;
 8 
 9 public class ThreadCooperation {
10     private static Account account = new Account();
11 
12     public static void main(String[] args) {
13         //开启大小为2线程池的,依次加入任务
14         ExecutorService executor = Executors.newFixedThreadPool(2);
15         executor.execute(new DepositTask());
16         executor.execute(new WithdrawTask());
17         //关闭线程池入口
18         executor.shutdown();
19         System.out.println("Operation------Balance");
20     }
21     //从账号取出钱
22     static class WithdrawTask implements Runnable {
23         @Override
24         public void run() {
25             try {
26                 while (true) {
27                     account.withdraw((int) (Math.random() * 10));
28                 }
29             } catch (Exception e) {
30             }
31         }
32     }
33     //往账号存钱
34     static class DepositTask implements Runnable {
35         @Override
36         public void run() {
37             try{
38             while (true) {
39                 account.deposit((int) (Math.random() * 10));
40                 Thread.sleep(1000);
41             }
42             }catch(Exception e){
43                 e.printStackTrace();
44             }
45         }
46     }
47     //内部类,只有‘balance’属性
48     static class Account {
49         private int balance = 0;
50         private static Lock lock = new ReentrantLock();
51         private static Condition signal = lock.newCondition();
52 
53         public int getBalance() {
54             return balance;
55         }
56         //前后加锁,解锁
57         public void deposit(int amount) {
58             lock.lock();
59             try {
60                 balance += amount;
61                 System.out.println("deposit "+amount+"----total:" + account.getBalance());
62                 signal.signalAll();        //not 'notifyAll()'
63             } finally {
64                 lock.unlock();
65             }
66         }
67         //同样加锁,解锁
68         public void withdraw(int amount) {
69             lock.lock();
70             try {
71                 while (balance < amount) {
72                     signal.await();
73                     System.out.println("signal await...");
74                 }
75                 balance -= amount;
76                 System.out.println("whthdraw "+amount+"----total:" + account.getBalance());
77             } catch (Exception e) {
78                     e.printStackTrace();
79             } finally {
80                 lock.unlock();
81             }
82         }
83     }
84 
85 }

输出结果:

  • 5.经典生产者消费者问题

(抱歉,时间关系!后续补上关于生产者消费者的实例,敬请关注~~)

原文地址:https://www.cnblogs.com/SeaSky0606/p/4722413.html