Java多线程基础-使用多线程(二)


|-目录
|  同步锁
  -|同步锁使用范围
  -|对象锁与静态锁
  -|死锁
|  volatile实现’内存共享’


-synchronized同步锁

1.同步锁使用范围
  同步锁使用场景:多个线程对同一个对象中的实例变量进行并发访问。
  方法体中声明的局部变量不需要同步处理。

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:200thread_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 关键字在方法声明中使用时,是起到锁的这样一个作用,作用:每次有且只有一个线程执行该方法的方法体。
2.对象锁与静态锁
  使用对象锁分为: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

  将ThreadSynchronizedObjectthreadMethodB改为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)代码块作用一样。
3.死锁
  出现死锁的情形:两个或多个线程处于永久等待状态,每个线程都等待其他线程释放所持有的资源(锁)。

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-11 volatile
private volatile boolean isPrint = true;
图1-12 volatile

  当sleep 1s后尝试停止线程,可从【图1-11】看出程序开关一直显示红色[运行状态],停止不了。这也就是第一章https://www.jianshu.com/p/d901b25e0d4a提到的,停止不了的死循环线程现象。
将isPrint变量增加volatile关键字后结果如【图1-12】程序正常退出。
  这里要提示一个技术点,如果将打印语句移到while循环里,同样的操作线程也能停止。关键点在println方法中有Synchronized结构体,synchronized作用于结构体时,作用:1)同步,2)让成员变量变为多线程可见;
故当我们又想让变量变为可见,又要同步则synchronized满足需求。

-总结

  这里主要介绍了synchronized对象锁,静态锁使用方式场景,volatile实现内存共享功能。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容

  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    胜浩_ae28阅读 5,102评论 0 23
  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    小徐andorid阅读 2,807评论 3 53
  • 一:java概述: 1,JDK:Java Development Kit,java的开发和运行环境,java的开发...
    慕容小伟阅读 1,788评论 0 10
  • 整理来自互联网 1,JDK:Java Development Kit,java的开发和运行环境,java的开发工具...
    Ncompass阅读 1,537评论 0 6
  • 一:java概述:1,JDK:Java Development Kit,java的开发和运行环境,java的开发工...
    ZaneInTheSun阅读 2,649评论 0 11