zoukankan      html  css  js  c++  java
  • java线程(1)-线程同步

    Java提供了两种创建线程方法:

    • 通过实现Runable接口;
    • 通过继承Thread类本身。

    线程同步

    为何使用同步?
     java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),  将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用, 
     从而保证了该变量的唯一性和准确性。
     
    5种同步方式
    1、同步方法
    即有synchronized关键字修饰的方法。 
     由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 
     内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
    注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类
     
    2、同步代码块
    即有synchronized关键字修饰的语句块。 
        被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
     synchronized(object){ 
        }
    
     注:同步是一种高开销的操作,因此应该尽量减少同步的内容。 
        通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。 
     1 package com.xhj.thread;
     2  
     3     /**
     4      * 线程同步的运用
     5      * 
     6      * @author XIEHEJUN
     7      * 
     8      */
     9     public class SynchronizedThread {
    10  
    11         class Bank {
    12  
    13             private int account = 100;
    14  
    15             public int getAccount() {
    16                 return account;
    17             }
    18  
    19             /**
    20              * 用同步方法实现
    21              * 
    22              * @param money
    23              */
    24             public synchronized void save(int money) {
    25                 account += money;
    26             }
    27  
    28             /**
    29              * 用同步代码块实现
    30              * 
    31              * @param money
    32              */
    33             public void save1(int money) {
    34                 synchronized (this) {
    35                     account += money;
    36                 }
    37             }
    38         }
    39  
    40         class NewThread implements Runnable {
    41             private Bank bank;
    42  
    43             public NewThread(Bank bank) {
    44                 this.bank = bank;
    45             }
    46  
    47             @Override
    48             public void run() {
    49                 for (int i = 0; i < 10; i++) {
    50                     // bank.save1(10);
    51                     bank.save(10);
    52                     System.out.println(i + "账户余额为:" + bank.getAccount());
    53                 }
    54             }
    55  
    56         }
    57  
    58         /**
    59          * 建立线程,调用内部类
    60          */
    61         public void useThread() {
    62             Bank bank = new Bank();
    63             NewThread new_thread = new NewThread(bank);
    64             System.out.println("线程1");
    65             Thread thread1 = new Thread(new_thread);
    66             thread1.start();
    67             System.out.println("线程2");
    68             Thread thread2 = new Thread(new_thread);
    69             thread2.start();
    70         }
    71  
    72         public static void main(String[] args) {
    73             SynchronizedThread st = new SynchronizedThread();
    74             st.useThread();
    75         }
    76  
    77     }
    View Code
     
    3、使用特殊域变量(volatile)实现线程同步
      a.volatile关键字为域变量的访问提供了一种免锁机制, 
      b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新, 
      c.因此每次使用该域就要重新计算,而不是使用寄存器中的值 
      d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量 
     //只给出要修改的代码,其余代码与上同
            class Bank {
                //需要同步的变量加上volatile
                private volatile int account = 100;
     
                public int getAccount() {
                    return account;
                }
                //这里不再需要synchronized 
                public void save(int money) {
                    account += money;
                }
            }
    View Code
    注:多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。 
        用final域,有锁保护的域和volatile域可以避免非同步的问题。
     
    4、使用重入锁实现线程同步
     ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 
     它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力
    class Bank {
                
                private int account = 100;
                //需要声明这个锁
                private Lock lock = new ReentrantLock();
                public int getAccount() {
                    return account;
                }
                //这里不再需要synchronized 
                public void save(int money) {
                    lock.lock();
                    try{
                        account += money;
                    }finally{
                        lock.unlock();
                    }
                    
                }
            }
    View Code
    注:关于Lock对象和synchronized关键字的选择: 
            a.最好两个都不用,使用一种java.util.concurrent包提供的机制, 
                能够帮助用户处理所有与锁相关的代码。 
            b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 
            c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁 

    5、使用局部变量实现线程同步

     如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本, 
        副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
           public class Bank{
                //使用ThreadLocal类管理共享变量account
                private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
                    @Override
                    protected Integer initialValue(){
                        return 100;
                    }
                };
                public void save(int money){
                    account.set(account.get()+money);
                }
                public int getAccount(){
                    return account.get();
                }
            }
    View Code
     注:ThreadLocal与同步机制 
            a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。 
            b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式 
  • 相关阅读:
    104.Maximum Depth of Binary Tree
    103.Binary Tree Zigzag Level Order Traversal
    102.Binary Tree Level Order Traversal
    101.Symmetric Tree
    100.Same Tree
    99.Recover Binary Search Tree
    98.Validate Binary Search Tree
    97.Interleaving String
    static静态初始化块
    serialVersionUID作用
  • 原文地址:https://www.cnblogs.com/arbitrary/p/5262597.html
Copyright © 2011-2022 走看看