Java多线程基础三

Java多线程基础三,第1张

Java多线程基础三

wait和notify方法
wait():让当前线程进入等待状态
notify()/notifyAll():唤醒在当前对象上等待的线程。
注意: wait, notify, notifyAll 都是 Object 类的方法

wait做了三件事:
1.让当前线程阻塞等待。(让这个线程的PCB从就绪队列拿到等待队列中)并准备接受通知
2.释放当前锁。要想使用wait/notify,必须搭配synchronized.需要先获取到锁,才有资谈wait。
《步骤1和2是要原子的完成,全部执行或全部不执行》
3.满足一定的条件被唤醒时(其他线程通过notify *** 作使这个阻塞的线程释放锁,并不是自己主动释放),这个线程再重新尝试获取到这个锁。

wait 结束等待的条件:
1.其他线程调用该对象的 notify 方法
2.wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).
3.其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常.

关于notify的使用:
1.也要放到synchronized中使用
2.notify *** 作是一次唤醒一个线程,如果有多个线程都在等待中,调用notify相当于随机唤醒了一个,其他线程保持原状。
3.调用notify这是通知对方被唤醒,但是调用notify本身的线程并不是立即释放锁,而是要等待当前的synchronized代码块执行完才能释放锁。(notify本身不会释放锁)

调用notifyAll,就是把三个PCB都唤醒了,都放回到就绪队列(虽然把这三个线程都放回去了,但是这三个线程的执行仍然是有先有后的,主要是这三个线程还要竞争锁)

static class WaitTask implements Runnable{
        private Object locker;
        public WaitTask(Object locker) {
            this.locker = locker;
        }

        @Override
        public void run() {
            synchronized (locker){
                try {
                    System.out.println("wait开始");
                    locker.wait();
                    System.out.println("wait结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    static class NotifyTask implements Runnable{
        private Object locker;
        public NotifyTask(Object locker) {
            this.locker = locker;
        }

        @Override
        public void run() {
            synchronized (locker){
                System.out.println("notify开始");
                locker.notifyAll();
                System.out.println("notify结束");
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();
        Thread t10 = new Thread(new WaitTask(locker));
        Thread t11 = new Thread(new WaitTask(locker));
        Thread t12 = new Thread(new WaitTask(locker));
        Thread t2 = new Thread(new NotifyTask(locker));

        t10.start();
        t11.start();
        t12.start();

        Thread.sleep(3000);
        t2.start();
    }

wait和sleep的区别【面试题】
1.等待时间上:sleep *** 作是指定一个固定时间来阻塞等待,wait的话既可以指定时间,也可以无限等待。
2.唤醒方式上:wait唤醒可以通过notify或者interrupt或者时间到来唤醒,sleep唤醒通过时间到或者interrupt唤醒
3.用途:wait主要的用途就是为了协调线程之间的先后顺序,这样的场景并不适合sleep。sleep单纯让该线程休眠,并不涉及到多个线程的配合。

多线程案例

单例模式(某个类只应该有唯一实例)

饿汉模式:static 在类加载阶段就把实例创建出来

public class ThreadDemo{
    //饿汉模式
    static class Singleton{
        //把构造方法设置成private,防止在类外面调用构造方法,也就禁止了调用者在其他地方创建实例的机会
        private Singleton(){}
        private static Singleton instance = new Singleton();
        public static Singleton getInstance(){
            return instance;
        }
    }
    public static void main(String[] args) {

    }
}

饿汉模式是线程安全的,不涉及修改 *** 作。

懒汉模式:通过getInstance方法来获取到实例。首次调用该方法的时候,才真正创建实例

public class ThreadDemo {
    static public class Singleton{
        private Singleton(){}
        private static Singleton instance = null;
        public static Singleton getInstance(){
            if(instance==null){
                instance = new Singleton();
            }
            return instance;
        }
    }
    public static void main(String[] args) {
        //通过这个方法来获取到实例,就能保证只有唯一实例
        Singleton singleton = new Singleton();
    }
}

如果实例已经创建完毕,后续再调用getInstance,此时不涉及修改 *** 作,此时线程仍然是安全的。
但是如果实例尚未创建,此时就可能会涉及修改,如果确实存在多个线程同时修改,就会涉及到线程安全问题。

如何解决线程安全问题:加锁
锁加在哪里:此处的线程不安全,主要是因为(if *** 作和 =) *** 作不是原子的。要想解决这里的线程安全问题,就需要把这两个 *** 作变成原子的。需要使用synchronized把它们给包裹上。

package java100_0926;

public class ThreadDemo22 {
    static public class Singleton2{
        //懒汉模式  加锁改进
        private Singleton2(){}
        private static Singleton2 instance = null;
        public static Singleton2 getInstance(){
            synchronized (Singleton2.class){
                if(instance==null){
                    instance = new Singleton2();
                }

            }
            return instance;
        }
    }
    public static void main(String[] args) {
        //通过这个方法来获取到实例,就能保证只有唯一实例
        Singleton2 singleton = new Singleton2();
    }
}

再接着改进:首次调用的时候加锁,后续调用的时候不加锁。

if(instance==null){
	synchronized (Singleton.class){
    	if(instance==null){
    		Singleton instance = new Singleton();
    	}
  	}
}

理解两个(==null)的不同 需要具有可能是一批线程进入了if语句内

还有一个很重要的问题!
当首批线程通过第一层if,进入到锁阶段,并创建好对象之后,这个时候,相当于已经把内存中instance的值修改为非null了。
后续批次的线程,通过第一层if的时候,也需要判定instance的值,但是这个判定不一定是从内存读的数据,也可能是从寄存器读的数据。

为了解决上述内存可见性问题:加volatile 防止内存优化

private static volatile Singleton instance =null;

阻塞队列:线程安全的队列
如果当前队列为空,尝试出队列,就会产生阻塞。一直阻塞到队列里元素不空。
如果当前队列满了,尝试入队列,也会产生阻塞。一直阻塞到队列里的元素不满为止。

Java标准库中内置了一个BlockingQueue这样的类来实现阻塞队列的功能,用法跟普通队列相似,入队列,出队列,没有取队首元素。

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.linkedBlockingQueue;

public class BlockQueue_TestDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue queue = new linkedBlockingQueue<>();
        // put带有阻塞功能,但是offer不带有。使用阻塞队列一般都是使用 put
        queue.put("hello");
        String elem = queue.take();
        System.out.println(elem);
        elem = queue.take();
        System.out.println(elem);
    }
}

有了阻塞队列,就可以实现生产者消费者模型

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.linkedBlockingQueue;

public class BlockQueue_TestDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue queue = new linkedBlockingQueue<>();

        // 消费者线程
        Thread customer = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    // 取队首元素
                    try {
                        Integer value = queue.take();
                        System.out.println("消费元素:"+value);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        customer.start();
        // 生产者线程
        Thread producer = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 10000 ; i++) {
                    System.out.println("生产了元素" + i);
                    try {
                        queue.put(i);
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        producer.start();
    }
}

模拟实现生产者消费者模型

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.linkedBlockingQueue;

public class BlockQueue_TestDemo {
    private int[] items = new int[1000];
    private int head = 0;
    private int tail = 0;
    private int size = 0;
    // 锁对象
    private Object locker = new Object();
    // 入队列
    public void put(int item) throws InterruptedException {
        synchronized (locker){
            while (size == items.length){
                // 队列已满,对于阻塞队列来说就要阻塞
                locker.wait();
            }
            items[tail] = item;
            tail++;
            // 如果达到末尾,就回到起始位置
            if(tail>=items.length){
                tail=0;
            }
            size++;
            // 唤醒take中的wait
            locker.notify();
        }
    }
    // 出队列
    public int take() throws InterruptedException {
        int ret = 0;
        synchronized (locker){
            while (size==0){
                locker.wait();
            }
            ret = items[head];
            head++;
            if(head>=items.length){
                head=0;
            }
            size--;
            // 唤醒put中的wait
            locker.notify();
        }
        return ret;
    }
    public static void main(String[] args){
        BlockingQueue queue = new linkedBlockingQueue<>();
        // 生产者线程
        Thread producer = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i < 1000; i++) {
                    System.out.println("生产了元素"+i);
                    try {
                        queue.put(i);
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        });
        producer.start();
        // 消费者线程
        Thread cumtomer = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        int value = queue.take();
                        System.out.println("消费了元素:" + value);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        });
        cumtomer.start();

        try {
            cumtomer.join();
            producer.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

定时器

标准库中提供了Timer类,Timer类的核心方法为schedule
schedule包含两个参数,第一个参数指定即将要执行的任务代码,第二个参数指定多长时间之后执行

import java.util.Timer;
import java.util.TimerTask;

public class BlockQueue_TestDemo {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello world");
            }
        },3000);
    }
}

定时器的构成:

一个带优先级的阻塞队列队列中的每个元素是一个Task对象Task中带有一个时间属性,队首元素就是即将执行的任务同时有一个worker线程一直扫描队首元素,看队首元素是否需要执行

一个定时器里面可以安排很多任务,这些任务会按照时间,谁先到时间,就先执行谁。

import java.util.TimerTask;
import java.util.concurrent.PriorityBlockingQueue;

public class BlockQueue_TestDemo {
    static class Task implements Comparable{
        private Runnable command;
        private long time;

        public Task(Runnable command, long time) {
            this.command = command;
            this.time = System.currentTimeMillis()+time;
        }

        public void run(){
            command.run();
        }

        @Override
        public int compareTo(Task o) {
            return (int)(time-o.time);
        }
    }
    static class Timer{
        // 使用这个带优先级版本的阻塞队列来组织这些任务
        private PriorityBlockingQueue queue = new PriorityBlockingQueue<>();
        // 存在的意义是避免worker线程出现忙等的情况
        private Object locker = new Object();

        public void schedule(Runnable command,long delay){
            Task task = new Task(command,delay);
            queue.put(task);

            // 每次插入新的任务都要唤醒扫描线程,让扫描线程能够重新计算wait的时间,保证新的任务也不会错过
            synchronized (locker){
                locker.notify();
            }
        }

        public Timer(){
            // 创建一个扫描线程,这个扫描线程就来判定当前的任务,看看是不是已经到时间能执行了
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        // 取出队列的首元素,判定时间是不是到了
                        try {
                            Task task = queue.take();
                            long curTime = System.currentTimeMillis();
                            if(task.time > curTime){
                                // 时间没到 暂时不执行
                                queue.put(task);
                                synchronized (locker){
                                    locker.wait(task.time-curTime);
                                }
                            }else {
                                task.run();
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            break;
                        }
                    }
                }
            });
            t.start();
        }

    }
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello world");
            }
        },3000);
    }
}

欢迎分享,转载请注明来源:内存溢出

原文地址: https://www.outofmemory.cn/zaji/5712240.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-18

发表评论

登录后才能评论

评论列表(0条)

保存