多线程编程核心技术日记

第一章

1. isInterrupted()interrupt()的区别

    public boolean isInterrupted() {
        return isInterrupted(false);
    }

    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

    /**
     * Tests if some Thread has been interrupted.  The interrupted state
     * is reset or not based on the value of ClearInterrupted that is
     * passed.
     */
    private native boolean isInterrupted(boolean ClearInterrupted);

interrupted() 判断当前线程是否有中断标记,并清除中断标记。(jdk的命名也可读性也不好)

2. Thread.yield()方法

    使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。

 yield()的作用是放弃当前的cpu资源,将它让给其他的任务去占用cpu执行时间,但放弃的时间不确定,有可能刚刚放弃马上就获得了时间片。

3. 线程的优先级setPriority(int newPriority)

    优先级高的分配的CPU资源会多点

4. 守护线程

5. 停止线程

   可以用interrupt()和renturn结合使用

 



第二章

1.synchronized 同步语句

synchronized可以重入不可以继承

synchronized同步方法 和 synchronized(this)  用this对象作为“对象监视器”来实现同步功能。

static synchronized同步方法 和 synchronized(class)  用class类作为“对象监视器”来实现同步功能。

当我们用synchronized(**)来同步时,要注意避免用字符串来作为“对象监视器”(详情见102页)

 2.volatile

volatile修饰变量,保证在多线程中变量的可见性(这句话可以理解为在一个方法里面每次去取volatile修饰的变量时都是在主内存去取而不是在私有栈去取)

下面2个图,一个是非volatile修饰,一个是有volatile修饰(很好理解吧)

 下面的代码我觉得是最能解释volatile的可见性。

 isRunning初始化为true

如果isRunning没有被volatile修饰,当另一个线程修改isRunning为false时,下图还一直循环(因为isRunning取的还是方法栈里面的变量值)

如果isRunning被volatile修饰,当另一个线程修改isRunning为false时,下图会打印“线程被停止了!”(因为isRunning取的还是主内存的值)

 

但是要记住,volatile不能保证同步,意思是不能保证原子性。

 



 第三章(线程间通信)

1.线程是独立的,如果让多个线程之间有联系可以用wait()和notify()来通信,

当线程要用wait()和notify(),必须要先获取对象锁,才能调用对象的await()和notify()方法。否则会出现java.lang.IllegalMonitorStateException异常。

2.当一个线程执行wait()之后,线程会停止运行,只能等待其他线程去notify().  (这个不是绝对的,见下面解释)

当另一个线程执行notify()时,当前线程并不会马上释放锁,只是随机挑选出其中一个wait()的线程,对其发送notify通知,等notify线程执行完同步方法后释放锁。

await状态的线程才能获得锁去执行代码。

3.当线程呈wati()状态时,调用线程的interrupt()方法时会出现interruptedException异常

4.带有参数的awati(long)方法,指如果在long时间内没有被其他线程notify唤醒时,会自己自动唤醒。

5.wait()方法只能由其他线程的notify去唤醒吗,NO!

那在什么情况下wait()方法会被唤醒呢,当调用wait()的对象是Thread时,如果这个线程对象在wait之前isAlive是true的时候,wait()会阻塞,

如果阻塞期间这个线程对象isAlive变为false的时候,wait()会别唤醒。看看下面代码。

当把Thread.sleep(2000);注释代码打开的时候,wait()会被唤醒。

public class MainThread  
{
    public static void main(String[] args) throws InterruptedException
    {
        SubThread sub = new SubThread();
        System.out.println("MainThread----子线程状态=" +sub.getState() + "    isAlive=" + sub.isAlive());
        sub.start();
        Thread.sleep(1000);
        synchronized(sub)
        {
            try{
                System.out.println("MainThread----子线程状态=" +sub.getState() + "   isAlive=" + sub.isAlive());
                sub.wait();
                System.out.println("MainThread----子线程状态=" +sub.getState() + "   isAlive=" + sub.isAlive());
                System.out.println("MainThread----执行结束");
            }
            catch(InterruptedException e){
                System.out.println("e" + e);
            }
        }  
    }
    
    public static class SubThread extends Thread  
    {  
        @Override
        public void run()  
        {
            try {
                System.out.println("SubThread------开始  状态" +getState() + "        isAlive=" + isAlive());
//                Thread.sleep(2000);
                System.out.println("SubThread--------结束");
            } catch (Exception ex) {}
        }  
    }
}  

 6. 线程的join方法就是上面的原理,(注意:join是线程的方法,wait和notify是Object的方法)

join方法:使所属的线程对象X正常执行run方法,而是当前线程Z进行无限期的阻塞,等待线程X执行完run方法销毁后再执行Z以后的代码。

在join过程中,如果当前线程Z被中断,则Z线程出现异常。

join和sleep的区别:因为join的原理是通过先锁住线程后调用wait方法实现的,所以它释放了线程的锁,而sleep不会释放锁

如下图,b.join的时候通过wait就释放了监听对象b。

 



第四章(ReentrantLock)

1.要实现同步除了可以用synchronized外还可以用RenntrantLock去实现;

用法就是lock.lock()和lock.unlock(); 其实最底层实现就是通过LockSupport的pack()和unpark(Thread)方法。

(知识点:当一个A线程park阻塞时,别的线程B除了可以通过unpark(A)去唤醒A,而且可以通过调用A.interrupt()去唤醒A并且不会出现InterruptedException异常)

2.RenntrantLock类里通过组合AbstractQueuedSynchronizer(AQS)去实现同步。

RenntrantLock类里有2个AQS(公平和不公平)可提供使用,通过构造器去决定使用那个具体的AQS,默认是不公平的。

2个AQS区别就是

公平AQS去lock时要判断队列里面是否有等待的线程,如果有的话就加入到队列最后,

非公平AQS去lock的时候不用判断队列里是否有线程等待,直接去竞争。如果失败了就加入到队列中去以后就是公平了。

下面是2个AQS分别获取lock的过程;

FairSync.lock()

 
    final void lock() {
        acquire(1);
    }

    public final void acquire(int arg) {
    //tryAcquire尝试获取锁,如果失败就addWaiter添加到线程队列,
    //acquireQueued会再次去获取锁,如果失败会通过LockSupport.park阻塞 等待队列前一个线程通过unpark去唤醒它
    //selfInterrupt的作用是(以后再说)
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

NonfairSync.lock()

        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
        /**
         * 其中非公平锁acquire过程中就tryAcquire跟公平锁不一样
         * 就是上面去掉黄色代码
         */

 详情细节可以参考http://www.cnblogs.com/skywang12345/p/3496147.html

http://www.cnblogs.com/waterystone/p/4920797.html

3.AQS原理简单描述

AQS有个Node节点类,有头有尾形成链表结构wait queue

那这个链表里面的数据是什么呢,就是当线程去通过lock.lock()时没有获取到锁时,把这个没有获取到锁的线程加入到链表尾部。

初始化的时候有4个线程0,1,2,3 当第一个线程0获取到锁。 链表结构组成是    (head)new node()-->等待线程1-->等待线程2-->(tail)等待线程3.

当线程0 调用unlock时,会唤醒头部的下个节点head.next() 即等待线程1。当线程1获取到锁的时候 会把自己设置为头部,同时清理它之前的头部节点即上面的new node().

这时链表结构变成:(head)线程1-->等待线程2-->(tail)等待线程3.

同时AQS里面有ConditionObject类,那这个类是干嘛的呢, 这个类不仅有类似object.wait 和 notify的作用。

同时还有指定唤醒哪个线程的作用(一个lock可以有多个condition,一个condition.signal()只能唤醒跟他相同condition.await()的线程,不同condition之间不能唤醒)

ConditionObject类里也有个链表结构condition queue

当获取到锁的线程A调用condition.await()时会创建一个节点加入到condition queue里面;会释放锁同时线程会阻塞。

如果这个时候另外一个线程B获取锁并通过condition.signal()会立刻唤醒A吗,答案是不一定。

因为这时如果wait queue里面有别的等待线程C,D..的话的.就不会马上唤醒A,因为condition.signal()会把线程A加入到wait queue的尾部

4.ReentrantReadWriteLock介绍

ReentrantReadWriteLock是读写锁,作用是

2个线程分别是写锁与写锁,互斥

2个线程分别是读锁与写锁,互斥

2个线程分别是写锁与读锁,互斥(这里要注意下,写入线程获取锁的同时可以再获取读取锁)

2个线程分别是读锁与读锁,异步,非互斥。

独立锁ReentrantLock 通过一个state字段区别锁是否被占用了,那读写锁怎么弄呢?,读锁和写锁应该由2个字段来判断占用的锁是读还是写吧。

显然现在一个state就不够用了。于是在ReentrantReadWrilteLock里面将这个字段一分为二,高位16位表示共享锁的数量,低位16位表示独占锁的数量(或者重入数量)

exclusiveCount是指写锁的数量,sharedCount是读锁的数量。

这里有位运算符,

x>>>y  表示 x向右移动多少位

x<<y  表示 x向左移动多少位 

x & y  表示 x二进制和y二进制 相同的位才合并,比如00010001 & 11111001 = 00010001 

        /*
         * Read vs write count extraction constants and functions.
         * Lock state is logically divided into two unsigned shorts:
         * The lower one representing the exclusive (writer) lock hold count,
         * and the upper the shared (reader) hold count.
         */

        static final int SHARED_SHIFT   = 16;
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

        /** Returns the number of shared holds represented in count  */
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
        /** Returns the number of exclusive holds represented in count  */
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
c >>> 16 右移16位, int 是32位,右移16位就是取前16位的值,
c & 2的16次 - 1 相对于c & 01111111 11111111 就是取后16位的值,
原文地址:https://www.cnblogs.com/shapeOfMyHeart/p/6667093.html