多线程编程基础之 wait()、notify()、sleep()、join()、yield()、synchronized关键字Lock锁等

前言:
在面试过程中 关于多线程编程这一块是经常问到的 为了更好的理解关于多线程编程基础特地的记录此文章把思路理清楚
线程的生命周期

  1. 首先线程一般是这样创建的:

new Thread(){run(){....}}.start();
new Thread(new Runnable(){run().....}).start();

2.来看一个经典案例 生产者和消费者的问题

    static class Product{
        public static String value;
    }
    //生产者线程
    static  class Producer extends Thread{
        @Override
        public void run() {
            while (true){
                if (Product.value==null){
                    Product.value="No"+System.currentTimeMillis();
                    System.out.println("产品:"+Product.value);
                }
            }
        }
    }
    //消费者线程
    static  class Consumer extends Thread{
        @Override
        public void run() {
            while (true){
                if (Product.value!=null){
                    Product.value=null;
                    System.out.println("产品已消费");
                }
            }
        }
    }
//调用:
public static void main(String[] args){
        new Producer().start();//开启生产
        new Consumer().start();//开启消费
    }
产品:null
产品已消费
产品:null
产品:No1492225732628
产品已消费
产品已消费
产品:No1492225732628
产品:No1492225732629

thread

读操作会优先读取工作内存的数据,如果工作内存中不存在,则从主内存中拷贝一份数据到工作内存中;写操作只会修改工作内存的副本数据,这种情况下,其它线程就无法读取变量的最新值
解决:
在解决这个问题的时候先来了解下Java中关于多线程中使用的一些关键字和一些方法的作用

关键字 作用
volatile 线程操作变量可见
Lock Java6.0增加的线程同步锁
synchronized 线程同步锁
wait() 让该线程处于等待状态
notify() 唤醒处于wait的线程
notifyAll() 唤醒所有处于wait状态的线程
sleep() 线程休眠
join() 使当线程处于阻塞状态
yield() 让出该线程的时间片给其他线程

注意:
1. wait()、notify()、notifyAll()都必须在synchronized中执行,否则会抛出异常
2. wait()、notify()、notifyAll()都是属于超类Object的方法
2. 一个对象只有一个锁(对象锁和类锁还是有区别的)

一. 使用volatile关键字:

 static class Product{
        //添加volatile关键字
        public volatile static String value;
    }
//调用
public static void main(String[] args){
        new Producer().start();//开启生产
        new Consumer().start();//开启消费
    }
产品:No1492229533263
产品已消费
产品:No1492229533263
产品已消费
产品:No1492229533263
产品已消费
产品:No1492229533263
产品已消费
产品:No1492229533263
产品已消费
产品:No1492229533263
产品已消费

Volatile: 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。volatile关键字会强制将修改的值立即写入主存,使线程的工作内存中缓存变量行无效。
缺点:但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值。Volatile 变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束。
二. 使用synchronized线程同步锁

static class Product{
        public static String value;
    }
//生产者线程
    static  class Producer extends Thread{
        Object object;
        public Producer(Object object) {
            this.object = object;
        }
        @Override
        public void run() {
            while (true) {
                synchronized (object) {
                    if (Product.value == null) {
                        Product.value = "No" + System.currentTimeMillis();
                        System.out.println("产品:" + Product.value);
                    }
                }
            }
        }
    }
//消费者线程
    static  class Consumer extends Thread{
        Object object;
        public Consumer(Object object) {
            this.object=object;
        }
        @Override
        public void run() {
            while (true) {
                synchronized (object) {
                    if (Product.value != null) {
                        Product.value = null;
                        System.out.println("产品已消费");
                    }
                }
            }
        }
    }
//调用
public static void main(String[] args){
        Object object=new Object();
        new Producer(object).start();//开启生产
        new Consumer(object).start();//开启消费
    }
产品:No1492244495050
产品已消费
产品:No1492244495050
产品已消费
产品:No1492244495050
产品已消费
产品:No1492244495050
产品已消费
产品:No1492244495050

上面通过synchronizedObject object=new Object();加锁 也叫对象锁 实现锁的互斥,当生产线程生产产品的时候会对object加锁 消费者线程会进入阻塞状态 直到生产线程完成产品的生产释放锁 反之也是同样的道理。但是这样只能被动的唤醒线程的执行 可以使用wait和notify来进行主动唤醒线程继续执行

//生产者线程
    static class Producer extends Thread {
        Object object;
        public Producer(Object object) {
            this.object = object;
        }
        @Override
        public void run() {
            while (true) {
                //对象锁
                synchronized (object) {
                    if (Product.value != null) {
                        try {
                            object.wait();//产品还未消费 进入等待状态
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    Product.value = "No" + System.currentTimeMillis();
                    System.out.println("产品:" + Product.value);
                    object.notify();//产品已生产 唤醒消费者线程
                }
            }
        }
    }
//消费者线程
    static class Consumer extends Thread {
        Object object;
        public Consumer(Object object) {
            this.object = object;
        }
        @Override
        public void run() {
            while (true) {
                synchronized (object) {
                    if (Product.value == null) {
                        try {
                            object.wait();//产品为空 进入等待状态
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    Product.value = null;
                    System.out.println("产品已消费");
                    object.notify();//产品已经消费 唤醒生产者线程生产
                }
            }
        }
    }
public static void main(String[] args){
        Object object=new Object();
        new Producer(object).start();//开启生产
        new Consumer(object).start();//开启消费
    }
产品:No1492246274190
产品已消费
产品:No1492246274190
产品已消费
产品:No1492246274190
产品已消费

通过添加wait notify关键字去主动唤醒生产者或消费者线程的执行
三.线程同步之ReentrantLock锁
与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁,缺点就是缺少像synchronized那样隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性,可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。

Lock lock=new ReentrantLock();
public void setSpData(String name){
        lock.lock();
        try {
            //线程同步操作 比如IO读写 等
        }finally {
            lock.unlock();
        }
    }

注意:
注意的是,千万不要忘记调用unlock来释放锁,否则可能会引发死锁等问题,
而用synchronized,JVM将确保锁会获得自动释放,这也是为什么Lock没有完全替代掉synchronized的原因
四.sleep(),join(),yield()的使用

sleep()让线程休息指定的时间,时间一到就继续运行

 new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);//休眠3秒 毫秒为单位
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();

join()让指定的线程先执行完再执行其他线程,而且会阻塞主线程

 static class B extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println("线程一:" + 1);
            }
        }
    }
static class A extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println("线程二:" + 1);
            }
        }
    }
public static void main(String[] args) {
        A a = new A();
        B b = new B();
        try {
            a.start();//启动线程
            a.join();//join 该线程优先执行 其他线程进入等待
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            b.start();//启动线程
            b.join();//join 该线程优先执行 其他线程进入等待
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
线程二:0
线程二:1
线程二:2
线程二:3
线程二:4
线程一:0
线程一:1
线程一:2
线程一:3
线程一:4

yield()将指定线程先礼让一下别的线程的先执行

注意:yield()会礼让给相同优先级的或者是优先级更高的线程执行,不过yield()这个方法只是把线程的执行状态打回准备就绪状态,所以执行了该方法后,有可能马上又开始运行,有可能等待很长时间

static class B extends Thread {
        String name;
        public B(String name) {
            this.name=name;
        }
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(name + i);
                if(i==3){
                    System.out.println("将时间片礼让给别的线程");
                    Thread.yield();
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
public static void main(String[] args) {
        new B("线程一:").start();
        new B("线程二:").start();
    }
线程一:0
线程一:1
线程一:2
线程一:3
将时间片礼让给别的线程
线程二:0
线程二:1
线程二:2
线程二:3
将时间片礼让给别的线程
线程二:4
线程一:4

其他:

  1. 线程优先级:通过setPriority(int priority)设置线程优先级提高线程获取时间的几率(这只是提高线程优先获取时间片的几率 而不是肯定优先执行) getPriority()获取线程的优先级 最高为10 最低为1 默认为5
public static void main(String[] args) {
        A a = new A("线程一:");
        B b = new B("线程二:");
        a.setPriority(3);
        b.setPriority(10);
        a.start();
        b.start();
    }
  1. 守护线程:前面所讲的是用户线程 其实还有一个守护线程,起作:用只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。守护线程的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者,在编写程序时也可以自己设置守护线程。
static class B extends Thread {
        String name;
        public B(String name) {
            this.name=name;
        }
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(name + i);
            }
        }
    }
    static class A extends Thread {
        String name;
        public A(String name) {
            this.name=name;
        }
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                System.out.println(name + i);
            }
        }
    }
public static void main(String[] args) {
        A a = new A("守护线程执行:");
        B b = new B("用户线程执行:");
        a.setDaemon(true);//设置a线程为守护线程 必须在start()前设置 不然或有异常
        a.isDaemon();//判断a线程是否为守护线程
        a.start();
        b.start();
    }
守护线程执行:0
守护线程执行:1
守护线程执行:2
守护线程执行:3
守护线程执行:4
守护线程执行:5
守护线程执行:6
守护线程执行:7
守护线程执行:8
用户线程执行:0
用户线程执行:1
用户线程执行:2
用户线程执行:3
用户线程执行:4
守护线程执行:9
守护线程执行:10
守护线程执行:11
守护线程执行:12
守护线程执行:13
守护线程执行:14
守护线程执行:15
守护线程执行:16
守护线程执行:17
Process finished with exit code 0

当用户线程执行完毕后 JVM虚拟了退出前 守护线程随着JVM一同结束工作
设置线程为守护线程 必须在start()前设置 不然或有异常

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

推荐阅读更多精彩内容