前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >谈谈多线程

谈谈多线程

作者头像
云海谷天
发布2022-08-09 14:03:10
1760
发布2022-08-09 14:03:10
举报
文章被收录于专栏:技术一点点成长

前言:

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

概念引入:

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

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

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

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

1)继承Thread

2)实现Runnable接口

代码语言:javascript
复制
 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.线程不安全

【不安全实例代码】

代码语言:javascript
复制
 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 }

输出结果:

代码语言:javascript
复制
Finally, the account get the total balance:3

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

分析

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

解决方法:

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

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

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

代码语言:javascript
复制
 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-代码修改部分】:

代码语言:javascript
复制
 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     }

输出结果:

代码语言:javascript
复制
Finally, the account get the total balance:100

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

  • 4.线程协作实例:

实例情境:

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

代码实现:

代码语言:javascript
复制
 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.经典生产者消费者问题

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2015-08-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概念引入:
  • 线程池指的是不需要每个任务用一个线程来存储,而是初始就给出一个“池”。线程池首先会初始池的大小,即可供同时进入的“任务”的数量,这样的话每当来一个新任务就直接往线程池里面扔,不需要再单独创建一个个线程。
  • 实例情境:
相关产品与服务
访问管理
访问管理(Cloud Access Management,CAM)可以帮助您安全、便捷地管理对腾讯云服务和资源的访问。您可以使用CAM创建子用户、用户组和角色,并通过策略控制其访问范围。CAM支持用户和角色SSO能力,您可以根据具体管理场景针对性设置企业内用户和腾讯云的互通能力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档