Làm đẹp Github Profile với README.md

Có thể các bạn đã biết hoặc chưa, nhưng github đã ra mắt chức năng để developers có thể làm màu hơn với profile github của mình. Như bên dưới là profile của mình 😀

Cách làm

Step 1

Để kích hoạt tính năng này, chúng ta sẽ tạo một repo mới với tên repo chính là tên username của chúng ta. Github sẽ hiển thị thông báo về repo đặc biệt này.

Step 2

Thêm file Readme.md vào repo. Ở bước này, các bạn có thể tùy ý chỉnh sửa các thông tin mà muốn thể hiện cho người khác mỗi khi họ vào trang profile của mình

Một số đường link giúp bạn tạo file Readme.md:

https://rahuldkjain.github.io/gh-profile-readme-generator/

https://awesome-github-readme-profile.netlify.app/

https://gprm.itsvg.in/

Step 3

Lưu lại và quay trở lại trang profile của bản thân để xem thành quả.

Rất đơn giản phải không nào, hi vọng với hướng dẫn trên mọi người có thể tự tạo một profile hoàng tráng cho bản thân mình.

Tìm hiểu về JWT

ở bài viết trước mình có đề cập đến Token Base và Cookie Base. Bạn nào chưa đọc có thể ấn vào đây để có thể đọc và hiểu về khái niệm giữa 2 thằng. Thì bài hôm nay mình sẽ viết về JWT, một dạng authen bằng token.

  1. JWT – Json Web Token là gì.

JWT hiểu theo cách đơn giản thì nó là 1 chuẩn giúp tạo ra 1 chuỗi mã hóa. Hay còn gọi là 1 token chứa dữ liệu. Nó giúp cho các hệ thống có thể trao đổi thông tin với nhau, hay việc trao đổi thông tin từ client đến server được an toàn và đáng tin cậy. Mình sẽ lấy 1 ví dụ cơ bản.

{
"username":"Dodv",
"address":"Ha Noi",
"age":24
}

mình có 1 đoạn json như thế này, mình sẽ dùng base 64 để encode đoạn json trên. Và kết quả của mình tạo được như sau.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkRvZHYiLCJhZGRyZXNzIjoiSGEgTm9pIiwiYWdlIjoyNH0.Yg0U-1AaeztfZ8X9LWFtBSWa_sexJ9grpLe7NdUwZSg

Và để biết được tại sao lại có thể từ 1 đoạn json bên trên mà có thể tạo ra 1 token như bên dưới. Thì mình cùng tìm hiểu về cấu trúc và cơ chế hoạt động của JWT.

2. Cấu trúc của JWT.

Nhìn vào hình bên trên thì có thể thấy cấu trúc của JWT sẽ bao gồm ba phần.
Phần HEADER, phần PAYLOAD và phần VERIFY SIGNATURE.

2.1. Phần HEADER.

Ở phần header sẽ chứa hai thông tin đó là thuật toán dùng để mã hóa, bên trên là nó dùng thuật toán HS256. Và token type là JWT. Sau đó nó dùng thuật toán là Base64 để mã hóa object json bên trên để tạo ra được chuỗi đầu tiền.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

2.2 Phần PAYLOAD

Ở phần payload sẽ tương tư như phần HEADER, nhưng ở đây object json mà nó mã hóa sẽ là dữ liệu mình truyền vào. Kết quả sau khi mã hóa. 1 lưu ý là nếu phần PAYLOAD bạn bỏ vào đó quá nhiều thông tin, thì đoạn mã hóa được sinh ra sẽ càng dài. Vì vậy hãy chỉ đưa những thông tin mà quan trọng và cần thiết vào phần PAYLOAD thôi nhé.

eyJ1c2VybmFtZSI6IkRvZHYiLCJhZGRyZXNzIjoiSGEgTm9pIiwiYWdlIjoyNH0

2.3 Phần VERIFY SIGNATURE

Đến đây thì bạn sẽ có thắc mắc, nếu encode bằng Base64 thì vẫn có thể được decode ngược lại. Và để đảm bảo tính bảo mật và an toàn, thì JWT sẽ có thêm 1 phần nữa đó là phần SIGNATURE.

Phần SIGNATURE được tạo ra như thế nào và nó hoạt động ra sao, hãy nhìn vào hình bên trên của mình. Ở phần này nó sẽ sử dụng cái thuật toán được khai báo ở phần HEADER. Tùy thuộc vào loại thuật toán mà nó sẽ có tham số đầu vào khác nhau. Như hình trên thì đối với thuật toán HS256 thì đầu vào của nó sẽ bao gồm 2 tham số. Đó là phần mã hóa của HEADER và PAYLOAD kết hợp với 1 đoạn secret key. Secret key này sẽ được nằm ở trên server.

Yg0U-1AaeztfZ8X9LWFtBSWa_sexJ9grpLe7NdUwZSg

Sự kết hợp giữa 3 phần sẽ tạo ra 1 đoạn token. Và đương nhiên là nếu chỉ cần thay đổi một trong 3 phần bất kì nào đó, thì đoạn token sẽ được thay đổi hoàn toàn. Nên trường hợp mà sửa đổi token để thay đổi thông tin là hoàn toàn không thể.

Ngoài ra thì mình cũng có thể thêm các cấu hình như thời gian hết hạn của 1 token. Thông thường 1 token sẽ có thời gian khá ngắn chỉ tầm 1-2 tiếng để đảm bảo tính bảo mật. Và mình phải dùng đến cơ chế refresh token, để khi token nó gần hết hạn thì mình dùng cái refresh token đó để xin cấp 1 token mới.

3. Vậy JWT được sử dụng như thế nào.

Sau khi user gửi thông tin bao gồm username và password lên server. Server sẽ verify thông tin đó. Nếu thông tin hợp lệ thì sẽ generate ra 1 JWT. Và mỗi lần user gửi yêu cầu lấy dữ liệu từ phía server thì sẽ gửi kèm cái JWT này. Và phía server sẽ dựa vào JWT vừa gửi lên để kiểm tra xem JWT có hợp lệ hay không, user này là ai, và có những quyền gì. Phần này mình đã nói khá chi tiết ở bài trước rồi. Mn có thể tham khảo lại. Có 2 mục đích chính khi sử dụng JWT.

Authentication: Đây là trường hợp phổ biến nhất thường sử dụng JWT. Khi người dùng đã đăng nhập vào hệ thống thì những request tiếp theo từ phía người dùng sẽ chứa thêm mã JWT. Điều này cho phép người dùng được cấp quyền truy cập vào các url, service, và resource mà mã Token đó cho phép. Phương pháp này không bị ảnh hưởng bởi Cross-Origin Resource Sharing (CORS) do nó không sử dụng cookie.

Trao đổi thông tin: JSON Web Token là 1 cách thức khá hay để truyền thông tin an toàn giữa các thành viên với nhau, nhờ vào phần signature của nó. Phía người nhận có thể biết được người gửi là ai thông qua phần signature. Và chữ ký được tạo ra bằng việc kết hợp cả phần header, payload lại nên thông qua đó ta có thể xác nhận được chữ ký có bị giả mạo hay không.

nguồn tham khảo : https://jwt.io/introduction/

Học Singleton Pattern trong 5 phút.

Đặt vấn đề

Trong bài viết này mình sẽ giúp các bạn trả lời 4 câu hỏi về Single pattern trong vòng 5 phút.

  1. Singleton Pattern là gì?
  2. Tại sao cần dùng Singleton Pattern
  3. Làm thế nào để implement Singleton Pattern
  4. Có những cách nào để implement Singleton Pattern Liệu có đủ không nhỉ các bạn cùng theo dõi nhé

1. Single Pattern là gì?

Theo Gang of Four patterns một cuốn sách rất nổi tiếng về design pattern thì Single Pattern là một design pattern trong số 5 design pattern thuộc nhóm Creational Design Pattern

CreationalStructureBehavioral
Abstract factoryAdapterChain of responsibility
BuilderBridgeCommand
FactoryCompositeInterpreter
PrototypeDecoratorIterator
#SingletonFacadeMediator
FlyweightMementoMemento
ProxyObserver
Strategy
Template Method
Visitor

Single Pattern là một design pattern mà

  1. Đảm bảo rằng một class chỉ có duy nhất một instance (khởi tạo – mình xin phép để nguyên không dịch từ này)
  2. Và cung cấp một cáchs toàn cầu để truy cấp tới instance đó.

Vậy tại sao cần phải sử dụng Single Pattern

2. Tại sao cần dùng Singleton Pattern?

Hầu hết các đối tượng trong một ứng dụng đều chịu trách nhiệm cho công việc của chúng và truy xuất dữ liệu tự lưu trữ (self-contained data) và các tham chiếu trong phạm vi được đưa ra của chúng. Tuy nhiên, có nhiều đối tượng có thêm những nhiệm vụ và có ảnh hưởng rộng hơn, chẳng hạn như quản lý các nguồn tài nguyên bị giới hạn hoặc theo dõi toàn bộ trạng thái của hệ thống. Ví dụ có thể có rất nhiều máy in trong hệ thống nhưng chỉ có thể tồn tại duy nhất một Sprinter Spooler (Phần quản lý máy in)

Hay

giả sử trong ứng dụng có chức năng bật tắt nhạc nền chẳng hạn, khi người dùng mở app thì ứng dụng sẽ tự động mở nhạc nền và nếu người dùng muốn tắt thì phải vào setting trong app để tắt nó, trong setting của app cho phép người dùng quản lí việc mở hay tắt nhạc, và trong trường hợp này bạn sẽ cần sử dụng singleton để quản lí việc này. Chắc chắn bạn phải cần duy nhất 1 instance để có thể ra lệnh bật hay tắt, tại sao ? vì đơn giản bạn không thể tạo 1 instance để mở nhạc rồi sau đó lại tạo 1 instance khác để tắt nhạc, lúc này sẽ có 2 instance được tạo ra, 2 instance này không liên quan đến nhau nên không thể thực hiện thực hiện việc cho nhau được, bạn phải hiểu rằng instance nào bật thì chỉ có instance đó mới được phép tắt nên dẫn đến phải cần 1 instance.

3.Làm thế nào để implement Singleton Pattern

Vậy là thế nào để có thể implement Singleton Pattern chúng ta cần trả lời 2 câu hỏi.

  1. Làm sao để 1 class chỉ có thể có duy nhất 1instance? Trả lời
  • Private constructor của class đó để đảm bảo rằng class lớp khác không thể truy cập vào constructor và tạo ra instance mới
  • Tạo một biến private static là thể hiện của class đó để đảm bảo rằng nó là duy nhất và chỉ được tạo ra trong class đó thôi.
  1. Làm sao để có thể ccung cấp một cáchs toàn cầu để truy cấp tới instance đó. Trả lời
  • Tạo một public static menthod trả về instance vừa khởi tạo bên trên, đây là cách duy nhất để các class khác có thể truy cập vào instance của class này

Vậy cụ thể có những cách nào để implement Singleton Pattern

4. Có những cách nào để implement Singleton Pattern

4.1 Eager initialization

public class EagerInitializedSingleton {

   private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();

   //private constructor to avoid client applications to use constructor
   private EagerInitializedSingleton(){}

   public static EagerInitializedSingleton getInstance(){
       return instance;
   }
}

Đây là cách dễ nhất nhưng nó có một nhược điểm là mặc dù instance đã được khởi tạo nhưng có thể sẽ không dùng tới. vì vậy chúng ta có cách thứ 2.

4.2 Lazy initialization

public class LazyInitializedSingleton {

    private static LazyInitializedSingleton instance;

    private LazyInitializedSingleton(){}

    public static LazyInitializedSingleton getInstance(){
        if(instance == null){
            instance = new LazyInitializedSingleton();
        }
        return instance;
        }
}

Cách này đã khắc phục được nhược điểm của cách 1 Eager initialization, chỉ khi nào geInstance được gọi thì instance mới được khởi tạo. Tuy nhiên cách này chỉ sử dụng tốt trong trường hợp đơn luồng, trường hợp nếu có 2 luồng cùng chạy và cùng gọi hàm getInstance tại cùng một thời điểm thì đương nhiên chúng ta có ít nhất 2 thể hiện của instance. Vậy ta phải làm sao với trường hợp đa luồng. chúng ta đi tới cách tiếp theo

4.3 Thread Safe initialization

public class ThreadSafeSingleton {

    private static ThreadSafeSingleton instance;

    private ThreadSafeSingleton(){}

    public static synchronized ThreadSafeSingleton getInstance(){
        if(instance == null){
            instance = new ThreadSafeSingleton();
        }
        return instance;
     }


Cách đơn gảin nhất là chúng ta gọi phương thức synchronized của hàm getInstance() và như vậy hệ thống đảm bảo rằng tại cùng một thời điểm chỉ có thể có 1 luồng có thể truy cập vào hàm getInstance(), và đảm bảo rằng chỉ có duy nhất 1 thể hiện của class Tuy nhiên một menthod synchronized sẽ chạy rất chậm và tốn hiệu năng vì vậy chúng ta cần cải tiến nó đi 1 chút.

4.4 Thread Safe Upgrade initialization

Mình tạm gọi nó là Thread Safe Upgrade initialization, thay vì chúng ta Thread Safe cả menthod getInstance() chúng ta chỉ Thread Safe một đoạn mã quan trong

ublic class ThreadSafeSingleton {
    private static ThreadSafeSingleton instance;
    private ThreadSafeSingleton(){}

    public static ThreadSafeSingleton getInstance(){
        if(instance == null){
            synchronized(ThreadSafeSingleton.class){
                if(instance == null){
                   instance = new ThreadSafeSingleton();
                }
            }
        }
        return instance;
     }
}

Có rất nhiều cách implement cho Singleton, mình thì hay sử dụng cách 2 cho những ứng dụng chỉ làm việc với 1 thread và cách thứ 4 cho trường hợp đa luồng. Các bạn hãy chọn cho mình cách implement phù hợp cho từng trường hợp nhé.👍 Trên đây là phần giới thiệu của mình về Singleton. Các bạn có thể xem và tham khảo.

nguồn : https://viblo.asia/p/hoc-singleton-pattern-trong-5-phut-4P856goOKY3

So sánh giữa cookie vs token authentication

Ở bài trước mình đã có 1 bài viết về nội dung là : Phân biệt sự khác nhau giữa Authentication và Authorization. Bạn có thể đọc lại nó ở đây

Thì ở bài này thì mình sẽ cùng nhau đi tìm hiểu và so sánh được việc authentication bằng cookie với token.

Cookie vs Token-Based Authentication

Ngày nay thì đa số các hệ thống sẽ áp dụng cách thứ 2 đó là authentication bằng Token-Base. Nhưng trước đó thì Cookie-Base cũng được sử dụng khá phổ biến.

Ở bài này mục đích của mình sẽ là chỉ tìm hiểu ở mức cơ bản, sẽ đưa ra những flow của chúng. Còn về chi tiết thì mọi người có thể tìm hiểu sâu hơn. Mình sẽ đưa ra cái nhìn tổng quan để mọi người biết và hình dung được việc authen bằng cả 2 hình thức. Còn về đi sâu chi tiết thì nó khá phức tạp, tại thời điểm mình viết bài này thì mức độ hiểu biết của mình chỉ ở mức cơ bản. Sau này nếu có hiểu biết nhiều hơn, sâu hơn thì mình sẽ chia sẻ nó ở một bài viết khác.

Flow làm việc của cookie vs token

nhìn vào hình vẽ bên trên thì flow 2 thằng sẽ gần giống nhau. Ở đây mình sẽ có 2 phần đó là browser server.

browser hay còn gọi là trình duyệt, là các ứng dụng mà bạn dùng để lướt web như chorme, coccoc… đóng vai trò là phía client.
Đầu tiên khi mà bạn login vào 1 hệ thống, thì phía trình duyệt của bạn sẽ gửi lên server thông tin username và password bạn vừa nhập ở phía trình duyệt của bạn vào

Ví dụ khi bạn login vào Facebook, thì bạn cần nhập thông tin username và password của bạn vào ô đăng nhập, sau đó khi bạn ấn button đăng nhập. Thì phía client sẽ gửi thông tin đó lên phía server để tiếp nhận và xử lý.

về phần phía server sau khi tiếp nhận được thông tin username và password, thì việc tiếp theo là phải check thông tin trong database để xem username và password có tồn tại trong hệ thống hay không. Sau đó trả lại kết quả cho phía client. Trong trường hợp mà thông tin username và password hợp lệ, thì phía server sẽ trả về cho client một thông tin nào đó (có thể là 1 đoạn token, hay 1 giá trị nào đó). Phía client sẽ lưu cái thông tin trả về đó lại và khi phía client muốn thực hiện 1 request đến server, thì client sẽ phải gửi kèm cái thông tin vừa lưu đó cùng với request lên server.
Ví dụ 1 xíu cho dễ hình dung nhé, mọi người còn nhớ câu chuyện về bầy dê và con sói không. Khi mà dê mẹ đi ra khỏi nhà, thì bà đã để lại những thông tin và dấu hiệu chỉ có dê mẹ và dê con biết. Chỉ có ai biết và nắm được thông tin kí hiệu đó thì mới là dê mẹ. Con sói đã cố gắng vào nhà nhưng vì không biết được mật mã nên đã không thể vào được.

Ở đây việc vào nhà cũng như việc bạn truy cập vào bên trong hệ thống, chỉ có những người của hệ thống mới có và nắm giữ được thông tin từ phía server trả về. Từ thông tin đó thì hệ thống mới biết và nhận dạng được là user đó có đúng là của hệ thống mình không. Lúc đó mới cấp quyền cho truy cập hệ thống.
Thì sau khi login thành công và nhận được thông tin từ phía server trả về, mỗi lần phía client request thì phải đính kèm thông tin đó theo. Phía server sẽ có nhiệm vụ là validate cái thông tin đính kèm đó. Nếu hợp lệ thì sẽ trả lại response cho phía client, còn trường hợp không hợp lệ thì sẽ không trả về dữ liệu.
Với cái flow này thì cả 2 sẽ tương tự như nhau, nhưng cách thực hiện và triển khai sẽ khác nhau. Vậy cùng đi tìm hiểu xem chúng khác nhau như thế nào nhé.

1. Cookie Base

Đối với cookie base, khi bạn gửi thông tin username và password lên server thì server sẽ tạo ra 1 session, hay còn gọi là 1 phiên làm việc giữa client và server. Phía server sẽ lưu thông tin session đó vào trong database và sẽ set cookie ở phía client với thông tin session id chính là cái id vừa mới tạo ra. Điều này để giúp nhận ra được đây là phiên làm việc giữa client này và server. 1 server sẽ làm việc với rất nhiều client, mỗi client sẽ nắm giữ session và id khác nhau. Khi mà phía client đã có cookie lưu lại session rồi thì với mỗi lần thực hiện request, cookie sẽ gửi lên bao gồm cả thông tin session đến phía server. Phía server sẽ lấy ra thông tin session id đó và đem so sánh với database để xem session đó có hợp lệ hay không. Và để kiểm tra xem session đó thuộc user nào, user đấy có thông tin gì, có những quyền nào đối với hệ thống, được phép truy cập những tài nguyên nào. Từ đó sẽ trả ra response, còn đối với session k hợp lệ, thì sẽ k trả ra dữ liệu.

Điều mình nhận thấy ngay ở đây là gì, mỗi khi có client truy cập đến server, phía server sẽ phải tạo ra một session và phải lưu trữ nó lại trong database để kiểm tra và xử lý nó.
Và mỗi 1 request, server sẽ lại phải lấy session id mà client gửi lên để query db xem session đó có hợp lệ hay không. Với số lượng client nhiều thì phía server sẽ phải lưu trữ và xử lý rất nhiều. Điều này làm giảm performance của hệ thống và tăng số lượng data mà hệ thống phải lưu trữ. Vì vậy mà cách authentication bằng Cookie Base ngày này không còn được sử dụng nhiều nữa. Thay vào đó là sẽ được thay thế bởi Token Base.
Thì Token Base hoạt động như thế nào, và nó khắc phục nhược điểm của Cookie Base ra sao thì mình sẽ cùng đi tìm hiểu tiếp về Token Base.

2. Token Base.

Tương tự như với Token Base, việc authentication bằng Cookie Base thì bạn vẫn sẽ phải gửi lên username và password. Sau khi check rằng username và password hợp lệ, thì server sẽ tạo ra 1 token. Ở đây thì mình khuyến khích dùng JWT (Json Web Token, mình sẽ làm 1 bài khác nói rõ hơn về JWT). Hiểu đơn giản là server sẽ tạo ra 1 token, và token sẽ chứa các thông tin liên quan đến việc dùng để verify ví dụ như role của user… Thì phía trình duyệt sau khi nhận dc token này, sẽ lưu vào local storage. Và mỗi khi thực hiện 1 request, trình duyệt sẽ đính kèm cái token này vào cái header authorization và gửi ngược lại lên server. Lúc này nhiệm vụ của server là đi validate cái token này, xem token có hợp lệ hay không. Và nếu token bị sửa đổi ở phía client thì phía server sẽ phát hiện ra và token đó sẽ không còn hợp lệ. Cơ chế ra sao thì ở bài sau mình sẽ nói rõ. Và ok khi mà nó kiểm tra được rằng token này đã hợp lệ, thì lúc này nó sẽ coi đây là 1 request hợp lệ và sẽ trả ra dữ liệu. Ở đây khác với Cookie Base , thằng Token Base sẽ không phải lưu trữ, quản lý các token. Không phải query xuống database để kiểm tra token. Việc này giúp cho việc thực hiện request sẽ nhanh và đơn giản hơn nhiều. Giảm thiểu công việc khi phát triển hệ thống. Và khi logout, thì bạn phải gửi 1 yêu cầu để server nó xóa cái token đó đi. Điều này nhằm đảm bảo tính bảo mật, nếu không thì token vẫn còn hợp lệ.

Và sau đây mình sẽ tổng hợp lại một số điểm khác nhau giữa 2 cách authentication bên trên.

Cảm ơn bạn đã theo dõi đến đây, mình mong rằng sau bài viết này. Bạn sẽ có 1 cái nhìn rõ hơn về việc authentication bằng Token BaseCookie Base. Từ đó sẽ có lựa chọn hợp lý với hệ thống của mình.

Link tham khảo :
https://dzone.com/articles/cookies-vs-tokens-the-definitive-guide
https://wp-rocket.me/blog/difference-json-web-tokens-vs-session-cookies/

Phân biệt sự khác nhau giữa Authentication và Authorization

Có lẽ trong quá trình lập trình bạn đã được nghe rất nhiều về 2 khái niệm authentication và authorization nhưng liệu bạn đã phân biệt được sự khác nhau giữa 2 khái niệm này? hay đôi khi bạn vẫn mập mờ không hiểu được đâu là authorization và đâu là authentication?

Hôm nay mình xin dịch lại bài viết sự khác biệt giữa authentication và authorization để giúp các bạn có thể hiểu rõ hơn về 2 khái niệm này. 

Cả 2 thuật ngữ thường được sử dụng kết hợp với nhau để nói về bảo mật, đặc biệt là khi nói đến quyền truy cập vào hệ thống. Cả hai đều là những chủ đề rất quan trọng thường đi kèm với các trang web như phần quan trọng trong cơ sở hạ tầng dịch vụ của mình. Tuy nhiên, cả hai thuật ngữ rất khác nhau với các khái niệm hoàn toàn khác nhau. Trong khi đó chúng thường được sử dụng trong bối cảnh tương tự với công cụ tương tự, chúng là hoàn toàn khác biệt với nhau.

Authentication (xác thực) có nghĩa là xác nhận danh tính của riêng bạn, trong khi authorization (ủy quyền) có nghĩa là cấp quyền truy cập vào hệ thống. Nói một cách đơn giản, authentication là quá trình xác minh bạn là ai, trong khi authorization là quá trình xác minh những gì bạn có quyền truy cập.

Authentication

Authentication là về việc xác thực thông tin đăng nhập của bạn như Tên người dùng / ID người dùng và mật khẩu để xác minh danh tính của bạn. Trong các public và private network, hệ thống xác thực danh tính người dùng thông qua mật khẩu đăng nhập. Authentication thường được thực hiện bởi tên người dùng và mật khẩu, và đôi khi kết hợp với các yếu tố xác thực, trong đó đề cập đến các cách khác nhau để được xác thực.

Các Authentication factor xác định các yếu tố khác nhau mà hệ thống sử dụng để xác minh một danh tính trước khi cấp cho anh ta quyền truy cập vào bất cứ điều gì từ việc truy cập file đến yêu cầu giao dịch ngân hàng. Một danh tính người dùng có thể được xác định bởi những gì anh ta biết, những gì anh ta có. Khi nói đến bảo mật, ít nhất hai hoặc cả ba yếu tố xác thực phải được xác minh để cấp cho ai đó quyền truy cập vào hệ thống.

Dựa trên cấp độ bảo mật, authentication factor có thể thay đổi theo một trong các cách sau:

  • Single-Factor Authentication – Nó là phương thức xác thực đơn giản nhất thường dựa vào mật khẩu đơn giản để cấp cho người dùng quyền truy cập vào một hệ thống cụ thể là một website hoặc network. Người này có thể yêu cầu quyền truy cập vào hệ thống chỉ bằng một trong các thông tin đăng nhập để xác minh danh tính của mình. Ví dụ phổ biến nhất về xác thực một yếu tố sẽ là thông tin đăng nhập chỉ yêu cầu mật khẩu đối với tên người dùng hoặc địa chỉ email.
  • Two-Factor Authentication – Như tên của nó, nó có một quy trình xác minh gồm hai bước, không chỉ yêu cầu tên người dùng và mật khẩu, mà còn một thứ mà chỉ người dùng biết, để đảm bảo mức độ bảo mật bổ sung, chẳng hạn như pin ATM, chỉ người dùng mới biết. Sử dụng tên người dùng và mật khẩu cùng với một thông tin bí mật bổ sung khiến cho những kẻ lừa đảo hầu như không thể đánh cắp dữ liệu có giá trị.
  • Multi-Factor Authentication – Nó có một phương thức xác thực tiên tiến nhất sử dụng hai hoặc nhiều mức bảo mật từ các loại xác thực độc lập để cấp quyền truy cập cho người dùng vào hệ thống. Tất cả các yếu tố phải độc lập với nhau để loại bỏ bất kỳ lỗ hổng nào trong hệ thống. Các tổ chức tài chính, ngân hàng và các cơ quan thực thi pháp luật sử dụng xác thực nhiều yếu tố để bảo vệ dữ liệu và ứng dụng của họ khỏi các mối đe dọa tiềm ẩn.

Ví dụ: khi bạn nhập thẻ ATM vào máy ATM, máy sẽ yêu cầu bạn nhập mã pin. Sau khi bạn nhập mã pin chính xác, ngân hàng sẽ xác nhận danh tính của bạn rằng thẻ thực sự thuộc về bạn và bạn là chủ sở hữu hợp pháp của thẻ. Bằng cách xác nhận mã pin thẻ ATM của bạn, ngân hàng thực sự xác minh được danh tính của bạn, được gọi là authentication. Nó chỉ đơn thuần xác định bạn là ai, không có gì khác.

Authorization

Mặt khác, Authorization xảy ra sau khi hệ thống của bạn được authentication (xác thực) thành công, cuối cùng cho phép bạn toàn quyền truy cập các tài nguyên như thông tin, file, cơ sở dữ liệu, quỹ, địa điểm, hầu hết mọi thứ. Nói một cách đơn giản, authorization xác định khả năng của bạn để truy cập hệ thống và ở mức độ nào. Khi danh tính của bạn được hệ thống xác minh sau khi xác thực thành công, bạn sẽ được phép truy cập tài nguyên của hệ thống.

Authorization là quá trình để xác định xem người dùng được xác thực có quyền truy cập vào các tài nguyên cụ thể hay không. Nó xác minh quyền của bạn để cấp cho bạn quyền truy cập vào các tài nguyên như thông tin, cơ sở dữ liệu, file, v.v. Authorization thường được đưa ra sau khi xác thực xác nhận các đặc quyền của bạn để thực hiện. Nói một cách đơn giản hơn, nó giống như cho phép ai đó chính thức làm điều gì đó hoặc bất cứ điều gì.

Ví dụ, quy trình xác minh và xác nhận ID nhân viên và mật khẩu trong một tổ chức được gọi là authentication, nhưng xác định nhân viên nào có quyền truy cập vào tầng nào được gọi là authorization. Hãy nói với bạn rằng bạn đang đi du lịch và bạn sẽ lên một chuyến bay. Khi bạn xuất trình vé và một số giấy tờ tùy thân trước khi nhận phòng, bạn sẽ nhận được thẻ lên máy bay xác nhận rằng cơ quan sân bay đã xác thực danh tính của bạn. Nhưng đó không phải là nó. Một tiếp viên hàng không phải ủy quyền cho bạn lên chuyến bay mà bạn được cho là đang bay, cho phép bạn truy cập vào bên trong máy bay và các tài nguyên của nó.

Truy cập vào một hệ thống được bảo vệ bởi cả authentication và authorization. Mọi nỗ lực truy cập hệ thống có thể được xác thực bằng cách nhập thông tin xác thực, nhưng chỉ có thể được chấp nhận sau khi ủy quyền thành công. Nếu nỗ lực được xác thực nhưng không được phép, hệ thống sẽ từ chối quyền truy cập vào hệ thống.

AuthenticationAuthorization
Authentication xác nhận danh tính của bạn để cấp quyền truy cập vào hệ thống.Authorization xác định xem bạn có được phép truy cập tài nguyên không.
Đây là quá trình xác nhận thông tin đăng nhập để có quyền truy cập của người dùng.Đó là quá trình xác minh xem có cho phép truy cập hay không.
Nó quyết định liệu người dùng có phải là những gì anh ta tuyên bố hay không.Nó xác định những gì người dùng có thể và không thể truy cập.
Authentication thường yêu cầu tên người dùng và mật khẩu.Các yếu tố xác thực cần thiết để authorization có thể khác nhau, tùy thuộc vào mức độ bảo mật.
Authentication là bước đầu tiên của authorization vì vậy luôn luôn đến trước.Authorization được thực hiện sau khi authentication thành công.
Ví dụ, sinh viên của một trường đại học cụ thể được yêu cầu tự xác thực trước khi truy cập vào liên kết sinh viên của trang web chính thức của trường đại học. Điều này được gọi là authentication.Ví dụ, authorization xác định chính xác thông tin nào sinh viên được phép truy cập trên trang web của trường đại học sau khi authentication thành công.

Summary

Mặc dù, cả hai thuật ngữ thường được sử dụng kết hợp với nhau, chúng có các khái niệm và ý nghĩa hoàn toàn khác nhau. Trong khi cả hai khái niệm này đều quan trọng đối với cơ sở hạ tầng dịch vụ web, đặc biệt là khi cấp quyền truy cập vào hệ thống, hiểu từng thuật ngữ liên quan đến bảo mật là chìa khóa. Trong khi hầu hết chúng ta nhầm lẫn một thuật ngữ này với một thuật ngữ khác, hiểu được sự khác biệt giữa chúng là điều quan trọng thực sự rất đơn giản. Nếu authentication là bạn là ai thì authorization là những gì bạn có thể truy cập và sửa đổi. Nói một cách đơn giản, authentication là xác định xem ai đó là người mà anh ta tuyên bố là. Mặt khác, Authorization xác định quyền của mình để truy cập tài nguyên.

Tài liệu: http://www.differencebetween.net/technology/difference-between-authentication-and-authorization/

father-son-crying-child-sad-daddy - Revive India

Vợ của anh vì một lý do ngoài ý muốn đã qua đời được 4 năm, anh vì không có cách nào có thể chăm sóc được con nên cảm thấy chán nản và mệt mỏi.Một buổi tối khi anh trở về nhà, vì quá mệt mỏi nên anh chỉ chào hỏi đứa con ngắn gọn và không muốn ăn cơm, cởi xong bộ comple liền lên giường nằm. Đúng lúc đó, ầm một tiếng, bát mì tôm làm bẩn hết chăn và ga trải giường, hóa ra trong chăn có một bát mì tôm. “Cái thằng ranh con này”, anh ta liền vớ một chiếc móc quần áo chạy ra ngoài đánh cho đứa con trai đang ngồi chơi một trận.Đứa con trai vừa khóc vừa nói:- Cơm sáng đã ăn hết rồi, đến tối con chưa thấy bố về thấy đói bụng nên đi tìm đồ ăn, con tìm thấy mì tôm trong tủ bếp, muốn nấu mì tôm ăn nhưng bố dặn không được tùy tiện dùng bếp gas nên con lấy nước nóng từ trong vòi tắm pha mì tôm, con pha một bát ăn, còn một bát để phần bố. Sợ mì tôm bị nguội nên con mang vào giường ủ trong chăn đợi bố về ăn cho nóng. Con mải chơi đồ chơi mới mượn được của bạn nên khi bố về đã quên không nói với bố.Anh không muốn đứa con thấy mình khóc nên vội vã vào nhà vệ sinh, mở vòi nước và khóc. Khi đã ổn định tinh thần, anh mở cửa phòng con trai và nhìn thấy đứa con trai trong bộ quần áo ngủ, nước mắt giàn giụa và tay đang cầm bức hình của mẹ nó.Từ đó trở đi, anh chăm sóc con trai tận tâm hơn, chu đáo hơn, khi con trai mới vào học cấp I, anh đánh con một trận nữa. Hôm đó, thầy giáo gọi điện về nhà báo con anh không đi học, anh lập tức xin nghỉ về nhà, chạy đi tìm con khắp nơi, sau vài tiếng đồng hồ đi tìm anh đến một cửa hàng bán văn phòng phẩm nhìn thấy đứa con đang đứng trước một đồ chơi điện tử, thế là anh tức giận đánh con, đứa con không một lời giải thích, chỉ nói “Con xin lỗi”.Một năm sau, anh nhận được điện thoại từ bưu điện, nói con trai anh đã bỏ một loạt các bức thư không viết địa chỉ vào hòm thư, cuối năm là lức bưu điện bận rộn nhất nên điều này gây ra rất nhiều khó khăn cho họ. Anh lập tức đến bưu điện, mang những bức thư đó về ném trước mặt con trai nói:- Sao mày lại làm những trò tai quái thế này hả?Thằng bé vừa khóc vừa trả lời:- Đây là những bức thư con gửi cho mẹ.Mắt người bố cay cay hỏi con:- Thế sao một lúc gửi nhiều thư như vậy?Đứa con nói:- Trước đây con còn thấp nên không bỏ thư vào hòm thư được, bây giờ con lớn có thể bỏ thư vào được rồi nên con mang gửi hết những bức thư con viết từ trước đến giờ.Ông bố nghe xong, tâm trạng rối bời không biết nói gì với con. Một lát sau ông bố nói:- Mẹ con giờ ở trên thiên đàng, sau này con viết thư xong, hãy đốt nó đi thì có thể gửi thư cho mẹ được đấy.Đợi đứa con ngủ, anh mở những bức thư đó xem đứa con muốn nói gì với mẹ, trong đó có một bức thư khiến anh vô cùng xúc động.“Mẹ thân yêu của con: Con nhớ mẹ lắm! Mẹ ơi, hôm nay ở trường con có một tiết mục mẹ cùng con biểu diễn, nhưng vì con không có mẹ nên con không tham gia, con cũng không nói cho bố biết vì sợ bố sẽ nhớ mẹ. Thế là bố đi khắp nơi tìm con, nhưng con muốn bố nhìn thấy con giống như đang đi chơi nên con đã cố ý đứng trước một đồ chơi điện tử. Tuy bố đã mắng con nhưng con đã kiên quyết không nói cho bố biết vì sao. Mẹ ơi, con ngày nào cũng thấy bố đứng trước ảnh mẹ ngắm rất lâu, con nghĩ bố cũng như con rất nhớ mẹ đấy!Mẹ ơi, con đã sắp quên giọng nói của mẹ rồi, con xin mẹ trong giấc mơ của con hãy để con được gặp mẹ một lần được không, để con nhìn thấy khuôn mặt của mẹ, nghe thấy giọng nói của mẹ, được không mẹ?Con nghe mọi người bảo nếu ôm bức ảnh của người mình nhớ vào lòng rồi đi ngủ thì sẽ mơ thấy người đó, nhưng mà mẹ ơi, vì sao con tối nào cũng làm như thế mà trong giấc mơ của con vẫn không gặp được mẹ?”Đọc xong bức thư, ông bố òa khóc. Anh không ngừng tự trách mình: phải làm sao mới có thể lấp được khoảng trống mà người vợ để lại đây?Chúng ta là những ông bố bà mẹ khi đã mang cuộc sống của đứa con đến với thế giới này có nghĩa là gánh trên vai trách nhiệm vô cùng to lớn. Khi đã là một người mẹ, không nên tăng ca quá nhiều, khi đã là một người bố, không nên uống quá nhiều rượu, đừng nên hút nhiều thuốc, phải chăm sóc tốt cho bản thân mới có thể yêu thương con hết lòng, tuyệt đối đừng nên vì muốn kiếm nhiều tiền mà hủy hoại sức khỏe của mình, không có sức khỏe thì những danh lợi kia có nghĩa lý gì. Và cũng đừng nghĩ rằng đợi đến khi bố mẹ có nhiều tiền thì sẽ như thế này như thế kia, nào ai biết sau này chuyện gì sẽ xảy ra, có thể sau một giây mọi chuyện đã khác.Những ông bố bà mẹ xin đừng vì những chuyện nhỏ nhặt mà dễ dàng ly hôn. Vì đau thương lớn nhất sau sự đổ vỡ đó không ai hết mà chính là thuộc về đứa con. Bạn đã kết hôn hay chưa kết hôn thì hãy nhớ một điều, xin hãy quý trọng “nó”.

Tại sao GraphQL là tương lai của API

Image for post

Dạo gần đây thì mình được tiếp xúc với GraphQL, nói chính xác hơn là thời gian rảnh và mình có tìm hiểu về nó. Trước đó thì mình đã làm nhiều với REST và Soap. Và mình nhận ra rằng GraphQL có những điểm cải tiến và nó khắc phục được một số các nhược điểm của 2 người anh đi trước đó. GraphQL được phát triển bởi Facebook, tuy tại thời điểm mình viết bài viết này, xung quanh mình mọi người vẫn đang dùng REST khá là phổ biến, nhưng mình tin rằng với những thế mạnh của GraphQL thì nó sẽ là tương lai mới của API. Còn bao lâu nữa thì mình không dám nói trước 😀

– Lịch sử ra đời và phát triển của GraphQL.
GraphQL được Facebook phát triển vào năm 2012 và được phát triển công khai vào năm 2015. Vài năm gần đây GraphQL đang phát triển mạnh mẽ và được rất nhiều công ty lớn sử dụng như Spotify, Facebook, GitHub, NYTimes, Netflix, Walmart, và rất nhiều công ty khác. Ở bài viết này thì chúng ta sẽ tìm hiểu về GraphQL và tại sao mình lại nói nó sẽ là tương lai của API.

Thì đầu tiên, GraphQL là gì ?
GraphQL: A query language for your API, là một cú pháp mô tả cách yêu cầu lấy dữ liệu, và thường được dùng để load data từ một server cho client. GraphQL bao gồm 3 điểm đặc trưng bao gồm cho phép client xác định chính xác những gì dữ liệu họ cần, làm cho việc tổng hợp dữ liệu từ nhiều nguồn dễ dàng hơn và nó sử dụng một type system để mô tả dữ liệu.

Trước khi đi tìm hiểu sâu hơn về GraphQL thì chúng ta lướt qua về REST và Soap để có cái nhìn tổng qua hơn về cả 3.
– SOAP (Simple Object Access Protocol) và REST (Representational State Transfer)  là 2 đáp án cho 1 câu hỏi: làm sao để truy cập Web services. Sự lựa chọn ban đầu có thể dễ dàng, nhưng nhiều lúc cũng rất khó khăn với những dự án phức tạp.

SOAP là chuẩn protocol để access các Webservice chuẩn, được phát triển bởi Microsoft, nó được sử dụng rộng rãi một thời gian khá lâu trước và có những lợi ích nhất định.

SOAP định nghĩa 1 phương thức (set of rules) chuẩn để kết nối dựa trên XML để trao đổi thông tin. SOAP sử dụng nhiều phương thức kết nối như HTTP và SMTP.


REST là người đến sau, nó sữa chữa những vấn đề của SOAP và đưa ra cách đơn giản hơn để truy cập Web service. Tuy nhiên, đôi lúc thì SOAP cũng dễ sử dụng hơn, đôi lúc REST cũng có những vấn đề. Cả 2 công nghệ này đều cần cân nhắc trước khi quyết định sử dụng.

REST mô tả 1 tập các công thức mà ở đó dữ liệu có thể được trao đổi thông qua 1 chuẩn interface (ví dụ HTTP). REST không chưa tầng message layer để định nghĩa các hàm và ràng buộc dữ liệu, vì vậy mà truyền dữ liệu bằng REST cũng ít tốn băng thông hơn, thường nhận và gửi bằng JSON. Client có thể access tài nguyên dựa trên URI và nhận được response cùng với state.

Và ngày nay thì REST được ưa chuộng và được thay thế rất nhiều cho SOAP, tuy nhiên thì với sự phát triển của ứng dụng và web. REST càng ngày càng bộc lộ nhiều vấn đề.

1 .A lot of endpoints
Với mỗi API, REST sẽ tạo ra 1 endpoint, Ví dụ bạn thiết kế 1 hệ thống gồm rất nhiều API, mỗi API phục vụ cho 1 mục đích khác nhau. Thì Rest sẽ tạo ra từng đó endpoint.

Image for post


Vídụ bạn muốn hiển thị một list posts, và ở dưới mỗi post là một list like, bao gồm cả tên người dùng và avatar. Cách giải quyết đơn giản là thay đổi API của ‘posts’ để nó bao gồm một ‘like’ array chứa thông tin về người dùng.

Thế nhưng khi làm như vậy cho các app mobile thì bạn sẽ phát hiện ra tốc độ của chúng chạy quá chậm. Vì thế mà giờ đây bạn sẽ cần tới 2 endpoints, một với likes và một với không likes.

Giờ thì còn có thêm một vấn đề khác xuất hiện: trong khi postsđược lưu trữ trong một MySQL database thì likes lại được lưu tại Redis store! Bạn biết mình phải làm gì trong trường hợp nào không?

Mở rộng vấn đề trên ra với việc Facebook phải quản lí vô số data source và API clients thì cũng là điều dễ hiểu khi REST APIs bị đánh giá là cũ kĩ bởi những hạn chế của nó.

-Giải pháp:

Giải pháp mà Facebook đưa ra đến từ một ý tưởng rất đơn giản: Thay vì có đến hàng tá “endpoint” ngu ngốc, sao lại không dùng chỉ một “endpoint” thông minh với khả năng tiếp thu những Query phức tạp rồi đưa ra output data với loại type tùy theo yêu cầu của client.

Thực tế mà nói, GraphQL như là một layer nằm giữa client và data source, sau khi nhận yêu cầu của client thì nó sẽ kiếm những thông tin cần từ các data source và đưa lại cho client theo format họ muốn. Vẫn chưa hiểu? Thế thì đến lúc dùng ví dụ ẩn dụ rồi đây!

Như bạn thấy đấy, REST model cũ giống y như việc bạn đặt cái bánh Pizza, rồi gọi ship hàng online và kêu bên tiệm giặt ủi đem đồ đến cho bạn. Tất cả diễn ra với 3 cuộc gọi và 3 cửa hàng.

GraphQL mặt khác lại giống như là thư kí riêng của bạn vậy: Sau khi bạn đưa địa chỉ của 3 cửa hàng và nói yêu cầu của bạn thì GraphQL sẽ làm hết mọi chuyện còn lại trong khi bạn chỉ việc chờ chúng được chuyển đến cho mình.

Nói cách khác GraphQL tạo ra một ngôn ngữ chuẩn (standard language) để thực hiện những công việc này.

2.Over-fetching and under-fetching of information
Một điều nữa gây khó chịu cho nhiều nhà phát triển là việc quá tải và thiếu thông tin của REST. Điều này là do các API REST luôn trả về một cấu trúc cố định. Chúng ta có thể nhận được chính xác dữ liệu mà chúng ta muốn trừ khi chúng ta tạo một endpoint cụ thể cho điều đó.
Ví dụ bạn muốn tạo 1 API để lấy ra thông tin người dùng bao gồm firstName, lastName và age . Thì không có cách nào để bạn có thể lấy chính xác 3 thông tin trên mà không phải lấy ra toàn bộ thông tin người dùng. Vì bạn không thể biết trước rằng tại thời điểm này người dùng muốn lấy ra những thông tin gì mà để thiết kế API riêng cho việc này được. Cứ cho rằng bạn biết chắc là tại thời điểm này người dùng chỉ lấy ra 3 thông tin bên trên và bạn tạo 1 API và query DB chỉ để lấy ra 3 thông tin bên trên. Vậy chẳng nhẽ với mỗi lần muốn thêm hay bớt thông tin cần lấy thì bạn lại tạo ra một API cho việc đó ? Điều này là không khả thi, thay vào đó thì REST sẽ lấy ra toàn bộ thông tin người dùng và sẽ trả ra tùy theo thông tin được nhận từ đầu vào. Nghĩa là cho dù bạn chỉ cần lấy 1 thông tin của user là name thôi thì REST cũng sẽ lấy ra toàn bộ thông tin của user đó và trả ra cho bạn mỗi name.

Image for post

3.Versioning
Lấy một ví dụ đơn giản, công ty yêu cầu bạn phải nâng cấp tính năng cho việc tra cứu thông tin người dùng từ v1 lên v2. Đương nhiên là tính năng cũ v1 vẫn phải được đưa vào sử dụng cho đến khi tính năng mới v2 được ra mắt. Thì với việc thiết kế API theo REST, bạn sẽ phải đánh version cho API như kiểu bên dưới :

https://example.com/api/v1/users/12312
https://example.com/api/v2/users/12312

Việc này đôi khi có thể gây nhầm lẫn và sẽ có những khó khăn trong quá trình thiết kế và phát triển. Nhưng với GraphQL thì điều này trở nên không cần thiết.

Vậy tại sao lại nói GraphQL là tương lai của API

Quay trở lại năm 2012, khi mà Facebook vẫn đang sử dụng REST và họ gặp nhiều vấn đề cho việc phát triển của mình. Và đó là nguyên nhân họ tạo ra GraphQL.
Những nhược điểm của REST API:
– Hiệu suất kém
– Có quá nhiều endpont
– Quả tải và thiếu thông tin
– Phải thay đổi version mỗi khi cần thay đổi
– API khó hiểu

Với những vấn đè bên trên, các nhà phát triển Facebook đã tìm hiểu cách khắc phục và tạo ra một thiết kế mới và họ đặt tên cho nó là GraphQL. Về cơ bản thì nó là sự thay thế cho việc sử dụng REST và mang lại nhiều cải tiến.
Với việc sử dụng GraphQL, nó mang lại nhiều ưu điểm, hãy điểm qua một vài thứ mới mẻ của GrapQL.

-Single endpoint
không cần phải tạo ra quá nhiều endpoint cho việc lấy dữ liệu, GrapQL chỉ cần tạo ra duy nhất 1 endpont và sẽ trả ra kết quả như mình mong muốn

Image for post

-With GraphQL you fetch only the data you need
khắc phục được nhược điểm của REST, GraphQL sẽ chỉ trả về đúng những thứ mà bạn muốn mà không phải tải tất cả các thứ không liên quan. Điều này mang lại tốc độ và hiệu suất cao hơn REST

Image for post

-GraphQL makes it easy to start building APIs and be consistent

Nhiều người nghĩ là việc sử dụng GraphQL sẽ khó và phức tạp vì nó liên quan đến schema và single endpont. Nhưng nó dễ dàng hơn so với bạn nghĩ, và hiện nay GraphQL đang support hơn 12 ngôn ngữ.

GraphQL là một open source, nghĩa là cộng đồng có thể đóng góp vào việc phát triển và cải thiện nó. Khi Facebook ra mắt GrapQL, nó nhận được rất nhiều sự ủng hộ từ các nhà phát triển. Ngày nay thì GraphQL càng ngày càng phát triển, và mình nghĩ rằng trong 1 tương lai gần nào đó, nó sẽ có thể thay thế cho REST.

Image for post

Khi mới được nghe và chưa có cơ hội tiếp xúc với GraphQL thì mình nghĩ nó chỉ là 1 kiểu thiết kế API thông thường, nhưng sau quá trình làm quen và tìm hiểu thì mình nhận ra nó thực sự là 1 đột phá trong thiết kế API. GraphQL sẽ là tương lai của thiết kế API, và đó là lý do vì sao rất nhiều công ty lớn đang sử dụng.

Vào tháng 11 năm 2018, GraphQL đã tạo ra một nền tảng GraphQL, hợp tác với Linux. Và Facebook vẫn đang khuyến khích các nhà phát triển để xây dựng thêm tài liệu và công cụ cho GraphQL. Nó sẽ mang lại 1 tương lai mới cho việc thiết kế API, một tương lai đảm bảo và bên vững hơn.
Đương nhiên là nó sẽ không thể lập tức thay thế cho REST được, bởi vì hiện tại có rất nhiều các ứng dụng to đang sử dụng REST cho việc thiết kế API của họ. Và những ứng dụng đó không thể viết lại được trong vòng một đêm hay vài ngày được mà là phải một quá trình lâu dài.

Lời kết

GraphQL có vẻ sẽ khá phức tạp khi bạn mới nhìn vào bởi nó là một kĩ thuật dựa trên nhiều lĩnh vực khác nhau của công nghệ hiện đại. Tuy nhiên, nếu bạn kiên tri học hỏi thì tôi tin rằng bạn sẽ hiểu được nó một cách tường tận.

Vì thế cho dù bạn có dùng nó hay không thì cũng đều đáng bỏ thời gian ra để làm quen với GraphQL. Hiện nay, càng ngày càng có nhiều công ty và framework sử dụng nó, rất có thể GraphQL sẽ trở thành một phần không thể thiếu cho các web developer trong tương lai.

HTTP methods trong thiết kế API

Phần lớn các API được viết bằng HTTP, mà chúng ta đều đã biết về cách dùng HTTP methods theo chuẩn RESTful khi thiết kế Web API (Nếu bạn chưa biết, đọc thêm ở đây). Tuy nhiên, nhận thấy ngay là HTTP methods đã được thiết kế như từ đầu rồi, RESTful API chỉ là nguyên tắc dựa theo chuẩn này thôi. Vì vậy thì để thiết kế HTTP API cho ngon lành thì có vẻ chúng ta cần đọc lại 1 chút về HTTP methods.

Hiện tại thì HTTP đã ra tới bản thứ 3, tuy nhiên HTTP methods không bị cập nhật gì, vậy nên mình sẽ bắt đầu với RFC-7231

Common Method Properties

OK, chưa tính tới chi tiết các methods, hãy bắt đầu từ các thuộc tính chung của một method Common Method Properties . Theo đoạn này thì một HTTP methods có thể có các thuộc tính chung sau:

Safe Methods

Là các methods kiểu read-only, tức là nó chỉ lấy thông tin từ server về mà không thay đổi trạng thái của tài nguyên. Các method thay đổi trạng thái tài nguyên còn lại dĩ nhiên là Unsafe.

Idempotent Methods

Là các methods mà khi gửi một request nhiều lần thì tác động lên tài nguyên server là giống như gửi một lần. Nói cách khác thì request dạng này là request có thể lặp lại (repeat).

Hiển nhiên các Safe Methods cũng là Idempotent Methods.

Cacheable Methods

Như tên, method nào thì cache được. Tuy nhiên, phải hiểu đúng ở đây là có thể cache, chứ không phải lúc nào cũng cache.

Tóm lại, chúng ta có bảng sau

Image for post

PUT là UPSERT — PATCH là UPDATE

Có khá nhiều tài liệu ghi PUT là update, nhưng theo design của HTTP, PUT là request theo kiểu tôi muốn đối tượng này tồn tại trên server.

  • Nếu đối tượng đã tồn tại, replace nó.
  • Nếu đối tượng chưa tồn tại, tạo mới nó.

Điều này có nghĩa, sau khi gọi PUT, chắc chắn sẽ tồn tại đối tượng vừa PUT trên . Và với tính chất replace or create, PUT nên được thiết kế với body là một đối tượng hoàn chỉnh. Ví dụ:

Nếu chúng ta có đối tượng user như sau

{
"id": "string",
"first_name": "string",
"last_name": "string",
"website": "string"
}

Khi đó PUT 1 user lên server sẽ cần đầy đủ các field, dù trước đó đã tồn tại user này hay chưa

PUT /users/dodv

{
    "id": "dodv",
    "first_name": "Do",
    "last_name": "Dao",
    "website": "dodv.me"
}

Đừng trả về NOT FOUND cho DELETE

Cũng giống như PUT, DELETE là Idempotent Method. Có nghĩa là nếu chúng ta gọi DELETE 2 lần liên tiếp, thì kết quả tương tự như gọi 1 lần. Với tính chất này, có thể hiểu DELETE là hàm để đảm bảo đối tượng không còn khả dụng trên server.

DELETE nên được thiết kế kiểu này:

DELETE /users/dodv
200 OK

DELETE /users/dodv
200 OK

Hơn là kiểu này:

DELETE /users/dodv
200 OK

DELETE /users/dodv
404 Not Found

Dù POST là cacheable, nhưng …

Bắt đầu với quote mà mình siêu thích

There are only two hard things in Computer Science: cache invalidation and naming things.

Rồi, thực sự việc cache cho POST là cái gì đó khá dị. Và nó thực sự dị nếu đọc chỉ qua. Cụ thể, RFC-7234 có viết về Cache Invalidation như sau:

Đó, Unsafe method đã không cache rồi, POST nó còn không phải Idempotent, thế nên cache của POST khá khó hiểu. Sau 1 lúc đọc lòng vòng thì mình cũng hiểu ra vấn đề như sau:

Cơ bản thì việc cache cho POST là cực kỳ hạn chế và ít xảy ra. POST chỉ được cache khi có thông tin về cache (kiểu max-age hoặc Expires). Tuy nhiên, cache của POST dị là vì nó chỉ cache response của POST chứ không phải cache request. Cụ thể:

  • Cache response của POST để dùng cho cái GET tiếp theo: bằng cách set Content-Location hoặc Location header.
  • Request POST tiếp theo không có cache gì cả.

Tuy nhiên, mặc dù RFC cho phép việc cache này, nhưng cách này là cache response của request POST cho request GET, nên có một số CDN hoặc browser không cho cache kiểu này (đoạn này là đọc trên Stack Overflow — chưa kiểm chứng — thấy Firefox có support theo RFC rồi)

Tóm lại thì …

  • Nhiều khi bạn viết thế nào cũng chạy được, kể cả GET /delete_user?id=dodv, tuy nhiên làm theo chuẩn thì dễ hơn cho đồng đội và cả đối tác nữa.
  • Việc hiểu bản chất sẽ làm mình dễ nhớ hơn. Giờ ít nhất cũng không cần băn khoăn giữa PUT và PATCH chẳng hạn.
  • Các method còn lại như HEAD, OPTIONS, TRACE cũng có nhiều thứ khá hay, nên đọc thêm.
  • Lý thuyết là thế, thực tiễn là thực tiễn .Nếu đối tác của bạn là 1 công ty to đùng, và làm sai chuẩn, và bạn đang cần tích hợp vào hệ thống của họ, thì thôi, đừng tranh cãi làm gì nhé =))

Các thì trong tiếng Anh: Bảng tóm tắt 12 thì tiếng Anh

Các thì trong tiếng Anh
Trong tiếng Anh được chia thành 12 thì cơ bản theo 3 mốc thời gian: Hiện tại, Quá khứ và Tương lai.

1. Thì hiện tại đơn –  Present simple

Công thức với Động từ thường

  • Khẳng định: S + V(s/es) + O
  • Phủ định: S + do/does not + V_inf + O
  • Nghi vấn: Do/Does + S + V_inf + O?

Công thức với Động từ tobe:

  • Khẳng định: S + am/is/are + O.
  • Phủ định: S + am/is/are  not + O.
  • Nghi vấn: Am/is/are + S + O?

Cách dùng:

  • Diễn tả một sự thật hiển nhiên, một chân lý.
  • Diễn tả một thói quen, sở thích hay hành động được lặp đi lặp lại ở hiện tại.
  • Diễn tả một lịch trình, chương trình, một thời gian biểu.

Dấu hiệu nhận biết thì hiện tại đơn: Trong câu thường có những từ chỉ tần suất như: Everyday/night/week, often, usually, always, sometimes,…

2. Thì hiện tại tiếp diễn – Present continuous tense

Công thức:

  • Khẳng định: S + am/is/are + V_ing + …
  • Phủ định: S + am/is/are not + V_ing + …
  • Nghi vấn: Am/Is/Are + S + V_ing + …?

Cách dùng:

  • Diễn tả hành động đang xảy ra và kéo dài ở hiện tại.
  • Diễn tả dự định, kế hoạch sắp xảy ra trong tương lai đã định trước.
  • Diễn tả sự phàn nàn về việc gì đó, dùng với “Always”.
  • Dùng để cảnh báo, đề nghị và mệnh lệnh.

Dấu hiệu nhận biết thì hiện tại tiếp diễn: Trong câu thường có những cụm từ chỉ thời gian sau sau: Now, at the moment, at present, right now, look, listen, be quiet.…

3. Thì hiện tại hoàn thành – Present perfect tense

Công thức:

  • Khẳng định: S + has/have + V3/ed + O
  • Phủ định: S + has/have not + V3/ed + O
  • Nghi vấn: Have/has + S + V3/ed + O?

Cách dùng:

  • Diễn tả 1 hành động xảy ra trong quá khứ nhưng vẫn còn ở hiện tại và tương lai.
  • Diễn tả hành động xảy ra và kết quả trong quá khứ nhưng không nói rõ thời gian xảy ra.
  • Diễn tả hành động vừa mới xảy ra.
  • Nói về kinh nghiệm, trải nghiệm.

Dấu hiệu nhận biết thì hiện tại hoàn thành: Trong câu thường có những từ sau: Since, for, Already, just, ever, never, yet, recently, before,…

4. Thì hiện tại hoàn thành tiếp diễn – Present perfect continuous tense

Công thức:

  • Câu khẳng định: S + has/have been + V_ing
  • Câu phủ định: S + has/have not been + V-ing
  • Câu nghi vấn:  Have/Has + S + been + V-ing?

Cách dùng:

  • Diễn tả hành động xảy ra diễn ra liên tục trong quá khứ, tiếp tục kéo dài đến hiện tại. 
  • Diễn tả hành động vừa kết thúc, mục đích nêu kết quả của hành động.

Dấu hiệu nhận biết: Trong câu thì hiện tại hoàn thành tiếp diễn thường có các từ sau: All day/week, since, for, for a long time, recently, lately, up until now,…

5. Thì quá khứ đơn – Past simple tense

Công thức với Động từ thường

  • Câu khẳng định: S + V2/ed + O
  • Câu phủ định: S + did not + V_inf + O
  • Câu nghi vấn: Did + S + V_inf + O ?

Công thức với Động từ tobe:

  • Câu khẳng định: S + was/were + O
  • Câu phủ định: S + were/was not + O
  • Câu nghi vấn: Was/were + S + O?

Cách dùng:

  • Diễn tả một hành động đã xảy ra và đã kết thúc tại thời điểm trong quá khứ. 
  • Diễn tả những hành động xảy ra liên tiếp tại thời trong điểm quá khứ.
  • Diễn đạt một hành động xen vào một hành động đang diễn ra tại thời điểm trong quá khứ 

Dấu hiệu nhận biết: Các từ thường xuất hiện trong câu ở thì quá khứ đơn: Yesterday, last night/ last week/ last month/year, ago,…

Cách phát âm -ed

Trong thì quá khứ các động sẽ được thêm đuôi “ed” vào sau động từ trừ một số động từ bất quy tắc. Các bạn tham khảo về cách phát âm ed:

  • Đuôi /ed/ được phát âm là /t/ khi động từ quá khứ có phát âm kết thúc là /s/, /f/, /p/, /ʃ/, /tʃ/, /k/ 
  • Đuôi /ed/ được phát âm là /id/ khi động từ quá khứ có phát âm kết thúc là /t/ hay /d/
  • Đuôi /ed/ được phát âm là /d/ với những động từ quá khứ thuộc trường hợp còn lại.

6. Thì quá khứ tiếp diễn – Past continuous tense

Công thức:

  • Câu khẳng định: S + were/ was + V_ing + O
  • Câu phủ định: S + were/was+ not + V_ing + O
  • Câu nghi vấn: Were/was+S+ V_ing + O?

Cách dùng:

  • Để diễn tả hành động đang xảy ra tại một thời điểm trong quá khứ.
  • Diễn tả một hành động đang xảy ra trong quá khứ thì có một hành động khác xen vào.
  • Diễn tả những hành động xảy ra song song với nhau.

Dấu hiệu nhận biết: Trong câu thì quá khứ tiếp diễn thường có trạng từ thời gian trong quá khứ với thời điểm xác định.Ví dụ: At/At this time + thời gian quá khứ (at 7 o’clock yesterday), in + năm quá khứ, in the past,…

7. Thì quá khứ hoàn thành – Past perfect tense

Công thức:

  • Câu khẳng định: S + had + V3/ed + O
  • Câu phủ định: S + had + not + V3/ed + O
  • Câu nghi vấn: Had + S + V3/ed + O?

Cách dùng:

  • Diễn tả hành động đã hoàn thành trước một thời điểm trong quá khứ
  • Diễn đạt một hành động đã xảy ra trước một hành động khác trong quá khứ. Hành động xảy ra trước dùng quá khứ hoàn thành – xảy ra sau dùng quá khứ đơn

Dấu hiệu nhận biết: Trong câu thường có các từ: By the time, prior to that time, before, after, as soon as, until then,… 

8. Thì quá khứ hoàn thành tiếp diễn – Past perfect continuous tense 

Công thức:

  • Câu khẳng định: S + had been + V_ing + O
  • Câu phủ định: S + had + not + been + V_ing + O
  • Câu nghi vấn: Had + S + been + V_ing + O? ​

Cách dùng:

  • Diễn tả một hành động xảy ra liên tục trước một hành động khác trong quá khứ.
  • Diễn tả một hành động xảy ra kéo dài liên tục trước một thời điểm được xác định trong quá khứ. 

Dấu hiệu nhận biết: Trong câu thường chứa các từ như Until then, by the time, prior to that time, before, after… 

9. Thì tương lai đơn – Simple future tense

Công thức:

  • Câu khẳng định: S + shall/will + V(infinitive) + O
  • Câu phủ định: S + shall/will + not + V(infinitive) + O
  • Câu nghi vấn: Shall/will+S + V(infinitive) + O?

Cách dùng:

  • Diễn tả một dự đoán không có căn cứ xác định.
  • Diễn tả dự định đột xuất xảy ra ngay lúc nói.
  • Diễn tả lời ngỏ ý, hứa hẹn, đề nghị, đe dọa.

Dấu hiệu nhận biết: Trong câu thường có các từ: tomorrow, next day/week/month/year, in + thời gian…

10. Thì tương lai tiếp diễn – Future continuous tense

Công thức:

  • Câu khẳng định: S + will/shall + be + V-ing
  • Câu phủ định: S + will/shall + not + be + V-ing
  • Câu nghi vấn: Will/shall + S + be + V-ing?

Cách dùng:

  • Diễn tả về một hành động xảy ra trong tương lai tại thời điểm xác định.
  • Diễn tả về một hành động đang xảy ra trong tương lai thì có hành động khác chen vào. 

Dấu hiệu nhận biết: Trong câu thường có các cụm từ: next time/year/week, in the future, and soon,…

11. Thì tương lai hoàn thành – Future perfect tense

Công thức:

  • Câu khẳng định: S + shall/will + have + V3/ed
  • Câu phủ định: S + shall/will not + have + V3/ed
  • Câu nghi vấn: Shall/Will+ S + have + V3/ed?

Cách dùng :

  • Diễn tả về một hành động hoàn thành trước một thời điểm xác định trong tương lai.
  • Diễn tả về một hành động hoàn thành trước một hành động khác trong tương lai. 

Dấu hiệu nhận biết: Đi kèm với các từ: by/by the time/by the end of + thời gian trong tương lai,…

12. Thì tương lai hoàn thành tiếp diễn – Future perfect continuous tense

Công thức:

  • Câu khẳng định: S + shall/will + have been + V-ing + O
  • Câu phủ định: S + shall/will not+ have + been + V-ing
  • Câu ghi vấn: Shall/Will + S+ have been + V-ing + O?

Cách dùng:

  • Diễn tả một hành động xảy ra trong quá khứ tiếp diễn liên tục đến một thời điểm cho trước trong tương lai.

Dấu hiệu nhận biết: Có chứa For + khoảng thời gian + by/before + mốc thời gian trong tương lai: by then, by the time,…

Cách để giấu “tài liệu học tập” cực kì đơn giản.

Chào mọi người, ngày hôm nay thì mình sẽ chỉ mọi người một mẹo khá đơn giản để có thể giấu đi tài liệu học tập, hay bất kì cái gì mà không muốn ai đó động vào máy tính của mình và thấy được nó. Để làm được cách này thì việc bạn làm chỉ cần copy và paste. Không cần cài bất kì tool nào hay một kĩ năng cao siêu nào ở đây hết.

Việc đầu tiên là các bạn copy đoạn code sau cho mình :

cls
@ECHO OFF
title My Folder
if EXIST "Control Panel.{21EC2020-3AEA-1069-A2DD-08002B30309D}" goto UNLOCK
if NOT EXIST DoDV goto MDLOCKER
:CONFIRM
echo Ban co chac chan muon khoa thu muc DoDV? (C=Co/K=Khong)
set/p "cho=>"
if %cho%==C goto LOCK
if %cho%==c goto LOCK
if %cho%==K goto END
if %cho%==k goto END
echo Invalid choice.
goto CONFIRM
:LOCK
ren DoDV "Control Panel.{21EC2020-3AEA-1069-A2DD-08002B30309D}"
attrib +h +s "Control Panel.{21EC2020-3AEA-1069-A2DD-08002B30309D}"
echo Folder locked
goto End
:UNLOCK
echo Hay nhap mat khau de mo khoa:
set/p "pass=>"
if NOT %pass%== pass goto FAIL
attrib -h -s "Control Panel.{21EC2020-3AEA-1069-A2DD-08002B30309D}"
ren "Control Panel.{21EC2020-3AEA-1069-A2DD-08002B30309D}" DoDV
echo Folder Unlocked successfully
goto End
:FAIL
echo Invalid password
goto end
:MDLOCKER
md DoDV
echo DoDV created successfully
goto End
:End

Sau đó mở notepad lên và paste nó vào như video mình hướng dẫn bên dưới đây.

Rất đơn giản đúng không nào, chúc mọi người thành công.

SOLID Trong Lập Trình Hướng Đối Tượng.

SOLID là viết tắt của 5 tính chất được coi là 5 nguyên tắc “vàng” trong lập trình hướng đối tượng. Ở bài viết này chúng ta sẽ đi tìm hiểu xem 5 nguyên tắc đó là gì mà được mệnh danh là nguyên tắc vàng và cách chúng ta ứng dụng chúng vào lập trình.

SOLID là tên viết tắt của 5 nguyên lý hợp thành lại bao gồm:

  • Single responsibility principle
  • Open/closed principle
  • Liskov substitution principle
  • Interface segregation principle
  • Dependency inversion principle

1. Single responsibility principle:

Nguyên lý đầu tiên, tương ứng với chữ S trong SOLID. Nội dung nguyên lý:
– Một class chỉ nên giữ 1 trách nhiệm duy nhất (Nghĩa là chỉ có thể sửa đổi class đó với 1 lý do duy nhất).

Khi một class có quá nhiều chức năng, nó sẽ trở lên cồng kềnh và phức tạp. Nếu yêu cầu bài toán thay đổi thì đồng nghĩa với việc phải thay đổi lại code. Nó sẽ trở lên khó khăn, mất thời gian và công sức. Cùng nhìn vào ví dụ bên dưới :

ví dụ class doWork cần thực hiện logic của 4 function là A, B, C và D.
chỉ cần logic của 1 trong 4 hàm thay đổi thì class doWork sẽ phải sửa lại. Theo đúng nguyên lý thì chúng ta nên tách ra làm 4 class riêng. Mỗi class xử lý logic riêng biệt, vì vậy nếu có thay đổi ở 1 trong 4 class trên thì các class còn lại sẽ không bị ảnh hưởng. Nó sẽ dễ dàng hơn trong việc sửa chữa, nâng cấp hay quản lý.

2. Open/closed principle:

Nguyên lý thứ hai, tương ứng với chữ O trong SOLID. Có nội dung như sau:
Có thể thoái mái mở rộng 1 class, nhưng không được sửa đổi bên trong class đó (open for extension but closed for modification).

Theo nguyên lý này, mỗi khi ta muốn thêm chức năng,.. cho chương trình, chúng ta nên viết class mới mở rộng class cũ ( bằng cách kế thừa hoặc sở hữu class cũ) không nên sửa đổi class cũ. Ví dụ :

Dễ thấy nếu trong tương lại ta thêm nhiều class nữa, muốn tính diện tích của nó ta lại phải sửa class Geometry, viết thêm chừng đó hàm if nữa. Sau khi chỉnh sửa, ta phải compile và deploy lại class Geometry.
Sau khi chỉnh sửa lại như sau:

3. Liskov Substitution Principle

Nguyên lý thứ ba, tương ứng với chữ L trong SOLID. Nội dung nguyên lý:

Trong một chương trình, các object của class con có thể thay thế class cha mà không làm thay đổi tính đúng đắn của chương trình.

Đến nguyên lý thứ 3 này có chút hơi khó hiểu, nhưng không sao. Hãy thử tưởng tượng bạn có 1 class cha là Vịt. Các class VịtBầu, VịtXiêm, VịtCỏ có thể kế thừa class này, các class này sẽ chạy bình thường. Tuy nhiên nếu ta viết class VịtChạyPin, cần pin mới chạy được. Khi class này kế thừa class Vịt, vì không có pin không chạy được, sẽ gây lỗi. Đó là 1 trường hợp vi phạm nguyên lý này. Đến đây bạn đã hình dung và hiểu được phần nào về nguyên lý thứ 3 này chưa. Đơn giản hơn là khi các class con kế thừa từ class cha, nó phải sở hữu những đặc điểm chung và bắt buộc của lớp cha. Bạn là con của bố bạn và đương nhiên bạn không thể mang họ ông hàng xóm và có vẻ ngoài giống đến 90% ông ấy được phải không nào 😀

4. Interface Segregation Principle.

Nguyên lý thứ tư, tương ứng với chữ I trong SOLID. Nội dung nguyên lý:

Thay vì dùng 1 interface lớn, ta nên tách thành nhiều interface nhỏ, với nhiều mục đích cụ thể.

Nguyên lý này khá dễ hiểu. Hãy tưởng tượng chúng ta có 1 interface lớn, khoảng 100 methods. Và đương nhiên bất kì class nào implements  từ interface đó thì chúng cũng phải implements hết 100 methods đó. Việc này sẽ gây rất nhiều khó khăn cho người lập trình viên và dẫn đến việc dư thừa không cần thiết. Khi tách interface ra thành nhiều interface nhỏ, gồm các method liên quan tới nhau, việc implement và quản lý sẽ dễ hơn, tiết kiệm thời gian.

5. Dependency inversion principle.

Nguyên lý cuối cùng, tương ứng với chữ D trong SOLID. Nội dung nguyên lý:

1. Các module cấp cao không nên phụ thuộc vào các modules cấp thấp. 
   Cả 2 nên phụ thuộc vào abstraction.
2. Interface (abstraction) không nên phụ thuộc vào chi tiết, mà ngược lại.
( Các class giao tiếp với nhau thông qua interface, 
không phải thông qua implementation.)

Nguyên lý này khá lắt léo, mình sẽ lấy ví dụ thực tế. Chúng ta đều biết 2 loại đèn: đèn tròn và đèn huỳnh quang. Chúng cùng có đuôi tròn, do đó ta có thể thay thế đèn tròn bằng đèn huỳnh quanh cho nhau 1 cách dễ dàng.

20817695_images1672955_bong

Ở đây, interface chính là đuôi tròn, implementation là bóng đèn tròn và bóng đèn huỳnh quang. Ta có thể swap dễ dàng giữa 2 loại bóng vì ổ điện chỉ quan tâm tới interface (đuôi tròn), không quan tâm tới implementation.

Trong code cũng vậy, khi áp dụng Dependency Inverse, ta chỉ cần quan tâm tới interface. Để kết nối tới database, ta chỉ cần gọi hàm Get, Save … của Interface IDataAccess. Khi thay database, ta chỉ cần thay implementation của interface này.

Mình nói khá kĩ về nguyên lí này vì nó khá quan trọng. Về sau mình sẽ viết 1 bài riêng về Dependency Injection (Dependency Injection chỉ là 1 trong những pattern để hiện thực Dependency Inversion, Dependency Injection != Dependency Inversion nhé các bạn)/

Bài viết khá dài, xin có lời khen với các bạn đã kiên nhẫn đọc hết. Ở những bài viết sau mình sẽ giải thích rõ hơn về từng nguyên lý SOLID này, cùng với code mình họa + cách áp dụng nguyên lí vào quá trình code.

Link tham khảo: http://blogs.msdn.com/b/cdndevs/archive/2009/07/15/the-solid-principles-explained-with-motivational-posters.aspx

Vector trong java collection.

Cũng 1 thời gian khá lâu rồi mình không viết tiếp tục seri collection trong java rồi. Nay để tiếp tục thì mình sẽ viết về Vector. 2 bài trước thì mình có đã viết về ArrayList và LinkedList rồi. Nếu ai chưa đọc thì có thể ấn vào đâyđây nữa để tìm hiểu lại.

Thì cũng như ArrayList và LinkedList, Vector cũng implements từ interface List nên nó sẽ có những method để tương tác giống như ArrayList và LinkedList.
Về bản chất thì Vector sẽ giống với ArrayList về cách lưu trữ phần tử và đảm bảo thứ tự các phần tử được add vào. Tức là khi bạn add một mảng các phần tử vào Vector thì chúng sẽ được lưu liên tiếp nhau trên ô nhớ và đảm bảo thứ tự add vào của chúng. Ví dụ bạn add 1 mảng các số tự nhiên như bên dưới đây

Thì như các bạn thấy, thứ tự các phần tử sẽ được giữ nguyên như ban đầu.
Vậy tại sao nó lại sinh ra Vector trong khi muốn lưu trữ các phần tử theo danh sách liên tiếp và đảm bảo vị trí thì ta có thể dùng ArrayList ?
Để biết được câu trả lời thì cùng mình tìm hiểu tiếp nhé, tuy có những điểm chung vì đều có thằng cha là List, nhưng trong một gia đình anh chị em ruột vẫn khác nhau được đúng không. Và Vector cũng vậy, nó có những điểm khác và 2 thằng kia không có. Mình sẽ liệt kê chúng ra cho mọi người biết ngay đây.

  • Cơ chế thay đổi mảng của Vector khác so với LinkedList, như bài trước mình có đề cập là khi số lượng phần tử của ArrayList vượt quá khỏi khả năng chứa của nó thì nó sẽ tăng lên 50% là bằng 150% kích thước cũ. Nhưng ở Vector thì con số ấy là 100% tức là gấp đôi hay bằng 200 % kích thước ban đầu.
  • Vector có thể khởi tạo trước độ dài của mảng, mặc định khi khởi tạo với từ khóa new thì Vector có kích thước mảng là 0, sau khi add vào phần tử đầu tiên thì độ dài mảng sẽ được tăng lên thành 10. Nhưng có 1 điều mà ArrayList lẫn LinkedList không làm được đó là set được độ dài của mảng.

Một lưu ý là nếu bạn insert một giá trị mà index của nó lớn hơn độ dài của mảng thì sẽ lập tức bị lỗi như sau.

  • Vector là synchronized còn ArrayList và LinkedList là non-synchronized. có nghĩa là Vector chỉ có thể chạy đơn luồng và không hỗ trợ chạy đa luồng

Vector sẽ không cho phép nhiều hơn 1 Thread được can thiệp vào nó tại đồng thời 1 điểm, điều này đảm bảo cho việc dữ liệu trong Vector sẽ không bị xung đột khi có nhiều Thread tương tác với Vector. Và nó dẫn đến việc Vector sẽ xử lý chậm hơn ArrayList ví nó là synchronized. Tức là, trong môi trường đa luồng, các Threadgiữ nó ở trong trạng thái runnable hoặc non-runnable cho đến khi Thread hiện tại giải phóng đối tượng đó.

  • Ngoài được duyệt bằng Iterator thì Vector còn có thể duyệt được bằng Enumeration.
  • Và 1 điều nữa đó là ArrayList không là một lớp legacy, nó được tạo ra từ phiên bản JDK 1.2 còn Vector là một lớp legacy.

Phiên bản Java đầu tiên không bao gồm Collection Framework. Nó chỉ định nghĩa một vài lớp và interface cung cấp các phương thức để lưu trữ các đối tượng. Khi Collection Framework được thêm vào trong J2SE 1.2, các lớp gốc đã được tái cấu trúc để hỗ trợ các collection interface. Các lớp này còn được gọi là lớp Legacy. Tất cả các lớp và interface Legacy được thiết kế lại bởi JDK 5 để hỗ trợ Generics.

Dưới đây là các Legacy Class được định nghĩa trong package java.util:

  1. Dictionary
  2. HashTable
  3. Properties
  4. Stack
  5. Vector

Mình sẽ không demo lại các method vì như mình nói, nó đều implements từ interface List nên các method nó cũng sẽ có tương tự như việc bận dùng với ArrayList hay LinkedList. Tóm lại thì sau bài học này chúng ta đã biết được Vector là gì, các thức xử lý dự liệu và lưu trữ ra sao. Nó giống và khác gì so với các collection trong List interface khác. Mình sẽ tòm gọn lại thành bảng bên dưới như sau.

ArrayListVector
1) ArrayList là non-synchronized.Vector là synchronized.
2) ArrayList tăng 50% kích thước hiện tại nếu số phần tử vượt quá khả năng chứa của nó.Vector tăng 100% nghĩa là tăng gấp đôi kích thước hiện tại nếu số phần tử vượt quá khả năng chứa của nó..
3) ArrayList không là một lớp legacy, nó được tạo ra từ phiên bản JDK 1.2.Vector là một lớp legacy
4) ArrayList là nhanh hơn vì nó là non-synchronized.Vector là chậm hơn ví nó là synchronized. Tức là, trong môi trường đa luồng, các thread giữ nó ở trong trạng thái runnable hoặc non-runnable cho đến khi thread hiện tại giải phóng đối tượng đó.
5) ArrayList sử dụng Iterator để duyệt các phần tử.Vector sử dụng Enumeration và Iterator để duyệt các phần tử.

Có bất kì thắc mắc hay góp ý nào thì hãy comment cho mình biết, để các bài viết sau được hoàn thiện hơn. Cảm ơn mọi người đã theo dõi blog của mình.

Eway cho tôi những gì? Niềm vui nho nhỏ của 1 dev mới vào nghề.

Dạo này ít viết blog, nhưng sắp tới mình sẽ cố gắng viết, chia sẻ nhiều điều mình học được ở công ty mới. Mình mới chuyển công ty sau gần 7 tháng làm việc ở Eway, ngày đầu tiên mình đến phỏng vấn ở Eway, khá tệ vì lúc đó mới ra trường. Kiến thức ở mức kém nên đương nhiên là không được đánh giá cao ở màn phỏng vấn. Trước đó có pv ở CMC và được nhận. Nhưng sau buổi chia sẻ, nói chuyện với anh Vũ và anh Hiệp hôm phỏng vấn. Mình lại thấy thích Eway và muốn làm việc ở đây. Sau may mắn cũng được cho cơ hội làm việc, mức lương ở Eway lúc đó thấp hơn rất nhiều so với CMC trả cho mình. Nhưng nhận thấy bản thân còn nhiều yếu kém, cần trau dồi kiến thức nên mình quyết định chọn công ty Product để học hỏi. Trước đó mình từng ở Fsoft suốt năm cuối đại học. Nhưng tới thời điểm bây giờ, Eway là công ty để lại cho mình ấn tượng và nhiều kỉ niệm nhất. Ở đó có 1 cái gì đấy rất gia đình mà không phải riêng mình, ai cũng sẽ nhận thấy. Mình là người hướng ngoại nên việc hòa nhập một cộng đồng là điều không khó. Vào đây mình quen được rất nhiều anh, chị và có cả các em. Nói chung mọi người rất vui và mình rất quý mọi người, ở đây mọi người rất thoải mải. Không hề có khoảng cách sếp hay nhân viên, thậm chí a CTO cũng đi uống trà đã, bắn thuốc lào và chém gió với anh em 😀 Không biết mình có may mắn quá không khi vào team anh Tùng, người mà giúp mình được như bây giờ, anh đã giúp mình học hỏi rất nhiều điều, là người chưa bao giờ cáu gắt hay khắt khe với mình kể cả mình làm sai. Đôi lúc thấy a ý hiền quá, bảo sao toàn bị vợ và các em bắt nạt.
Thời gian làm việc ở Eway không hẳn lâu, nhưng cũng đủ để Eway có thể để lại trong mình một ấn tượng nào đó. Mình quen được gần như hết các anh chị em team Tech, các anh ở đây rất giỏi và luôn sẵn sàng giúp đỡ mình. Mình luôn biết ơn vì điều đó, con người ở đây có cái gì đó khiến mình cảm thấy nó gần gũi và thoải mái. Ở đây có nhiều con người, đến từ nhiều nơi khác nhau, tính cách cũng khác nhau. Nhưng mình lại thấy quý tất cả mọi người. Người mà mình luôn thấy biết ơn là anh Tùng, a đã giúp mình nhiều điều trong quãng thời gian mình làm việc ở đây. Ở đây mình được học hỏi, được chia sẻ kiến thức, kinh nghiệm hay những câu truyện trong cuộc sống. Được đi team building, đánh bòng bàn và chơi bi lắc nữa 😀 kể ra thì còn lâu mới hết, nhưng còn nhiều điều tuyệt vời ở đây nữa, mình sẽ không thể kể hết ở đây được nên ai muốn thử thì có thể đến và trải nghiệm. Tin mình đi, k hối hận đâu. Giờ thì mình đã chuyển công ty vì vài lý do cá nhân, k phải là mình k thích Eway nữa mà là tại thời điểm lúc này thì mình thấy việc mình chuyển công ty là điều tốt hơn. Sau này khi trưởng thành hơn có thể mình sẽ quay lại đó nếu mọi người chào đón mình. Giờ đi làm trên đoạn đường đó vẫn hay nhìn lên tòa EcoLife như một thói quen. Ở công ty mới mình có mấy người bạn quen từ trước nên cũng vui, không khó để mình hòa nhập vào một môi trường mới, làm quen với những con người mới. Mình làm việc ở chỗ mới dc 3 hôm. Cũng không gặp nhiều khó khăn, vì cơ bản là ở Eway mình đã được học, được rèn luyện những kĩ năng cơ bản. Hôm trước thấy mọi người tranh luận về 1 vấn đề, mình là người mới nhưng tự tin đứng ra giải thích cho mọi người. Vì điều này mình học được Eway rồi. Gặp lỗi lại nghĩ “lỗi này anh Tùng chỉ mình rồi” hay “cái này anh Tùng nói hoặc bảo mình tìm hiểu rồi”
À chia sẻ là vào được công ty 2 hôm, mình đã được 1 anh dẫn đi để phỏng vấn 1 bạn bằng tuổi vào lớp fresher. Mình lại thấy mình cách đây 1 thời gian trước, mặc dù bây giờ đã trường thành hơn nhưng mình vẫn nhủ bản thân là phải cố gắng học hỏi nữa. Mặc dù khá run nhưng mọi chuyện khá ok cho đến cuối buổi phỏng vấn. Mình được khen và anh ấy có nhờ mình làm mentor cho 1 lớp fresher. Mình muốn từ chối vì bản thân kém, k đủ tự tin để đứng lớp. Nhưng vẫn đồng ý vì muốn thử một lần xem sao. Bước vào lớp khá run vì mình chưa bao giờ làm vậy trước giờ cả. Nhưng thật mừng là mọi người thấy khá happy khi mình chia sẻ những điều mà mình học được. Còn nhận được khá nhiều câu hỏi, mọi người còn muốn mình chia sẻ thêm kiến thức và nói mình là chia sẻ khá dễ hiểu. Vui hơn nữa là blog của mình được chia sẻ giữa các bạn fresher =))) cuối ngày mình cảm thấy khá vui và nó làm mình cười trong suốt quãng thời gian mình trở về nhà. Thực ra bản thân rất thích việc chia sẻ cho mọi người. Đó cũng là lý do mình viết blog, ngoài tự học ra mình còn muốn chia sẻ cho những người chưa biết nó nữa. Sắp tới mình sẽ viết 1 seri về Spring Boot, mình cũng đang học thôi nhưng học đến đâu sẽ chia sẻ lại cho mọi người đến đó.
Làm mấy ngày đầu ở công ty mới hơi nhớ Eway, nhớ ae G8 tí thôi =)) mặc dù mình không làm việc ở đấy nữa nhưng mình vẫn giữ liên lạc với mọi người. Mình chỉ là không làm việc cùng với nhau nữa thôi chứ mình vẫn còn giữ liên lạc với mọi người. Thi thoảng có cơ hội mình sẽ ghé lại đó chơi và thăm mọi người. Bài viết này chỉ là cảm nghĩ của mình về Eway, công ty mình đã gắn bó 1 thời gian. Mình không khen chê hay so sánh gì ở đây cả, chỉ là chút cảm nghĩ bản thân. Có thể nó không hoàn toàn đúng nhưng với mình thì nó là như vậy.
Cảm ơn nếu ai đó đã dành thời gian để đọc đến tận đây, bạn có câu chuyện gì không. Để dưới comment và mình sẽ lắng nghe nó như cách bạn lắng nghe câu chuyện của mình vậy ^^

Source Code check info Network using java.

Lâu lắm rồi không viết blog, nay tự dưng ngồi mò mac vs ip để nghịch thì nảy ra ý tưởng làm 1 tool auto get ra các thông tin luôn. Sau muốn lấy thì click cái là xong. Đây là source cho ai thích.

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package javaapplication20;

/**
 *
 * @author Dao Van Do
 */
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;

import javafx.stage.Stage;

public class NetworkChecker extends Application {

    Stage window;

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        window = primaryStage;
        window.setTitle("Wi-FI Connection Checker");

        //GridPane with 10px padding around edge
        GridPane grid = new GridPane();
        grid.setPadding(new Insets(10, 10, 10, 10));
        grid.setVgap(10);
        grid.setHgap(10);

        //Name Label - constrains use (child, column, row)
        Label nameLabel = new Label("Find my IP Address:");
        nameLabel.setId("bold-label");
        GridPane.setConstraints(nameLabel, 0, 0);

        //Search IP Address 
        Button IPlookupButton = new Button("Search for IP");
        GridPane.setConstraints(IPlookupButton, 3, 0);

        //Name Input
        TextField nameInput = new TextField();
        nameInput.setPromptText("IP Address");
        GridPane.setConstraints(nameInput, 1, 0);

        IPlookupButton.setOnAction(event -> {
            try {
                InetAddress thisIp = InetAddress.getLocalHost();
                nameInput.setText("IP:" + thisIp.getHostAddress());
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        //MAC Address Lookup Label
        Label passLabel = new Label("MAC Address Look UP:");
        GridPane.setConstraints(passLabel, 0, 1);

        //MAC Address Input
        TextField passInput = new TextField();
        passInput.setPromptText("MAC Address");
        GridPane.setConstraints(passInput, 1, 1);

        //Search MAC Address 
        Button MacAddressButton = new Button("Search for MAC Address");
        GridPane.setConstraints(MacAddressButton, 3, 1);

        MacAddressButton.setOnAction(event -> {

            InetAddress ip;
            try {

                ip = InetAddress.getLocalHost();
                NetworkInterface network = NetworkInterface.getByInetAddress(ip);
                byte[] mac = network.getHardwareAddress();

                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < mac.length; i++) {
                    sb.append(String.format("%02X%s", mac[i], (i < mac.length - 1) ? "-" : ""));
                }
                passInput.setText(sb.toString());

            } catch (UnknownHostException e) {
                e.printStackTrace();
            } catch (SocketException e) {
                e.printStackTrace();

            }
        }
        );

        //Wi-Fi Connection Button
        Button WifiButton = new Button("Search my Wi-Fi");
        GridPane.setConstraints(WifiButton, 1, 3);

        TextField WifiInfo = new TextField();
        GridPane.setConstraints(WifiInfo, 1, 4);

        WifiButton.setOnAction(event -> {

            try {
                Process process = Runtime.getRuntime().exec("arp -a");
                process.waitFor();
                BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                int i = 0;
                while (reader.ready()) {
                    i++;
                    String ip = reader.readLine();
                    if (i >= 4) {
                        ip = ip.substring(2, 56) + "\n";
                    }
                    WifiInfo.setText(ip);
                    WifiInfo.setPrefWidth(WifiInfo.getText().length() * 7);
                }
            } catch (IOException | InterruptedException ioe) {
                ioe.printStackTrace();
            }

        });

        //Add everything to grid
        grid.getChildren().addAll(nameLabel, IPlookupButton, nameInput, passLabel, passInput, MacAddressButton, WifiButton, WifiInfo);

        Scene scene = new Scene(grid, 600, 300);
//        scene.getStylesheets().add("colour.css");
        window.setScene(scene);
        window.show();
    }

}

Seri chia sẻ cùng DoDV

Nghe cái tên nó hơi xàm tí nhưng nó cũng đúng =))) Vì bài viết này mình chỉ dùng để chia sẻ với mọi người.
-Mọi người hỏi thì mình trả lời, không biết thì đi tìm hiểu rồi trả lời. Còn tìm hiểu rồi mà không biết thì đi hỏi. Còn hỏi rồi mà k biết thì chịu =)))
– Mọi người chia sẻ mình lắng nghe, cần tư vấn thì mình sẽ tư vấn, mình chia sẻ cũng mong mọi người đọc. Link chia sẻ ở đây nhé mn.
Mở bài là câu hỏi chắc của 1 cu em clb của mình.

Câu hỏi 1 : anh ơi, em định học block 5 Java core nhưng trong đầu chưa có chữ gì, em nên học và bắt đầu như nào ạ.
Trả lời : Câu trả lời khá đơn giản, không biết gì thì phải học từ cái cơ bản nhất. Học thật chắc các kiến thức cơ bản. Có thể lên youtube xem mấy seri kiến thức java cơ bản, sau đó thực hành lại. Code nhiều vào, tập thói quen code mới nhớ được. Follow các blog như blog của a chẳng hạn =)) hoặc đi hỏi người nào biết. Chú ý cách hỏi lầ hỏi hướng giải quyết thôi chứ đừng hỏi kiểu ” a ơi bug này là gì, a fix giúp em với” dần sẽ có thói quen cứ gặp khó là hỏi. Trừ khi bản thân thấy quá khó mới hỏi. Hỏi xong chú ý cách người ta giải quyết vấn đề mà học tập. Sau đó tự làm lại để sau gặp còn biết mà nhớ. Nói chung phải chăm chứ đừng lười như a =)) a lười lắm nên bh cũng đang học lại đây. Chúc em thành công

Câu hỏi thứ 2 : Còn trẻ, lựa chọn giữa ở lại công ty để xây dựng sự nghiệp hay nhảy công ty để trải nghiệm?
Trả lời : Chia sẻ một chút với anh là đến bh e vẫn cảm thấy em may mắn và lựa chọn đúng đắn khi ở lại Adflex, em cũng làm ở 1 số chỗ khác rồi nhưng e chưa thấy ở đâu được như ở đây. Ở đây em học hỏi được rất nhiều cái chắc không thể nói 1 hay 2 câu được.
Đúng là em đã từng hỏi bản thân vài lần là có nên đi làm chỗ khác không hay tiếp tục ở lại đây. Có lý do khiến em muốn đi nhưng có lý do khiến em muốn ở lại. Lý do em muốn ở lại Adflex là vì con người nhiều hơn. Và hiện tại thì em vẫn đang học tập ở Adflex, và em mong muốn sẽ học hỏi được ở nhiều bậc đàn anh hơn. Thực ra em rất ngưỡng mộ các a ở công ty. Đi đâu ai hỏi em mày làm công ty nào, e đều tự hào khoe công ty có nhiều anh giỏi, và cách anh ấy rất nhiệt tình. Đã có lần em xin nghỉ việc, nhưng sau đó e có nói chuyện với một số anh, và em cũng suy nghĩ. Đúng là bản thân còn trẻ, còn sức khỏe, còn thời gian. Mình nên biết bản thân mình là cần gì tại thời điểm này, và mục tiêu lâu dài là gì. Và e vẫn quyết định ở lại Adflex. Không biết bao giờ thì em bị đuổi việc =)) nhưng hiện tại thì e chưa có ý định đi làm chỗ khác. Điều này em không chắc trong tương lại, nhưng có 1 điều em chắc chắn là nếu nghỉ làm ở đây thì em sẽ buồn và nhớ mọi người lắm. Cũng mong có 1 buổi để mọi người chia sẻ với nhau. E cũng sẽ đăng kí chia sẻ về cảm nhận của em trong thời gian em ở Adflex. Thực sự thì vài dòng như này không hết được đâu anh ạ 😀

Câu hỏi 3 : xóa khoảng cách chuỗi string trong C tại sao lại phải tách riêng một hàm xóa đầu chuỗi, giữa chuỗi, cuối chuỗi mầ không phải cho luôn vào một vòng lặp thế em
Trả lời : Thực ra thì câu hỏi khá chung chung, không áp dụng vào 1 bài toán cụ thể nên không biết được nên hay không nên ở trường hợp này. Thường thì em sẽ không tách ra hàm riêng cho 1 việc là xóa khoảng trắng của chuỗi cả. Nhưng nếu đề bài yêu cầu là truyền vào 1 String và xóa các khoảng trắng ở đâu hoặc ở cuối hoặc ở giữa thì việc tách hàm ra sẽ hợp lý hơn. Có thể anh đọc được comment này của em thì hãy gửi em xem đề bài và code. Lúc đó có thể em sẽ có câu trả lời hay hơn.

Generics trong java.

Generics là một tính năng của Java giúp cho lập trình viên có thể chỉ định rõ kiểu dữ liệu mà họ muốn làm việc với một class, một interface hay một phương thức nào đó. Trong bài viết này, chúng ta sẽ cùng tìm hiểu về Generics trong Java.

1.Generics có quan trọng không và tại sao lại cần có Generics?

Mình xin trả lời là Generics có quan trọng.
Generics là một khái niệm được đưa vào Java từ phiên bản 5. Trước khi đưa ra khái niệm Generics là gì, chúng ta hãy xem một đoạn code của Java trước phiên bản 5.

Như bạn đã biết ArrayList là một danh sách, bạn có thể thêm, xóa, sửa và truy cập vào các phần tử của danh sách. Mình đã có 1 bài viết nói về ArrayList. Ai chưa hiểu thì có thể ấn vào đây để đọc.

Với khai báo trên, giả định rằng chúng ta mong muốn chỉ làm việc với đối tượng kiểu Integer. Nhưng bởi vì list là một collection của đối tượng Object nên chúng ta có thể sử dụng nó với bất kỳ kiểu dữ liệu nào. Tại nơi nào đó trong chương trình bạn thêm vào danh sách này một phần tử không phải Integer. Khai báo sau sẽ hợp lệ:

Như bạn thấy, mình có thể thêm các phần tử kiểu Integer, String, Boolean. Tuy nhiên, khi bạn lấy ra các phần tử và ép kiểu về Integer, một ngoại lệ sẽ bị ném ra.

mình sẽ demo 1 ngoại lệ khi ép kiểu:

Kiểu dữ liệu của List là String, get phần tử 0 là kiểu String và có giá trị là “hello”, đương nhiên “hello” không thể nào ép kiểu sang int được.

Hoặc không cần đến lúc chạy trương trình, generics giúp bạn xác định kiểu dữ liệu ngay từ khi bạn khởi tạo.

Đó là nguyên nhân của sự cần thiết phải có của generics trong Java. Với Generics, chúng ta có thể chỉ định kiểu dữ liệu mà chúng ta sẽ làm việc ngay thời điểm biên dịch (compile time).

2. Một số quy ước đặt tên kiểu tham số Generic

Đặt tên kiểu tham số là rất quan trọng để học Genericics. Nó không bắt buộc, tuy nhiên chúng ta nên đặt theo quy ước chung để dễ đọc, dễ bảo trì. Các kiểu tham số thông thường như sau:

  • E- Element (phần tử – được sử dụng phổ biến trong Collection Framework)
  • K – Key (khóa)
  • V – Value (giá trị)
  • N – Number (kiểu số: Integer, Double, Float, …)
  • T – Type (Kiểu dữ liệu bất kỳ thuộc Wrapper class: String, Integer, Long, Float, …)
  • S, U, V … – được sử dụng để đại diện cho các kiểu dữ liệu (Type) thứ 2, 3, 4, …

3. Ký tự Diamond <>

Trong Java 7 và các phiên bản sau, bạn có thể thay thế các đối số kiểu dữ liệu cần thiết để gọi hàm khởi tạo (constructor) của một lớp Generic bằng cặp dấu <>. Trình biên dịch sẽ xác định hoặc suy ra các kiểu dữ liệu từ ngữ cảnh sử dụng.

Ví dụ, bạn có thể tạo một list <Integer> với câu lệnh sau:

Để biết thêm thông tin về ký hiệu <>, bạn xem thêm trên trang document của Oracle.

Thôi muộn rồi đi ngủ, mai viết tiếp. Ai nỡ đọc được đến đây rồi thì mai quay lại tìm hiểu tiếp với mình nha 😀

Tổng quan về constructor trong java.

Phương thức khởi tạo, hay gọi Hàm khởi tạo cũng được, bạn cũng có thể gọi là Constructor, mình thì mình sẽ dùng constructor luôn cho ngắn gọn.

Constructor là gì?

Thực chất thì constructor này cũng là một phương thức, nhưng nó đặc biệt ở chỗ là, ngay khi mà bạn khởi tạo một đối tượng bằng từ khóa new, thì constructor của đối tượng đó sẽ lập tức được gọi đến một cách tự động. Có nghĩa là nếu với phương thức bình thường, bạn phải gọi đến nó thông qua toán tử chấm (“.”) thì phương thức đó mới được thực thi, còn với constructor, ngay khi biên dịch đến từ khóa new, hệ thống sẽ thực thi một constructor tương ứng của đối tượng, tùy vào constructor nào mà bạn chỉ định.

Mục đích chính mà constructor mang lại, không gì khác ngoài tác dụng Khởi tạo. Constructor giúp đối tượng vừa được tạo ra đó, có cơ hội được khởi tạo các giá trị cho các thuộc tính bên trong nó. Hoặc có thể giúp đối tượng đó gọi đến các phương thức tương ứng khác nhằm khởi tạo các logic bên trong đối tượng.

Constructor được nắm gọn trong các ý sau :
Constructor trong Java là một block code được gọi khi một thể hiện của một đối tượng được tạo và bộ nhớ được cấp phát cho đối tượng đó.
Constructor là một loại phương thức đặc biệt được sử dụng để khởi tạo một đối tượng. Bạn cũng có thể sử dụng access modifiers trong khi khai báo Constructor.
Constructor được gọi tại thời điểm tạo đối tượng. Nó khởi tạo các giá trị để cung cấp dữ liệu cho các đối tượng, đó là lý do tại sao nó được gọi là constructor.

Constructor là một phần quan trọng phải hiểu để có thể học lập trình Java hiệu quả. Vì vậy, để bắt đầu, hãy đến với các Quy tắc tạo Constructor trong Java.

Trước khi hiểu rõ hơn về cách sử dụng một constructor, chúng ta hãy xem cách khai báo chúng.

Khai Báo Constructor :
Trước hết mình xin nói qua cú pháp cho một constructor, để bạn có thể mang ra so sánh với việc khai báo một phương thức bình thường, xem có khác gì không nhé. Cú pháp của một constructor như sau.

[khả_năng_truy_cập]  tên_phương_thức  () {
     // Các dòng code
}

Như vậy bạn cũng có thể thấy sự khác biệt, tuy nhiên mình cũng điểm qua các thành phần bên trong cú pháp trên cho nó rõ ràng.

– Đầu tiên, constructor không có kiểu trả về như phương thức bình thường nhé.

– Khả năng truy cập –Bạn có thể sử dụng access modifiers trong khi khai báo Constructor. Tuy nhiên trong bài học hôm nay, mình đều sẽ dùng public cho các constructor, nó có nghĩa là ở đâu cũng có thể dùng đến các constructor này.

– tên phương thức – Khác với các phương thức bình thường, tên của constructor phải cùng với tên lớp. Để giúp phân biệt đâu là constructor và đâu là phương thức bình thường í mà. (constructor của mình trùng tên với class, đều là “Student”).

– các tham số truyền vào – Phần này thì giống với phương thức bình thường, không có gì để nói thêm.

Vậy chốt lại chúng ta có 2 quy tắc để khởi tạo 1 constructor.

Các quy tắc tạo constructor trong java

  1. Tên constructor phải giống tên lớp chứa nó.
  2. Constructor không có kiểu trả về tường minh.

Có phải constructor chỉ đơn giản là 1 phương thức có cùng tên với tên lớp chứa nó và không có kiểu trả về thôi không ? Mình xin trả lời là không, cùng mình tìm hiểu sâu hơn về constructor nhé.

Các kiểu của java constructor :

Có 2 kiểu của constructor:

  1. Constructor mặc định (không có tham số truyền vào).
  2. Constructor tham số.
các kiểu constructor trong java

1.Constructor mặc định trong java.
-Một constructor mà không có tham số được gọi là constructor mặc định.
Như mình demo bên trên, thì constructor Student mình tạo không hề có tham số truyền vào, và những constructor như vậy gọi là constructor mặc định.

Thường thì bạn sẽ không cần khởi tạo 1 constructor mặc định vì trong java, khi chạy trương trình thì trình biên dịch sẽ tự động khởi tạo 1 constructor mặc định trong lớp đó.
Nhưng mình khuyên các bạn là nên tạo 1 constructor mặc định, lý do vì sao thì cùng mình đi xuống tìm hiểu loại constructor tiếp theo. Giờ thì mình sẽ demo 1 đoạn code sử dụng constructor mặc định.

Trong ví dụ trên, mình không tạo bất kỳ constructor nào, vì vậy trình biên dịch tự động tạo một constructor mặc định.
Constructor mặc định cung cấp các giá trị mặc định như 0, null, (tùy thuộc vào kiểu dữ liệu) … tới đối tượng được khởi tạo. Vì bên trên mình không khởi tạo bất kì giá trị nào cho đối tượng Student nên 2 thuộc tính mặc định là name và age sẽ lần lượt là null và 0.

2.Constructor tham số.

Constructor mặc định là constructor không có tham số vậy constructor có tham số là constructor có tham số truyền vào, đơn giản không nào 😀
Constructor tham số được sử dụng để cung cấp các giá trị khác nhau cho các đối tượng khác nhau.

Ví dụ nhé.

Như bạn thấy thì mình đã khởi tạo 4 constructor, trong đó có 3 constructor truyền vào tham số và 1 constructor mặc định mình tạo ban đầu.
Trong class, bạn có thể khởi tạo 1 hoặc nhiều constructor, tùy thuộc vào số lượng thuộc tính của đối tượng. Và các constructor không được phép có tham số truyền vào giống nhau. Bạn có thể truyền 1, 2 hay bao nhiêu số lượng thuộc tính của đối tượng. Nhưng không được phép tạo một constructor mà có tham số truyền vào giống nhau.

Khi bạn tạo 2 constructor có cùng kiểu tham số truyền vào thì lập tức trình biên dịch sẽ báo lỗi là đã tồn tại 1 constructor như vậy trong class. Việc này nhằm mục đíc để phân biệt các constructor với nhau.

Mình truyền tham số theo lần lượt các constructor mình tạo, khi 1 constructor mà không có truyền vào 1 thuộc tính của đối tượng, thì mặc định nó sẽ là 0 hoặc null tùy theo kiểu dữ liệu (Nhắc lại bên trên tí :v )

Ví dụ bên trên cũng là 1 ví dụ về nạp chồng constructor hay còn gọi là Constructor Overloading trong java.

Constructor Overloading là một kỹ thuật trong Java. Bạn có thể tạo nhiều constructor trong cùng một lớp với danh sách tham số truyền vào khác nhau. Trình biên dịch phân biệt các constructor này thông qua số lượng và kiểu của các tham số truyền vào.

Thì đến đây bạn cũng đã hiểu phần nào về constructor trong java rồi, mình sẽ tổng kết lại bằng 1 bảng sau đây.

ConstructorPhương thức
Constructor được sử dụng để khởi tạo trạng thái của một đối tượng.Phương thức được sử dụng để thể hiện hành động của một đối tượng.
Constructor không có kiểu trả về.Phương thức có kiểu trả về. (trừ void không có kiểu trả về)
Constructor được gọi ngầm.Phương thức được gọi tường minh.
Trình biên dịch Java tạo ra constructor mặc định nếu bạn không có constructor nào.Phương thức không được tạo ra bởi trình biên dịch Java.
Tên của constructor phải giống tên lớp.Tên phương thức có thể giống hoặc khác tên lớp.

Cảm ơn mọi người vì đã bỏ thời gian để đọc blog của mình, hẹn các bạn vào bài viết sau.

LinkedList trong java collection.

Mấy hôm nay do hơi bận công việc nên mình không viết blog được nhiều. Lúc mà mình bắt tay vào viết bài này là 11h hơn vừa xong việc luôn.
Thì hôm trước mình có giới thiệu về ArrayList, bài hôm nay mình sẽ nói qua về LinkedList.
LinkedList :  (hay còn gọi là Danh sách liên kết). Giống như ArrayList là một lớp triển khai của List Interface trong Collections Framework nên nó sẽ có một vài đặc điểm và phương thức tương đồng với List.
Điểm giống nhau giữa ArrayList và LinkedList :
+ duy trì thứ tự của phần tử được thêm vào.
+  Cả hai lớp này đều là lớp không đồng bộ (non-synchronized).

Ngoài ra thì LinkedList còn là một lớp triển khai của Queue Interface.

Về việc khởi tạo 1 LinkedList, cũng tương tự như ArrayList ta làm như sau.

List<String> myList1 = new LinkedList<>();

hoặc

 LinkedList<String> linkedList = new LinkedList<>();

Bạn có thể khai báo bằng một trong hai cách trên, còn vì sao như vậy mình mình đã nói ở bài trước. Nếu ai chưa đọc bài trước thì có thể ấn vào đây để đọc lại.

Lớp LinkedList trong java sử dụng cấu trúc danh sách liên kết Doubly Linked List để lưu trữ các phần tử.

Một Danh sách liên kết (Linked List) là một dãy các cấu trúc dữ liệu được kết nối với nhau thông qua các liên kết (link). Hiểu một cách đơn giản thì Danh sách liên kết là một cấu trúc dữ liệu bao gồm một nhóm các nút (node) tạo thành một chuỗi. Mỗi nút gồm dữ liệu ở nút đó và tham chiếu đến nút kế tiếp trong chuỗi.
Khác với ArrayList là ArrayList là 1 tập hợp các phần tử liên tiếp nhau trong bộ nhớ còn LinkedList thì không liên tiếp.

Danh sách liên kết là cấu trúc dữ liệu được sử dụng phổ biến thứ hai sau mảng. Dưới đây là các khái niệm cơ bản liên quan tới Danh sách liên kết:

  • Link (liên kết): mỗi link của một Danh sách liên kết có thể lưu giữ một dữ liệu được gọi là một phần tử.
  • Next: Mỗi liên kết của một Danh sách liên kết chứa một link tới next link được gọi là Next.
  • First: một Danh sách liên kết bao gồm các link kết nối tới first link được gọi là First.

Biểu diễn Danh sách liên kết (Linked List)

Danh sách liên kết có thể được biểu diễn như là một chuỗi các nút (node). Mỗi nút sẽ trỏ tới nút kế tiếp.

Dưới đây là một số điểm cần nhớ về Danh sách liên kết:

  • Danh sách liên kết chứa một phần tử link thì được gọi là First.
  • Mỗi link mang một trường dữ liệu và một trường link được gọi là Next.
  • Mỗi link được liên kết với link kế tiếp bởi sử dụng link kế tiếp của nó.
  • Link cuối cùng mang một link là null để đánh dấu điểm cuối của danh sách.

Các loại Danh sách liên kết (Linked List)

Dưới đây là các loại Danh sách liên kết (Linked List) đa dạng:

  • Danh sách liên kết đơn (Simple Linked List): chỉ duyệt các phần tử theo chiều về trước.
  • Danh sách liên kết đôi (Doubly Linked List): các phần tử có thể được duyệt theo chiều về trước hoặc về sau.
  • Danh sách liên kết vòng (Circular Linked List): phần tử cuối cùng chứa link của phần tử đầu tiên như là next và phần tử đầu tiên có link tới phần tử cuối cùng như là prev.

Các hoạt động cơ bản trên Danh sách liên kết

Dưới đây là một số hoạt động cơ bản có thể được thực hiện bởi một danh sách liên kết:

  • Hoạt động chèn: thêm một phần tử vào đầu danh sách liên kết.
  • Hoạt động xóa (phần tử đầu): xóa một phần tử tại đầu danh sách liên kết.
  • Hiển thị: hiển thị toàn bộ danh sách.
  • Hoạt động tìm kiếm: tìm kiếm phần tử bởi sử dụng khóa (key) đã cung cấp.
  • Hoạt động xóa (bởi sử dụng khóa): xóa một phần tử bởi sử dụng khóa (key) đã cung cấp.

Hoạt động chèn trong Danh sách liên kết

Việc thêm một nút mới vào trong danh sách liên kết không chỉ là một hoạt động thêm đơn giản như trong các cấu trúc dữ liệu khác (bởi vì chúng ta có dữ liệu và có link). Chúng ta sẽ tìm hiểu thông qua sơ đồ dưới đây. Đầu tiên, tạo một nút bởi sử dụng cùng cấu trúc và tìm vị trí để chèn nút này.

Giả sử chúng ta cần chèn một nút B vào giữa nút A (nút trái) và C (nút phải). Do đó: B.next trỏ tới C.

NewNode.next −> RightNode;


Hình minh họa như sau:

Bây giờ, next của nút bên trái sẽ trỏ tới nút mới.

LeftNode.next −> NewNode;

Quá trình trên sẽ đặt nút mới vào giữa hai nút. Khi đó danh sách mới sẽ trông như sau:

Các bước tương tự sẽ được thực hiện nếu chèn nút vào đầu danh sách liên kết. Trong khi đặt một nút vào vị trí cuối của danh sách, thì nút thứ hai tính từ nút cuối cùng của danh sách sẽ trỏ tới nút mới và nút mới sẽ trỏ tới NULL.

Hoạt động xóa trong Danh sách liên kết

Hoạt động xóa trong Danh sách liên kết cũng phức tạp hơn trong cấu trúc dữ liệu khác. Đầu tiên chúng ta cần định vị nút cần xóa bởi sử dụng các giải thuật tìm kiếm.

Bây giờ, nút bên trái (prev) của nút cần xóa nên trỏ tới nút kế tiếp (next) của nút cần xóa.

LeftNode.next −> TargetNode.next;

Quá trình này sẽ xóa link trỏ tới nút cần xóa. Bây giờ chúng ta sẽ xóa những gì mà nút cần xóa đang trỏ tới.

TargetNode.next −> NULL;

Nếu bạn cần sử dụng nút đã bị xóa này thì bạn có thể giữ chúng trong bộ nhớ, nếu không bạn có thể xóa hoàn toàn hẳn nó khỏi bộ nhớ.

Hoạt động đảo ngược Danh sách liên kết

Với hoạt động này, bạn cần phải cẩn thận. Chúng ta cần làm cho nút đầu (head) trỏ tới nút cuối cùng và đảo ngược toàn bộ danh sách liên kết.

Chúng ta phải đảm bảo rằng nút cuối cùng này sẽ không bị thất lạc, do đó chúng ta sẽ sử dụng một số nút tạm (temp node – giống như các biến tạm trung gian để lưu giữ giá trị). Tiếp theo, chúng ta sẽ làm cho từng nút bên trái sẽ trỏ tới nút trái của chúng.

Hoạt động đảo ngược Danh sách liên kết

Sau đó, nút đầu tiên sau nút head sẽ trỏ tới NULL.

Hoạt động đảo ngược Danh sách liên kết

Chúng ta sẽ làm cho nút head trỏ tới nút đầu tiên mới bởi sử dụng các nút tạm.

Hoạt động đảo ngược Danh sách liên kết

Bây giờ Danh sách liên kết đã bị đảo ngược.

Đọc đến đây có thể vẫn hơi mung lung đúng không nào =)) nếu chưa hiểu lắm thì quay lại 1 lần nữa đọc để xem cách thức LinkedList nó xử lý và lưu trữ dữ liệu ra sao nhé. Còn bây giờ thì chúng ta đi vào demo về việc xử dụng LinkedList.

Như đã nói bên trên thì LinkedList là 1 thể hiện từ List Interface nên nó cũng chứa các phương thức sau đây.

Phương thứcMô tả
void add(int index, Object element)Chèn element đã xác định tại index đã cho. Ném một IndexOutOfBoundsException nếu index đã cho là ở bên ngoài dãy (index < 0 || index > size())
boolean add(Object o)Phụ thêm phần tử đã cho tới cuối của List này
boolean addAll(Collection c)Phụ thêm tất cả phần tử trong collection đã cho tới cuối của list này, theo thứ tự mà chúng được trả về bởi Iterator của collection đã cho. Ném một NullPointerException nếu collection đã cho là null
boolean addAll(int index, Collection c)Chèn tất cả phần tử trong collection đã cho vào trong List này, bắt đầu từ vị trí đã cho. Ném NullPointerException nếu collection đã cho là null
void addFirst(Object o)Chèn phần tử đã cho vào phần đầu của list này
void addLast(Object o)Phụ thêm phần tử đã cho vào phần cuối của list này
void clear()Gỡ bỏ tất cả phần tử từ list này
Object clone()Trả về một shallow copy của LinkedList này
boolean contains(Object o)Trả về true nếu list này chứa phần tử đã cho. Chính thức hơn, trả về true nếu và chỉ nếu list này chứa ít nhất một phần tử e để mà (o==null ? e==null : o.equals(e))
Object get(int index)Trả về phần tử tại vị trí đã cho. Ném IndexOutOfBoundsException nếu index ở bên ngoài dãy (index < 0 || index >= size())
Object getFirst()Trả về phần tử đầu tiên trong list này. Ném NoSuchElementException nếu list này là trống
Object getLast()Trả về phần tử cuối trong list này. Ném NoSuchElementException nếu list này là trống
int indexOf(Object o)Trả về index trong list này cho sự xuất hiện đầu tiên của phần tử đã cho, hoặc -1 nếu List này không chứa phần tử này
int lastIndexOf(Object o)Trả về index trong list này cho sự xuất hiện cuối của phần tử đã cho, hoặc -1 nếu List này không chứa phần tử này
ListIterator listIterator(int index)Trả về một list-iterator của phần tử trong list này (trong dãy chính xác), bắt đầu từ vị trí đã cho trong list. Ném IndexOutOfBoundsException nếu index đã cho ở bên ngoài dãy (index < 0 || index >= size())
Object remove(int index)Gỡ bỏ phần tử tại vị trí đã cho. Ném NoSuchElementException nếu list này là trống
boolean remove(Object o)Gỡ bỏ sự xuất hiện đầu tiên của phần tử đã cho. Ném NoSuchElementException nếu list này trống. Ném IndexOutOfBoundsException nếu index ở bên ngoài dãy (index < 0 || index >= size())
Object removeFirst()Gỡ bỏ và trả về phần tử đầu tiên từ list này. Ném NoSuchElementException nếu list là trống
Object removeLast()Gỡ bỏ và trả về phần tử cuối từ list này. Ném NoSuchElementException nếu list là trống
Object set(int index, Object element)Thay thế phần tử tại vị trí đã cho trong list này với phần tử đã cho. Ném IndexOutOfBoundsException nếu index đã cho ở ngoài dãy (index < 0 || index >= size())
int size()Trả về số phần tử trong list này
Object[] toArray()Trả về một mảng chứa tất cả phần tử trong list này trong đúng thứ tự. Ném NullPointerException nếu mảng đã xác định là null
Object[] toArray(Object[] a)Trả về một mảng chứa tất cả phần tử trong list này trong đúng thứ tự; kiểu runtime của mảng trả về là như của mảng đã xác định

Mình sẽ đi vào ví dụ cho dễ hiểu nhé.
Ví dụ về việc khởi tạo 1 LinkedList có kiểu dữ liệu là String, sau đó sẽ add các phần tử vào LinkedList.

Tạo 1 LinkedList có kiểu dữ liệu là Student.

Như các bạn thấy thì LinkedList cũng có thể lưu phần tử trùng nhau và cả phần tử null.

Vì đều là thể hiện của List Interface nên các phương thức thao tác cũng gần như ArrayList mình đã giới thiệu ở bài trước, mình sẽ không đi demo hết các phương thức nữa. Các bạn có thể thử, sẽ dễ nhớ và hiểu hơn. Nếu lỗi thì cứ comment ở dưới mình sẽ giải đáp.

Vậy sau khi học qua ArrayList và LinkedList thì các bạn rút ra được những gì. Mình sẽ tổng hợp lại ở bảng bên dưới để các bạn có cái nhìn tổng quan hơn về ArrayList và LinkedList. Từ đó có thể sử dụng để giải các bài toán phù hợp.

ArrayListLinkedList
ArrayList sử dụng mảng động để lưu trữ các phần tửLinkedList sử dụng danh sách liên kết (Doubly Linked List) để lưu trữ các phần tử.
Cấu trúc dữ liệu (Structure)ArrayList  là một cấu trúc dữ liệu dựa trên chỉ mục (index), trong đó mỗi phần tử (element) được liên kết với một chỉ mụcCác phần tử trong LinkedList được gọi là node, mỗi node cần lưu trữ 3 thông tin: tham chiếu phần tử trước nó, giá trị của phần tử và một tham chiếu tới phần tử kế tiếp.
Thao tác thêm và xóa (Insertion And Removal)Thao tác thêm và xóa phần tử với ArrayList là chậm bởi vì nó sử dụng nội bộ mảng. Bởi vì sau khi thêm hoặc xóa các phần tử cần sắp xếp lại.Thao tác thêm và xóa phần tử với LinkedList nhanh hơn ArrayList. Bởi vì nó không cần sắp xếp lại các phần tử sau khi thêm hoặc xóa. Nó chỉ cần cập nhật lại tham chiếu tới phần tử phía trước và sau nó
Độ phức tạp: O(n).Độ phức tạp: O(1).
Thao tác tìm kiếm hoặc truy xuất phần tử (Retrieval)Truy xuất phần tử trong ArrayList nhanh hơn LinkedList. Bởi vì các phần tử trong ArrayList được lưu trữ dựa trên chỉ mục (index).Thao tác truy xuất phần tử trong LinkedList chậm hơn nhiều so với ArrayList. Bởi vì, nó phải duyệt qua lần lượt các phần tử từ đầu tiên cho đến cuối cùng.
Độ phức tạp:  O(1).Độ phức tạp:  O(n).
Truy xuất ngẫu nhiên (Random Access)ArrayList có thể truy xuất ngẫu nhiên phần tử.LinkedList không thể truy xuất ngẫu nhiên. Nó phải duyệt qua tất cả các phần tử từ đầu tiên đến phần tử cuối cùng để tìm phần tử.
Trường hợp sử dụng (Usage)ArrayList chỉ có thể hoạt động như một list vì nó chỉ implements giao tiếp List.LinkedList có thể hoạt động như một ArrayList, stack (hàng đợi), queue (hàng đợi), Singly Linked List and Doubly Linked List vì nó implements các giao tiếp List và Deque.
Sử dụng bộ nhớArrayList yêu cầu ít bộ nhớ hơnso với LinkedList. Bởi vì ArrayList chỉ lưu trữ dữ liệu (data) của nó và chỉ mục (index).LinkedList yêu cầu nhiều bộ nhớ hơn so với ArrayList. Bởi vì LinkedList lưu giữ thông tin của nó và tham chiếu tới phần tử trước và sau nó.

Kết luận.
ArrayList tốt hơn trong việc lưu trữ và truy xuất dữ liệu (get).
LinkedList tốt hơn trong việc thao tác dữ liệu (thêm/ xóa).

Cảm ơn các bạn đã đọc blog của mình, nếu thấy hay thì hãy chia sẻ cho mọi người biết hoặc để lại vài lời nhận xét nhé. Đó sẽ là động lực để mình làm các bài viết sau hay hơn.

ArrayList trong java collection

Ở bài viết trước thì chúng ta đã biết sơ qua về collection, đã phần nào hình dùng collection là gì, và biết cách thao tác cơ bản với chúng. Thì ở các bài viết sau của seri java collection thì mình sẽ cùng đi sâu hơn về các collection để xem cách thức chúng hoạt động, lưu trữ và xử lý dữ liệu của chúng như nào. Đặc điểm của từng collection, ưu và nhước điểm của từng loại để có thể áp dụng chúng vào các bài toàn thực tế một cách phù hợp nhất.

Thì bài hôm nay mình sẽ cùng tìm hiểu về ArrayList. Nhắc lại bài trước một xíu, nếu ai quên hoặc chưa đọc thì có thể ấn vào đây để có thể xem và nhớ lại những kiến thức mình đã giới thiệu ở bài trước. Ở bài trước mình có nói qua về Array, và Array không phải là 1 collection nhé. Vậy giữa Array và ArrayList có gì giống và khác nhau. Nghe tên đã thấy có gì giống với khác nhau rồi đúng không 😀

Mỗi một đặc điểm thì mình sẽ cố gắng đưa vào những ví dụ cụ thể nhất để mọi người dễ hình dung.
Thì điểm khác nhau đầu tiên giữa Array và ArrayList đó là
+ Array : Kích thước cố định.
+ ArrayList : Kích thước có thể thay đổi được.
nào xem ví dụ nhé.

Vậy là mình đã thực hiện khởi tạo 1 array với số lượng phần tử là 4. Và sau 2 vòng for thì mình đã add 4 phần tử vào trong array và đọc chúng ra. Như mình nói nói bên trên thì array là 1 mảng cố định số lượng phần tử, vậy sẽ như thế nào nếu mình add thêm 1 phần tử thứ 5 vào array bên trên.

sau khi mình tăng vòng for lên 1 thì lúc này vòng for chạy đến 5 tức là bằng độ dài ban đầu của mảng + 1.
Ngay lập tức trình duyệt báo lỗi “java.lang.ArrayIndexOutOfBoundsException”.
Vậy khi add thì như vậy còn nếu đọc số lượng phần tử lớn hơn mảng thì sao.

Nó vẫn sẽ đọc được 4 phần tử đầu nhưng đến phần tử thứ 5 sẽ bị lỗi vì không tìm thấy. Đây cũng chính là 1 nguyên nhân dẫn đến việc bị nullpointerexception trong java.

Để có thể đưa ra kết luận đầu tiên thì chúng ta đến với ví dụ về ArrayList nhé xem nó khắc phục vấn đề trên như thế nào.

Như mọi người thấy thì mình không hề khai báo số lượng phần tử, và việc thêm lẫn đọc dữ liệu của arraylist rất đơn giản. Để biết được tại sao arraylist lại làm được như vậy thì chúng ta tìm hiểu sâu hơn bên trong xem thư viện nó xử lý vấn đề quản lý kích thước mảng của arraylist như thế nào.

nôm na là khi bạn add phần từ đầu vào mảng thì lúc này mảng sẽ có kích thước mặc định là 10, và khi kích thước của mảng đạt tới 10 rồi thì nó sẽ tiếp tục tăng kích thước lên cho tới giá trị là “Integer.MAX_VALUE – 8;”

1 trích dẫn nho nhỏ là để có thể tìm hiểu sâu hơn thì bạn có thể ấn vào xem thư viện hoặc đọc doc của nó để biết cách thức nó xử lý như thế nào. Cảm ơn anh Tùng đã chỉ cho em kĩ năng này, mặc dù nó vẫn ở mức độ quèn 😀

Tiếp đến với sự khác nhau thứ 2:
Array : Có thể lưu trữ dữ liệu kiểu nguyên thủy và String
ArrayList : Chỉ có thể lưu trữ dữ liệu kiểu đối tượng. Kể từ Java 5, kiểu nguyên thủy được tự động chuyển đổi trong các đối tượng được gọi là auto-boxing.

Sự khác nhau thứ 3 :
Array : Chỉ có thuộc tính length.
ArrayList: Có nhiều phương thức để thao tác với dữ liệu.

còn với arrayList

Và sự khác nhau nữa đó là:
Array : Tốc độ lưu trữ và thao tác nhanh hơn.
ArrayList : Tốc độ lưu trữ vào thao tác chậm hơn.
Mình sẽ làm 1 ví dụ đo thời gian để add 100.000.000 phần tử vào mảng.

Đây là thời gian để add 100tr phần tử vào mảng, mất tận 42901 minisecon tức gần 43 giây.

Còn đây là với Array, con số nhanh hơn đáng kinh ngạc đúng không ạ. Cấu hình máy mình tương đối mạnh. Mọi người thử chạy xem hết bao nhiêu giây nhé.

Và vì sao ArrayList lại chậm hơn rất nhiều so với Array. Bản chất cả 2 cái đều có đặc điểm chung là các phần tử được lưu trữ ở địa chỉ liên tục nhau trong ô nhớ. Nhưng Array cần phải khởi tạo số lượng phần tử trước còn ArrayList sẽ tự động tặng kích thước mảng lên theo số lượng phần tử được add vào.
Như bên trên mình đã nói là mặc định khởi tạo ArrayList sẽ có kích thước mảng là 10. Chính vì vậy mà phía trên mình đã add 100tr phần tử vào mảng.
Khi insert 1 phần tử vào ArrayList, trường hợp tốt nhất thì ArrayList chỉ cần cập nhật index cho phần tử được thêm vào. Trường hợp xấu là ArrayList không đủ vùng nhớ (mặc định là chứa được 10 phần tử), trình xử lý phải copy toàn bộ các element sang một vùng nhớ khác lớn hơn (x1.5 lần) chính điều này làm cho tốc độ insert của ArrayList chậm hơn rất nhiều so với Array.
Tương tự với việc xóa 1 phần tử trong ArrayList. Độ phức tạp là giống với việc insert nên nó có độ phức tạp là O(n).
À bổ xung thêm 1 điểm khác nhau nho nhỏ nữa là để duyệt Array thì ta chỉ có thể dùng for. Nhưng với ArrayList là 1 collection nên chúng ta có thể dùng iterator để duyệt.

Qua những ví dụ trên thì chắc các bạn cũng bắt đầu hiểu được phần nào về ArrayList, cách nó lưu trữ dữ liệu, và các thao tác cơ bản với ArrayList.

Vì ArrayList là 1 thực thể của List nên nó sẽ có rất nhiều các phương thức để thao tác với nó. Mình sẽ liệt kê những phương thức và tác dụng của nó.

Các Ví dụ ArrayList trong Java

Khởi tạo một ArrayList

Để khai báo một ArrayList, chúng ta cần phải import gói thư viện java.util.ArrayList của Java. Cú pháp import như sau:

Ngoài ra, nếu chúng ta đã biết trước số lượng phần tử thì chúng ta có thể khai báo kèm với số lượng phần tử của nó. Ví dụ dưới đây sẽ khai báo một ArrayList có kiểu String và có 20 phần tử:

Hiển thị các phần tử có trong ArrayList

Để hiển thị các phần tử có trong ArrayList, chúng ta có các cách như sau:

Hiển thị theo tên của ArrayList.

Duyệt các phần tử của ArrayList – sử dụng vòng lặp for

Duyệt các phần tử của ArrayList – sử dụng vòng lặp foreach

Duyệt các phần tử của ArrayList – sử dụng Iterator.

Để sử dụng được Iterator chúng ta cần phải import gói thư viện java.util.Iterator của Java.

Duyệt các phần tử của ArrayList – sử dụng ListIterator.

Vì ArrayList là một lớp triển khai của List Interface nên nó cũng có thể sử dụng ListIterator để duyệt qua các phần tử của nó. Để sử dụng được ListIterator chúng ta cần phải import gói thư viện java.util.ListIterator của Java.

Các phương thức addAll(), removeAll(), retainAll() của lớp ArrayList

Ví dụ sau minh họa cách sử dụng các phương thức addAll(), removeAll(), retainAll() của lớp ArrayList trong Java:

Kết quả :

Truy cập phần tử của ArrayList

Java cung cấp cho chúng ta phương thức get() để truy cập đến 1 phần tử bất kỳ trong ArrayList thông qua chỉ số của phần tử đó. Chỉ số của ArrayList trong Java bắt đầu từ 0.

Cập nhật giá trị của phần tử Arraylist

Để cập nhật giá trị của phần tử trong ArrayList, Java cung cấp cho chúng ta phương thức set(index, element), trong đó index là chỉ số của phần tử cần cập nhật và element là phần tử mới để thay thế.

Xóa phần tử ArrayList

Để xóa phần tử trong ArrayList, Java cung cấp cho chúng ta 2 phương thức có sẵn đó là phương thức clear() và phương thức remove().

Phương thức clear()

Phương thức clear() sẽ xóa tất cả các phần tử có trong ArrayList. Sau đây là ví dụ minh họa phương thức này.

Phương thức remove()

Phương thức remove() sẽ xóa phần tử ra khỏi ArrayList theo 2 cách đó là xóa dựa vào chỉ số của phần tử và xóa trực tiếp phần tử đó (không cần biết đến chỉ số của nó). Ví dụ dưới đây sẽ minh họa 2 cách xóa này:

Tìm kiếm một phần tử ArrayList

Để tìm kiếm một phần tử trong ArrayList thì chúng ta có 3 phương pháp tìm kiếm như sau:

Tìm kiếm trực tiếp phần tử.

Để tìm kiếm trực tiếp phần tử, chúng ta sẽ sử dụng phương thức contains() . Kết quả trả về là true nếu tìm thấy, ngược lại trả về false.

Tìm kiếm vị trí xuất hiện đầu tiên của 1 phần tử trong ArrayList.

Để tìm kiếm vị trí xuất hiện đầu tiên của 1 phần tử trong ArrayList, chúng ta sẽ sừ dụng phương thức indexOf(). Kết quả của phương thức này sẽ trả về chỉ số xuất hiện đầu tiên của phần tử đó trong ArrayList, ngược lại nếu không tìm thấy trả về -1.

Tìm kiếm vị trí xuất hiện cuối cùng của 1 phần tử trong List.

Để tìm kiếm vị trí xuất hiện cuối cùng của 1 phần tử trong ArrayList, chúng ta sẽ sừ dụng phương thức lastIndexOf(). Kết quả của phương thức này sẽ trả về chỉ số xuất hiện cuối cùng của phần tử đó trong ArrayList, ngược lại nếu không tìm thấy trả về -1.

Chuyển ArrayList sang mảng (Array) trong Java

Phương thức toArray() trong Java được dùng để chuyển đổi một ArrayList sang mảng tương ứng. Sau đây là ví dụ minh họa phương thức này:

Tạo ArrayList có kiểu generic là String

Tạo ArrayList có kiểu generic là đối tượng do người dùng định nghĩa

Vậy qua bài học hôm nay thì bạn đã biết được ArrayList là gì, các xử lý và thao tác với ArrayList. Vì kiến thức còn hạn hẹp nên có gì sai xót thì mọi người comment góp ý để các bài viết sau tốt hơn. Cảm ơn mọi người ❤

nguồn tham khảo : https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html

Tổng quan về Collection trong Java

Để tiếp tục seri java căn bản thì hôm này mình sẽ viết tiếp về collection trong java, có nhiều người hỏi sao không viết tuần tự đi mà cứ nhảy cóc như thế này, thì mình xin trả lời là việc viết blog như này cũng chính là cách mình tự học và hệ thống lại kiến thức. Nên phần nào mà bản thân cảm thấy yếu, chưa chắc chắn lắm hoặc có hứng thú thì mình sẽ viết 😀
Thì hôm nay mình sẽ cùng đi tìm hiểu về collection trong java. Nghe khá quen thuộc đúng không, chắc hẳn ai theo ngành lập trình thì đều biết hoặc ít nhất đã từng nghe qua về nó rồi. Một điều cần lưu ý và dễ gây nhẫm lẫn cho những bạn mới học hay mới làm quen với vấn đề này. Đó là sự nhầm lẫn giữa Collection vs Collections
“Collection” và “Collections” trong java là hai khái niệm khác nhau.

Collections trong java là một khuôn khổ cung cấp một kiến trúc để lưu trữ và thao tác tới nhóm các đối tượng. Tất cả các hoạt động mà bạn thực hiện trên một dữ liệu như tìm kiếm, phân loại, chèn, xóa,… có thể được thực hiện bởi Java Collections.

Collection trong java là một root interface trong hệ thống cấp bậc Collection. Java Collection cung cấp nhiều interface (Set, List, Queue, Deque vv) và các lớp (ArrayList, Vector, LinkedList, PriorityQueue, HashSet, LinkedHashSet, TreeSet…).

Và vấn đề mình đề cập đến trong bài viết này là Collection. Còn về Collections thì có thể ở những bài viết sau mình cũng sẽ nói tới.
Chỉ cần biết là nó khác nhau thôi còn khác nhau như thế nào và bản chất mỗi cái ra sao chúng ra cùng đi tìm hiểu nào. Lest go!

  1. Giới thiệu:
    Collection hay còn hiểu nôm na là 1 tập hợp. Tập hợp là 1 sự tụ tập của một số hữu hạn hay vô hạn các đối tượng nào đó. Người ta khẳng định những đối tượng này được gọi là các phần tử của tập hợp và bất kỳ một đối tượng nào cũng đều có thể được đưa vào một tập hợp(theo wiki).
    Vậy trong lập trình thì tập hợp được hiểu như thế nào ?

Java cũng như các ngôn ngữ khác hỗ trợ mảng (array) như một tập hợp cơ bản nhất, xong việc làm việc với mảng là không thuận tiện trong nhiều trường hợp bởi vì trong thời gian sống của mảng việc tăng thêm phần tử hoặc xóa các phần tử của mảng rất khó khăn và phải trả giá đắt về hiệu năng chương trình nếu cố tình làm điều đó .Hình minh họa một mảng:

Các giới hạn của việc sử dụng mảng (Array)

  • Mảng rất cơ bản và quen thuộc .
    • Lưu trữ các kiểu tham chiếu (object), các kiểu nguyên thủy (primitive type).
    • int[] myArray = new int[]{1,4,3};
    • Object[] myArrayObj = new Object[] { “Object”, new Integer(100) };
  • Mảng có kích cỡ và số chiều cố định.
    • Khó khăn cho việc mở rộng mảng
  • Các phần tử được đặt và tham chiếu một cách liên tiếp nhau trong bộ nhớ.
    • Khó khăn cho việc xóa một phần tử ra khỏi mảng .

Xóa phần tử ra khỏi mảng

Các phần tử của một mảng được đặt liên tiếp nhau trong bộ nhớ điều đó là khó khăn khi bạn cố tình bỏ đi một phần tử nào đó trong mảng, nó mất tính liên tiếp. Thông thường một kỹ thuật mà thường sử dụng là tạo một mảng mới lưu trữ các đối tượng của mảng ban đầu và bỏ đi các phần tử không cần thiết, nhưng điều này làm giảm hiệu năng của chương trình. Với trường hợp mở rộng mảng cũng với kỹ thuật tương tự là khởi tạo một mảng mới với kích cỡ lớn hơn sau đó thì copy các phần tử mảng cũ sang cho mảng mới.
Rõ ràng mảng không phải là một cách tốt cho nhiều trường hợp của ứng dụng .

Và việc xử dụng Array ngày nay không được khuyến khích vì có quá nhiều nhược điểm, thay vào đó chúng ta có Collection. Chúng có thể khắc phục được nhược điểm của array.
Và 1 điều nhấn mạnh cho các bạn là : Array không phải Collection.
Mình đã từng bị nhầm điều này trước đây 😀

Mình vừa nói là Collection sinh ra và nó khắc phục được các nhược điểm của mảng, vậy Collection chính xác là gì, nó là tập hợp của những thứ gì, và vì sao nó lại có thể khắc phục được những điểm yếu của mảng. Chúng ta bắt đầu tìm hiểu nào.

2.Hệ thống phân cấp của Collection Framework

Các kiểu tập hợp của Collection Framework được xây dựng trên cơ sở một số interface trong package java.util. Và được phân chia ra làm 2 hệ thống phân cấp dẫn đầu bởi 2 interface java.util.Collection chứa danh sách các đối tượng và java.util.Map chứa các cặp key/value.

Dưới đây là mô tả những interface chính của Collection

  • Set: là một collection không thể chứa 2 giá trị trùng lặp. Set được sử dụng để biểu diễn các bộ, chẳng hạn như bộ tú lu khơ, thời khóa biểu của học sinh, các tiến trình đang chạy trên máy tính…
  • List: là một collection có thứ tự (đôi khi còn được gọi là một chuỗi). List có thể chứa các phần tử trùng lặp. Thường có quyền kiểm soát chính xác vị trí các phần tử được chèn vào và có thể truy cập chúng bằng index (vị trí của chúng).
  • Queue (hàng đợi): là một collection được sử dụng để chứa nhiều phần tử trước khi xử lý. Bên cạnh các thao tác cơ bản của collection, Queue cung cấp các thao tác bổ sung như chèn, lấy ra và kiểm tra. Queue có thể được sử dụng như là FIFO (first-in, first-out – vào trước, ra trước)
  • Deque: là một collection được sử dụng để chứa nhiều phần tử trước khi xử lý. Ngoài các thao tác cơ bản của collection, một Deque cung cấp các thao tác bổ sung như chèn, lấy ra và kiểm tra. Deques có thể được sử dụng như là FIFO (first-in, first-out – vào trước, ra trước) và LIFO (last-in, first-out – vào sau, ra trước). Trong một Deque, tất cả các phần tử mới có thể được chèn vào, lấy ra và lấy ra ở cả hai đầu.
  • Map: là một đối tượng ánh xạ mỗi key tương úng với một giá trị. Map không thể chứa giá trị trùng lặp. Mỗi key có thể ánh xạ đến nhiều nhất một giá trị.

Dưới đây là mô tả 2 interface được sắp xếp của Set mà Map

  • SortedSet: là một Set chứa các phần tử theo thứ tự tăng dần.
  • SortedMap: là một Map chứa các phần tử được sắp xếp theo thứ tự tăng dần của key của chúng. Các SortedMap được sử dụng cho các collection theo thứ tự tự nhiên của cặp key/value, chẳng hạn như từ điển và danh bạ điện thoại.

Iterable interface

Iterable interface chứa dữ liệu thành viên Iterator interface

Iterator interface

Giao tiếp Iterator cung cấp phương tiện để lặp đi lặp lại các thành phần từ đầu đến cuối của một collection.

Các phương thức của Iterator interface

Chỉ có ba phương pháp trong giao tiếp Iterator như sau:

Các phương thức của interface Collection trong java

Có nhiều phương thức được khai báo trong interface Collection như sau:

Mình sẽ ví dụ về collection trong java:

Đây sẽ là đoạn import thư viện để có thể sử dụng các Collection.

Còn đây sẽ là đoạn Output sau khi mình chạy đoạn code trên.

Duyệt các phần tử của collection

Có 2 cách để duyệt các phần tử của collection trong java.

  1. Sử dụng Iterator interface.
  2. Sử dụng vòng lặp for-each.

Đây sẽ là output:

Như bạn đã thấy có rất nhiều cách để duyệt 1 list, và bài hôm nay mình giới thiệu qua cho các bạn về Collection. Còn rất nhiều các dạng của collection mà mình chưa đề cập đến. Mình chỉ lấy ví dụ những cái phổ biến và thường xuyên được sử dụng để các bạn có thể phần nào hình dung được collection là gì. Ở bài tiếp theo thì mình sẽ đi sâu hơn về từng loại trong collection. Cách thức chúng lưu trữ, xử lý dữ liệu như thế nào. Và ưu, nhược điểm của từng cái để bạn biết khi nào cần dùng đến chúng và để áp dựng xử lý các bài toàn phù hợp. Hẹn gặp lại vào tối mai 😀

Đối Tượng (Object) & Lớp (Class)

Lại là mình đây, thời gian này mình sẽ chăm chỉ đọc và viết blog hơn. Cũng cảm thấy happy vì điều này 😀 Ở bài trước thì mình đang viết dở về seri ActiveMQ, do một vài lý do nên mình sẽ tiếp tục seri đó vài ngày tiếp theo. Còn bây giờ thì mình sẽ viết 1 số bài liên quan đến kiến thức java core căn bản. Điều mà mình cảm thấy rất quan trọng mà bản thân còn đang thiếu hụt kiến thức quá nhiều. Đây sẽ là lúc mình bù đắp những thiếu hụt đó cho bản thân.
Không lan man nữa, đọc tiêu đề thì chắc mọi người cũng biết nội dung bài viết hôm nay rồi chứ. Có thắc mắc vì sao không viết từ những cái căn bản như “Ngôn ngữ lập trình là gì, setup môi trường java, hay viết trương trình hello world căn bản chẳng hạn”
Đơn giản là vì những thứ đó là cái căn bản mà bất cứ ai học lập trình đều phải biết.
Ok bắt đầu nào. Chúng ta sẽ cùng nhau tìm hiểu Đối Tượng (Object) và Lớp (Class).

Khái Niệm Đối Tượng (Object):
-Vậy đối tượng ở đây là gì, search từ khóa “đối tượng là gì” thì kết quả trả về ngay trang đầu tiên là wiki. và đây là kết quả

Nghe thì có vẻ lan man, nhưng thực chất là… lan man thật. Mình nói đùa thôi 😀
Bạn có đang thắc mắc đối tượng này với “đối tượng” trong lập trình nó có gì giống và khác nhau không ?
Để trả lời cho câu hỏi trên thì mình sẽ lấy 1 ví dụ đơn giản sau:
Ví dụ nhà bạn có mở 1 cửa hàng điện thoại và bạn phải viết 1 trương trình để quan lý điện thoại thì bạn chọn bản điện thoại là một đối tượng để quản lý.
Nhưng mình nói rằng Khách hàng cũng là một đối tượng. Và Hóa đơn cũng là một đối tượng nữa. Đấy, tất cả những gì xuất hiện trong một ứng dụng, mà bạn muốn chúng là một thực thể để quản lý, thì bạn cứ xem nó là một đối tượng thôi.
Ngoài ra thì nếu bạn đã chọn thực thể nào đó là đối tượng, thì đối tượng đó ắt hẳn phải có các Trạng thái và Hành vi.

Trạng Thái Của Đối Tượng:
Trạng thái của đối tượng nói lên các đặc thù, hay đặc trưng của đối tượng đó.
Ví dụ bạn “nắm” lấy một đối tượng là iphone 11 pro max trong ứng dụng quản lý điện thoại của bạn để xem thì bạn sẽ thấy các trạng thái của đối tượng này như : Điện thoại này màu vàng, có 3 cam thuộc Apple và dùng khá sướng. Nhưng nếu bạn “cầm” một chiếc điện thoại samsung galaxy s20 ultra thì bạn lấy thấy các trạng thái khác như máy màu trắng, có nhiều hơn 3 cam, thuộc Samsung và dùng sướng không kém. Thấy người ta review vậy thì nói vậy chứ có được dùng đâu 😀

Bạn thấy đấy, tư duy trong lập trình theo Hướng đối tượng trông rất thực tế đúng không nào. Bạn nhớ các trạng thái này của đối tượng nhé, chúng ta sẽ cần đến nó ở các ví dụ trong các kiến thức tiếp theo bên dưới.

Hành Vi Của Đối Tượng

Khác với trạng thái là các đặc điểm của đối tượng. Thì Hành vi chính là các hành động của Đối tượng đó, hay có thể hiểu đó là các hoạt động mà đối tượng đó có trách nhiệm phải thực hiện.

Ví dụ cho dễ hiểu tí nhỉ. Chúng ta có bài toán tình chu vi và diện tích hình tròn. Và khi chúng ta dần chuyển các code tính toán ở hàm main() vào cho đối tượng Hình tròn. Thì các hàm tính toán đó trở thành các hành vi của đối tượng Hình tròn này. Điều này có nghĩa là Hình tròn phải có trách nhiệm thực hiện các hành vi tính Chu vi và Diện tích hình tròn đó. Và dĩ nhiên Hình tròn không có trách nhiệm phải thực hiện các hành động tính Chu vi và Diện tích hình vuông nhé, nó sẽ thuộc về sự quản lý của đối tượng Hình vuông!!! Bạn đã hiểu rõ hơn về hành vi của từng đối tượng rồi đúng không nào. Vậy cùng tiếp tục tìm hiểu về Lớp (Class) nhé.

Khái Niệm Lớp (Class)

Nếu bạn đã nắm về khái niệm Đối tượng rồi. Thì với khái niệm Lớp sẽ đơn giản hơn một tí. Lớp được xem là một khuôn mẫu để tạo ra các Đối tượng. Ngắn gọn vậy thôi đó bạn, nhưng ngắn vậy thì chẳng hiểu gì cả. Thực chất không khó lắm đâu, bạn nhất định phải phân biệt giữa Lớp và Đối tượng. Chúng ta cùng đi từ từ qua các ví dụ sau.

Ví Dụ 1: Làm Bánh

Nào chúng ta hãy quên đi kiến thức lập trình một chút, hãy bắt tay vào làm các bánh quy bơ như sau. Giả sử bạn đã biết hết các công thức làm bánh. Và mình nhờ bạn giúp mình làm ra chúng với hình trái tim như này để mình mang đi tặng “người ấy”.

Nhưng bạn lại nói rằng biết công thức thì chưa đủ. Nếu muốn bạn làm chính xác các bánh có hình thù như vậy, thì hãy đưa bạn cái khuôn. Vâng, thì đây là cái khuôn, mình chuẩn bị sẵn cả rồi, nếu bạn muốn.

Ví Dụ 2: Thiết Kế Các Mẫu Xe Hơi

Mình muốn các bạn đi lan man một chút qua ví dụ kế tiếp. Giả sử bạn là một họa sĩ 3D, chuyên về vẽ texture hoàn thiện hình ảnh cho xe hơi. Với yêu cầu rằng bạn hãy cho ra thành phẩm là những chiếc xe hơi với các màu như sau.

Ồ tất nhiên bạn sẽ làm được thôi. Nhưng trước hết bạn yêu cầu một bản vẽ concept về mẫu xe hơi này, thì bạn mới có thể khoác lên chúng các màu sắc được chứ. Vâng mình cũng đã chuẩn bị cho bạn, concept đây.

Những ví dụ trên đây mang lại một thông điệp gì. Mình muốn các bạn hình dung từ thực tế rằng, các Đối tượng mà chúng ta cần quan tâm đến từ các ví dụ này chính là những thứ hữu hình có thể nhìn thấy hay tương tác được từ phía người dùng. Như mỗi thành phẩm bánh quy bơ ở ví dụ 1 là một Đối tượng riêng biệt. Hay mỗi thành phẩm xe hơi với từng màu sắc khác nhau ở ví dụ 2 cũng là một Đối tượng riêng biệt.

Vậy thì những khuôn mẫu để tạo nên các thành phẩm đó, như cái khuôn hình trái tim, hay concept xe hơi, chính là Lớp mà chúng ta cần phải tìm hiểu.

Ví Dụ 3: Làm Phần Mềm Quản Lý Sinh Viên

Giờ thì không ví dụ lan man nữa. Chúng ta quay về lĩnh vực lập trình với ví dụ về quản lý Sinh viên. Giả sử trong ngôi trường mà chúng ta cần quản lý có các Sinh viên sau.

đây là lớp mình từ hồi đi làm từ thiện, và mình là cái đứa mặc áo trắng ở trung tâm bức ảnh kià 😀

Áp dụng từ các ví dụ 1 và 2 đi nào. Sinh viên chắc chắn là đối tượng cần quản lý rồi. Vậy đâu sẽ là khuôn mẫu cho các đối tượng Sinh viên này. Trong trường hợp này, cái Khuôn mẫu ấy hơi trừu tượng xíu, nó sẽ là một hình tượng chung chung với các nhãn như: Giới tínhTênLớpTuổiQuê quánChiều caoCân nặng. Như hình bên dưới.

ừ khái khuôn mẫu đó, hay ta đã biết nó là Lớp, nó sẽ giúp tạo ra nhiều Đối tượng Sinh viên khác nhau. Như Đô-Giới tính Nam-23 tuổi-Lớp Java-…. Hay Quý “rách”-Giới tính Bê đê-23 tuổi-Lớp C#-….

Vậy bạn đã hiểu khái niệm Lớp là một khuôn mẫu để tạo ra các Đối tượng rồi đúng không nào. Và nếu như Đối tượng có các Trạng thái và Hành vi, thì Lớp cũng có các Thuộc tính và Phương thức tương ứng.

Khai Báo Lớp

Từ bài hôm trước tới giờ chúng ta chỉ lan man nói và nói. Giờ thì đến lúc code rồi. Nhưng trước khi đi vào cách thức code một Lớp, thì hãy cùng nhau đi qua phần này trước.

Hình Dung Về Lớp

Khi bạn muốn đưa một thực thể nào đó về một đối tượng để quản lý, thì bạn phải tạo khuôn mẫu cho đối tượng đó. Đó chính là Lớp. Và trước khi bắt tay vào tạo một Lớp, bạn phải hình dung Lớp đó với ba thành phần chính như biểu diễn sau.

Cú Pháp Khai Báo Lớp

Một khi bạn đã hình dung ra một Lớp với ba thành phần như trên, thì việc biểu diễn ra thành code Java sẽ theo cú pháp sau.

class tên_lớp {
     các_thuộc_tính;
     các_phương_thức;
}

Trong đó:

– class chỉ là một keyword cho biết bạn đang khai báo một Lớp.
– tên_lớp là tên định danh cho Lớp đó, quy luật đặt tên cho Lớp cũng giống như quy luật đặt tên cho biến. Nhưng bạn lưu ý một tí là tên_lớp này nên viết in hoa hết từng chữ đầu của mỗi từ nhé, ví dụ như HinhTron, SinhVien, HoaDon,… để mà phân biệt với tên khi bạn khai báo một đối tượng sẽ được nói ở phần dưới đây.
– các_thuộc_tính và các_phương_thức sẽ được mình nói rõ hơn ở bài sau.

Bài Thực Hành Số 1

Bài thực hành này chúng ta sẽ tiến hành xây dựng lớp Hình tròn mà ở bài hôm trước mình có ví dụ cho các bạn xem.

Chú ý là bài thực hành này không đòi hỏi các bạn phải hiểu hết tất cả các dòng code, các bạn chỉ cần nhìn vào cấu trúc tổng quát của lớp Hình tròn như những gì mình nói đến ở bài hôm nay. Tất cả những chi tiết đắt giá của Lớp sẽ được mình nói tiếp ở các bài học sau.

Chúng ta tạm thời code vào chung với nơi đang chứa đựng hàm main(), và bên dưới hàm main() này như sau.

Mình nói thêm một chút. Với khai báo một Lớp như trên thì:

– tên_lớp lúc này chính là HinhTron.
– các_thuộc_tính chính là các biến và hằng, bao gồm PIrcvdt.
– các_phương_thức chính là các hàm nhapBanKinh()tinhChuVi()tinhDienTich()inChuVi()inDienTich().

Khai Báo Và Khởi Tạo Đối Tượng

Việc khai báo một đối tượng hoàn toàn không có một cú pháp chung, nó tùy vào đặc thù từng Lớp của Đối tượng đó mà chúng ta sẽ nói cụ thể ở bài sau. Nhưng bạn nên nhớ có một điểm chung, đó là cần phải có từ khóa new trong khai báo và khởi tạo các đối tượng này.

Chúng ta hãy cùng thực hành khai báo đối tượng Hình tròn.

Bài Thực Hành Số 2

Bạn hãy thử khai báo đối tượng Hình tròn bên trong hàm main() như sau.

Một lần nữa đừng chú trọng quá vào cách thức sử dụng các hàm trong một đối tượng, bạn sẽ sớm quen với Đối tượng sớm thôi.

Bạn chỉ cần chú ý một số chỗ.

– Dòng code HinhTron hinhTron = new … chính là nơi bạn khai báo một đối tượng là hinhTron. Đối tượng hinhTron này được tạo ra từ lớp HinhTron. Như bạn biết rằng bạn có thể tạo ra bao nhiêu đối tượng Hình tròn nào khác từ lớp HinhTron này (như ví dụ code ngay bên dưới đây). Thậm chí bạn có thể tạo ra Mảng các đối tượng Hình tròn mà chúng ta sẽ xem xét ở một bài khác.

Đừng để ý cái vàng vàng kia, nó chỉ là cảnh bảo warning từ IDE thôi 😀

– Từ khóa new như mình nói là bắt buộc khi khai báo và khởi tạo các kiểu dữ liệu đối tượng này, nó khác với khi bạn khai báo một biến ở kiểu dữ liệu nguyên thủy. Các bạn có thể tìm và đọc những bài viết biến. Theo sau từ khóa new là gì thì mình sẽ nói rõ ở bài sau nhé.
– Các dòng code còn lại là các dòng gọi đến các Thuộc tính hay các Phương thức của đối tượng đó.

Giờ đây nếu thực thi dòng code này thì bạn đã có thể tương tác với console được rồi đấy nhé.

Chúng ta có thể kết thúc bài học hôm nay ở đây. Như vậy là các bạn vừa làm quen với hai khái niệm cơ bản nhất trong lập trình Hướng đối tượng, đó là Lớp (Class) và Đối tượng (Object). Và cùng nhau định nghĩa và khởi tạo đối tượng hinhTron từ lớp HinhTron để thực hành thử. Chúng ta sẽ nói sâu hơn về cấu trúc và cách sử dụng Đối tượng ở các bài học tiếp theo.

Cảm ơn bạn đã đọc các bài viết của mình, kiến thức hạn hẹp nên đôi khi cách viết và trình bầy không được ổn cho lắm nên mọi người thông cảm 😀
à mà bạn nào lười đọc thì có thể xem video tại đây. Xin chào và hẹn mọi người ở các bài chia sẻ sau.

Seri ActiveMQ: Tìm hiểu về Apache ActiveMQ

Chào mọi người, đã khá lâu từ bài viết trước của mình. Dạo này mình có đang tìm hiểu về ActiveMQ nên muốn chia sẻ cho mn. Vì kiến thức là của chung và ai cũng có quyền được biết nó. Đây cũng là 1 cách để bản thân mình tự học và có chỗ đọc lại nếu quên.

Đầu tiên chúng ta cần biết ActiveMQ là gì? và tại sao phải cần đến nó.

Trước khi tìm hiểu kĩ hơn về ActiveMQ thì chúng ta cần phải biết có các loại message queue nào và phân loại được chúng.

Nếu bạn là một backend-developer chắc hẳn bạn không còn xa lạ gì với những hệ thống message-queue. Hầu như project nào của mình cũng có sự xuất hiện của message-queue và việc khó khăn nhất là lựa chọn nên dùng cái nào và không nên dùng cái nào.


Thử tưởng tượng một ngày sếp của bạn muốn tích hợp một hệ thống message queue hoặc bạn cảm thấy nên sử dụng một hệ thống message queue để giải quyết bài toán mà team đang gặp phải. Bạn bắt đầu tìm kiếm và nhận ra rằng có quá nhiều hệ thống message queue tồn tại. Mình có thể liệt kê một số loạt mà mình biết dưới đây :

  1. RabitMQ
  2. ActiveMQ
  3. Kafka
  4. SQS
  5. ZeroMQ
  6. MSMQ
  7. IronMQ
  8. Kinesis
  9. RocketMQ

Sau một hồi tìm hiểu mình nhận ra một điều, đó là mặc dù cùng là message queue nhưng lại được chia làm 2 loại có mục đich sử dụng và những tính năng liên quan hoàn toàn khác nhau.

Mình tạm chia thành 2 loại như sau:

So sánh cách hoạt động của 2 loại:

Dựa vào bảng trên, ta có thể thấy được sự khác nhau cơ bản giữa 2 loại, cũng như cách sử dụng trong tưng bài toán cụ thể.

  • Đối với loại “message base”: là những loại message queue truyền thống, thích hợp làm hệ thống trao đổi message giữa các service. Việc đảm bảo mỗi consumer đều nhận được message và duy nhất một lần là quan trọng nhất.
  • Đối với loại “data-pipeline”, có cách lưu trữ message cũng như truyền tải message đến consumer hoàn toán khác với hệ thống message queue truyền thống. Việc đảm bảo mỗi consumer đều phải nhận được message và duy nhất một lần không phải là ưu tiên số một, mà thay vào đó là khả năng lưu trũ message vả tốc độ truyền tải message. Khi có message mới, consumer sẽ lựa chọn số lượng message mà mình muốn lấy, chính vì thế mà cùng một message consumer có thể nhận đi nhận lại nhiều lần. Những hệ thống sử dụng message queue loại này thường là hệ thống Event Sourcing, hoặc hệ thống đồng bộ dữ liệu từ những database khác nhau như Debezium.

Khi các bạn lựa chọn message queue cho hệ thống của mình, các bạn nên xác định rõ mục địch của hệ thống messague queue để xem mình cần loại trong hai loại trên. Việc xác định được loại message queue nào mình cần sẽ giúp các bạn giảm bớt thời gian tìm hiểu cũng như tìm được chính sác cái mà mình cần.

Đôi khi chúng ta cũng thấy một số hệ thống sẽ sử dụng nhiều loại message queue, thường sẽ là 1 của “message base” và 1 của “data pipeline” để tận dụng tối đa ưu điểm của từng loại vào giải quyết bài toán cụ thể.

Vậy là các bạn đã biết là ngoài ActiveMQ ra thì còn rất nhiều loại message queue khác và mỗi loại sẽ phục vụ một nhu cầu sử dụng khác nhau và có những ưu nhược điểm khác nhau tùy thuộc vào bài toán mà chúng ta sử dụng. Nhưng ở seri này mình chỉ muốn tìm hiểu đến ActiveMQ thôi vì đây là loại message queue mà công ty mình dùng và sắp tới mình phải thuyết trình vấn đề này trước mặt các anh có cả chục năm kinh nghiệm @@!

Trước khi bàn đến ActiveMQ mình xin đề cập đến Producer–Consumer pattern để trả lời câu hỏi mình đề cập bên trên.
Search google với từ khóa “Producer–Consumer pattern” có rất nhiều bài viết nói về nó. Nhưng mình sẽ tòm lại bằng 1 ví dụ hết sức đơn giản.

Để dễ hiểu ta có thể hình dung một tình huống như sau:

Việc xếp hàng để thực hiện một hành vi nào đấy là rất phổ biến, ví dụ như xếp hàng mua vé tàu, người mua vé phải xếp hàng dài đợi chờ rất lâu để có thể mua vé tàu, vì việc này phải xử lý tuần tự, người này mua xong mới tới người khác. Trong kỹ thuật người ta gọi đây là xử lý Synchronized tức là xử lý đồng bộ.

Với một vài trường hợp như các tác vụ là độc lập không cần chờ nhau, thì việc này sẽ gây ra việc tắc nghẽn hệ thống vì task vụ trước phải sử lý xong với đến tác vụ sau.

Để giải quyết tình huống này ta có thể xây dựng một hệ thống tiếp nhận yêu cầu liên tục mà không cần xử lý ngay, rồi dữ liệu sẽ được rút ra rồi xử lý dần dần như cái cabinet trong hình sau.

Trong kỹ thuật người ta gọi đây là xử lý bất đồng bộ as-Synchronized

Việc này sẽ giúp cho việc hệ thống có thể nhận yêu cầu liên tục từ “Customer” mà không cần chờ đợi, tác vụ sẽ được “Worker” xử lý dần cho đến khi các tác vụ được xử lý hết.

Nhưng sẽ có tình huống quá nhiều yêu cầu mà Worker không xử lý kịp, dẫn đến cabinet bị đầy và việc tiếp nhận sẽ bị chậm đi, để giải quyết việc này ta có thể bổ xung thêm nhiều Worker hoặc chia ra thành nhiều cabinet mỗi cabinet có một Worker.

Vần đề nêu trên trong lập trình người ta gọi là Producer–Consumer Problem. Producer ở đây là khách hàng, người gửi yêu cầu, còn Consumer ở đây được hiểu là Worker người xử lý yêu cầu của khách hàng, còn cái cabinet chưa yêu cầu được gọi là Queue.

Bây giờ về lại với chủ đề chính là ActiveMQ. ActiveMQ là một Message-oriented middleware (MOM) giúp cho việc chuyển nhận message theo cơ chế queue as-Synchronized dùng để giải quyết Producer-Consumer Problem. Điểm mạnh của ActiveMQ ta có thể dùng để gửi nhận message với nhiều hệ thông với các nền tảng khác nhau, đúng như tên gọi “Hệ thống trung gian chuyển tải gói tin”.

Các thành phần chính trong ActiveMQ

Producer/Publisher: Thành phần tạo và gửi tin (ActiveMQ-Client).
Broker trung gian hay Message Oriented Middleware (MOM) (ActiveMQ-Broker).
Consumer/Subcriber: Thành phần nhận tin từ Producer thông qua MOM (ActiveMQ-Client).

Cơ bản là như vậy , bài sau mình sẽ có demo cho các bạn nhé :D.

Vòng Đời Của Thread

Với việc làm quen với Thread ở bài hôm trước, bạn đã biết rằng có hai cách để chúng ta tạo ra một Thread rồi.

Bước sang phần này của Thread, chúng ta cùng tìm hiểu sâu hơn về Thread, để xem khi bạn tạo ra một Thread nào đó, thì vòng đời của nó sẽ như thế nào? Thread đó sẽ trải qua những trạng thái nào trong vòng đời đó? Dựa vào các trạng thái đó, làm sao để các Thread có thể đồng bộ hoá, hay có thể hiểu là tự điều chỉnh độ ưu tiên trong việc thực thi tác vụ giữa các Thread trong cùng một Process với nhau? Mời bạn cùng đến với những kiến thức thú vị này hôm nay.

Trước hết, chúng ta cùng trả lời thắc mắc đầu tiên.

Vòng Đời Của Một Đối Tượng Là Gì?

Nếu đã làm quen với Android, thì bạn đã từng biết đến khái niệm “vòng đời” này rồi, như Vòng đời Activity, Vòng đời Fragment. Mình nhắc lại một chút thôi, đó là sở dĩ chúng ta xem xét “vòng đời” của một đối tượng nào đó, là khi mà đối tượng đó có một thời gian sống nhất định, và trong quá trình sống của đối tượng đó chúng ta muốn biết nó có thể sẽ trải qua nhiều trạng thái khác nhau như thế nào. Các trạng thái đó có phải là Sinh, Lão, Bệnh, Tử hay không? ^^

Tóm cái gì đó lại là, không phải cái gì chúng ta cũng đều nói về vòng đời của nó cả, chỉ những đối tượng có thời gian sống đủ lâu, và trải qua nhiều trạng thái trong quá trình sống, thì chúng ta mới xem xét đến vòng đời của nó thôi, như Activity và Fragment bên kiến thức Android, và Thread trong kiến thức Java này chẳng hạn.

Vậy tìm hiểu vòng đời, hay các trạng thái bên trong một vòng đời để làm gì.

Tại Sao Nên Tìm Hiểu Vòng Đời Của Một Đối Tượng?

Một lý do chính đáng nhất để chúng ta nên biết về vòng đời của một đối tượng nào đó, ngoài việc nó được sinh ra khi nào, và bị chết khi nào. Thì sự hiểu các trạng thái mà đối tượng đó trải qua trong quá trình sống đó cũng khá là quan trọng. Khi bạn nắm được các trạng thái của một vòng đời, bạn sẽ hiểu về đối tượng đó nhiều hơn, từ đó bạn có thể dễ dàng can thiệp vào nó, chèn vào các trạng thái đó các tác vụ tương ứng phù hợp nhất. Mục đích cuối cùng là làm cho ứng dụng của chúng ta trở nên mạnh mẽ hơn, và thậm chí, thông minh hơn nữa kìa. Chi tiết như thế nào mời các bạn cùng xem tiếp.

Tìm Hiểu Vòng Đời Của Thread

Nào chúng cùng quay lại phần chính của bài học, và cùng nhau tìm hiểu vòng đời của Thread. Trước hết mời bạn xem sơ qua vòng đời này thông qua sơ đồ sau.

Sơ Đồ Minh Họa Vòng Đời Của Thread

Sơ đồ này dựng lên dựa trên các trạng thái đã được định nghĩa bên trong khai báo enum của lớp Thread. Enum là gì thì chúng ta sẽ nói đến ở bài học sau. Cơ bản thì bạn cứ hiểu enum giúp chúng ta định nghĩa ra một tập hợp các hằng số vậy, và trong tình huống này các hằng số này cũng chính là các trạng thái của vòng đời Thread.

Bạn có thể vào trong lớp Thread để xem việc khai báo các giá trị bên trong một enum là như thế nào. Bạn có thể vào bên trong Thread từ Eclipse để xem, hoặc có thể xem hình sau.

Mô Tả Vòng Đời Của Thread

Như mình có nói, sơ đồ hay các enum bên trong một Thread đã thể hiện rõ nhất các trạng thái bên trong một vòng đời của Thread này. Chúng được mô tả một cách tổng quát như sau.

Ngay khi bạn tạo mới một Thread, nhưng vẫn chưa gọi đến phương thức start(), trạng thái của nó sẽ là NEW.

Còn khi bạn đã gọi đến start(), Thread đó sẽ vào trạng thái RUNNABLE, trạng thái này đưa Thread vào hàng đợi để đợi hệ thống cấp tài nguyên và khởi chạy sau đó.

Trong quá trình Thread đang chạy, nếu có bất kỳ tác động nào, ngoại trừ làm kết thúc vòng đời của Thread, nó sẽ vào trạng thái BLOCKED, hoặc WAITING, hoặc TIMED_WAITING.

Cuối cùng, khi một Thread kết thúc, nó đến trạng thái TERMINATED.

Tổng quan là vậy, còn chi tiết từng trạng thái thì mình mời các bạn đến với mục tiếp theo sẽ rõ.

Các Trạng Thái Bên Trong Một Vòng Đời

Mục này chúng ta sẽ xem kỹ từng trạng thái một. Cái chính của mục này đó là giúp chúng ta hiểu được khi nào mà một Thread rơi vào một trạng thái nào đó. Qua đó bạn có thể tận dụng cho các mục đích cụ thể ở các project cụ thể của bạn sau này.

NEW

Trạng thái này rất dễ hiểu, khi bạn khởi tạo một Thread, nhưng vẫn chưa gọi đến phương thức start() của nó, thì Thread này sẽ rơi vào trạng thái NEW. Không tin à, mời bạn đến bài thực hành sau.

Bài Thực Hành Số 1

Ở bài thực hành đầu tiên này, chúng ta cùng xem trạng thái khi mà một Thread được khởi tạo nhưng phương thức start() vẫn chưa được gọi có phải là NEW hay không.

Để có thể xem được trạng thái của một Thread, chúng ta sẽ gọi đến phương thức getState(). Phương thức này được xây dựng sẵn ở lớp cha Thread.

Bạn hãy tạo mới một Thread nhé. Tạo bằng cách kế thừa từ lớp Thread hay implement từ interface Runnable cũng được. Hãy đặt tên Thread này là MyThread. Đây là cách mình xây dựng MyThread.

Để xem được trạng thái NEW này, ở phương thức main() bạn hãy khai báo MyThread rồi in ra ngay getState() mà không cần phải start() nó.

Và đây là “thành phẩm”.

RUNNABLE

Trạng thái này xảy ra khi Thread đã được gọi phương thức start(). Ồ, bạn cũng nên biết một chút rằng không phải start() xong là Thread được chạy ngay đâu, nó còn phải chờ đợi hệ thống cấp phát tài nguyên xong xuôi thì mới bắt đầu chạy. Chính vì vậy mà bên trong trạng thái này dường như chia ra làm 2 trạng thái con, đó là, Ready to Run – Chờ đợi cấp phát tài nguyên, và Running – Đã chính thức chạy.

Bài Thực Hành Số 2

Bài này chúng ta sẽ thử gọi phương thức start() của MyThread ở Bài thực hành số 1 trên kia. Rồi cũng gọi đến phương thức getState() ngay sau đó để xem trạng thái của MyThread lúc này là gì nhé.

Code ở phương thức main() như sau.

Lưu ý rằng không phải lúc nào code trên đây cũng luôn in trạng thái RUNNABLE ra console đâu nhé. Vì sao vậy? Bạn có thể thấy rằng bên trong MyThread chỉ có in ra console chuỗi “Thread Start” thôi, sau khi in xong chuỗi này Thread sẽ kết thúc vòng đời của nó ngay. Do đó có trường hợp geState() ở phương thức main() sẽ gọi khi MyThread đã kết thúc rồi, nên RUNNABLE có thể sẽ không được in ra (mà là một trạng thái nào đó khác ở các mục sau bạn sẽ rõ) là vậy.

BLOCKED

Một Thread khi rơi vào trạng thái BLOCKED là khi nó không có đủ điều kiện để chạy. Không đủ điều kiện để chạy là như thế nào? Bạn có thể hiểu là, bản chất các Thread trong một ứng dụng đều có khả năng chạy song song khi chúng được start(). Như vậy thì sẽ xảy ra trường hợp cùng một thời điểm nào đó, sẽ có nhiều hơn một Thread đều có “mưu đồ” muốn chỉnh sửa một File hay một đối tượng nào đó, chúng ta gọi tắt các File hay các đối tượng bị “tranh chấp” này là các tài nguyên dùng chung. Nếu có sự tranh chấp này xảy ra, sẽ khiến cho ứng dụng bị lỗi, có thể dẫn đến mất mát dữ liệu hoặc các tính toán sai lầm. Do đó, trong Java có một cơ chế giúp điều khiển các Thread, cơ chế này đảm bảo một thời điểm nào đó chỉ có một Thread có thể can thiệp vào tài nguyên dùng chung mà thôi. Cơ chế này liên quan đến khái niệm Synchronization (đồng bộ hoá) mà chúng ta sẽ có một bài riêng về nó. Như vậy nếu có sự đồng bộ hoá này xảy ra, thì chỉ một Thread là được ưu tiên sử dụng đến tài nguyên dùng chung này, các Thread còn lại bị khoá và phải đợi cho Thread ưu tiên kia sử dụng xong tài nguyên rồi mới được chạy, các Thread bị khoá này sẽ bị rơi vào trạng thái BLOCKED.

Như vậy để có thể nhìn thấy được trạng thái này, chúng ta sẽ làm quen trước một tí với kiến thức về Đồng bộ hoá ở bài thực hành sau, để rồi bài sau chúng ta cùng nhau nói kỹ hơn về nó nhé.

Bài Thực Hành Số 3

Để thực hành mục này, chúng ta hãy tạo ra một tài nguyên dùng chung, chính là một lớp nào đó, mình đặt tên lớp dùng chung này là DemoSynchronized. Trong lớp này có chứa một phương thức static có đánh dấu synchronized. Phương thức này có tên commonResource(). Bạn cũng đừng tập trung vào từ khoá synchronized quá, bài sau mình sẽ giải thích rõ. Bạn chỉ cần hiểu rằng phương thức commonResource() này được đánh dấu synchronized sẽ được hệ thống “bảo trợ” sao cho chỉ có một Thread được truy cập đến nó mà thôi.

Nào chúng ta cứ xây dựng trước lớp này nhé.

Sau đó chúng ta để cho MyThread (đã code ở các bài thực hành trên đây) có cơ hội gọi đến commonResource(). Như sau.

Và rồi ở phương thức main(), chúng ta sẽ tạo nhiều hơn một đối tượng của MyThread, cụ thể là 2 đối tượng, bạn cũng có thể tạo ra 3, hay 4 MyThread để kiểm chứng. Sau khi tạo ra các MyThread, chúng ta đều cùng start() chúng, để chúng cùng lúc gọi đến commonResource() khi chạy. Sau đó bạn chỉ cần “ung dung” gọi getState() của chúng.

public class MainClass {
     
    public static void main(String[] args) {
        // Khai báo nhiều đối tượng của MyThread
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();
         
        // Đều start() hết các đối tượng MyThread
        // để xem Thread nào sẽ được vào commonResource()
        myThread1.start();
        myThread2.start();
         
        // In ra các trạng thái của chúng
        System.out.println(myThread1.getName() + ": " + myThread1.getState());
        System.out.println(myThread2.getName() + ": " + myThread2.getState());
    }
}

Kết quả là, chỉ có một Thread lúc này là RUNNABLE thôi, còn lại sẽ đều là BLOCKED.

WAITING

Trạng thái này xảy ra khi một Thread phải đợi Thread nào đó hoàn thành tác vụ của nó, với một khoảng thời gian không xác định trước. Trạng thái này khác với trạng thái BLOCKED trên kia nhé, ở trên kia là các Thread bị hệ thống khoá lại khi cùng truy xuất chung đến một tài nguyên hệ thống. Còn trạng thái này là giữa các Thread tự điều đình với nhau. BLOCKED giống như các phương tiện bị chú cảnh sát giao thông chặn lại để nhường cho phương tiện được ưu tiên khác. Còn WAITING là tự các phương tiện tự nhường nhịn nhau, không cần phải có công an điều tiết ấy mà.

Do là các Thread sẽ nhường nhịn nhau, nên nếu một Thread nào đó có động thái gọi đến một trong các phương thức sau, nó sẽ “nhường” và tự rơi vào trạng thái WAITING này, các phương thức đó là.

– Object.wait()
– Thread.join()
– LockSupport.park()

Cụ thể các phương thức trên đây là như thế nào thì mời các bạn đến bài học sau sẽ rõ. Bài hôm nay chúng ta thử thực hành với phương thức join(). Khi một Thread gọi đến phương thức join() của Thread khác, nó sẽ phải đợi Thread khác đó chết đi thì nó mới được thực hiện tiếp tác vụ còn lại của nó.

Lưu ý rằng các phương thức mình liệt kê trên đây không có tham số truyền vào nhé.

Chúng ta cùng đến với bài thực hành để hiểu rõ hơn về trạng thái này của Thread.

Bài Thực Hành Số 4

Trước hết chúng ta cùng chỉnh sửa một tí lớp MyRunnable như sau.

Code trên đây chưa liên quan gì đến việc đưa Thread vào trạng thái WAITING đâu nhé. Code này chỉ có in ra console cho thấy MyRunnable vừa được “Start”, rồi cho làm một việc nặng nặng nào đó, như lặp 100 lần, mỗi lần lặp sẽ ngủ 100 mili giây thôi. Cuối cùng sẽ in ra console cho thấy MyRunnable đã “End”.

Tiếp theo chúng ta đến với lớp MyThread. Ở MyThread này, khi được khởi chạy, chúng ta cố tình khai báo rồi khởi chạy MyRunnable luôn. Nhưng khi vừa mới khởi chạy MyRunnable, chúng ta gọi đến phương thức join() của nó. Điều này báo với hệ thống rằng, MyThread này sẽ đợi MyRunnable chạy hết (kết thúc vòng đời của MyRunnable) thì MyThread mới chạy tiếp. Bạn cứ code rồi khởi chạy nhé, đền bài học sau mình sẽ nói rõ hơn về phương thức join() này.

Đây là code của MyThread.

Sau đó ở phương thức main() bạn chỉ cần khởi chạy MyThread, đợi khoảng 100 mili giây thì in trạng thái của MyThread ra console để xem chơi. Sở dĩ phải đợi một tí mới in trạng thái của MyThread là vì để đảm bảo MyThread có đủ thời gian để khởi chạy MyRunnable nữa, rồi thời gian mà MyThread vào WAITING nhường cho MyRunnable nữa, bạn in vội quá thì khó mà trông thấy trạng thái của MyThread.

public static void main(String[] args) {
    MyThread myThread = new MyThread();
         
    myThread.start();
         
    try {
        Thread.sleep(100);
        System.out.println("MyThread State: " + myThread.getState());
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
         
}

Kết quả in ra console như sau.

TIMED_WAITING

Cũng tương tự với WAITING trên kia thôi, nhưng khi này các phương thức khiến một Thread “nhường” cho một Thread khác thực thi có truyền vào đối số là khoảng thời gian mà Thread đó nhường. Các phương thức đó là.

– Thread.sleep(long milis)
– Object.wait(int timeout) hay Object.wait(int timeout, int nanos)
– Thread.join(long milis)
– LockSupport.parkNanos()
– LockSupport.parkUtil()

Chà, phương thức sleep() quen thuộc lắm đúng không. Giờ thì bạn mới hiểu, rằng ở đâu đó trong Thread khi gọi đến Thread.sleep() này, thì Thread đó sẽ rơi vào trạng thái TIMED_WAITING và “nhường” cho các Thread khác chạy trong khoảng thời gian mili giây chỉ định trước.

Tuy nhiên chúng ta cũng sẽ nói rõ các phương thức này ở bài học sau. Giờ thì xem code của các bài thực hành nào.

Bài Thực Hành Số 5

Với bài thực hành này bạn chỉ cần chỉnh sửa một chút so với Bài thực hành 4 trên kia. Cái nơi mà MyThread khởi chạy MyRunnable rồi gọi join() để nhường cho MyRunnable í, giờ bạn hãy truyền giá trị mili giây vào phương thức join() này. Nó có nghĩa rằng là tuy MyThread có nhường MyRunnable chạy trước đấy, nhưng chỉ nhường với một khoản thời gian đã chỉ định thôi, hết thời gian đó là tao chạy, đụng ai thì đụng nhé.

public class MyThread extends Thread {
 
    @Override
    public void run() {
        System.out.println("MyThread Start");
        Thread myRunnableThread = new Thread(new MyRunnable());
        myRunnableThread.start();
         
        try {
            myRunnableThread.join(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
         
        System.out.println("MyThread End");
    }
}

Nếu bạn thực thi chương trình, hãy để ý kỹ, sau khi in ra “MyThread State: TIMED_WAITING” rồi, MyThead sẽ đợi MyRunnable trong khoảng thời gian (chưa tới) 500 mili giây còn lại, và sẽ in ra “MyThread End”. Vấn đề là MyRunnable vẫn chưa kết thúc vòng lặp, nên mãi sau nó mới kết thúc và in ra “MyRunnable End”. Bạn có thấy sự nhịp nhàng giữa các Thread không nào.

TERMINATED

Trạng thái này đánh dấu sự kết thúc vòng đời của Thread. Xảy ra khi Thread kết thúc hết các tác vụ bên trong phương thức run() của nó, hoặc có những kết thúc một cách không bình thường khác, như rơi vào Exception chẳng hạn.

Bài Thực Hành Số 6

Code của bài này không có gì nhiều. Bạn cứ lấy code của Bài thực hành số 5 trên kia. Rồi ở phương thức main(), bạn sleep() lâu lâu một tí, nhằm mục đích đợi cho MyThread kết thúc tác vụ của nó rồi thì in trạng thái của nó ra. Mình cho thời gian ngủ là 20 giây, như sau.

public static void main(String[] args) {
    MyThread myThread = new MyThread();
         
    myThread.start();
         
    try {
        Thread.sleep(20000);
        System.out.println("MyThread State: " + myThread.getState());
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
         
}

Kết quả in ra console như sau.

Phù! Chúng ta vừa đi qua kiến thức về vòng đời của một Thread, qua đó chúng ta biết được một Thread sẽ trải qua các trạng thái của nó như thế nào trong suốt đời sống của nó. Chắc bạn cũng biết rằng không phải lúc nào một Thread cũng trải qua cả đủ các trạng thái kể trên đâu nhé, có Thread chỉ NEWRUNNABLE rồi TERMINATED thôi. Tuy nhiên qua kiến thức về vòng đời này, bạn cũng đã biết được sơ sơ cách các Thread đồng bộ hoá, nhường nhịn nhau để thực thi các tác vụ như thế nào rồi đúng không nào. Và kiến thức về Thread cũng còn khá nhiều và không kém phần thú vị. Hẹn các bạn ở các bài học sau.

Các Cách Thức Để Tạo Một Thread

Ở bài hôm trước bạn cũng đã làm quen với một cách để tạo ra một Thread rồi. Nhưng mình mong muốn bài hôm nay bạn hãy… quên kiến thức bài trước đi, chúng ta cùng đi lại từ đầu cho nó hệ thống nào.

Trong Java, có hai cách để bạn tạo một Thread. Cả hai cách này đều có tần suất sử dụng như nhau (theo quan sát của mình). Nhiệm vụ của bạn là phải biết rõ cả hai cách này. Tại sao phải biết cả hai cách? Ngoài việc bạn phải biết hết để có thể khai báo và sử dụng, bạn còn phải biết để còn đọc hiểu source code của người khác khi họ không dùng giống bạn nữa.

Vậy hai cách để tạo ra Thread là gì.

Cách 1 – Kế Thừa Từ Lớp Thread

Cách này ở bài hôm trước… ồ mình đã kêu các bạn quên đi rồi mà. Vậy thì, với cách này bạn làm như sau.

– Bạn tạo mới một lớp và kế thừa lớp này từ lớp cha Thread.
– Trong lớp mới tạo đó, bạn override phương thức run().
– Cuối cùng, ở nơi khác, khi muốn tạo ra một Thread từ lớp này, bạn khai báo đối tượng cho nó, rồi gọi đến phương thức start() của nó để bắt đầu khởi chạy Thread.

Thật đơn giản đúng không nào. Không ngờ kiến thức về Thread lại dễ đến vậy. Như bạn đã biết sơ qua từ bài hôm trước rằng, để khai báo một lớp là Thread, thì đơn giản chỉ kế thừa nó từ lớp cha Thread, chính phương thức run() bên trong lớp đó sẽ trở thành một Luồng xử lý bởi hệ thống khi đâu đó bên ngoài gọi đến phương thức start() của lớp này (thực ra start() là sự kế thừa từ lớp cha Thread).

Chúng ta cùng đến với bài thực hành để hiểu rõ hơn.

Thực Hành Tạo Một Thread Bằng Cách Kế Thừa Từ Lớp Thread

Bài thực hành này chúng ta thử nghiệm tạo một Thread đếm ngược 10 giây. Khi start, Thread sẽ bắt đầu in ra console giá trị 10, mỗi một giây trôi qua Thread sẽ giảm con số này đi một đơn vị và lại in ra console, đến khi giảm đến giá trị 0 Thread sẽ in “Hết giờ”.

Bạn có thể sử dụng lại project đã tạo từ bài hôm trước, hôm nay bạn tạo một lớp mới có tên CountDownThread. Lớp này sẽ kế thừa từ lớp Thread như những gì mình đã nói ở các gạch đầu dòng trên kia như sau.

Một khung sườn cho Thread chỉ như vậy thôi. Như đã nói, CountDownThread khi được start sẽ bắt đầu đếm ngược từ 10 giây, đến 0 giây sẽ hiển thị chuỗi “Hết giờ”. Việc hiển thị số giây ra console thì bạn biết rồi, mình chỉ bật mí là để làm cho con số này chỉ được cập nhật và hiển thị ở mỗi giây thì chúng ta sử dụng phương thức Thread.sleep(1000). Phương thức này làm cho các Thread đang chạy trở nên “ngủ” trong một khoảng thời gian được tính bằng mili giây, trong trường hợp này chúng ta truyền vào 1000 mili giây, tức là 1 giây. Sau khi ngủ hết thời lượng cho phép, Thread sẽ “thức dậy” và thực hiện tiếp tác vụ của nó. Chú ý là bạn phải try catch phương thức Thread.sleep() này với một Checked Exception có tên InteruptedException. Và mình sẽ nói rõ về phương thức Thread.Sleep() ở bài sau nhé. Còn đây là code hoàn chỉnh của CountDownThread.

Để khởi chạy Thread vừa tạo thì chúng ta sẽ gọi phương thức start() của nó như sau.

Còn đây là màn hình console của “bộ đếm giờ” mà chúng ta vừa tạo. Cứ mỗi một giây sẽ có một con số xuất hiện cho đến khi chữ “Hết giờ” xuất hiện cuối cùng sẽ là lúc kết thúc chương trình (và kết thúc cả CountDownThread).

Cách 2 – Impement Từ Interface Runnable

Nếu như cách trên kia thì bạn phải kế thừa từ lớp Thread, thì cách này bạn lại implement một interface có tên Runnable. Với cách này bạn làm như sau.

– Bạn tạo mới một lớp và implement lớp này với Runnable.
– Trong lớp mới tạo đó, bạn override phương thức run().
– Cuối cùng, ở nơi khác, khi muốn tạo ra một Thread từ lớp này, trước hết bạn khai báo đối tượng cho nó, rồi bạn khai báo thêm một đối tượng của Thread nữa và truyền đối tượng của lớp này vào hàm khởi tạo của Thread. Khi phương thức start() của lớp Thread vừa tạo được gọi đến, thì phương thức run() bên trong lớp dẫn xuất của Runnable sẽ được gọi để tạo thành một Luồng trong hệ thống.

Nghe có vẻ phức tạp hơn cách thứ nhất trên kia đúng không nào. Nhưng bạn cũng nên thử qua cho biết bằng cách đến với bài thực hành sau.

Thực Hành Tạo Một Thread Bằng Cách Implement Từ Interface Runnable

Chúng ta vẫn sẽ xây dựng lại ví dụ về một Thread đếm ngược 10 giây trên kia bằng cách thứ 2 này.

Với cách này thì bạn chỉ cần chỉnh sửa một tí ở lớp CountDownThread của bạn, sao cho từ extends Thread sang implements Runnable là xong. Bạn hãy xem code sau sẽ rõ.

Vấn đề khai báo một Thread không khác nhau lắm giữa hai cách đúng không nào. Khác biệt nhiều hơn sẽ nằm ở cách khởi chạy Thread. Code ở phương thức main() sẽ phải thay đổi như sau.

Bạn hãy thực thi lại chương trình. Kết quả hai cách làm này đều cho ra kết quả như nhau cả.

Áp Dụng Kiến Thức Lớp Vô Danh Trong Việc Tạo Mới Một Thread

Nếu bạn đã quên lớp Vô Danh là lớp gì rồi, thì có thể đọc lại bài học ở link này.

Còn nếu bạn thắc mắc Thread thì liên quan gì đến lớp Vô Danh? Thì mình sẽ giải thích sơ qua thế này. Cơ bản thì Thread được xem là một cách gọn nhẹ cho hệ thống (và cả chúng ta) để thực thi các tác vụ song song. Và để làm cho sự gọn nhẹ đó càng thêm gọn nhẹ (về mặt quản lý code), thì việc kết hợp giữa Thread và lớp Vô Danh sẽ là giải pháp tốt cho ý này. Vì khi đó, chúng ta sẽ không cần thiết phải khai báo rõ ràng một lớp Thread nào cả, chỉ đơn giản là dựng lên một lớp Vô Danh, và start nó thôi.

Việc kết hợp giữa Thread và lớp Vô Danh là khá phổ biến, và người ta đã gộp 2 cái tên này lại thành một tên chung, gọi là Thread Vô Danh (Anonymous Threads).

Nào chúng ta cùng xem các cách sau để “vô danh hóa” một Thread. Mình dùng lại ví dụ Thread đếm ngược trên kia để bạn xem nhé.

Tạo Một Thread Vô Danh Từ Việc Kế Thừa Lớp Thread

Nào, chúng ta cùng tạo lại một Thread từ việc kế thừa lớp Thread, nhưng “vô danh hóa” nó như sau.

Đấy, trên đây là một Thread Vô Danh. Đến đây sẽ có nhiều bạn thắc mắc rằng Thread Vô Danh thực chất có giúp làm gọn hơn cho việc quản lý code hay không. Thì mình có vài ý muốn trao đổi thêm như sau.

Thực ra việc sử dụng Thread Vô Danh so với Thread bình thường có làm code trở nên gọn hay không cũng tùy vào cách nhìn code của mỗi người thôi. Bạn xem, với bài thực hành xây dựng một Thread bình thường trên kia (mình sẽ gọi tắt Thread-bình-thường là Thread), bạn phải xây dựng một lớp CountDownThread.java hẳn hoi, code này có cái hay là rất tường minh. Còn với code ví dụ ở mục này, chúng ta đã tạo ra một đối tượng countDownThread không phải từ lớp CountDownThread hay từ lớp Thread, mà là từ một lớp Vô Danh kế thừa từ lớp Thread nhé. Cách khai báo này giúp giảm đi việc phải tạo ra một file Java nào khác, chúng ta chỉ đơn giản khai báo và dùng thôi, ngoài ra thì Thread Vô Danh còn dùng được các thành viên của lớp chứa nó nữa.

Điểm khác biệt nữa giữa việc khai báo một Thread và một Thread Vô Danh là, với Thread bạn có thể xây dựng constructor cho nó, nên bạn có thể truyền vào Thread các biến nào đó phục vụ cho logic của ứng dụng. Còn Thread Vô Danh thì không có constructor, nên bạn có thể phải dùng biến toàn cục của lớp khai báo.

Nhưng bạn cũng nên cân nhắc, dù cho cách sử dụng Thread Vô Danh khá là nhanh chóng và tiện lợi, chúng có thể sẽ làm code ở lớp sử dụng này phình lên, khó quản lý hơn nếu có quá nhiều Thread Vô Danh như thế này đấy nhé.

Quay lại kiến thức của Thread Vô Danh, với code trên đây, chúng ta còn có thể viết gọn hơn lại nữa cơ. Bằng việc không cần phải khai báo tên đối tượng, mà có thể start() luôn, như thế này.

Tạo Một Thread Vô Danh Bằng Cách Implement Từ Interface Runnable

Nếu bạn hiểu Thread Vô Danh từ cách kế thừa lớp Thread trên kia, thì việc tạo một Thread Vô Danh từ interface Runnable có lẽ bạn cũng có thể tự viết được.

Code này cũng có thể viết ngắn gọn hơn bằng cách bỏ đi khai báo đối tượng từ lớp Thread như sau.

Hoặc có thể ngắn gọn hơn nữa khi không cần khai báo đối tượng của Thread Vô Danh. Nhưng khi này bạn phải truyền lớp Vô Danh này vào Thread như là một tham số. Như sau.

Hi vọng kiến thức về Thread Vô Danh không làm bạn quá đau đầu. Mình mời các bạn cùng đến với bài tập sau đây để có thể “quen tay” hơn trong việc tạo ra một Thread.

Thread Và Các Khái Niệm

Khái Niệm Thread, Hay Multithread

Thread hay Multithread đều có ý nghĩa như nhau trong kiến thức của bài học này. Thread dịch ra tiếng Việt là Luồng, và Multithread là Đa luồngLuồng ở đây chính là Luồng xử lý của hệ thống. Và bởi vì lý do chính đáng để cho Thread ra đời cũng chính là để cho các ứng dụng có thể điều khiển nhiều Thread khác nhau một cách đồng thời, mà nhiều Thread đồng thời như vậy cũng có nghĩa là Đa Thread, hay Multithread. Chính vì vậy mà kiến thức Thread hay Multithread cũng chỉ là một.

Vai trò của Thread hay Multithread dĩ nhiên là cái gì đó liên quan đến Đa LuồngĐa Nhiệm rồi. Nói cụ thể ra đó là hệ thống sẽ hỗ trợ chúng ta tách các tác vụ của ứng dụng ra làm nhiều Luồng (hay Thread), và hệ thống sẽ giúp xử lý các Luồng này một cách đồng thời. Như vậy nếu theo như những gì chúng ta làm quen với Java từ trước đến giờ, đó là nếu chúng ta có các tác vụ ABC, với các cách code cũ, hệ thống sẽ luôn xử lý tuần tự các tác vụ này, giả sử A sẽ được xử lý trước tiên, rồi đến B, và cuối cùng là đến C. Nhưng sau bài học hôm nay, nếu chúng ta tổ chức sao cho mỗi AB và C là mỗi Thread, thì sẽ rất tuyệt vì chúng ta hoàn toàn có thể kêu hệ thống xử lý cả A, B và C cùng một lúc.

Một ví dụ thực tế để dễ hình dung hơn về Thread như sau. Giả sử bạn đang dùng ứng dụng Google Maps trên nền Android được viết bằng Java. Bạn có nhu cầu tìm một địa chỉ trên bản đồ, địa chỉ đó là “Chợ Bến Thành” chẳng hạn.

Khi bạn gõ từng chữ cái vào khung tìm kiếm ở trên cùng của màn hình, bạn mong muốn được thấy các gợi ý địa điểm sẽ liên tục được cập nhật theo từng chữ bạn gõ vào ở dưới. Như hình trên. Bạn thấy rằng, nếu không có Thread, hệ thống sẽ đợi bạn gõ xong một chữ, rồi mới bắt đầu tìm kiếm và gợi ý các địa điểm liên quan đến chữ đó, và trong lúc hệ thống đang tìm kiếm, bạn không thể gõ được chữ kế tiếp, vì với cách làm này, cùng một lúc hệ thống chỉ đáp ứng một chuyện thôi, hoặc là nhận ký tự bạn gõ hoặc là tìm kiếm, hết. Nhưng với việc áp dụng Multithread, chúng ta sẽ có 2 Thread chạy song song, một Thread nhận dữ liệu nhập vào của người dùng, Thread còn lại cứ dựa vào dữ liệu đã nhập đấy mà tìm kiếm, điều này làm cho trải nghiệm của người dùng được trọn vẹn, hệ thống mượt mà, nhanh chóng, không bị giật.

Bạn có thể xem mình minh họa việc đáp ứng của hệ thống đối với từng trường hợp tìm kiếm như ví dụ trên ở hình bên dưới. Trong đó, để tìm thấy kết quả “Chợ Bến Thành” xuất hiện trong danh sách gợi ý. Thì với cột Không Multithread bên trái, việc bạn gõ một từ rồi đợi hệ thống tìm kiếm, thì kết quả tìm được sẽ rất lâu so với cột MultiThread bên phải, việc bạn gõ và việc hệ thống tìm kiếm được tách ra, và thực hiện một cách song song nhau, là rất nhanh chóng và dễ chịu.

Phân Biệt Các Khái Niệm Liên Quan

Mục này mình nói thêm, cho các bạn mới tiếp cận với Thread (thậm chí với các bạn đã hiểu rõ Thread rồi) đỡ bị lăn tăn khi đọc các tài liệu liên quan đến kiến thức hôm nay. Khi đó hẳn các bạn sẽ bị loạn lên với các khái niệm sau: ThreadMultithreadTaskMultitaskProcessMultiprocess.

Đầu tiên, ở mức bao quát nhất, bạn đều biết các hệ thống máy tính ở thời đại ngày nay đều được nhắc đến với việc có khả năng xử lý đa nhiệm. Đa nhiệm ở đây chính là việc xử lý nhiều nhiệm vụ cùng một lúc. Việc xử lý đa nhiệm này càng ngày càng phổ biến và được quan tâm nhiều hơn khi các dòng máy tính đều cho ra đời các cỗ máy với nhiều CPU. Như vậy cái nhiệm vụ mà hệ thống cần xử lý đó thường được gọi là Task, và một hệ thống đa nhiệm được gọi là Multitask.

Từ nhu cầu muốn xử lý Multitask đó, hệ thống mới định ngĩa ra các ProcessProcess được hiểu là một chương trình. Khi ứng dụng của bạn được khởi chạy, chúng sẽ được hệ thống tạo ra một Process và ứng dụng đó sẽ được thực thi và được quản lý bên trong Process đó. Hệ thống sẽ quản lý một lúc nhiều Process khác nhau, mỗi Process như vậy được cấp phát các tài nguyên hệ thống một cách độc lập với nhau. Và bởi cùng một lúc có thể có nhiều Process (hay nhiều ứng dụng) chạy song song với nhau, nên người ta gọi cái sự Đa Process này là Multiprocess.

Khi một Process được tạo, ứng dụng bên trong Process đó có thể tạo ra nhiều Thread khác. Về cơ bản thì Thread cũng sẽ được hệ thống quản lý như Process vậy, tức là chúng có thể được chạy song song với nhau, nên mới có thêm khái niệm Multithread. Nhưng các Thread bên trong một Process chỉ được hoạt động bên trong giới hạn của Process đó thôi. Các Thread sẽ được sử dụng các tài nguyên y như là Process của nó được phép. Nhưng có một khác biệt đó là các Thread rất nhẹ và có thể dễ dàng chia sẻ tài nguyên dùng chung với nhau bên trong một Process.

Như vậy thôi. Và mặc dù lan man nhiều khái niệm như vậy, nhưng bạn nên nhớ, bài học này chúng ta chỉ quan tâm đến một loại khái niệm mà thôi, đó chính là Thread.

Khi Nào Thì Sử Dụng Thread

Câu hỏi này tuy dễ trả lời, song nếu suy nghĩ kỹ nó lại chứa đựng nhiều thông tin hơn chúng ta tưởng.

Đầu tiên như mình có nói đến một cách giông dài trên kia rằng, nếu như bạn muốn có nhiều tác vụ muốn làm việc đồng thời với nhau, thì hãy sử dụng các Thread. Chẳng hạn bạn vừa muốn ứng dụng lấy thông tin người dùng nhập gì vào khung hội thoại, vừa gọi lên server để kiểm tra kết quả nhập vào. Hay nếu bạn lập trình game, bạn vừa muốn game nhận tín hiệu điều khiển từ bàn phím, song song với việc vẽ nhân vật trên màn hình theo sự điều khiển đó, song song với việc điều khiển các chướng ngại vật, hoặc điều khiển các đường đạn bắn, các hiển thị về điểm số, v.v…

Ý thứ hai trong việc sử dụng Thread là, cũng na ná như việc muốn xử lý các tác vụ đồng thời, nhưng khi này bạn mong muốn được điều khiển các Luồng theo một sự đồng bộ nhất định. Chẳng hạn bạn khởi động nhiều Thread trong một ứng dụng, nhưng bạn muốn các Thread khác tuy được khởi động nhưng khoan hãy chạy, mà phải đợi một Thread nào đó kết thúc thì mới được hoạt động. Ở Tập 3 của loạt bài về Thread mình sẽ nói rõ cách sử dụng này của Thread.

Ý thứ ba trong việc sử dụng Thread, cũng không ngoài việc muốn xử lý các tác vụ đồng thời, nhưng khi này là tình huống khi bạn muốn ứng dụng thực thi các tác vụ quá lớn. Lớn ở đây thường là lớn về mặt thời gian. Ví dụ khi ứng dụng phải download một file từ server, hay phải thực hiện công việc gì đó mà thời gian hoàn thành của nó không phải tức thời. Khi đó nếu không có Thread, các tác vụ lâu lắc này có thể sẽ làm ảnh hưởng nghiêm trọng đến trải nghiệm của người dùng.

Chúng ta sẽ đến phần ví dụ về Thread thông qua bài thực hành sau đây để giúp bạn hiểu rõ hơn.

Thực Hành Xây Dựng Ứng Dụng Quay Số Ngẫu Nhiên

Bài thực hành này giúp bạn có một cảm quan ban đầu về việc Thread là gì và nó hoạt động như thế nào. Mình không mong muốn các bạn phải hiểu hết các dòng code của bài thực hành này, nhưng mình hi vọng các bạn sẽ thử code, và thực thi thành công chương trình. Nếu có bất kỳ lỗi nào phát sinh xảy ra với bạn thì cứ liên lạc với mình hoặc để lại comment ở dưới bài viết hôm nay nhé.

Bạn cứ tưởng tượng rằng bài thực hành này đang xây dựng một trò chơi. Trong trò chơi này có một bàn xoay, trên đó có đánh các con số từ 0 đến 100. Người chơi sẽ bắt đầu xoay bàn xoay, sau khi bàn xoay được xoay thì người chơi chẳng nhìn thấy các con số trên đó, cho đến khi họ dừng bàn xoay lại, thì con số nào ở ngay người chơi chính là con số được người chơi chọn lựa.

Ồ, ứng dụng của chúng ta sẽ không xây dựng theo kiểu một trò chơi hoàn chỉnh như thế đâu. Thay vào đó chúng ta sẽ tập trung vào logic của trò chơi. Chúng ta sẽ thay việc xoay bàn xoay bằng một lệnh gõ vào ký tự bất kỳ trên console  Sau khi nhận ký tự vừa gõ, thay vì có một bàn xoay xoay tít, thì chúng ta sẽ khởi chạy một ThreadThread này sẽ lần lượt “quay” các con số, sao cho nó đếm tuần tự từ 0 đến 100 rồi lại quay lại số 0Thread của chúng ta chạy mãi cho đến khi người chơi nhập thêm một ký tự từ console và Thread kết thúc. Con số mà Thread vừa “dừng” lại chính là số được người chơi chọn lựa.

Bạn đã hiểu yêu cầu của việc xây dựng trò chơi này rồi đúng không nào. Để bắt đầu vào xây dựng trò chơi, bạn nên tự tạo một project mới. Bạn có thể đặt tên cho project này là ThreadLearning cũng được. Sau đó tạo mới một class có chứa phương thức main() trong đó.

Tiếp theo, bạn hãy tự tạo mới một lớp mới có tên CountTheNumberThread. Khoan hãy code gì cho lớp này cả. Lớp này chính là Thread với trách nhiệm “quay vòng” các con số như chúng ta đã bàn đến. Tổng quan thì ứng dụng của chúng ta có cấu trúc như hình sau.

Nào giờ bạn hãy code cho thân lớp CountTheNumberThread như sau.

Dù cho có thể bạn chưa hiểu rõ về Thread, nhưng mình cũng muốn giải thích một chút, để đến bài học sau bạn sẽ dễ dàng tiếp cận với Thread hơn. Đầu tiên thì lớp CountTheNumberThread muốn thành một Thread thì nó phải kế thừa từ lớp Thread (và còn có cách khác nữa để biến một lớp thành Thread mà bài sau chúng ta sẽ nói đến). Sau đó bên trong lớp này phải override phương thức run(). Chính các code bên trong phương thức run() sẽ là một LuồngLuồng này sẽ được hệ thống thực thi song song với các Luồng đang thực thi khác nếu có trong hệ thống. Vậy thôi, ngoài hai ý lớn mình nói đến như này ra thì các khai báo còn lại của CountTheNumberThread đều quá đỗi quen thuộc nên mình không trình bày nhiêu khê.

Đến lúc này thì CountTheNumberThread cũng chỉ là một lớp mà thôi. Muốn khởi chạy lớp này để trở thành một Thread thì mình mời bạn đến với các code ở phương thức main() như sau.

public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
         
    // Đợi người dùng nhấn một phím để bắt đầu
    System.out.println("Nhấn phím bất kỳ để quay số");
    scanner.nextLine();
         
    // Khai báo & Khởi chạy CountTheNumberThread như là một Thread thông qua
    // phương thức start()
    CountTheNumberThread countingThread = new CountTheNumberThread();
    countingThread.start();
         
    // Đợi người dùng nhấn một phím để kết thúc
    System.out.println("Nhấn phím bất kỳ để kết thúc quay số");
    scanner.nextLine();
         
    // Ngừng Thread và xem hiện đang "quay" tới số nào
    countingThread.end();
    System.out.println("Con số may mắn: " + countingThread.getCount());
}

Nhìn vào code trên, bạn có thể thấy chút khác biệt giữa việc sử dụng một Thread so với một lớp bình thường khác. Nếu như việc khởi tạo một lớp bình thường mà bạn biết so với một Thread là như nhau. Thì với một Thread, để khởi chạy Thread đó (tức là bạn biến nó thành một Luồng trong hệ thống), bạn phải gọi phương thức start() của nó, đây là phương thức được xây dựng sẵn ở lớp Thread. Khi start() được gọi, Thread cha sẽ khởi chạy các code bên trong phương thức run() mà bạn đã khai báo bên trong CountTheNumberThread. Và thế là việc đón nhận dữ liệu gõ vào từ bàn phím ở phương thức main() sẽ song song với vòng lặp bên trong phương thức run() này, không code nào phải chờ đợi code nào cả.

Nếu bạn thực thi chương trình, hãy nhập một ký tự vào console để bắt đầu “quay số” và nhập một lần nữa để kết thúc, bạn sẽ nhận được một số may mắn của riêng bạn như sau.

Opps! Mình quay ra số 69, còn bạn thì sao. Chúc bạn quay tay may mắn.

Bài Tập

Với bài tập này mình muốn bạn hãy thử bỏ qua việc kế thừa Thread của lớp CountTheNumberThread, khi đó thì phương thức run() của lớp này cũng không còn từ khóa override nữa, như vậy bạn đã biến lớp này thành một lớp bình thường và run() cũng chỉ là một phương thức bình thường khác. Rồi sau đó ở main() bạn đừng gọi countingThread.start() nữa mà hãy gọi countingThread.run(). Tức là bạn không sử dụng Thread trong trường hợp này. Bạn hãy thực thi lại ứng dụng và so sánh xem việc áp dụng Thread với trò chơi này sẽ khác với việc không áp dụng Thread sẽ như thế nào nhé.

Chúng ta vừa mới tiếp cận kiến thức khá mới mẻ và thú vị của Java về vấn đề Đa nhiệm, cụ thể là Thread. Bài hôm nay chỉ mới là các kiến thức làm quen ban đầu, Thread còn rất nhiều kiến thức thú vị khác mà mình sẽ lần lượt trình bày ở các bài viết sắp tới nữa.

Tạo trang giống vầy với WordPress.com
Tham gia