学习JAVA并发编程之美笔记(一)

技术类书籍是拿来获取知识的,不是拿来收藏的,你得到了书籍不意味着你得到了知识,所以请不要得到书籍后就觉得沾沾自喜,要经常翻阅!!经常翻阅!

 Java并发编程线程基础

写在前面的话:这是我自己学习本书记录的感悟。

 Q1:什么是线程?


       解释线程前需要说清什么是进程,因为线程是进程中的一个实体,线程本身不会独立存在。进程是操作系统进行资源分配和调度的基本单位,线程是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的资源。操作系统分配资源是按照进程的,但是cpu分配资源是按照线程的。因为真正占用cpu运行的是线程,所以说线程是cpu分配的基本单位。

 Q2:线程通知与等待


 wait();
 当一个线程调用一个共享变量的wait()方法时,调用线程会被阻塞挂起。
 (1)其他线程调用该共享变量的notify()或notifyAll()方法。
 (2)其他线程调用该线程的interupt()方法,改线程抛出interuptedException异常返回。
 注意若是调用共享对象wait()方法的线程没有拿到该对象的监视器锁时会抛出IllegaMonitorStateException异常。

package com.example.thread;

import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;

/**
 * 生产者、消费者经典例子
 *
 * @Author qiulijun
 * @Date 16:33 2019/2/25
 * @Param
 * @return
 **/
@Slf4j(topic = "生产者、消费者")
public class Queue {

    public static ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(16, true);

    public static void production() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (queue) { // 拿到检索监视器锁
                    while (queue.size() == 10) {
                        try {
                            log.info("线程0 wait");
                            queue.wait();
                        } catch (InterruptedException e) {
                            log.error("线程等待,被中断", e);
                        }
                    }
                    for (int i = 0; i < 10; i++) {
                        log.info("添加i " + i);
                        try {
                            queue.put(i);
                        } catch (InterruptedException e) {
                            log.error("线程等待,被中断", e);
                        }
                    }
                    queue.notifyAll();
                }
            }
        });

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (queue) { // 拿到检索监视器锁
                    while (queue.size() == 0) {
                        try {
                            log.info("线程2 wait");
                            queue.wait();
                        } catch (InterruptedException e) {
                            log.error("线程等待,被中断", e);
                        }
                    }
                    log.info("---------------" + queue.size());
                    log.info("---------------" + queue);
                    try {
                        for (int i = 0; i < 10; i++) {
                            log.info("移除i " + i);
                            queue.take();
                        }
                    } catch (InterruptedException e) {
                        log.error("线程等待,被中断", e);
                    }
                    log.info("---------------" + queue);
                    queue.notifyAll();
                }
            }
        });
        thread.start();
        thread1.start();
    }

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

}

这是经典的生产者、消费者。在使共享变量wait时先将监视器锁拿到,所以不会抛出异常。通过判断队列大小,避免虚假唤醒。

在释放资源时当前线程只会释放调用的wait();方法的资源,假如线程持有多个共享资源其他资源不会被释放。

join();

会在调用线程中进行阻塞,等待其他线程执行完成。

package com.example.thread;

public class TestJoin {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("德玛西亚");
            }
        });

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("诺克萨斯");
            }
        });

        thread.start();
        thread1.start();
        System.out.println("。。。。");
        thread.join();
        thread1.join();
        System.out.println("。。。。");
    }
}

 

主线程进行到
thread.join();
thread1.join();
会进行阻塞,子线程进行执行。
输出结果为:
        。。。。
        德玛西亚
        诺克萨斯
        。。。。
假如将
//thread.join();
//thread1.join();
注释
输出结果为
        。。。。
        。。。。
        德玛西亚
        诺克萨斯

sleep();

Thread类中的静态方法,当一个执行中线程调用了sleep方法,线程会暂时让出指定时间的执行权。不参与cpu的调度,当时线程所拥有的监视器资源不会释放。

yield();

Thread类中有个一静态yield方法,当线程调用这个方法时,实际暗示线程调度器当前线程请求让出自己的cpu使用,但是线程调度器可以无条件忽略这个暗示。假如让出成功,会处于就绪状态。线程调度器会从就绪队列中取一个优先级最高的一个线程,有可能还是这个线程获得cpu执行权。

线程中断

interrupt();终端线程,线程A运行线程B将A线程interrupt方法掉用并且设置为true并立即返回。设置标记不会使A线程中断,会继续往下执行。A线程调用wait、join、sleep方法进入阻塞状态时,线程B调用interrupt会抛出中断异常IntrruptedExption返回。
package com.example.thread;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ArrayBlockingQueue;

@Slf4j(topic = "中断")
public class TestJoin {

    public static ArrayBlockingQueue a = new ArrayBlockingQueue<Integer>(10);

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("德玛西亚");
                synchronized (a) {
                    while (true) {
                        try {
                            a.wait();
                        } catch (InterruptedException e) {
                            log.error("阻塞状态被中断" ,e);
                        }
                    }
                }
            }
        });

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("诺克萨斯");
            }
        });


        thread.start();
        thread1.start();
        System.out.println("。。。。");
        thread.interrupt();
        thread.join();
        thread1.join();
        System.out.println("。。。。");
    }
}

输出结果

。。。。
诺克萨斯
德玛西亚
17:39:16.703 [Thread-0] ERROR 中断 - 阻塞状态被中断
java.lang.InterruptedException: null
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at com.example.thread.TestJoin$1.run(TestJoin.java:20)
	at java.lang.Thread.run(Thread.java:745)
isInterrupted();检测当前线程是否被中断,是返回true。
interrupted();检测当前线程是否被中断,是返回true并且清除终端标识。

Q3:理解线程上下文切换

 

       在多线程编程中,线程个数一般都大于 CPU 个数,而每个 CPU 时同一时刻只能被一个线程使用,为了让用户感觉多个线程是在同时执行的,CPU 资源的分配采用了时间片轮转策略,也就是给每个线程分配 个时间片,线程在时间片内占用 CPU 执行任务。当线程使用完时间片后,就会处于就绪状态并让出CPU让其他线程占用 这就是上下文切换从当前线程的上下文切换到了其他线程 那么就有个问题,让出 CPU 的线程等下次轮到自己占有 CPU 时如何知道自己之前运行到哪里了?所以在切换线程上下文时需要保存当前线程的执行现场,当再次执行时根据保存的执行现场信息恢复执行现场。

      线程上下文切换时机有当前线程的CPU时间片使用完处于就绪状态时,当前线程被其他线程中断时。


Q4:ThreadLocal

 

       我理解ThreadLocal是将一份共享变量,复制给每个线程。同时每个线程只会对这个变量进行读,所以共享变量不会改变。ThreadLocal操作变量是不需要加锁保证数据一致性。

public class Test {

    public static void main(String[] args) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                ThreadLocal threadLocal = new ThreadLocal();
                threadLocal.set("1");
                threadLocal.get();
            }
        });
    }
}
threadLocal.set("1");
内部实现
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);// 假如线程刚刚初始化map肯定为null.
    if (map != null)
        map.set(this, value); // 将当前线程与value存入,当前线程中threadLocals变量中
    else
        createMap(t, value);// 初始化当前线程中threadLocals属性
}

map.set(this, value);
内部实现
ThreadLocalMap getMap(Thread t) { // t是当前线程,t.threadLocals就能拿到当前线程中threadLocals属性
     return t.threadLocals;
}

createMap(t, value);
内部实现
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

map.set(this, value);
内部实现
private void set(ThreadLocal<?> key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
             ThreadLocal<?> k = e.get();

             if (k == key) {
                  e.value = value;
                  return;
             }

             if (k == null) {
                  replaceStaleEntry(key, value, i);
                  return;
             }
         }

         tab[i] = new Entry(key, value);
         int sz = ++size;
         if (!cleanSomeSlots(i, sz) && sz >= threshold)
             rehash();
}

很明显ThreadLocal的作用是将共享变存入线程中的工具。维护线程和变量的map在每个线程中。

但是ThreadLocal是不支持继承的!

public class Test {

    public static void main(String[] args) {
        ThreadLocal threadLocal = new ThreadLocal();
        threadLocal.set("1");
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                Object o = threadLocal.get();
                System.out.println(o);
            }
        });
        thread.start();
        Object o = threadLocal.get();
        System.out.println(o);
    }
}

输出结果:

1
null

Process finished with exit code 0

在主线程设置属性在子线程不能取到,如果想要取到值就需要通过inheritableThreadLocals这个属性。

public class Test {
    static InheritableThreadLocal threadLocal = new InheritableThreadLocal();
    public static void main(String[] args) {

        threadLocal.set("1");
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                Object o = threadLocal.get();
                System.out.println(o);
            }
        });
        thread.start();
        Object o = threadLocal.get();
        System.out.println(o);
    }
}
输出结果:
1
1

Process finished with exit code 0

使用InheritableThreadLocal能够继承属性原因:

 private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name.toCharArray();

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (parent.inheritableThreadLocals != null) // 判断父线程中inheritableThreadLocals 是否为空,不为空将其中值赋予当前线程。
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

第一章基本完成,为其他技术做铺垫。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值