dylanguo1012

dylanguo1012 查看完整档案

填写现居城市国立中央大学(台湾)  |  计算机 编辑  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

dylanguo1012 发布了文章 · 9月13日

介绍在使用 Solidity 以太坊升级智能合约的挑战

在开发软件的时候,我们经常需要发布新的版本来增加新的功能或者修复bug。当涉及到智能合约开发时,也没有什么区别。虽然将智能合约更新到新版本通常不像更新其他类型的相同复杂性的软件那么简单。

大多数区块链,尤其是像 Ethereum 这样的公链,都实现了不可变的特性。理论上不允许任何人改变区块链的 "过去"。不可变更性适用于区块链中的所有交易,包括用于部署智能合约和相关代码的交易。换句话说,一旦智能合约的代码被部署到区块链上,它将永远 "原样 "地 "活着"--没有人可以改变它。如果发现了bug或者需要添加新的功能,我们无法直接修改部署合约的代码。

如果一个智能合约是不可更改的,那么你如何能够将它升级到新的版本?答案就在于将新的智能合约部署到区块链上。但这种方法会带来一些需要解决的挑战。最基本也是最常见的是,所有使用智能合约的用户都需要参考新合约版本的地址,第一个合同的版本应该被禁用,强制每个用户使用新版本。

通常,你需要确保旧版本的数据(状态)被迁移或以某种方式提供给新版本。在最简单的情况下,这意味着你需要将旧版本中的状态复制/迁移到新合同的版本中。

下面的章节将更详细地描述这些挑战。为了更好的说明,我们用下面两个版本的 MySmartContract 作为参考。

// Version 1
contract MySmartContract {
    uint32 public counter;
    constructor() public {
        counter = 0;
    }
    function incrementCounter() public {
        counter += 2; // This "bug" is intentional.
    }
}

// Version 2
contract MySmartContract {
    uint32 public counter;
    constructor(uint32 _counter) public {
        counter = _counter;
    }
    function incrementCounter() public {
        counter++;
    }
}

用户可参考新合同的地址。

当部署到区块链时,智能合约的每个实例都被分配到一个唯一的地址。该地址用于引用智能合约的实例,以便调用其方法并从/向合约的存储(状态)读取/写入数据。当你将合同的更新版本部署到区块链时,合同的新实例将部署在一个新的地址。这个新地址与第一个合约的地址不同。这意味着,所有与智能合约交互的用户、其他智能合约和/或 dApp(去中心化应用)都需要更新,以便它们使用更新版本的地址。剧透:有一些选项可以避免这个问题,你会在本节最后看到。

那么,让我们考虑以下情况。你用上面 Version 1 的代码创建 MySmartContract。它被部署到区块链的地址 A1(这不是一个真实的 Ethereum 地址--仅用于说明目的)。所有想要与 Version 1交互的用户都需要使用地址 A1 来引用它。现在,经过一段时间后,我们注意到了方法 incrementCounter 中的 bug:它是以2来递增计数器,而不是以1来递增它,所以我们实现了一个修复,产生了 MySmartContract 的 Version 2 版本。这个新合约的版本被部署到地址 D5 的区块链上。此时,如果用户想要与 Version 2 进行交互,需要使用地址 D5,而不是 A1。这就是为什么所有与 MySmartContract 交互的用户都需要更新,以便他们参考新的地址 D5 的原因。

你可能同意强迫用户更新不是最好的方法,考虑到更新智能合约的版本应该对使用它的用户尽可能的透明。有不同的策略可以用来解决这个问题。可以使用一些设计模式,如 Registry、不同类型的 Proxies 来使升级更容易,并为用户提供透明度。另一个很好的选择是使用 Ethereum Name Service,并注册一个用户朋友的名字,解析到你的合约地址。有了这个选择,合约的用户不需要知道合约的地址,只需要知道它的用友名。因此,升级到新的地址对你的合约用户来说将是透明的。具体采取何种策略,取决于智能合约的使用场景。

禁用旧版合约

我们在上一节中了解到,所有用户都需要更新才能使用 Version 2 的地址(D5),或者我们的合同应该实现某种机制,使这个过程对用户透明。尽管如此,如果你是合同的拥有者,你可能要执行所有用户只使用最新的 D5 版本。如果用户无意中或没有使用 A1,你要保证 Version 1 已经被废弃,无法使用。

在这种情况下,你可以实现一种技术来停止 MySmartContract 的 Version 1。这个技术是由一个名为 Circuit Breaker 的设计模式实现的。它通常也被称为可暂停合同或紧急停止。

一般来说,Circuit Breaker 可以停止智能合约的功能。此外,它还可以启用特定的功能,这些功能只有在合约被停止时才能使用。这种模式通常实现了某种访问限制,因此只有被允许的行为者(如管理员或所有者)才有必要的权限来触发断路器并停止合约。

这种模式可以使用的一些场景有。

  • 当发现一个bug时,停止合同的功能。
  • 在达到某个状态后停止某些合约的功能(经常与状态机模式一起使用)。
  • 在升级过程中停止合同的功能,因此外部行为者不能在升级过程中改变合同的状态。
  • 在部署新版本后停止合同的废弃版本。

现在让我们来看看如何实现一个断路器,以停止 MySmartContract 的 incrementCounter 功能,所以计数器不会改变在迁移过程中。这个修改需要在 Version 1 中,也就是第一次部署的时候进行。

// Version 1 implementing a Circuit Breaker with access restriction to owner
contract MySmartContract {
    uint32 public counter;
    bool private stopped = false;
    address private owner;
    /**
    @dev Checks if the contract is not stopped; reverts if it is.
    */
    modifier isNotStopped {
        require(!stopped, 'Contract is stopped.');
        _;
    }
    /**
    @dev Enforces the caller to be the contract's owner.
    */
    modifier isOwner {
        require(msg.sender == owner, 'Sender is not owner.');
        _;
    }
    constructor() public {
        counter = 0;
        // Sets the contract's owner as the address that deployed the contract.
        owner = msg.sender;
    }
    /**
    @notice Increments the contract's counter if contract is active.
    @dev It will revert if the contract is stopped. See modifier "isNotStopped"
    */
    function incrementCounter() isNotStopped public {
        counter += 2; // This is an intentional bug.
    }
    /**
    @dev Stops / Unstops the contract.
    */
    function toggleContractStopped() isOwner public {
        stopped = !stopped;
    }
}

在上面的代码中,你可以看到 MySmartContract 的 Version 1 现在实现了一个修改器 isNotStopped。如果合同被停止,这个修饰符将恢复交易。函数 incrementCounter 被修改为使用修饰符 isNotStopped,所以它将只在合约未停止时执行。通过这个实现,就在迁移开始之前,合约的所有者可以调用函数 toggleContractStopped 并停止合约。请注意,这个函数使用修饰符 isOwner 来限制合同所有者的访问。

要了解更多关于 Circuit Breakers 的信息,请务必查看 Consensys 关于 Circuit Breakers 的帖子和 OpenZeppelin 对 Pausable 合约的参考实现。

合同的数据(状态)迁移

大多数智能合约需要在其内部存储中保持某种状态。根据不同的用例,每个合约所需要的状态变量的数量有很大的不同。在我们的例子中,原来 MySmartContract 的 Version 1 有一个单一的状态变量计数器。现在考虑 MySmartContract 的 Version 1 已经使用了一段时间。当你发现incrementCounter 函数的 bug 时,counter 的值已经在 100 了。这种情况下会产生一些问题:

  • 你将如何处理 MySmartContract Version 2 的状态?
  • 你可以在 Version 2 中把计数器重置为 0(零),还是应该从 Version 1 中迁移状态,以确保计数器在 Version 2 中初始化为 100?

这些问题的答案将取决于用例。在本文的例子中,这是一个非常简单的场景,而且 counter 没有重要的用法,如果将 counter 重置为 0,你不会有任何问题。但是,这不是大多数情况下所希望的方法。假设你不能将值重置为 0,需要在第2版中将 counter 设置为 100。在 MySmartContract 这样一个简单的合约中,这并不困难。你可以改变Version 2的构造函数来接收计数器的初始值作为参数。在部署时,你会把值 100 传递给构造函数,这就解决了你的问题。实现这个方法后,MySmartContract Version 2的构造函数会是这样的。

constructor(uint32 _counter) public {
    counter = _counter;
}

如果你的用例像上面介绍的那样简单(或类似),从数据迁移的角度来看,这可能是最合适的方式。实现其他方法的复杂性就不值得了。但是,请记住,大多数生产就绪的智能合约并不像 MySmartContract 那样简单,而且经常有更复杂的状态。

现在考虑一个使用多个结构、映射和数组的合约。如果你需要在具有如此复杂存储的合约版本之间复制数据,你可能会面临以下一个或多个挑战:

  • 一堆交易要在区块链上处理,这可能需要相当长的时间,这取决于数据集。
  • 用于处理从"版本1"读取数据并写入"版本2"的附加代码(除非手动完成)。
  • 花真金白银来支付 GAS。记住,你需要支付 GAS 来处理区块链中的交易。根据 Ethereum 黄皮书--附录G. 费用表,用于向 Ethereum 写入数据的上位代码 SSTORE 操作,"当存储值从零设置为非零时 "需要花费 20000 个天然气单位,"当存储值的零度不变时 "需要花费 5000 个天然气单位。
  • 通过使用某种机制(如断路器)冻结 Version 1 的状态,以确保在迁移过程中没有更多的数据附加到 Version 1 上。
  • 实现访问限制机制,以避免外部各方(与迁移无关)在迁移期间调用版本2的功能。为了确保版本一的数据可以复制/迁移到版本二,而不会在版本二中受到损害和/或破坏,需要这样做。

在状态较为复杂的合约中,执行升级所需的工作相当重要,而且在区块链上复制数据会产生相当大的气成本。使用库和代理可以帮助你开发更容易升级的智能合约。采用这种方法,数据将被保存在一个存储状态但不承担任何逻辑的合约中(状态合约)。第二个合约或库实现了逻辑,但不承担状态(逻辑合约)。所以当发现逻辑中的bug时,只需要升级逻辑合约,而不用担心迁移状态合约中存储的状态(见下文注)。

注:这种方法一般使用Delegatecall。状态合约使用delegatecall调用逻辑合约中的函数。然后逻辑合约在状态合约的上下文中执行它的逻辑,也就是说 "存储、当前地址和余额仍然参考调用合约,只是代码取自被调用的地址"。(来自上面提到的Solidity文档)。

让 MySmartContract 更容易升级

下面你可以看到,如果我们实现本文中描述的变化,版本1和版本2会是什么样子。需要再次提及的是,考虑到 MySmartContract 的简单性:状态变量和逻辑,其使用的策略是可以接受的。
首先,让我们看看版本1的变化。

// Version 1 — Without Upgradable Mechanisms
contract MySmartContract {
    uint32 public counter;
    constructor() public {
        counter = 0;
    }
    function incrementCounter() public {
        counter += 2; // This "bug" is intentional.
    }
}

在下面的代码中,版本1实现了一个带有访问限制机制的断路器,一旦合同被废弃,所有者可以停止合同。

//Version 1 — With Deprecation Mechanism
contract MySmartContract {
    uint32 public counter;
    bool private stopped = false;
    address private owner;
    /**
    @dev Checks if the contract is not stopped; reverts if it is.
    */
    modifier isNotStopped {
        require(!stopped, 'Contract is stopped.');
        _;
    }
    /**
    @dev Enforces the caller to be the contract's owner.
    */
    modifier isOwner {
        require(msg.sender == owner, 'Sender is not owner.');
        _;
    }
    constructor() public {
        counter = 0;
        // Sets the contract's owner as the address that deployed the contract.
        owner = msg.sender;
    }
    /**
    @notice Increments the contract's counter if contract is active.
    @dev It will revert is the contract is stopped. See modifier "isNotStopped"
    */
    function incrementCounter() isNotStopped public {
        counter += 2; // This is an intentional bug.
    }
    /**
    @dev Stops / Unstops the contract.
    */
    function toggleContractStopped() isOwner public {
        stopped = !stopped;
    }
}

现在让我们看看第二版会是怎样的。第二版--没有可升级的机制:

contract MySmartContract {
    uint32 public counter;
    constructor(uint32 _counter) public {
        counter = _counter;
    }
    function incrementCounter() public {
        counter++;
    }
}

在下面的代码中,第2版实现了与第1版相同的断路器和访问限制机制。此外,它实现了一个构造函数,允许在部署期间设置计数器的初始值。这个机制可以使用,它可以在升级时使用,从旧版本复制数据。
版本2 - 具有简单的升级机制。

//Version 2 — With Simple Upgradable Mechanism
contract MySmartContract {
    uint32 public counter;
    bool private stopped = false;
    address private owner;
    /**
    @dev Checks if the contract is not stopped; reverts if it is.
    */
    modifier isNotStopped {
        require(!stopped, 'Contract is stopped.');
        _;
    }
    /**
    @dev Enforces the caller to be the contract's owner.
    */
    modifier isOwner {
        require(msg.sender == owner, 'Sender is not owner.');
        _;
    }
    constructor(uint32 _counter) public {
        counter = _counter; // Allows setting counter's initial value on deployment.
        // Sets the contract's owner as the address that deployed the contract.
        owner = msg.sender;
    }
    /**
    @notice Increments the contract's counter if contract is active.
    @dev It will revert is the contract is stopped. See modifier "isNotStopped"
    */
    function incrementCounter() isNotStopped public {
        counter++; // Fixes bug introduced in version 1.
    }
    /**
    @dev Stops / Unstops the contract.
    */
    function toggleContractStopped() isOwner public {
        stopped = !stopped;
    }
}

虽然上述变化实现了一些有助于智能合约升级的机制,但本文开头所描述的第一个挑战--用户要参考新合约的地址,并不是这些简单的技术就能解决的。需要更高级的模式,比如 Proxies 和 Registry,或者使用 ENS 给你的合约注册一个用户友好的名字,来避免所有用户升级参考第二版的新地址。

结束语

在 Ethereum 白皮书的 DAO 部分描述了可升级智能合约的原理,内容如下。

"虽然理论上代码是不可变的,但我们可以很容易地绕过这一点,并通过将代码分块放在单独的合约中,并将调用哪些合约的地址存储在可修改的存储空间中,来实现事实上的可变性。*”

虽然这是可以实现的,但智能合约的升级是相当具有挑战性的。区块链的不可变性为智能合约的升级增加了更多的复杂性,因为它迫使你仔细分析智能合约的使用场景,了解可用的机制,然后决定哪些机制适合你的合约,这样潜在的和可能的升级就会很顺利。

智能合约升级性是一个活跃的研究领域。相关的模式、机制和最佳实践仍在不断讨论和发展中。使用库和一些设计模式,如断路器、访问限制、代理和注册表,可以帮助你解决一些挑战。但是,在比较复杂的场景下,仅靠这些机制可能无法解决所有问题,你可能需要考虑更复杂的模式,比如本文没有提到的 Eternal Storage。

你可以在这个github仓库中查看完整的源代码,包括相关的单元测试(为了简单起见,本文没有提到)。

原文:https://levelup.gitconnected.com/introduction-to-ethereum-smart-contract-upgradability-with-solidity-789cc497c56f

查看原文

赞 0 收藏 0 评论 0

dylanguo1012 发布了文章 · 9月11日

SQL: 最有价值的技能之一

在我的职业生涯中, 我学到了很多技能, 但没有什么技术技能比 SQL 更有用. 在我看来, SQL 是最有价值的技能, 有以下几个原因:

  1. 它在不同的工作和学科中都很有价值
  2. 只需要学习一次, 并不需要重新学习
  3. 你看起来像是个超级英雄. 当你知道有很多人都对SQL不太精通的时候, 你就显得格外的强大

让我对以上每一点都详细解释一下

SQL 是一个你可以随处使用的工具

无论你是什么工作, SQL 都会想办法让你的生活更轻松. 例如今天我作为一名产品经理, 我的工作是看数据, 分析我们在产品方面的效率如何, 并制定产品路线图. 如果我们刚刚发布了一个新功能, 那么关于是否有人浏览过该功能的数据很可能就在关系型数据库中的某个地方. 如果我正在努力跟踪关键的业务指标, 比如月度增长, 同样也很可能就放在关系型数据库的某个地方. 在我们所做的几乎所有事情的另一端, 都很可能有一个使用 SQL 的记录系统. 知道如何能最原生地访问这些数据, 就可以省去大量的精力, 而不用去问别人这些数据是什么.

但即使在成为产品经理之前, 我也会使用 SQL 来告知我系统内发生了什么. 作为一名工程师, 它常常能让我比用 Ruby 或 Python 等脚本更快地获取我想要的信息. 当我的 webapp 速度变慢的时候, 了解执行的 SQL 和优化它的方法是不可缺少的. 是的, 这有点超出了对 SQL 的基本理解...... 但是在查询中添加一个索引, 而不是滚动我自己的本地缓存, 这所提升的效率很值得花额外的时间去学习.

SQL 是持久性的

记得大约20年前, 我创建了第一个网页. 那是一个神奇的网页, 然后我引入了一些 Javascript 来使它更令人印象深刻, 提示用户点击 Yes/No 或给我一些输入. 然后大约 10 年后, jQuery 出现了, 虽然它有时比较啰嗦, 而且也有新的东西要学, 但它让东西整体上更漂亮, 所以我致力于重新学习 jQuery 的 JS 方法. 然后就跟上了 Angular -> React/Ember 的步伐, 现在我有一整条流水线把基本的 Javascript 引入到我的网站中, 而现实是我仍然在努力完成和 20 年前一样的事情, 让人点击 Yes/No.

相比之下, SQL 并没有真正改变. (其实它已经改变了, 有现代的 SQL, 但我仍然认为相比其他语言的变动不那么显著). 当然, 我们每隔几年就会有一个新的标准, 偶尔也会有一些新的东西出现, 比如对窗口函数或 CTE 的支持, 但 SQL 的基础知识是非常通用持久的. 学习一次SQL可以让你在你的职业跨度中大量重复使用它, 而不必重新学习. 不要误会我的意思, 我喜欢学习新的东西, 但我宁愿学习一些真正新的东西, 而不是仅仅用另一种方法来完成同样的任务.

SQL 看起来比你想的更好

熟练掌握 SQL 人并不多, 大多数开发者跳过它, 很少有人真正了解 SQL, 所以掌握 SQL 的人可能看起来比实际更像精英. 过去在一家拥有数百名工程师的公司中, 我每周会收到多个同样的请求, 来自从初级工程师到主要工程师各种人:"嘿, 你能帮忙写一个查询吗?" 因为你很擅长这样的事情, 可以帮助其他人.

所以如果你还没有熟练掌握SQL, 还等什么? 你想看起来像个 SQL 弱鸡吗?


本人言: 其实我个人举得学好 SQL 带来的受益远比原作者描述的更多, 以下是我的几点感受:

  1. 学好 SQL, 有助于理解排序, 二分查找, B+ 树等数据结构和算法内容.
  2. 学好 SQL, 有助于理解和运用锁, 例如行锁, 表锁, 悲观锁, 乐观锁. 最终实现内功的修炼, 进而提升系统设计能力, 可以说这是成为一名架构师的捷径.
  3. 学好 SQL, 掌握事务部分, 有助于学习 Spring 框架的内容, 这才是事务内容的根源所在。

原文链接: http://www.craigkerstiens.com/2019/02/12/sql-most-valuable-skill/

查看原文

赞 0 收藏 0 评论 0

dylanguo1012 发布了文章 · 9月11日

Java如何停止线程

 Java中停止线程的原则是什么?

在 Java 中, 最好的停止线程的方式是使用中断 interrupt, 但是这仅仅是会通知到被终止的线程 "你该停止运行了", 被终止的线程自身拥有决定权 (决定是否、以及何时停止), 这依赖于请求停止方和被停止方都遵守一种约定好的编码规范.

  • 任务和线程的启动很容易. 在大多数时候, 我们都会让它们运行直到结束, 或者让它们自行停止.然而, 有时候我们希望提前结束任务或线程, 或许是因为用户取消了操作,或者服务需要被快速关闭, 或者是运行超时或出错了.
  • 要使任务和线程能安全、快速、可靠地停止下来, 并不是一件容易的事. Java没有提供任何机制来安全地终止线程. 但它提供了中断 (Interruption), 这是一种协作机制,能够使一个线程终止另一个线程的当前工作.
  • 这种协作式的方法是必要的, 我们很少希望某个任务、线程或服务立即停止,因为这种立即停止会使共享的数据结构处于不一致的状态. 相反, 在编写任务和服务时可以使用一种协作的方式: 当需要停止时,它们首先会清除当前正在执行的工作, 然后再结束. 这提供了更好的灵活性, 因为任务本身的代码比发出取消请求的代码更清楚如何执行清除工作.
  • 生命周期结束 (End-of-Lifecycle) 的问题会使任务、服务以及程序的设计和实现等过程变得复杂, 而这个在程序设计中非常重要的要素却经常被忽略. 一个在行为良好的软件与勉强运的软件之间的最主要区别就是, 行为良好的软件能很完善地处理失败、关闭和取消等过程.

 处理中断的最好方法是什么?

优先选择在方法上抛出异常.

用 throws InterruptedException 标记你的方法, 不采用 try 语句块捕获异常,以便于该异常可以传递到顶层, 让run方法可以捕获这一异常, 例如:

void subTask() throws InterruptedException
    sleep(delay);
}

由于 run 方法内无法抛出 checked Exception (只能用 try catch), 顶层方法必须处理该异常, 避免了漏掉或者被吞掉的情况, 增强了代码的健壮性.

 如果不能抛出中断, 要怎么做?

如果不想或无法传递 InterruptedException (例如用 run 方法的时候, 就不让该方法 throws InterruptedException), 那么应该选择在 catch 子句中调用 Thread.currentThread().interrupt() 来恢复设置中断状态, 以便于在后续的执行依然能够检查到刚才发生了中断.

代码演示详见视频, 在这里, 线程在sleep期间被中断, 并且由 catch 捕获到该中断, 并重新设置了中断状态, 以便于可以在下一个循环的时候检测到中断状态, 正常退出.

 为什么用 volatile 停止线程不够全面?

解答: 这种做法是错误的, 或者说是不够全面的, 在某些情况下虽然可用, 但是某些情况下有严重问题。

这种方法在《Java并发编程实战》中被明确指出了缺陷, 我们一起来看看缺陷在哪里:

此方法错误的原因在于, 如果我们遇到了线程长时间阻塞 (这是一种很常见的情况, 例如生产者消费者模式中就存在这样的情况), 就没办法及时唤醒它, 或者永远都无法唤醒该线程, 而 interrupt 设计之初就是把 wait 等长期阻塞作为一种特殊情况考虑在内了, 我们应该用 interrupt 思维来停止线程.

查看原文

赞 0 收藏 0 评论 0

dylanguo1012 发布了文章 · 9月11日

Java多线程的实现方式

 有多少种实现线程的方法?

  1. 从不同的角度看,会有不同的答案。
  2. 典型答案是两种,分别是实现 Runnable 接口和继承 Thread 类,然后具体展开说;
  3. 但是,我们看原理,其实Thread类实现了 Runnable 接口,并且看 Thread 类的 run 方法,会发现其实那两种本质都是一样的,run 方法的代码如下:
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

方法一和方法二,也就是 “继承Thread类然后重写 run()” 和 “实现 Runnable 接口并传入 Thread 类” 在实现多线程的本质上,并没有区别,都是最终调用了 start() 方法来新建线程。这两个方法的最主要区别在于 run() 方法的内容来源:

方法一:最终调用 target.run();

方法二:run() 整个都被重写

  1. 然后具体展开说其他方式;

还有其他的实现线程的方法,例如线程池等,它们也能新建线程,但是细看源码,从没有逃出过本质,也就是实现 Runnable 接口和继承 Thread 类。

  1. 结论:我们只能通过新建 Thread 类这一种方式来创建线程,但是类里面的 run 方法有两种方式来实现,第一种是重写 run 方法,第二种实现 Runnable 接口的 run 方法,然后再把该 runnable 实例传给 Thread 类。除此之外,从表面上看线程池、定时器等工具类也可以创建线程,但是它们的本质都逃不出刚才所说的范围。

以上这种描述比直接回答一种、两种、多种都更准确。

 实现 Runnable 接口相比于继承 Thread 类的优点:

  1. 从代码架构角度: 具体的任务 (run 方法) 应该和 "创建和运行的机制 (Thread 类) " 解耦, 用 Runnable 对象可以实现解耦.
  2. 实现继承 Thread 的方式的话, 那么每次想新建一个任务, 只能新建一个独立的线程, 而这样做的损耗会比较大(比如从头开始创建一个线程、执行完毕以后再销毁等。如果线程的实际工作内容,也就是 run() 函数里只是简单的打印一行文字的话,那么可能线程的实际工作内容还不如损耗来的大)。如果使用 Runnable 和线程池, 可以反复利用同一个线程, 就可以大大减小这样的损耗.
  3. 继承 Thread 类以后,由于 Java 语言不支持双继承, 这样就无法再继承其他的类, 限制了可扩展性.

因此通常我们优先选择 Runnable 接口.

 FutureTask、线程池以及定时器属于多线程的实现方式吗?

多线程的实现方式,在代码中写法千变万化,但其本质万变不离其宗。

以上的观点之所以错误,是因为他们都只不过是包装了 new Thread(),我们如果把能新建线程的类都称作是一种实现线程的方法,那么就太流于表面了,而没有理解到底层的原理。

而随着 JDK 的发展,这样的类会越来越多,我们肯定也没办法熟悉每一种具有新建线程能力的类,因为有些类根本不常用。

 线程池:

以下是使用线程池实现线程的代码

public class ThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            executorService.submit(new Task() {
            });
        }
    }
}
  

class Task implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    System.out.println(Thread.currentThread().getName());
    }
}
public Thread newThread(Runnable r) {
    Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
    if (t.isDaemon())
        t.setDaemon(false);
    if (t.getPriority() != Thread.NORM_PRIORITY)
        t.setPriority(Thread.NORM_PRIORITY);
    return t;
}

 FutureTask:

以下是使用 FutureTask 类实现线程的代码

public class FutureTaskDemo {
    public static class CallerTask implements Callable<String> {
        @Override
        public String call() throws Exception {
            return "Hello";
        }
    }
  
    public static void main(String[] args) {
        // 创建异步任务
        FutureTask<String> futureTask = new FutureTask<>(new CallerTask());
         // 启动线程, 调用 futureTask 中 run()
         new Thread(futureTask).start();
         try {
             String result = futureTask.get();
             System.out.println(result);
         } catch (ExecutionException | InterruptedException e) {
            e.printStackTrace();
         }
    }
}

从源码中可以看到 FutureTask 类实现了 RunnableFuture 接口

public class FutureTask<V> implements RunnableFuture<V> {

而 RunnableFuture 接口继承了 Runnable 接口

public interface RunnableFuture<V> extends Runnable, Future<V> {
     /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
     void run();
}

其中, futureTask 实例实现了 Runnable 接口, 并传入 Thread 的构造函数中, 也就是说在执行 start() 的时候会调用 futureTask 中的 run() .

     // 创建异步任务
     FutureTask<String> futureTask = new FutureTask<>(new CallerTask());
     // 启动线程
     new Thread(futureTask).start();

以下是 FutureTask 的部分构造函数和 run() 源码

public FutureTask(Callable<V> callable) {
     if (callable == null)
        throw new NullPointerException();
     this.callable = callable;
     this.state = NEW; // ensure visibility of callable
}

 ...
 
 public void run() {
     if (state != NEW ||
         !RUNNER.compareAndSet(this, null, Thread.currentThread()))
         return;
     try {
         Callable<V> c = callable;
         if (c != null && state == NEW) {
            V result;
         boolean ran;
     try {
         result = c.call();
         ran = true;
     } catch (Throwable ex) {
         result = null;
         ran = false;
         setException(ex);
     }
     if (ran)
        set(result);
}

 ...

我们自己实现的 CallerTask 类作为 Callable 对象传入 FutureTask 构造函数中, 紧接着在 run() 中会去调用传进来的 callable 对象的 call(), 也就是我们自己实现的 call(), 并将结果使用 set() 存到 outcome 中.

简而言之, 使用 FutureTask 实现线程的时候本质上还是重写了 Runnable 的 run(), 不过区别在于 FutureTask 可以拿到任务的返回结果, 而继承 Thread 类和实现 Runnable 接口都无法拿到返回结果.

 start方法的执行流程是什么?

  1. 检查线程状态, 只有 NEW 状态下的线程才能继续, 否则会抛出 IllegalThreadStateException (在运行中或者已结束的线程,都不能再次启动,详见CantStartTwice10 类)
  2. 被加入线程组
  3. 调用 start0() 方法启动线程

注意点:

start 方法是被 synchronized 修饰的方法, 可以保证线程安全;

由 JVM 创建的 main 方法线程和 system 组线程, 并不会通过 start 来启动.

 为何调用 start(), 而不是直接调用 run() 呢?

调用 start() 之后, 一个线程才会被加入线程组, 进入生命周期. 如果仅仅从 main 中直接调用 run(), 那只是从 main 线程执行了一次 run() 方法, 并无新线程产生.

查看原文

赞 0 收藏 0 评论 0

dylanguo1012 发布了文章 · 6月17日

Ubuntu 20.04 依赖包安装错误解决方案

一、排除问题

  之前因为国内使用官方源更新 apt 速度太慢, 所以后来换成了阿里源, 但是之后有一次在安装的时候手抖按到 ctrl + c 了 :), 然后悲剧就开始了...

image.png

  再次安装的时候就会如图所示报错出现这一串

E: Unmet dependencies. Try 'apt --fix-broken install' with no packages (or specify a solution).

  于是我尝试使用 apt --fix-broken install 又会出现下图的疯狂报错找不到文件.

image.png

  google之后用过sudo apt autoremove sudo apt-get install -f
  等等指令都没有用.

  最后我发现是我软件源配置的是18.04的模板, 然而我是20.04...这里一定要注意不同版本的ubuntu如果软件源的模板没有对应上, 很有可能出现问题.

  比如 ubuntu 18.04 对应的是

deb http://cn.archive.ubuntu.com/ubuntu/ boinc main restricted universe multiverse

  而 ubuntu 20.04 则是

deb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse

  他们的版本代号是不同的!!!

二、解决问题:更换正确的软件源

 1.更改之前先记得备份一下

# 首先备份源列表
sudo cp /etc/apt/sources.list /etc/apt/sources.list_backup
  1. 打开sources.list文件修改, 换成对应版本的软件源, 国内一半用阿里云中科大清华比较快.
# 打开sources.list文件
sudo vim /etc/apt/sources.list
#  阿里源 Ubuntu 18.04
deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
# 中科大源 Ubuntu 20.04
deb https://mirrors.ustc.edu.cn/ubuntu/ focal main restricted universe multiverse
deb-src https://mirrors.ustc.edu.cn/ubuntu/ focal main restricted universe multiverse
deb https://mirrors.ustc.edu.cn/ubuntu/ focal-updates main restricted universe multiverse
deb-src https://mirrors.ustc.edu.cn/ubuntu/ focal-updates main restricted universe multiverse
deb https://mirrors.ustc.edu.cn/ubuntu/ focal-backports main restricted universe multiverse
deb-src https://mirrors.ustc.edu.cn/ubuntu/ focal-backports main restricted universe multiverse
deb https://mirrors.ustc.edu.cn/ubuntu/ focal-security main restricted universe multiverse
deb-src https://mirrors.ustc.edu.cn/ubuntu/ focal-security main restricted universe multiverse
deb https://mirrors.ustc.edu.cn/ubuntu/ focal-proposed main restricted universe multiverse
deb-src https://mirrors.ustc.edu.cn/ubuntu/ focal-proposed main restricted universe multiverse
  1. 刷新列表
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install build-essential
  1. 重新执行 fix 问题就解决啦
sudo apt --fix-broken install
查看原文

赞 0 收藏 0 评论 0

dylanguo1012 关注了专栏 · 6月17日

SegmentFault 行业快讯

第一时间为开发者提供行业相关的实时热点资讯

关注 25346

dylanguo1012 关注了专栏 · 6月17日

民工哥技术之路

公众号:民工哥技术之路、《Linux系统运维指南 从入门到企业实战》作者。专注系统架构、高可用、高性能、高并发,数据库、大数据、数据分析、Python技术、集群中间件、后端等开源技术分享。

关注 16082

dylanguo1012 关注了用户 · 6月17日

Java专职 @liuwei_5edf788413d5b

关注 126

dylanguo1012 关注了专栏 · 6月17日

Java 小词典

百无一用是书生,春鸟秋虫自做声。

关注 927

dylanguo1012 关注了用户 · 6月17日

Java中文社群 @vipstone

公众号「Java中文社群」编程•读书

关注 2695

认证与成就

  • 获得 0 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 6月16日
个人主页被 144 人浏览