微信号:guolin_blog

介绍:Android技术分享平台,在这里不仅可以学到各种Android相关的最新技术,还可以将你自己的技术总结分享给其他人,每周定期更新.

基础为王的今天,不可忽视的Java多线程知识

2019-04-03 08:01 三刀流剑客



今日科技快讯


4月2日,小米集团发布公告称,经小米董事会决议,雷军批准,根据股份奖励计划奖励合共2246. 6万股奖励股份予299名选定参与者。授出奖励当日的股份收市价为11.36港元,即总价值约2.2亿元人民币。所有选定参与者均为集团雇员,其中一名为集团高级管理层成员。奖励股份的归属期为一年三个月至十年。


作者简介


本篇文章来自三刀流剑客的投稿,和大家分享了Java多线程基础的相关知识,希望对大家有所帮助!

三刀流剑客的博客地址:

https://www.jianshu.com/u/ea4b5d51b1c4


synchronized同步锁


同步锁使用范围

同步锁使用场景:多个线程对同一个对象中的实例变量进行并发访问。

方法体中声明的局部变量不需要同步处理。

public class ThreadPrivateNumDemo {
    public static void main(String[] args) {
        final PrintPrivateNum privateNum = new PrintPrivateNum();
        Thread thread_1 = new Thread("thread_1") {
            public void run() {
                privateNum.printNum(Thread.currentThread().getName());
            };
        };
        Thread thread_2 = new Thread("thread_2") {
            @Override
            public void run() {
                privateNum.printNum(Thread.currentThread().getName());
            }
        };
        thread_1.start();
        thread_2.start();
    }
}

class PrintPrivateNum {
    public void printNum(String name) {
        int num = 0// 局部变量不需要同步锁
        if ("thread_1".equals(name)) {
            num += 300;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if ("thread_2".equals(name)) {
            num -= 100;
        }
        System.out.println(Thread.currentThread().getName() + ",Num:" + num);
    }
}

图1-1 同步锁[安全的局部变量]

从【图 1-1】可以验证安全的局部变量这句话,两个线程同时操作num变量,结果都是正常的。

class PrintPrivateNum {
    private int num = 0// 全局变量需要同步

图1-2 同步锁[实例变量]

将num局部变量改为全局变量,可以看出结果与想要的不太一样,我们想要的结果或许是thread_2,Num:300 thread_1,Num:200或thread_1,Num:-100 thread_2,Num:200,不会是【图1-2】中的结果,【图1-2】结果产生的原因是:thread_2修改Num=300后sleep,此时thread_1也同时进来修改了Num= 300 -100,故打印时Num已经变成了200。防止上述情况出现一般在方法声明加上synchronized关键字。

public synchronized void printNum(String name)

图1-3 同步锁[实例变量]

使用了synchronized 关键字则结果始终是【图1-3】,原理说明:synchronized 关键字在方法声明中使用时,是起到锁的这样一个作用,作用:每次有且只有一个线程执行该方法的方法体。

对象锁与静态锁

使用对象锁分为:synchronized(this)锁,synchronized(非this对象)锁。synchronized(this)锁与synchronized关键字在方法声明是一样的作用,优点都是解决多线程同步问题。synchronized(非this对象),对比与synchronized(this)的优点:提高多个方法同步的效率问题。

public class ThreadSynchronizedDemo {
    public static void main(String[] args) throws InterruptedException {
        final ThreadSynchronizedObject object = new ThreadSynchronizedObject();
        Thread thread_1 = new Thread("thread_1") {
            public void run() {
                try{
                    object.threadMethodA();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
        };
        Thread  thread_2 = new Thread("thread_2") {
            public void run() {
                try{
                    object.threadMethodB();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
        };
        thread_1.start();
        thread_2.start();
        Thread.sleep(3000);
        long start_time = (ThreadSynchronizedTimeUtils.mMethodAIntoTime - ThreadSynchronizedTimeUtils.mMethodBIntoTime) > 0 ? ThreadSynchronizedTimeUtils.mMethodAIntoTime
                : ThreadSynchronizedTimeUtils.mMethodBIntoTime;
        long end_time = (ThreadSynchronizedTimeUtils.mMethodAOutTime - ThreadSynchronizedTimeUtils.mMethodBOutTime) > 0 ? ThreadSynchronizedTimeUtils.mMethodAOutTime
                : ThreadSynchronizedTimeUtils.mMethodBOutTime;
        System.out.println("总耗时:" + (end_time - start_time));
    }
}

class ThreadSynchronizedObject {

    public synchronized void threadMethodA() throws InterruptedException {
        ThreadSynchronizedTimeUtils.setMethodAIntoTime();
        System.out.println(Thread.currentThread().getName() + ",进入threadMethodA");
        Thread.sleep(1000); ///<模拟方法请求耗时
        System.out.println(Thread.currentThread().getName() + ",退出threadMethodA");
        ThreadSynchronizedTimeUtils.setMethodAOutTime();
    }

    public void threadMethodB() throws InterruptedException {
        synchronized (this) {
            ThreadSynchronizedTimeUtils.setMethodBIntoTime();
            System.out.println(Thread.currentThread().getName() + ",进入threadMethodB");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + ",退出threadMethodB");
            ThreadSynchronizedTimeUtils.setMethodBOutTime();
        }
    }
}

class ThreadSynchronizedTimeUtils {

    public static long mMethodAIntoTime;
    public static long mMethodAOutTime;
    public static long mMethodBIntoTime;
    public static long mMethodBOutTime;

    public static void setMethodAIntoTime() {
        mMethodAIntoTime = System.currentTimeMillis();
    }

    public static void setMethodAOutTime() {
        mMethodAOutTime = System.currentTimeMillis();
    }

    public static void setMethodBIntoTime() {
        mMethodBIntoTime = System.currentTimeMillis();
    }

    public static void setMethodBOutTime() {
        mMethodBOutTime = System.currentTimeMillis();
    }
}

图1-4 对象锁

从上面代码以及结果可以得出两个结论:

  1. synchronized关键字与synchronized(this)是同一把锁(this对象)因为两个线程方法进入与退出始终是成对出现。

  2. synchronized(this)锁使多线程同步执行方法体中的内容。

这里有一个奇怪的现象出现,将main线程sleep(3000)放到获取end_time后打印start_time与end_time始终为0;【莫非是内存释放了?】

class ThreadSynchronizedObject {
    private Object object  = new Object();

    public synchronized void threadMethodA() throws InterruptedException {
        ThreadSynchronizedTimeUtils.setMethodAIntoTime();
        System.out.println(Thread.currentThread().getName() + ",进入threadMethodA");
        Thread.sleep(1000); ///<模拟方法请求耗时
        System.out.println(Thread.currentThread().getName() + ",退出threadMethodA");
        ThreadSynchronizedTimeUtils.setMethodAOutTime();
    }

    public void threadMethodB() throws InterruptedException {
        synchronized (object) {
            ThreadSynchronizedTimeUtils.setMethodBIntoTime();
            System.out.println(Thread.currentThread().getName() + ",进入threadMethodB");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + ",退出threadMethodB");
            ThreadSynchronizedTimeUtils.setMethodBOutTime();
        }
    }
}

图1-5 对象锁.jpg

将ThreadSynchronizedObject 类threadMethodB改为synchronized (非this)锁,效率如【图1-5】提升了一倍,故synchronized(非this)锁适用于各个实例方法都需要同步操作时。

静态锁:应用在static静态方法上,锁为当前*.java文件的Class类。

public class ThreadSynchronizedStaticDemo {
    public static void main(String[] args) {
        Thread thread_1 = new Thread("thread_1"){
            public void run() {
                synchronizedStaticService.methodA();
            };
        };
        Thread thread_2 = new Thread("thread_2") {
            public void run() {
                synchronizedStaticService.methodB();
            };
        };
        thread_1.start();
        thread_2.start();
    }
}

class synchronizedStaticService {
    public static synchronized void methodA() {
        try{
            System.out.println("" + Thread.currentThread().getName() + ",开始methodA()!");
            Thread.sleep(1000);
            System.out.println("" + Thread.currentThread().getName() +  ",退出methodA()!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void methodB() {
        synchronized(synchronizedStaticService.class) {
            try{
                System.out.println("" + Thread.currentThread().getName() + ",开始methodB()!");
                Thread.sleep(1000);
                System.out.println("" + Thread.currentThread().getName() + ",退出methodB()!");
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

图1-6 静态锁

从【图1-6】看出methodA与methodB两个方法都是按照顺序执行完成,可见静态方法中synchronized关键字与synchronized(当前类.class)代码块作用一样。

死锁

出现死锁的情形:两个或多个线程处于永久等待状态,每个线程都等待其他线程释放所持有的资源(锁)。

public class ThreadDealDemo {
    public static void main(String[] args) {
        final DealService dealService = new DealService();
        Thread thread_1 = new Thread("thread_1") {
            public void run() {
                dealService.methodA();
            };
        };
        Thread thread_2 = new Thread("thread_2") {
            public void run() {
                dealService.methodB();
            };
        };
        thread_1.start();
        thread_2.start();
    }
}

class DealService {
    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void methodA() {
        System.out.println("" + Thread.currentThread().getName() + ",等待获取lock1");
        synchronized (lock1) {
            try {
                System.out.println("" + Thread.currentThread().getName() + ",持有lock1");
                Thread.sleep(2000);
                System.out.println("" + Thread.currentThread().getName() + ",等待获取lock2");
                synchronized (lock2) {
                    System.out.println("" + Thread.currentThread().getName() + ",持有lock2");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void methodB() {
        System.out.println("" + Thread.currentThread().getName() + ",等待获取lock2");
        synchronized (lock2) {
            try {
                System.out.println("" + Thread.currentThread().getName() + ",持有lock2");
                Thread.sleep(2000);
                System.out.println("" + Thread.currentThread().getName() + ",等待获取lock1");
                synchronized (lock1) {
                    System.out.println("" + Thread.currentThread().getName() + ",持有lock1");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

图1-7 死锁

从【图1-7】结果来看,进程一直处于运行状态,thread_1等待获取thread_2所持有的lock_2,thread_2等待获取thread_1所持有的lock_1,这样进程不借助外力的情况下,处于永久等待状态。

图1-8 死锁

图1-9 死锁

图1-10 死锁

可以使用Java JDK自带工具jsp检测进程中死锁问题。

步骤

  1. jsp查询根据结果找出进程号;eg:上述工程进程名为ThreadDealDemo进程号为46580.

  2. jstrck -l 进程号获取当前进程堆栈信息,找到deallock信息。可以如【1-10】有相应的死锁堆栈信息,方便找到死锁对应点。


volatile实现“内存共享


volatile作用:使变量在多个线程可见。

volatile实现“共享内存”的解释:使用volatile让原本‘私有堆栈’中的操作,变成‘公共堆栈’中操作,这样内存在每个线程中都可见了。

图1-11 线程数据操作

public class ThreadVolatileDemo {
    public static void main(String[] args) throws InterruptedException {
        ThreadVolatileRunnable runnable = new ThreadVolatileRunnable();
        Thread thread = new Thread(runnable, "thread_1");
        thread.start();
        Thread.sleep(1000);
        runnable.setPrint(false);
    }
}

class ThreadVolatileRunnable implements Runnable {
    private boolean isPrint = true;

    public void setPrint(boolean flag) {
        this.isPrint = flag;
        if(!flag)
            System.out.println("" + Thread.currentThread().getName() + ",尝试让线程退出!");
    }

    public void run() {
        int num =0;
        while (isPrint) {
            num++;
        }
        System.out.println("" + Thread.currentThread().getName() + ",停止运行!num:" + num);
    }
}

图1-12 volatile

private volatile boolean isPrint = true;

图1-13 volatile

当sleep 1s后尝试停止线程,可从【图1-12】看出程序开关一直显示红色[运行状态],停止不了。

将isPrint变量增加volatile关键字后结果如【图1-13】程序正常退出。

这里要提示一个技术点,如果将打印语句移到while循环里,同样的操作线程也能停止。关键点在println方法中有Synchronized结构体,synchronized作用于结构体时,作用:

  1. 同步

  2. 让成员变量变为多线程可见

故当我们又想让变量变为可见,又要同步则synchronized满足需求。


推荐阅读:

Android组件化最佳实践-ARetrofit

或许你可以从这个角度去理解Handler

Kotlin的特性应用示例,原来还可以这么玩


欢迎关注我的公众号,学习技术或投稿

长按上图,识别图中二维码即可关注

 
郭霖 更多文章 抽奖月月有,好书送不停 吃透请求流程!深入分析okhttp3源码 无论什么级别的程序员,「微服务架构」都是你必须过的坎! 通过王者荣耀来学习策略模式 Android转型大数据?全套教程入门+提升+高薪开发必备!
猜您喜欢 第一篇 谈谈互联网后端基础设施 分享图片 【直播预告】荧火:手把手教你做有意思的设计 让神奇的Unity动画插件激活您的游戏