美文网首页
java多线程-01-基本概念

java多线程-01-基本概念

作者: hylexus | 来源:发表于2016-11-26 21:21 被阅读24次

    [TOC]

    声明

    该系列文章只是记录本人回顾java多线程编程时候记录的笔记。文中所用语言并非严谨的专业术语(太严谨的术语其实本人也不会……)。难免有理解偏差的地方,欢迎指正。
    另外,大神请绕路。不喜勿喷。
    毕竟好记性不如烂笔头嘛,而且许多东西只要不是你经常用的最终都会一丢丢一丢丢地给忘记。

    1 几个名词

    1.1 多线程

    简单理解,就是同一段程序同时以多条路径执行。
    而这里的“同时”一词表示的含义就看你具体的机器配置了:你有多个CPU或者多核,当然是同时并发执行了;如果你是单核单CPU那就只能是CPU时间片的切换来执行了。

    执行以下代码,可以看看一个普通java程序执行的时候到底有几个线程在运行:

    public static void main(String[] args) {
        List<ThreadInfo> infos = Arrays.asList(//
                ManagementFactory.getThreadMXBean().dumpAllThreads(false, false)//
        );
        infos.stream().forEach(e -> {
            System.out.println(e.getThreadId() + ":" + e.getThreadName());
        });
    }
    

    在本人的机器上,有如下输出:

    5:Attach Listener
    4:Signal Dispatcher
    3:Finalizer
    2:Reference Handler
    1:main
    

    1.2 线程上下文切换

    上面所说的多线程,避免不了的就是线程上下文的切换。
    举个简单的例子:
    你一边写着数据库操作的代码,同时你还要看看是否你的代码真的对数据库产生了你预期的影响。那么你就要不时地在代码和数据库查询之间来回切换。这来回切换当然是要花时间的。

    多线程也一样,多个线程之间的切换肯定是要付出代价的(比如线程堆栈信息的记录和恢复等)。
    所以,有些情况下,多线程可能不一定比单线程的效率好。
    比如,你的需求就是执行1+2+3这么简单的任务,你非要弄两个线程来计算。那一般情况下肯定是单线程比较快了。
    这就好比于你非要用hadoop或Spark来计算1+1一样的效率了……

    所以,不要为了使用多线程而使用多线程。

    1.3 线程死锁

    对于死锁的概念,大家肯定都有些认识了。
    比如操作系统原理中所讲的“哲学家进餐”等经典的死锁示例,虽然这个例子是讲的进程死锁,但是原理都是一样的。
    原因大致都是由于多个线程之间争用资源导致的。

    下面这段示例程序,一定会发生死锁:

    public class MyDeadLockDemo {
        private static String RESOURCE_A = "RESOURCE_A";
        private static String RESOURCE_B = "RESOURCE_B";
    
        public void playDemo() {
            new Thread(() -> {
                synchronized (RESOURCE_A) {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (RESOURCE_B) {
                        System.out.println("Thread A");
                    }
                }
            }).start();
    
            new Thread(() -> {
                synchronized (RESOURCE_B) {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (RESOURCE_A) {
                        System.out.println("Thread A");
                    }
                }
            }).start();
    
        }
    
        public static void main(String[] args) {
            new MyDeadLockDemo().playDemo();
        }
    }
    

    该示例中,第一个线程锁住了资源A等待资源B,第二个线程锁住了资源B等待资源A。所以一定会发生死锁。

    1.4 CAS

    请参看这位道友的文章:http://www.cnblogs.com/lintong/p/4373723.html

    2 java中和多线程有关的知识

    2.1 synchronized

    相比于下面要提到的volatile,synchronized更加的重量级。平时我们在一些源码看到的更多的往往是synchronized。

    用synchronized关键字修饰的方法或者代码块,当一个线程要访问该代码块的时候必须先获得锁,当然在访问结束(包括异常)的时候应该适当的释放锁。

    ** 同时也只能有一个线程处于代码块或方法中(排他性) ,进入代码块或方法的线程需要获取对象的监视器 **。

    ArrayList和Vector的size方法

    当然,获取锁和释放锁都是有性能开销的。据说,在java1.6中进行了优化。

    2.2 volatile

    在一定的角度来说volatile是轻量级的synchronized。
    它能保证当一个线程修改一个共享变量时,另外一个线程能读到这个已经被修改过的值。也就是说能确保所有线程看到这个变量的值是一致的。具体的底层原理就牵扯到CPU和主存以及两者之间的cache了。

    另外,volatile不会像synchronized那样需要线程上下文的切换。所以它比synchronized更加低成本。

    总之,任何对volatile修饰的成员变量的访问都都直接从共享内存中获取,对他的更改必须直接写回主存中。

    2.3 java中的锁

    在java中,任何对象都可以作为锁来使用。

    • 非静态的同步方法,锁是当前对象(this)
    • 静态同步方法,锁是当前类(.class或者说是Class类的实例)
    • 同步的方法块,锁是synchronized关键字所包围的对象

    通常所说的一个线程获得了锁,意味着:

    • 获得锁,获得了执行被同步代码的资格
    • 释放锁,可以向获取同一个锁的线程发送“消息”(notify,notifyAll)
    • 释放锁,该线程对本地变量的修改都会同步到主存中

    2.3.1 偏向锁

    同一个锁,往往会被同一个线程多次获得。

    如果程序没有竞争,则取消之前已经取得锁的线程的同步操作。
    也就是说,若某一锁被线程获取后,进入“偏向模式”,当线程再次请求这个锁时,就无需再进行相关的同步操作了。
    从而省去了同步所需的时间,等到竞争出现了,则锁退出偏向模式。

    相关的JVM参数

    //偏向锁启动的延时
    -XX:BiasedLockingStartupDelay=0
    //关闭偏向锁
    -XX:-UseBiasedLocking=false
    

    2.3.2 轻量级锁

    这里所说的轻量级锁是相对于传统的锁而言的。
    轻量级锁一般是是线程交替执行同步块。

    如果存在同一时间访问同一锁的情况,就会导致轻量级升级为重量级锁。

    2.3.3 重量级锁

    重量级锁一般而言指的是传统的java锁的实现方式。

    2.3.4 锁的对比

    **注:以下表格来自《Java并发编程的艺术》一书 **

    几种锁的对比

    2.4 java线程通信

    虽然java里也有消息传递这种第三方的线程通信模型,但我们常说的java线程通信还是指的是共享内存模型。

    假设两个线程A和B通信,至少有如下方式:

    • A修改volatile变量,B读这个volatile变量
    • A修改volatile变量,B用CAS更新这个volatile变量
    • A用CAS更新一个volatile变量,B用CAS更新这个volatile变量
    • A用CAS更新一个volatile变量,B读这个volatile变量

    参考文章

    相关文章

      网友评论

          本文标题:java多线程-01-基本概念

          本文链接:https://www.haomeiwen.com/subject/bwugpttx.html