Java 系列博客文章

从源码的角度再学「Thread」

原创: zhangshaolin 张少林同学
微信号: zhangshaolin_tonxue

功能介绍 分享

前言

Java中的线程是使用Thread类实现的,Thread在初学Java的时候就学过了,也在实践中用过,不过一直没从源码的角度去看过它的实现,今天从源码的角度出发,再次学习Java Thread,愿此后对Thread的实践更加得心应手。

从注释开始

相信阅读过JDK源码的同学都能感受到JDK源码中有非常详尽的注释,阅读某个类的源码应当先看看注释对它的介绍,注释原文就不贴了,以下是我对它的总结:

Thread是程序中执行的线程,Java虚拟机允许应用程序同时允许多个执行线程

每个线程都有优先级的概念,具有较高优先级的线程优先于优先级较低的线程执行

每个线程都可以被设置为守护线程

当在某个线程中运行的代码创建一个新的Thread对象时,新的线程优先级跟创建线程一致

当Java虚拟机启动的时候都会启动一个叫做main的线程,它没有守护线程,main线程会继续执行,直到以下情况发送

Runtime 类的退出方法exit被调用并且安全管理器允许进行退出操作

所有非守护线程均已死亡,或者run方法执行结束正常返回结果,或者run方法抛出异常

创建线程第一种方式:继承Thread类,重写run方法

1 //定义线程类 2 class PrimeThread extends Thread { 3 long minPrime; 4 PrimeThread(long minPrime) { 5 this.minPrime = minPrime; 6 } 7 public void run() { 8 // compute primes larger than minPrime 9  . . . 10 } 11 } 12 //启动线程 13 PrimeThread p = new PrimeThread(143); 14 p.start();

创建线程第二种方式:实现Runnable接口,重写run方法,因为Java的单继承限制,通常使用这种方式创建线程更加灵活

1 //定义线程 2 class PrimeRun implements Runnable { 3 long minPrime; 4 PrimeRun(long minPrime) { 5 this.minPrime = minPrime; 6 } 7 public void run() { 8 // compute primes larger than minPrime 9  . . . 10 } 11 } 12 //启动线程 13 PrimeRun p = new PrimeRun(143); 14 new Thread(p).start();

创建线程时可以给线程指定名字,如果没有指定,会自动为它生成名字

除非另有说明,否则将null参数传递给Thread类中的构造函数或方法将导致抛出 NullPointerException

Thread 常用属性

阅读一个Java类,先从它拥有哪些属性入手:

1 //线程名称,创建线程时可以指定线程的名称 2 private volatile String name; 3 4 //线程优先级,可以设置线程的优先级 5 private int priority; 6 7 //可以配置线程是否为守护线程,默认为false 8 private boolean daemon = false; 9 10 //最终执行线程任务的`Runnable` 11 private Runnable target; 12 13 //描述线程组的类 14 private ThreadGroup group; 15 16 //此线程的上下文ClassLoader 17 private ClassLoader contextClassLoader; 18 19 //所有初始化线程的数目,用于自动编号匿名线程,当没有指定线程名称时,会自动为其编号 20 private static int threadInitNumber; 21 22 //此线程请求的堆栈大小,如果创建者没有指定堆栈大小,则为0。, 虚拟机可以用这个数字做任何喜欢的事情。, 一些虚拟机会忽略它。 23 private long stackSize; 24 25 //线程id 26 private long tid; 27 28 //用于生成线程ID 29 private static long threadSeqNumber; 30 31 //线程状态 32 private volatile int threadStatus = 0; 33 34 //线程可以拥有的最低优先级 35 public final static int MIN_PRIORITY = 1; 36 37 //分配给线程的默认优先级。 38 public final static int NORM_PRIORITY = 5; 39 40 //线程可以拥有的最大优先级 41 public final static int MAX_PRIORITY = 10; Thread 构造方法

了解了属性之后,看看Thread实例是怎么构造的?先预览下它大致有多少个构造方法:

查看每个构造方法内部源码,发现均调用的是名为init的私有方法,再看init方法有两个重载,而其核心方法如下:

1 /** 2 * Initializes a Thread. 3 * 4 * @param g 线程组 5 * @param target 最终执行任务的 `run()` 方法的对象 6 * @param name 新线程的名称 7 * @param stackSize 新线程所需的堆栈大小,或者 0 表示要忽略此参数 8 * @param acc 要继承的AccessControlContext,如果为null,则为 AccessController.getContext() 9 * @param inheritThreadLocals 如果为 true,从构造线程继承可继承的线程局部的初始值 10 */ 11 private void init(ThreadGroup g, Runnable target, String name, 12 long stackSize, AccessControlContext acc, 13 boolean inheritThreadLocals) { 14 //线程名称为空,直接抛出空指针异常 15 if (name == null) { 16 throw new NullPointerException("name cannot be null"); 17 } 18 //初始化当前线程对象的线程名称 19 this.name = name; 20 //获取当前正在执行的线程为父线程 21 Thread parent = currentThread(); 22 //获取系统安全管理器 23 SecurityManager security = System.getSecurityManager(); 24 //如果线程组为空 25 if (g == null) { 26 //如果安全管理器不为空 27 if (security != null) { 28 //获取SecurityManager中的线程组 29 g = security.getThreadGroup(); 30 } 31 //如果获取的线程组还是为空 32 if (g == null) { 33 //则使用父线程的线程组 34 g = parent.getThreadGroup(); 35 } 36 } 37 38 //检查安全权限 39 g.checkAccess(); 40 41 //使用安全管理器检查是否有权限 42 if (security != null) { 43 if (isCCLOverridden(getClass())) { 44 security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); 45 } 46 } 47 48 //线程组中标记未启动的线程数+1,这里方法是同步的,防止出现线程安全问题 49 g.addUnstarted(); 50 51 //初始化当前线程对象的线程组 52 this.group = g; 53 //初始化当前线程对象的是否守护线程属性,注意到这里初始化时跟父线程一致 54 this.daemon = parent.isDaemon(); 55 //初始化当前线程对象的线程优先级属性,注意到这里初始化时跟父线程一致 56 this.priority = parent.getPriority(); 57 //这里初始化类加载器 58 if (security == null || isCCLOverridden(parent.getClass())) 59 this.contextClassLoader = parent.getContextClassLoader(); 60 else 61 this.contextClassLoader = parent.contextClassLoader; 62 this.inheritedAccessControlContext = 63 acc != null ? acc : AccessController.getContext(); 64 //初始化当前线程对象的最终执行任务对象 65 this.target = target; 66 //这里再对线程的优先级字段进行处理 67 setPriority(priority); 68 if (inheritThreadLocals && parent.inheritableThreadLocals != null) 69 this.inheritableThreadLocals = 70 ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); 71 //初始化当前线程对象的堆栈大小 72 this.stackSize = stackSize; 73 74 //初始化当前线程对象的线程ID,该方法是同步的,内部实际上是threadSeqNumber++ 75 tid = nextThreadID(); 76 }

另一个重载init私有方法如下,实际上内部调用的是上述init方法:

1 private void init(ThreadGroup g, Runnable target, String name, 2 long stackSize) { 3 init(g, target, name, stackSize, null, true); 4 }

接下来看看所有构造方法:

空构造方法

1 public Thread() { 2 init(null, null, "Thread-" + nextThreadNum(), 0); 3 }

内部调用的是init第二个重载方法,参数基本都是默认值,线程名称写死为"Thread-" + nextThreadNum()格式,nextThreadNum()为一个同步方法,内部维护一个静态属性表示线程的初始化数量+1:

1 private static int threadInitNumber; 2 private static synchronized int nextThreadNum() { 3 return threadInitNumber++; 4 }

与第一个构造方法区别在于可以自定义Runnable对象

自定义执行任务Runnable对象的构造方法

1 private static int threadInitNumber; 2 private static synchronized int nextThreadNum() { 3 return threadInitNumber++; 4 }

自定义执行任务Runnable对象和AccessControlContext对象的构造方法

1 Thread(Runnable target, AccessControlContext acc) { 2 init(null, target, "Thread-" + nextThreadNum(), 0, acc, false); 3 }

自定义线程组ThreadGroup和执行任务Runnable对象的构造方法

1 public Thread(ThreadGroup group, Runnable target) { 2 init(group, target, "Thread-" + nextThreadNum(), 0); 3 }

自定义线程名称name的构造方法

1 public Thread(String name) { 2 init(null, null, name, 0); 3 }

自定义线程组ThreadGroup和线程名称name的构造方法

1 public Thread(String name) { 2 init(null, null, name, 0); 3 }

自定义执行任务Runnable对象和线程名称name的构造方法

1 public Thread(Runnable target, String name) { 2 init(null, target, name, 0); 3 }

自定义线程组ThreadGroup和线程名称name和执行任务Runnable对象的构造方法

1 public Thread(ThreadGroup group, Runnable target, String name) { 2 init(group, target, name, 0); 3 }

全部属性都是自定义的构造方法

1 public Thread(ThreadGroup group, Runnable target, String name, 2 long stackSize) { 3 init(group, target, name, stackSize); 4 }

Thread提供了非常灵活的重载构造方法,方便开发者自定义各种参数的Thread对象。

常用方法

这里记录一些比较常见的方法吧,对于Thread中存在的一些本地方法,我们暂且不用管它~

设置线程名称

设置线程名称,该方法为同步方法,为了防止出现线程安全问题,可以手动调用Thread的实例方法设置名称,也可以在构造Thread时在构造方法中传入线程名称,我们通常都是在构造参数时设置

1 public final synchronized void setName(String name) { 2   //检查安全权限 3 checkAccess(); 4   //如果形参为空,抛出空指针异常 5 if (name == null) { 6 throw new NullPointerException("name cannot be null"); 7 } 8 //给当前线程对象设置名称 9 this.name = name; 10 if (threadStatus != 0) { 11 setNativeName(name); 12 } 13 } 获取线程名称

内部直接返回当前线程对象的名称属性

1 public final String getName() { 2 return name; 3 } 启动线程 1 public synchronized void start() { 2 //如果不是刚创建的线程,抛出异常 3 if (threadStatus != 0) 4 throw new IllegalThreadStateException(); 5 6 //通知线程组,当前线程即将启动,线程组当前启动线程数+1,未启动线程数-1 7 group.add(this); 8 9 //启动标识 10 boolean started = false; 11 try { 12 //直接调用本地方法启动线程 13 start0(); 14 //设置启动标识为启动成功 15 started = true; 16 } finally { 17 try { 18 //如果启动呢失败 19 if (!started) { 20 //线程组内部移除当前启动的线程数量-1,同时启动失败的线程数量+1 21 group.threadStartFailed(this); 22 } 23 } catch (Throwable ignore) { 24 /* do nothing. If start0 threw a Throwable then 25 it will be passed up the call stack */ 26 } 27 } 28 }

我们正常的启动线程都是调用Thread的start()方法,然后Java虚拟机内部会去调用Thred的run方法,可以看到Thread类也是实现Runnable接口,重写了run方法的:

1 @Override 2 public void run() { 3 //当前执行任务的Runnable对象不为空,则调用其run方法 4 if (target != null) { 5 target.run(); 6 } 7 }

Thread的两种使用方式:

继承Thread类,重写run方法,那么此时是直接执行run方法的逻辑,不会使用target.run();

实现Runnable接口,重写run方法,因为Java的单继承限制,通常使用这种方式创建线程更加灵活,这里真正的执行逻辑就会交给自定义Runnable去实现

设置守护线程

本质操作是设置daemon属性

1 public final void setDaemon(boolean on) { 2 //检查是否有安全权限 3 checkAccess(); 4 //本地方法,测试此线程是否存活。, 如果一个线程已经启动并且尚未死亡,则该线程处于活动状态 5 if (isAlive()) { 6 //如果线程先启动后再设置守护线程,将抛出异常 7 throw new IllegalThreadStateException(); 8 } 9 //设置当前守护线程属性 10 daemon = on; 11 } 判断线程是否为守护线程 1 public final boolean isDaemon() { 2 //直接返回当前对象的守护线程属性 3 return daemon; 4 } 线程状态

先来个线程状态图:

获取线程状态:

1 public State getState() { 2 //由虚拟机实现,获取当前线程的状态 3 return sun.misc.VM.toThreadState(threadStatus); 4 }

线程状态主要由内部枚举类State组成:

1 public enum State { 2 3 NEW, 4 5 6 RUNNABLE, 7 8 9 BLOCKED, 10 11 12 WAITING, 13 14 15 TIMED_WAITING, 16 17 18 TERMINATED; 19 }

NEW:刚刚创建,尚未启动的线程处于此状态

RUNNABLE:在Java虚拟机中执行的线程处于此状态

BLOCKED:被阻塞等待监视器锁的线程处于此状态,比如线程在执行过程中遇到synchronized同步块,就会进入此状态,此时线程暂停执行,直到获得请求的锁

WAITING:无限期等待另一个线程执行特定操作的线程处于此状态

通过 wait() 方法等待的线程在等待 notify() 方法

通过 join() 方法等待的线程则会等待目标线程的终止

TIMED_WAITING:正在等待另一个线程执行动作,直到指定等待时间的线程处于此状态

通过 wait() 方法,携带超时时间,等待的线程在等待 notify() 方法

通过 join() 方法,携带超时时间,等待的线程则会等待目标线程的终止

TERMINATED:已退出的线程处于此状态,此时线程无法再回到 RUNNABLE 状态

线程休眠

这是一个静态的本地方法,使当前执行的线程休眠暂停执行 millis 毫秒,当休眠被中断时会抛出InterruptedException中断异常

1 /** 2 * Causes the currently executing thread to sleep (temporarily cease 3 * execution) for the specified number of milliseconds, subject to 4 * the precision and accuracy of system timers and schedulers. The thread 5 * does not lose ownership of any monitors. 6 * 7 * @param millis 8 * the length of time to sleep in milliseconds 9 * 10 * @throws IllegalArgumentException 11 * if the value of {@code millis} is negative 12 * 13 * @throws InterruptedException 14 * if any thread has interrupted the current thread. The 15 * <i>interrupted status</i> of the current thread is 16 * cleared when this exception is thrown. 17 */ 18 public static native void sleep(long millis) throws InterruptedException; 检查线程是否存活

本地方法,测试此线程是否存活。 如果一个线程已经启动并且尚未死亡,则该线程处于活动状态。

1 /** 2 * Tests if this thread is alive. A thread is alive if it has 3 * been started and has not yet died. 4 * 5 * @return <code>true</code> if this thread is alive; 6 * <code>false</code> otherwise. 7 */ 8 public final native boolean isAlive(); 线程优先级

设置线程优先级

1 /** 2 * Changes the priority of this thread. 3 * <p> 4 * First the <code>checkAccess</code> method of this thread is called 5 * with no arguments. This may result in throwing a 6 * <code>SecurityException</code>. 7 * <p> 8 * Otherwise, the priority of this thread is set to the smaller of 9 * the specified <code>newPriority</code> and the maximum permitted 10 * priority of the thread's thread group. 11 * 12 * @param newPriority priority to set this thread to 13 * @exception IllegalArgumentException If the priority is not in the 14 * range <code>MIN_PRIORITY</code> to 15 * <code>MAX_PRIORITY</code>. 16 * @exception SecurityException if the current thread cannot modify 17 * this thread. 18 * @see #getPriority 19 * @see #checkAccess() 20 * @see #getThreadGroup() 21 * @see #MAX_PRIORITY 22 * @see #MIN_PRIORITY 23 * @see ThreadGroup#getMaxPriority() 24 */ 25 public final void setPriority(int newPriority) { 26 //线程组 27 ThreadGroup g; 28 //检查安全权限 29 checkAccess(); 30 //检查优先级形参范围 31 if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { 32 throw new IllegalArgumentException(); 33 } 34 if((g = getThreadGroup()) != null) { 35 //如果优先级形参大于线程组最大线程最大优先级 36 if (newPriority > g.getMaxPriority()) { 37 //则使用线程组的优先级数据 38 newPriority = g.getMaxPriority(); 39 } 40 //调用本地设置线程优先级方法 41 setPriority0(priority = newPriority); 42 } 43 } 线程中断

有一个stop()实例方法可以强制终止线程,不过这个方法因为太过于暴力,已经被标记为过时方法,不建议程序员再使用,因为强制终止线程会导致数据不一致的问题。

这里关于线程中断的方法涉及三个:

1 //实例方法,通知线程中断,设置标志位 2 public void interrupt(){} 3 //静态方法,检查当前线程的中断状态,同时会清除当前线程的中断标志位状态 4 public static boolean interrupted(){} 5 //实例方法,检查当前线程是否被中断,其实是检查中断标志位 6 public boolean isInterrupted(){}

interrupt() 方法解析

1 /** 2 * Interrupts this thread. 3 * 4 * <p> Unless the current thread is interrupting itself, which is 5 * always permitted, the {@link #checkAccess() checkAccess} method 6 * of this thread is invoked, which may cause a {@link 7 * SecurityException} to be thrown. 8 * 9 * <p> If this thread is blocked in an invocation of the {@link 10 * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link 11 * Object#wait(long, int) wait(long, int)} methods of the {@link Object} 12 * class, or of the {@link #join()}, {@link #join(long)}, {@link 13 * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)}, 14 * methods of this class, then its interrupt status will be cleared and it 15 * will receive an {@link InterruptedException}. 16 * 17 * <p> If this thread is blocked in an I/O operation upon an {@link 18 * java.nio.channels.InterruptibleChannel InterruptibleChannel} 19 * then the channel will be closed, the thread's interrupt 20 * status will be set, and the thread will receive a {@link 21 * java.nio.channels.ClosedByInterruptException}. 22 * 23 * <p> If this thread is blocked in a {@link java.nio.channels.Selector} 24 * then the thread's interrupt status will be set and it will return 25 * immediately from the selection operation, possibly with a non-zero 26 * value, just as if the selector's {@link 27 * java.nio.channels.Selector#wakeup wakeup} method were invoked. 28 * 29 * <p> If none of the previous conditions hold then this thread's interrupt 30 * status will be set. </p> 31 * 32 * <p> Interrupting a thread that is not alive need not have any effect. 33 * 34 * @throws SecurityException 35 * if the current thread cannot modify this thread 36 * 37 * @revised 6.0 38 * @spec JSR-51 39 */ 40 public void interrupt() { 41 //检查是否是自身调用 42 if (this != Thread.currentThread()) 43 //检查安全权限,这可能导致抛出{@link * SecurityException}。 44 checkAccess(); 45 46 //同步代码块 47 synchronized (blockerLock) { 48 Interruptible b = blocker; 49 //检查是否是阻塞线程调用 50 if (b != null) { 51 //设置线程中断标志位 52 interrupt0(); 53 //此时抛出异常,将中断标志位设置为false,此时我们正常会捕获该异常,重新设置中断标志位 54 b.interrupt(this); 55 return; 56 } 57 } 58 //如无意外,则正常设置中断标志位 59 interrupt0(); 60 }

线程中断方法不会使线程立即退出,而是给线程发送一个通知,告知目标线程,有人希望你退出啦~

只能由自身调用,否则可能会抛出 SecurityException

调用中断方法是由目标线程自己决定是否中断,而如果同时调用了wait,join,sleep等方法,会使当前线程进入阻塞状态,此时有可能发生InterruptedException异常

被阻塞的线程再调用中断方法是不合理的

中断不活动的线程不会产生任何影响

检查线程是否被中断:

1 /** 2 * Tests whether this thread has been interrupted. The <i>interrupted 3 * status</i> of the thread is unaffected by this method. 4 5 测试此线程是否已被中断。, 线程的<i>中断*状态</ i>不受此方法的影响。 6 * 7 * <p>A thread interruption ignored because a thread was not alive 8 * at the time of the interrupt will be reflected by this method 9 * returning false. 10 * 11 * @return <code>true</code> if this thread has been interrupted; 12 * <code>false</code> otherwise. 13 * @see #interrupted() 14 * @revised 6.0 15 */ 16 public boolean isInterrupted() { 17 return isInterrupted(false); 18 }

静态方法,会清空当前线程的中断标志位:

1 /** 2 *测试当前线程是否已被中断。, 此方法清除线程的* <i>中断状态</ i>。, 换句话说,如果要连续两次调用此方法,则* second调用将返回false(除非当前线程再次被中断,在第一次调用已清除其中断的*状态 之后且在第二次调用已检查之前), 它) 3 * 4 * <p>A thread interruption ignored because a thread was not alive 5 * at the time of the interrupt will be reflected by this method 6 * returning false. 7 * 8 * @return <code>true</code> if the current thread has been interrupted; 9 * <code>false</code> otherwise. 10 * @see #isInterrupted() 11 * @revised 6.0 12 */ 13 public static boolean interrupted() { 14 return currentThread().isInterrupted(true); 15 } 总结

记录自己阅读Thread类源码的一些思考,不过对于其中用到的很多本地方法只能望而却步,还有一些代码没有看明白,暂且先这样吧,如果有不足之处,请留言告知我,谢谢!后续会在实践中对Thread做出更多总结记录。

阅读更多

Linux、网络相关

重命名磁盘挂载分区卷标(磁盘名)

编辑ext2/ext3/FAT32/NTFS磁盘分区卷标
根据不同的磁盘分区类型,分别有3个程序可供选用.

Mtools 适用于 FAT32 格式分区.
ntfsprogs 适用于 NTFS 格式分区.
e2label适用于 ext2 和 ext3 型格式分区.
以上程序的具体使用说明分别如下:

[编辑]使用mtools编辑FAT32磁盘分区卷标

我想更改由系统自动挂载的USB设备中的FAT32分区卷标.我有两个外接硬盘驱动器(一个日常家用,一个公司工作用),其中一个是iPod.这两个驱动器都被系统以”sda1”或”sda2”等名称挂载于”/media”目录下,在电脑里我很难通过这些名字辨认出哪个文件夹是对应哪个驱动器.后来,我发现更改这些驱动器上的FAT32分区卷标不是件容易事.所以我觉得有必要将我是如何修改这些FAT32分区卷标的过程写下来.方便那些遇到同样问题的人.讲解之前首先明白:系统会将外接的驱动器自动挂载到”/media/”目录下,以”sda1”类似的卷标名命名分区,为了容易区别各分区,我们需要修改默认的卷标

按如下步骤一步一步操作即可更改FAT分区卷标:

操作指导

安装mtools 软件包

sudo apt-get install mtools
2) 系统自动装载插入的USB设备后,可以用如下命令查看新设备相关信息:

mount
and Note down where it says “sda1″ or similar

显示信息中”sda1”或与之相似的字段即是系统分配给设备的名字。

复制”/etc”目录下mtools.conf文件为新文件"~/.mtoolsrc"

cp /etc/mtools.conf ~/.mtoolsrc
4) 编辑刚复制的”~/.mtoolsrc”文件,在最后一行加入如下命令行:

drive i: file="/dev/sda2"
上面命令行中字段”sda2”应根据实际情况更改为你在第二步操作中所看到的新设备名称。
5) 更改命令提示符路径到”i:”盘:

mcd i:
6) 查看”i:”当前的卷标

sudo mlabel -s i:
7) 更改”i:”盘原始卷标为你喜欢的新卷标名:

sudo mlabel i:my-ipod
你可以将上述命令行操作中的”my-ipod”字段替换为你喜欢的名字,用以代表插入的USB设备。

检查更改是否成功

sudo mlabel -s i:
经过以上操作,电脑显示如下信息: Volume label is MY-IPODYou’re

恭喜!卷标修改已经成功.下次插入USB设备后,你可以在目录” /media/MY-IPOD”下找到你USB设备上的文件。

[编辑]使用ntfsprogs 修改NTFS分区卷标

操作指导

安装ntfsprogs软件包

sudo apt-get install ntfsprogs
2) NTFS分区驱动器插入后被自动装载,可以用如下命令查看此新设备相关信息:

mount
显示信息中”sda1”或与之相似的字段即是系统分配给新设备的名字。

更改原始卷标为你喜欢的新卷标名:

sudo ntfslabel /dev/sda1 newlabel
replace newlabel with what you would like to name the usb drive / Harddisk

用你喜欢的卷标名替换此命令中的单词”newlabel” 。

和FAT分区不同,更改NTFS分区卷标后你必须卸下此设备(卸载步骤:系统->管理->磁盘管理->硬盘分区下面的”禁用”按钮)

检查更改是否成功

重启电脑后查看相应NTFS分区卷标是否改变.如果你更改的是USB设备卷标,你需要重新插入设备。

[编辑]使用e2label更改 ext2 或ext3 分区卷标

操作指南

设备被自动加载后,可以用如下命令查看此新设备相关信息:

mount
显示信息中”sda1”或与之相似的字段即是系统分配给新设备的名字。

更改原始卷标为你喜欢的新卷标名:

sudo e2label /dev/sda1 newlabel
用你喜欢的卷标名替换此命令中的单词”newlabel”

更改分区卷标后你必须卸下此设备(卸载步骤:系统->管理->磁盘管理->硬盘分区下面的”禁用”按钮)。

检查更改是否成功:

重启电脑后查看相应分区卷标是否改变.如果你更改的是USB设备卷标,你需要重新插入设备。

原文:http://www.cnblogs.com/xusion/articles/3015145.html

阅读更多

html/css/js/各种前端框架/工具

MySQL的InnoDB的幻读问题

MySQL InnoDB事务的隔离级别有四级,默认是“可重复读”(REPEATABLE READ)。

未提交读(READ UNCOMMITTED)。另一个事务修改了数据,但尚未提交,而本事务中的SELECT会读到这些未被提交的数据(脏读)。 提交读(READ COMMITTED)。本事务读取到的是最新的数据(其他事务提交后的)。问题是,在同一个事务里,前后两次相同的SELECT会读到不同的结果(不重复读)。 可重复读(REPEATABLE READ)。在同一个事务里,SELECT的结果是事务开始时时间点的状态,因此,同样的SELECT操作读到的结果会是一致的。但是,会有幻读现象(稍后解释)。 串行化(SERIALIZABLE)。读操作会隐式获取共享锁,可以保证不同事务间的互斥。

四个级别逐渐增强,每个级别解决一个问题。

脏读,最容易理解。另一个事务修改了数据,但尚未提交,而本事务中的SELECT会读到这些未被提交的数据。 不重复读。解决了脏读后,会遇到,同一个事务执行过程中,另外一个事务提交了新数据,因此本事务先后两次读到的数据结果会不一致。 幻读。解决了不重复读,保证了同一个事务里,查询的结果都是事务开始时的状态(一致性)。但是,如果另一个事务同时提交了新数据,本事务再更新时,就会“惊奇的”发现了这些新数据,貌似之前读到的数据是“鬼影”一样的幻觉。

借鉴并改造了一个搞笑的比喻:

脏读。假如,中午去食堂打饭吃,看到一个座位被同学小Q占上了,就认为这个座位被占去了,就转身去找其他的座位。不料,这个同学小Q起身走了。事实:该同学小Q只是临时坐了一小下,并未“提交”。 不重复读。假如,中午去食堂打饭吃,看到一个座位是空的,便屁颠屁颠的去打饭,回来后却发现这个座位却被同学小Q占去了。 幻读。假如,中午去食堂打饭吃,看到一个座位是空的,便屁颠屁颠的去打饭,回来后,发现这些座位都还是空的(重复读),窃喜。走到跟前刚准备坐下时,却惊现一个恐龙妹,严重影响食欲。仿佛之前看到的空座位是“幻影”一样。

一些文章写到InnoDB的可重复读避免了“幻读”(phantom read),这个说法并不准确。

做个试验:(以下所有试验要注意存储引擎和隔离级别)

mysql> show create table t_bitfly\G;
CREATE TABLE t_bitfly (
id bigint(20) NOT NULL default '0',
value varchar(32) default NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=gbk

mysql> select @@global.tx_isolation, @@tx_isolation;
+-----------------------+-----------------+
| @@global.tx_isolation | @@tx_isolation  |
+-----------------------+-----------------+
| REPEATABLE-READ       | REPEATABLE-READ |
+-----------------------+-----------------+

试验一:

t Session A                                   Session B
|
| START TRANSACTION;            START TRANSACTION;
|
| SELECT * FROM t_bitfly;
| empty set
|                                                             INSERT INTO t_bitfly
|                                                             VALUES (1, 'a');
|
| SELECT * FROM t_bitfly;
| empty set
|                                                             COMMIT;
|
| SELECT * FROM t_bitfly;
| empty set
|
| INSERT INTO t_bitfly VALUES (1, 'a');
| ERROR 1062 (23000):
| Duplicate entry '1' for key 1
v (shit, 刚刚明明告诉我没有这条记录的)

如此就出现了幻读,以为表里没有数据,其实数据已经存在了,傻乎乎的提交后,才发现数据冲突了。

试验二:

t Session A                                     Session B
|
| START TRANSACTION;              START TRANSACTION;
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|                                                            INSERT INTO t_bitfly
|                                                            VALUES (2, 'b');
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|                                                            COMMIT;
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|
| UPDATE t_bitfly SET value='z';
| Rows matched: 2  Changed: 2  Warnings: 0
| (怎么多出来一行)
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | z     |
| |    2 | z     |
| +------+-------+
|
v

本事务中第一次读取出一行,做了一次更新后,另一个事务里提交的数据就出现了。也可以看做是一种幻读。

那么,InnoDB指出的可以避免幻读是怎么回事呢?

http://dev.mysql.com/doc/refman/5.0/en/innodb-record-level-locks.html

By default, InnoDB operates in REPEATABLE READ transaction isolation level and with the innodb_locks_unsafe_for_binlog system variable disabled. In this case, InnoDB uses next-key locks for searches and index scans, which prevents phantom rows (see Section 13.6.8.5, “Avoiding the Phantom Problem Using Next-Key Locking”).

准备的理解是,当隔离级别是可重复读,且禁用innodb_locks_unsafe_for_binlog的情况下,在搜索和扫描index的时候使用的next-key locks可以避免幻读。

关键点在于,是InnoDB默认对一个普通的查询也会加next-key locks,还是说需要应用自己来加锁呢?如果单看这一句,可能会以为InnoDB对普通的查询也加了锁,如果是,那和序列化(SERIALIZABLE)的区别又在哪里呢?

MySQL manual里还有一段:

13.2.8.5. Avoiding the Phantom Problem Using Next-Key Locking (http://dev.mysql.com/doc/refman/5.0/en/innodb-next-key-locking.html)

To prevent phantoms, InnoDB uses an algorithm called next-key locking that combines index-row locking with gap locking.

You can use next-key locking to implement a uniqueness check in your application: If you read your data in share mode and do not see a duplicate for a row you are going to insert, then you can safely insert your row and know that the next-key lock set on the successor of your row during the read prevents anyone meanwhile inserting a duplicate for your row. Thus, the next-key locking enables you to “<span style="padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px;">lock</span>” the nonexistence of something in your table.

我的理解是说,InnoDB提供了next-key locks,但需要应用程序自己去加锁。manual里提供一个例子:

SELECT * FROM child WHERE id > 100 FOR UPDATE;

这样,InnoDB会给id大于100的行(假如child表里有一行id为102),以及100-102,102+的gap都加上锁。

可以使用show innodb status来查看是否给表加上了锁。

再看一个实验,要注意,表t_bitfly里的id为主键字段。实验三:

t Session A                                       Session B
|
| START TRANSACTION;                START TRANSACTION;
|
| SELECT * FROM t_bitfly
| WHERE id<=1
| FOR UPDATE;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|                                                           INSERT INTO t_bitfly
|                                                           VALUES (2, 'b');
|                                                           Query OK, 1 row affected
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|                                                           INSERT INTO t_bitfly
|                                                           VALUES (0, '0');
|                                                           (waiting for lock ...
|                                                           then timeout)
|                                                           ERROR 1205 (HY000):
|                                                           Lock wait timeout exceeded;
|                                                           try restarting transaction
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|                                                           COMMIT;
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
v

可以看到,用id<=1加的锁,只锁住了id<=1的范围,可以成功添加id为2的记录,添加id为0的记录时就会等待锁的释放。

MySQL manual里对可重复读里的锁的详细解释:

http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read

For locking reads (SELECT with FOR UPDATE or LOCK IN SHARE MODE),UPDATE, and DELETE statements, locking depends on whether the statement uses a unique index with a unique search condition, or a range-type search condition. For a unique index with a unique search condition, InnoDB locks only the index record found, not the gap before it. For other search conditions, InnoDB locks the index range scanned, using gap locks or next-key (gap plus index-record) locks to block insertions by other sessions into the gaps covered by the range.

一致性读和提交读,先看实验,实验四:

t Session A                                                    Session B
|
| START TRANSACTION;                             START TRANSACTION;
|
| SELECT * FROM t_bitfly;
| +----+-------+
| | id | value |
| +----+-------+
| |  1 | a     |
| +----+-------+
|                                                                        INSERT INTO t_bitfly
|                                                                                VALUES (2, 'b');
|                                                                        COMMIT;
|
| SELECT * FROM t_bitfly;
| +----+-------+
| | id | value |
| +----+-------+
| |  1 | a     |
| +----+-------+
|
| SELECT * FROM t_bitfly LOCK IN SHARE MODE;
| +----+-------+
| | id | value |
| +----+-------+
| |  1 | a     |
| |  2 | b     |
| +----+-------+
|
| SELECT * FROM t_bitfly FOR UPDATE;
| +----+-------+
| | id | value |
| +----+-------+
| |  1 | a     |
| |  2 | b     |
| +----+-------+
|
| SELECT * FROM t_bitfly;
| +----+-------+
| | id | value |
| +----+-------+
| |  1 | a     |
| +----+-------+
v

如果使用普通的读,会得到一致性的结果,如果使用了加锁的读,就会读到“最新的”“提交”读的结果。

本身,可重复读和提交读是矛盾的。在同一个事务里,如果保证了可重复读,就会看不到其他事务的提交,违背了提交读;如果保证了提交读,就会导致前后两次读到的结果不一致,违背了可重复读。

可以这么讲,InnoDB提供了这样的机制,在默认的可重复读的隔离级别里,可以使用加锁读去查询最新的数据。

http://dev.mysql.com/doc/refman/5.0/en/innodb-consistent-read.html

If you want to see the “freshest” state of the database, you should use either the READ COMMITTED isolation level or a locking read:
SELECT * FROM t_bitfly LOCK IN SHARE MODE;

结论:MySQL InnoDB的可重复读并不保证避免幻读,需要应用使用加锁读来保证。而这个加锁度使用到的机制就是next-key locks。

==================== 结尾 ====================

作者: bitfly. 转载请注明来源或包含本信息. 谢谢
链接: http://blog.bitfly.cn/post/mysql-innodb-phantom-read/

阅读更多
APK反编译总结
1.准备环境

win7
android-sdk_r24.0.2-windows.zip
jdk7
android studio1.5
eclipse
charles

温馨提示:后面三个工具都需要jre|jdk环境,请首先安装jdk.

2.抓包过程

通过在PC端安装charles软件,android端设置网络代理,抓取网络数据包。

2.1、PC端:在pc端创建wifi热点共享给外设->CMD命令行

netsh wlan set hostednetwork mode=allow ssid=abcd key=abcd1234

选择正在使用的网络连接,右键共享->勾选并选择刚刚创建的热点连接

netsh wlan start hostednetwork

charles:proxy setting 设置代理端口,若需https抓包请设置ssl选项,并且客户端安装charles证书

2.2、客户端:WLAN设置刚创建的“abcd”共享,并指定代理IP和端口号(自己ipconfig查看即可)

3.准备反编译工具

主要针对jvm的class文件和android虚拟机字节码smali,所需软件如下:

apktool_2.0.0rc4.zip ---- 可以得到apk里的资源和smali文件 dex2jar-2.0.zip ---- 获得class文件 jd-gui.exe ---- 反解class文件 signapk.rar ---- 修改smali或者资源文件,重新打包签名,***DEBUG*** 4.开始吧

这里以反编译土豆 app为例:

得到res和smali

java -jar apktool.jar d -d ..\..\youku\tudou\tudoushipin_61.apk -o ..\..\youku\tudou\tudoushipin_61

得到class

dex2jar.bat tudoushipin_61.apk

对上面的class使用jd-gui反编译,并导入eclipse

5.上演调试 && android studio

将smali文件导入到android studio

5.1、找到刚才apktool反解的目录找到AndroidManifest.xml,LAUNCHER对应的Activity标签上加入可被debug的配置android:debuggable="true",并保存。

5.2、假设我们现在把断点加载app的启动入口:
找到APK的入口Activity类(搜索关键字LAUNCHER你懂得),也就是:com.tudou.ui.activity.WelcomeActivity。

到了关键性的一步,找到这个Activity对应的smali文件;
定位到入口方法:onCreate;
在下面加入DEBUG代码,app启动时加入断点会停在这个位置;
说明一下:这段代码是smali的语法更多了解可以自行Google,OK。

a=0;// invoke-static {}, Landroid/os/Debug;->waitForDebugger()V

说明:根据你的需要可以把断点加到任意位置,前提是你要知道它在对应的smali文件的哪一行:方法是拿反编译后的Java文件和smali对应着去看,然后再找;后面的DEBUG也是这个思路(剧透)。

5.3、对修改后的apk重新打包

i.重新打包:

java -jar apktool.jar b -d ..\..\youku\tudou\tudoushipin_61 -o debug-tudou.apk

ii.重新签名:

java -jar signapk\signapk.jar signapk\testkey.x509.pem signapk\testkey.pk8 debug-tudou.apk debug-tudou.sign.apk

iii.一切可能都不是那么顺利):(

5.4、开启android studio-->基于知名的IntelliJ IDEA开发

1.导入之前反编译得到的smali文件到android studio,并在‘前面加debug代码’的地方加入断点。
2.找一部android手机(模拟器就算了,又慢又总是不兼容),安装刚才的签名后的apk,通过USB数据线接入PC。

5.5、有一些必要的说明

1.默认安装完android studio,例如:C:\dev\android\sdk
2.对于android Dalvik虚拟机的调试监控,DDMS已经被废弃了,新的是tools下的monitor工具,将其启动
3.在monitor中会看到devices中会出现小手机图标,端口号一般是8600

6.开始远程调试

1.android studio中菜单栏->RUN->Edit Configuration -> Remote(这根在eclipse中差不多)
指定host:localhost,端口:8600,module:smali所在的位置
启动app-->运行debug即可 -> 顺利的话光标会定位到你刚才的断点处。

2.观察Android Monitor窗口
观察Debugger tag,可以查看对象和变量的值

@hell 分享

阅读更多

交流讨论专区,各种水~

【室内设计参考】2017.10.12更新
汇总

1、玄关设计:置物、鞋架、雨伞、衣帽、装饰。
2、卫生间楼上一个,楼下一个。楼下公共,楼上主卧独卫。户型图一层有两个卫生间可以去掉一个,看看怎么弄。
3、楼梯空间尽量设计成书架;材质:木质;不要玻璃的; 使用竖条栏杆。
4、卧室浅色金刚板就行。其他区域瓷砖。
5、斜屋面功能设计:衣帽、储物。
6、灯具简约,不要复杂灯具。
7、一层带直角窗的卧室,栏杆太难看,去掉。
8、厨房尽量提高空间利用,厨房不用嵌入式的烤箱,但要设计一下微波炉、烤箱等一些简单的厨房电器的存放位置。
9、一层厨房位置边上的卫生间区域可否把墙打掉?我觉得可以设计成平时3-4人吃饭的的小吧台这样的功能。这样餐厅的空间可以利用一下。人多吃饭需要大桌子,平时用不到,需要收纳起来,帮我设计一下大桌收纳问题。
10、二楼通往阳台区域可以设计成休息娱乐的房间,带书架设计。

客厅/卧室整体风格参考






楼梯参考

要点:楼梯空间尽量设计成书架,书架前方可以有休息的沙发这样的设计;楼梯材质:木质;不要玻璃的;使用竖条栏杆。(大概功能性就这样)。
楼梯边上的飘窗,栏杆换掉。

地板

卧室浅色金刚板就行。其他区域瓷砖。浅色暖色大块瓷砖~

房门

白色

斜屋面

功能:衣帽、储物。

阳台

一楼的阳台墙不知道可否打掉?有没有必要打掉? 不是很了解咨询一下。

二楼阳台需要洗衣槽和放洗衣机的地方,一楼卧室阳台排水系统预留好就行,先不做洗衣槽。

卫生间参考

注:不需要浴缸。

楼上一个,楼下一个。楼下公共,楼上主卧独卫。两个卫生间都需要淋雨配置。

厨房参考

厨房边上的卫生间如果可以打掉,就这设计成吃饭的地方吧,做个类似吧台的样子,平时3-4人吃饭即可。

参考:

灯具

简约,不要复杂灯具。

卧室拐角

这部分栏杆去掉,可以参考上图这样的设计,再设计一个安全防护。

阅读更多