1. 程式人生 > >三種方式實現分散式鎖

三種方式實現分散式鎖

方案一:資料庫樂觀鎖

樂觀鎖通常實現基於資料版本(version)的記錄機制實現的,比如有一張紅包表(t_bonus),有一個欄位(left_count)記錄禮物的剩餘個數,使用者每領取一個獎品,對應的left_count減1,在併發的情況下如何要保證left_count不為負數,樂觀鎖的實現方式為在紅包表上新增一個版本號欄位(version),預設為0。

異常實現流程
-- 可能會發生的異常情況
-- 執行緒1查詢,當前left_count為1,則有記錄
select * from t_bonus where id = 10001 and left_count > 0

-- 執行緒2查詢,當前left_count為1,也有記錄
select * from t_bonus where id = 10001 and left_count > 0 -- 執行緒1完成領取記錄,修改left_count為0, update t_bonus set left_count = left_count - 1 where id = 10001 -- 執行緒2完成領取記錄,修改left_count為-1,產生髒資料 update t_bonus set left_count = left_count - 1 where id = 10001
通過樂觀鎖實現

-- 新增版本號控制欄位
ALTER TABLE table ADD COLUMN version
INT DEFAULT '0' NOT NULL AFTER t_bonus; -- 執行緒1查詢,當前left_count為1,則有記錄,當前版本號為1234 select left_count, version from t_bonus where id = 10001 and left_count > 0 -- 執行緒2查詢,當前left_count為1,有記錄,當前版本號為1234 select left_count, version from t_bonus where id = 10001 and left_count > 0 -- 執行緒1,更新完成後當前的version為1235,update狀態為1,更新成功
update t_bonus set version = 1235, left_count = left_count-1 where id = 10001 and version = 1234 -- 執行緒2,更新由於當前的version為1235,udpate狀態為0,更新失敗,再針對相關業務做異常處理 update t_bonus set version = 1235, left_count = left_count-1 where id = 10001 and version = 1234
方案二:基於Redis的分散式鎖

SETNX命令(SET if Not eXists)
語法:SETNX key value
功能:原子性操作,當且僅當 key 不存在,將 key 的值設為 value ,並返回1;若給定的 key 已經存在,則 SETNX 不做任何動作,並返回0。
Expire命令
語法:expire(key, expireTime)
功能:key設定過期時間
GETSET命令
語法:GETSET key value
功能:將給定 key 的值設為 value ,並返回 key 的舊值 (old value),當 key 存在但不是字串型別時,返回一個錯誤,當key不存在時,返回nil。
GET命令
語法:GET key
功能:返回 key 所關聯的字串值,如果 key 不存在那麼返回特殊值 nil 。
DEL命令
語法:DEL key [KEY …]
功能:刪除給定的一個或多個 key ,不存在的 key 會被忽略。

第一種:使用redis的setnx()、expire()方法,用於分散式鎖
  1. setnx(lockkey, 1) 如果返回0,則說明佔位失敗;如果返回1,則說明佔位成功
  2. expire()命令對lockkey設定超時時間,為的是避免死鎖問題。
  3. 執行完業務程式碼後,可以通過delete命令刪除key。

這個方案其實是可以解決日常工作中的需求的,但從技術方案的探討上來說,可能還有一些可以完善的地方。比如,如果在第一步setnx執行成功後,在expire()命令執行成功前,發生了宕機的現象,那麼就依然會出現死鎖的問題

第二種:使用redis的setnx()、get()、getset()方法,用於分散式鎖,解決死鎖問題
  1. setnx(lockkey, 當前時間+過期超時時間) ,如果返回1,則獲取鎖成功;如果返回0則沒有獲取到鎖,轉向2。
  2. get(lockkey)獲取值oldExpireTime ,並將這個value值與當前的系統時間進行比較,如果小於當前系統時間,則認為這個鎖已經超時,可以允許別的請求重新獲取,轉向3。
  3. 計算newExpireTime=當前時間+過期超時時間,然後getset(lockkey, newExpireTime) 會返回當前lockkey的值currentExpireTime。
  4. 判斷currentExpireTime與oldExpireTime 是否相等,如果相等,說明當前getset設定成功,獲取到了鎖。如果不相等,說明這個鎖又被別的請求獲取走了,那麼當前請求可以直接返回失敗,或者繼續重試。
  5. 在獲取到鎖之後,當前執行緒可以開始自己的業務處理,當處理完畢後,比較自己的處理時間和對於鎖設定的超時時間,如果小於鎖設定的超時時間,則直接執行delete釋放鎖;如果大於鎖設定的超時時間,則不需要再鎖進行處理。

import cn.com.tpig.cache.redis.RedisService;
import cn.com.tpig.utils.SpringUtils;

/**
 * Created by IDEA
 * User: shma1664
 * Date: 2016-08-16 14:01
 * Desc: redis分散式鎖
 */
public final class RedisLockUtil {

    private static final int defaultExpire = 60;

    private RedisLockUtil() {
        //
    }

    /**
     * 加鎖
     * @param key redis key
     * @param expire 過期時間,單位秒
     * @return true:加鎖成功,false,加鎖失敗
     */
    public static boolean lock(String key, int expire) {

        RedisService redisService = SpringUtils.getBean(RedisService.class);
        long status = redisService.setnx(key, "1");

        if(status == 1) {
            redisService.expire(key, expire);
            return true;
        }

        return false;
    }

    public static boolean lock(String key) {
        return lock2(key, defaultExpire);
    }

    /**
     * 加鎖
     * @param key redis key
     * @param expire 過期時間,單位秒
     * @return true:加鎖成功,false,加鎖失敗
     */
    public static boolean lock2(String key, int expire) {

        RedisService redisService = SpringUtils.getBean(RedisService.class);

        long value = System.currentTimeMillis() + expire;
        long status = redisService.setnx(key, String.valueOf(value));

        if(status == 1) {
            return true;
        }
        long oldExpireTime = Long.parseLong(redisService.get(key, "0"));
        if(oldExpireTime < System.currentTimeMillis()) {
            //超時
            long newExpireTime = System.currentTimeMillis() + expire;
            long currentExpireTime = Long.parseLong(redisService.getSet(key, String.valueOf(newExpireTime)));
            if(currentExpireTime == oldExpireTime) {
                return true;
            }
        }
        return false;
    }

    public static void unLock1(String key) {
        RedisService redisService = SpringUtils.getBean(RedisService.class);
        redisService.del(key);
    }

    public static void unLock2(String key) {    
        RedisService redisService = SpringUtils.getBean(RedisService.class);    
        long oldExpireTime = Long.parseLong(redisService.get(key, "0"));   
        if(oldExpireTime > System.currentTimeMillis()) {        
            redisService.del(key);    
        }
   }

}

public void drawRedPacket(long userId) {
    String key = "draw.redpacket.userid:" + userId;

    boolean lock = RedisLockUtil.lock2(key, 60);
    if(lock) {
        try {
            //領取操作
        } finally {
            //釋放鎖
            RedisLockUtil.unLock(key);
        }
    } else {
        new RuntimeException("重複領取獎勵");
    }
}
Spring AOP基於註解方式和SpEL實現開箱即用的redis分散式鎖策略
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * RUNTIME
 * 定義註解
 * 編譯器將把註釋記錄在類檔案中,在執行時 VM 將保留註釋,因此可以反射性地讀取。
 * @author shma1664
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RedisLockable {


    String[] key() default "";

    long expiration() default 60;
}
import javax.annotation.Resource;

import java.lang.reflect.Method;

import com.autohome.api.dealer.util.cache.RedisClient;
import com.google.common.base.Joiner;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

/**
 * Created by IDEA
 * User: mashaohua
 * Date: 2016-09-28 18:08
 * Desc:
 */
@Aspect
@Component
public class RedisLockAop {

    @Resource
    private RedisClient redisClient;

    @Pointcut("execution(* com.autohome.api.dealer.tuan.service.*.*(..))")
    public void pointcut(){}

    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint point) throws Throwable{
        Signature signature = point.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        String targetName = point.getTarget().getClass().getName();
        String methodName = point.getSignature().getName();
        Object[] arguments = point.getArgs();

        if (method != null && method.isAnnotationPresent(RedisLockable.class)) {
            RedisLockable redisLock = method.getAnnotation(RedisLockable.class);
            long expire = redisLock.expiration();
            String redisKey = getLockKey(targetName, methodName, redisLock.key(), arguments);
            boolean isLock = RedisLockUtil.lock2(redisKey, expire);
            if(!isLock) {
                try {
                    return point.proceed();
                } finally {
                    unLock2(redisKey);
                }
            } else {
                throw new RuntimeException("您的操作太頻繁,請稍後再試");
            }
        }

        return point.proceed();
    }

    private String getLockKey(String targetName, String methodName, String[] keys, Object[] arguments) {

        StringBuilder sb = new StringBuilder();
        sb.append("lock.").append(targetName).append(".").append(methodName);

        if(keys != null) {
            String keyStr = Joiner.on(".").skipNulls().join(keys);
            String[] parameters = ReflectParamNames.getNames(targetName, methodName);
            ExpressionParser parser = new SpelExpressionParser();
            Expression expression = parser.parseExpression(keyStr);
            EvaluationContext context = new StandardEvaluationContext();
            int length = parameters.length;
            if (length > 0) {
                for (int i = 0; i < length; i++) {
                    context.setVariable(parameters[i], arguments[i]);
                }
            }
            String keysValue = expression.getValue(context, String.class);
            sb.append("#").append(keysValue);
        }
        return sb.toString();
    }

<!-- https://mvnrepository.com/artifact/javassist/javassist -->
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.18.1-GA</version>
</dependency>
import javassist.*;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.apache.log4j.Logger;

/**
 * Created by IDEA
 * User: mashaohua
 * Date: 2016-09-28 18:39
 * Desc:
 */
public class ReflectParamNames {
    private static Logger log = Logger.getLogger(ReflectParamNames.class);
    private  static ClassPool pool = ClassPool.getDefault();

    static{
        ClassClassPath classPath = new ClassClassPath(ReflectParamNames.class);
        pool.insertClassPath(classPath);
    }

    public static String[] getNames(String className,String methodName) {
        CtClass cc = null;
        try {
            cc = pool.get(className);
            CtMethod cm = cc.getDeclaredMethod(methodName);
            // 使用javaassist的反射方法獲取方法的引數名
            MethodInfo methodInfo = cm.getMethodInfo();
            CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
            LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
            if (attr == null) return new String[0];

            int begin = 0;

            String[] paramNames = new String[cm.getParameterTypes().length];
            int count = 0;
            int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;

            for (int i = 0; i < attr.tableLength(); i++){
                //  為什麼 加這個判斷,發現在windows 跟linux執行時,引數順序不一致,通過觀察,實際的引數是從this後面開始的
                if (attr.variableName(i).equals("this")){
                    begin = i;
                    break;
                }
            }

            for (int i = begin+1; i <= begin+paramNames.length; i++){
                paramNames[count] = attr.variableName(i);
                count++;
            }
            return paramNames;
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            try {
                if(cc != null) cc.detach();
            } catch (Exception e2) {
                log.error(e2.getMessage());
            }


        }
        return new String[0];
    }
}
在需要使用分散式鎖的地方添加註解
/**
 * 抽獎介面
 * 新增redis分散式鎖保證一個訂單隻有一個請求處理,防止使用者刷禮物,支援SpEL表示式
 * redisLockKey:lock.com.autohome.api.dealer.tuan.service.impl.drawBonus#orderId
 * @param orderId 訂單id
 * @return 抽中的獎品資訊
 */
@RedisLockable(key = {"#orderId"}, expiration = 120)
@Override
public BonusConvertBean drawBonus(Integer orderId) throws BonusException{
    // 業務邏輯
}
第三種方案:基於Zookeeper的分散式鎖
利用節點名稱的唯一性來實現獨佔鎖

ZooKeeper機制規定同一個目錄下只能有一個唯一的檔名,zookeeper上的一個znode看作是一把鎖,通過createznode的方式來實現。所有客戶端都去建立/lock/${lock_name}_lock節點,最終成功建立的那個客戶端也即擁有了這把鎖,建立失敗的可以選擇監聽繼續等待,還是放棄丟擲異常實現獨佔鎖。

package com.shma.example.zookeeper.lock;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

/**
 * Created by IDEA
 * User: mashaohua
 * Date: 2016-09-30 16:09
 * Desc:
 */
public class ZookeeperLock implements Lock, Watcher {
    private ZooKeeper zk;
    private String root = "/locks";//根
    private String lockName;//競爭資源的標誌
    private String myZnode;//當前鎖
    private int sessionTimeout = 30000;
    private List<Exception> exception = new ArrayList<Exception>();

    /**
     * 建立分散式鎖,使用前請確認config配置的zookeeper服務可用
     * @param config 127.0.0.1:2181
     * @param lockName 競爭資源標誌,lockName中不能包含單詞lock
     */
    public ZookeeperLock(String config, String lockName){
        this.lockName = lockName;
        // 建立一個與伺服器的連線
        try {
            zk = new ZooKeeper(config, sessionTimeout, this);
            Stat stat = zk.exists(root, false);
            if(stat == null){
                // 建立根節點
                zk.create(root, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
        } catch (IOException e) {
            exception.add(e);
        } catch (KeeperException e) {
            exception.add(e);
        } catch (InterruptedException e) {
            exception.add(e);
        }
    }

    @Override
    public void lock() {
        if(exception.size() > 0){
            throw new LockException(exception.get(0));
        }
        if(!tryLock()) {
            throw new LockException("您的操作太頻繁,請稍後再試");
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        this.lock();
    }

    @Override
    public boolean tryLock() {
        try {
            myZnode = zk.create(root + "/" + lockName, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            return true;
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return tryLock();
    }

    @Override
    public void unlock() {
        try {
            zk.delete(myZnode, -1);
            myZnode = null;
            zk.close();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Condition newCondition() {
        return null;
    }

    @Override
    public void process(WatchedEvent watchedEvent) {
        //
    }

}

ZookeeperLock lock = null;
try {
    lock = new ZookeeperLock("127.0.0.1:2182","test1");
    lock.lock();
    //業務邏輯處理
} catch (LockException e) {
    throw e;
} finally {
    if(lock != null)
        lock.unlock();
}
利用臨時順序節點控制時序實現

/lock已經預先存在,所有客戶端在它下面建立臨時順序編號目錄節點,和選master一樣,編號最小的獲得鎖,用完刪除,依次方便。
演算法思路:對於加鎖操作,可以讓所有客戶端都去/lock目錄下建立臨時順序節點,如果建立的客戶端發現自身建立節點序列號是/lock/目錄下最小的節點,則獲得鎖。否則,監視比自己建立節點的序列號小的節點(比自己建立的節點小的最大節點),進入等待。
對於解鎖操作,只需要將自身建立的節點刪除即可。

package com.shma.example.zookeeper.lock;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

/**
 * Created by IDEA
 * User: mashaohua
 * Date: 2016-09-30 16:09
 * Desc:
 */
public class DistributedLock implements Lock, Watcher{
    private ZooKeeper zk;
    private String root = "/locks";//根
    private String lockName;//競爭資源的標誌
    private String waitNode;//等待前一個鎖
    private String myZnode;//當前鎖
    private CountDownLatch latch;//計數器
    private int sessionTimeout = 30000;
    private List<Exception> exception = new ArrayList<Exception>();

    /**
     * 建立分散式鎖,使用前請確認config配置的zookeeper服務可用
     * @param config 127.0.0.1:2181
     * @param lockName 競爭資源標誌,lockName中不能包含單詞lock
     */
    public DistributedLock(String config, String lockName){
        this.lockName = lockName;
        // 建立一個與伺服器的連線
        try {
            zk = new ZooKeeper(config, sessionTimeout, this);
            Stat stat = zk.exists(root, false);
            if(stat == null){
                // 建立根節點
                zk.create(root, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
            }
        } catch (IOException e) {
            exception.add(e);
        } catch (KeeperException e) {
            exception.add(e);
        } catch (InterruptedException e) {
            exception.add(e);
        }
    }

    /**
     * zookeeper節點的監視器
     */
    public void process(WatchedEvent event) {
        if(this.latch != null) {
            this.latch.countDown();
        }
    }

    public void lock() {
        if(exception.size() > 0){
            throw new LockException(exception.get(0));
        }
        try {
            if(this.tryLock()){
                System.out.println("Thread " + Thread.currentThread().getId() + " " +myZnode + " get lock true");
                return;
            }
            else{
                waitForLock(waitNode, sessionTimeout);//等待鎖
            }
        } catch (KeeperException e) {
            throw new LockException(e);
        } catch (InterruptedException e) {
            throw new LockException(e);
        }
    }

    public boolean tryLock() {
        try {
            String splitStr = "_lock_";
            if(lockName.contains(splitStr))
                throw new LockException("lockName can not contains \\u000B");
            //建立臨時子節點
            myZnode = zk.create(root + "/" + lockName + splitStr, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
            System.out.println(myZnode + " is created ");
            //取出所有子節點
            List<String> subNodes = zk.getChildren(root, false);
            //取出所有lockName的鎖
            List<String> lockObjNodes = new ArrayList<String>();
            for (String node : subNodes) {
                String _node = node.split(splitStr)[0];
                if(_node.equals(lockName)){
                    lockObjNodes.add(node);
                }
            }
            Collections.sort(lockObjNodes);
            System.out.println(myZnode + "==" + lockObjNodes.get(0));
            if(myZnode.equals(root+"/"+lockObjNodes.get(0))){
                //如果是最小的節點,則表示取得鎖
                return true;
            }
            //如果不是最小的節點,找到比自己小1的節點
            String subMyZnode = myZnode.substring(myZnode.lastIndexOf("/") + 1);
            waitNode = lockObjNodes.get(Collections.binarySearch(lockObjNodes, subMyZnode) - 1);
        } catch (KeeperException e) {
            throw new LockException(e);
        } catch (InterruptedException e) {
            throw new LockException(e);
        }
        return false;
    }

    public boolean tryLock(long time, TimeUnit unit) {
        try {
            if(this.tryLock()){
                return true;
            }
            return waitForLock(waitNode,time);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    private boolean waitForLock(String lower, long waitTime) throws InterruptedException, KeeperException {
        Stat stat = zk.exists(root + "/" + lower,true);
        //判斷比自己小一個數的節點是否存在,如果不存在則無需等待鎖,同時註冊監聽
        if(stat != null){
            System.out.println("Thread " + Thread.currentThread().getId() + " waiting for " + root + "/" + lower);
            this.latch = new CountDownLatch(1);
            this.latch.await(waitTime, TimeUnit.MILLISECONDS);
            this.latch = null;
        }
        return true;
    }

    public void unlock() {
        try {
            System.out.println("unlock " + myZnode);
            zk.delete(myZnode,-1);
            myZnode = null;
            zk.close();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    public void lockInterruptibly() throws InterruptedException {
        this.lock();
    }

    public Condition newCondition() {
        return null;
    }

    public class LockException extends RuntimeException {
        private static final long serialVersionUID = 1L;
        public LockException(String e){
            super(e);
        }
        public LockException(Exception e){
            super(e);
        }
    }

}

相關推薦

方式實現分散式

方案一:資料庫樂觀鎖 樂觀鎖通常實現基於資料版本(version)的記錄機制實現的,比如有一張紅包表(t_bonus),有一個欄位(left_count)記錄禮物的剩餘個數,使用者每領取一個獎品,對應的left_count減1,在併發的情況下如何要保證left_count不為負數,樂觀鎖的實現方式為在紅包表

詳解Linux搭建vsftp服務器通過方式實現文件傳輸

x86 sys fig passwd 問題: mage vpd cee 啟用 概述 FTP(File Transfer Protocol)中文稱為“文件傳輸協議”。用於Internet上的控制文件的雙向傳輸。 工作原理 一、主動模式: 1、客戶端通過用戶名和密碼登錄服務器

WPFの方式實現快捷鍵

原文: WPFの三種方式實現快捷鍵 最近,對wpf新增快捷鍵的方式進行了整理。主要用到的三種方式如下: 一、wpf命令: 資源中新增命令 <Window.Resources> <RoutedUICommand x:Key="ToolCapClick" Text

實現執行緒的第方式 實現Callable介面

package com.juc; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /**

方式實現java單例

java的單例模式主要分為:餓漢模式和懶漢模式 餓漢模式:餓漢很飢餓,所以直接先new出來,再提供。 餓漢模式程式碼:載入class檔案的時候就例項化。 package com.jkf.redis; public class HungryManSingleto

方式實現遠端restful 介面呼叫

1,基本介紹 Restful介面的呼叫,前端一般使用ajax呼叫,後端可以使用的方法比較多,   本次介紹三種:     1.HttpURLConnection實現     2.HttpClient實現     3.Spring的RestTemplate      

以下是JAVA中方式實現檔案字元統計

以下是JAVA中三種方式實現檔案字元統計 package com.lyc.gui; import java.io.FileReader; import java.io.IOException; import java.util.HashMap; import java.util.Ha

方式實現二維碼(java)

一. 通過使用zxing方式實現:  jar準備: https://github.com/zxing/zxing 下載原始碼,將core/src/main/Java/下的所有檔案和javase/src/main/java/下的所有檔案一起打成jar檔案zxing.jar

Android 方式實現圓形ImageView

所有方式均繼承了ImageView 圓形圖片實現一:BitmapShader package com.open.widget; import android.content.Context; import android.graphics.Bitmap; impor

vue方式實現:全選、反選、全不選

方法一:v-model 與 [{checked:true},…] <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="vi

python使用遞迴、尾遞迴、迴圈方式實現斐波那契數列

    在最開始的時候所有的斐波那契程式碼都是使用遞迴的方式來寫的,遞迴有很多的缺點,執行效率低下,浪費資源,還有可能會造成棧溢位,而遞迴的程式的優點也是很明顯的,就是結構層次很清晰,易於理解     可以使用迴圈的方式來取代遞迴,當然也可以使用尾遞迴的方式來實現。    

【框架】[Spring]AOP攔截-方式實現自動代理

這裡的自動代理,我講的是自動代理bean物件,其實就是在xml中讓我們不用配置代理工廠,也就是不用配置class為org.springframework.aop.framework.ProxyFactoryBean的bean。 總結了一下自己目前所學的知識

【Android進度條】方式實現自定義圓形進度條ProgressBar

總結了3種方法: 1.多張圖片切換 2.自定義顏色 3.旋轉自定義圖片 其它: Android自定義控制元件NumberCircleProgressBar(圓形進度條)的實現:點選開啟連結 橫線帶數字進度條:點選開啟連結

方式實現欄佈局(聖盃佈局,雙飛翼佈局前端面試的佈局問題)

1.float+margin效果如下:html程式碼如下: <div class="main">im center</div> <div class="left">

方式實現input的keyup延時事件

從事IT兩年了,第一次寫技術文章,之所以一直不敢寫,因為越做這行越感覺自己菜(此處容我做一個悲傷的表情...) 今天做一個keyup的延時事件,對看清楚了,是keyup,不是男性延時! 網上搜了的方法實現有些困難,後來找大神同事幫忙想了幾個辦法出,得以解決。 一、網上找到的

方式實現Android頁面底部導航欄

我們在Android手機上使用新浪微博和QQ等一些軟體時,經常會遇到類似下面這種頁面底部導航欄的控制元件,使用這種導航欄可以在手機螢幕的一頁中顯示儘可能多的內容,如下圖所示: 下面我將實現這種導航欄的三種方法總結如下: 一、使用TabHost實現(TabHost在新版的A

android方式實現自由移動的view

public class MoveView extends ImageView { private int x, y; private int r, l, t, b; public MoveView(Context context) { this(context, n

Thinking in Java---執行緒通訊+方式實現生產者消費者問題

前面講過執行緒之間的同步問題;同步問題主要是為了保證對共享資源的併發訪問不會出錯,主要的思想是一次只讓一個執行緒去訪問共享資源,我們是通過加鎖的方法實現。但是有時候我們還需要安排幾個執行緒的執行次序,而在系統內部執行緒的排程是透明的,沒有辦法準確的控制執行緒的切

方式實現限制IP訪問

方式一:Linux防火牆實現 #阻止所有IP訪問 iptables -A INPUT -s 0.0.0.0/0 -p tcp --dport 80 -j DROP #然後再新增白名單 iptables

方式實現自定義圓形頁面載入中效果的進度條,包含一個好看的Android UI

效果圖如下:下載地址 樣式一、通過動畫實現定義res/drawable/loading.xml如下: <?xml version="1.0" encoding="UTF-8"?> ​ <animation-list android:oneshot=