JAVASE(21)等待唤醒机制、悲乐观锁的内存可见性问题、线程池、定时器和设计模式

等待唤醒机制:

不同种类的线程之间的通信问题

  • 生产线程:生产出了资源,需要通知消费线程来消费
  • 消费线程:消费了资源,等待生产线程来生产

线程的随机性导致数据安全性问题

线程等待唤醒机制:
  • 作为生产线程来说,生产资源,通知消费线程来消费,等待消费线程来消费。
  • 作为消费线程来说,消费线程,通知生产喜爱能撑来生产。
  • 通过线程的
使用的方法:

void wait() 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。 wait()方法一但等待,就必须释放锁。唤醒之后多次线程还会再次争抢时间片。从哪里等待,被唤醒后还会在此处醒来。

void wait(long timeout) 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。
void wait(long timeout, int nanos) 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。

void notify() 唤醒在此对象监视器上等待的单个线程。
void notifyAll() 唤醒在此对象监视器上等待的所有线程。

面试题:

sleep方法和wait方法的区别:

共同点:可以使线程都处于阻塞状态

不同点:

  1. sleep必须设置时间量,wait可以设置时间量也可以不同设置。
  2. sleep方法在线程阻塞后,不释放锁。wait方法等待后会立即释放锁。

代码展示:

public class MyTest{
    public static void main(String[] args) {
       //线程的之间的等待唤醒机制,也就是生长者消费者模式
        //1.定义一个资源
        //2.要有一个生产线程
        //3.要有一个消费线程
        //4.测试类
        Student student = new Student();
        SetThread th1 = new SetThread(student);
        GetThread th2 = new GetThread(student);
        th1.start();
        th2.start();
    }
}

public class Student {
    public String name;
    public int age;
    public boolean flag = false;
    //这个标记的意思是,代表有没有资源
    // false代表没有资源,true 代表有资源
}

public class SetThread extends Thread {
    Student student;
    int i = 0;
    public SetThread(Student student) {
        this.student = student;
    }
    @Override
    public void run() {
        while (true) {
            synchronized (student) {
                //作为生产者来说:有了资源,等着,通知消费线程来消费
                if (student.flag) {
                    try {
                        student.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if (i % 2 == 0) {
                    student.name = "张三";
                    student.age = 23;
                    //等待...
                } else {
                    student.name = "李四";
                    student.age = 24;
                }
                //通知消费线程,去消费
                student.flag = true; //修改标记
                student.notify();//唤醒等待的线程,唤醒之后,多个线程还要再次争抢时间片
                i++;
            }
        }
    }
}

public class GetThread extends Thread {
    Student student;
    public GetThread(Student student) {
        this.student = student;
    }
    @Override
    public void run() {
        while (true) {
            synchronized (student) {

                if (!student.flag) {
                    try {
                        student.wait();
                        //没有资源等待,wait()方法一旦等待,
                        // 就必须释放锁,从哪里等待,被唤醒后,就从这里醒来
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //消费了资源,通知生产线程去生产
                System.out.println(student.name + "===" + student.age);
                student.flag = false;
                student.notify();
                //唤醒等待的 线程
            }
        }
    }
}

内存可见性问题:

一、 volatile 解决内存可见性问题

1.Java内存模型

想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的。

Java内存模型规定了所有的变量都存储在主内存中。每条线程中还有自己的工作内存,线程的工作内存中保存了被该线程所使用到的变量(这些变量是从主内存中拷贝而来)。线程对变量的所有操作(读取,赋值)都必须在工作内存中进行。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

2.Java中的可见性

对于可见性,Java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见。

注意:

  • volatile 不具备“互斥性”
  • volatile 不能保证变量的“原子性”(数据的重复出现,刷新太慢)

代码显示:

public class TestVolatile {
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        new Thread(td).start();
        while (true) {
            if (td.isFlag()) {
                System.out.println("------------------");
                break;
            }
        }
    }
}

class ThreadDemo implements Runnable {
    volatile private boolean flag = false;
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
        }
        flag = true;
        System.out.println("flag=" + isFlag());
    }
    public boolean isFlag() {
        return flag;
    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}
二、CAS算法

CAS算法:比较并交换,是一种硬件支持,比使用synchronized加锁的方式,更加的减少资源的使用.
下面的链接时关于CAS算法的详细的说明。
https://www.jianshu.com/p/21be831e851e

悲观锁和乐观锁

synchronized悲观锁,存在互斥性

volatileCAS算法都是乐观锁,不存在互斥性

线程的几种状态

  1. 新建状态:线程被创建出来
  2. 就绪状态:抢夺时间片中
  3. 运行状态:抢夺到CPU运行权
  4. 阻塞状态:抢夺到CPU运行权,但不能执行
  5. 死亡状态

[外链图片转存失败(img-Nq1FVaPH-1564485057675)(C:\Users\Administrator\Desktop\线程的运行状态图.jpg)]

采用匿名内部类开启线程

方式1:
public class Demo {
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                System.out.println("方法1执行了");
            }
        }.start();
    }
}
方式2:
public class Demo {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("方式2执行了");
            }
        }) {
        }.start();
    }
}

线程池

概念:

是一个容器,装有线程对象的容器

为什么要线程池?

在这里插入图片描述

虽然线程是个轻量级的东西, 但是对于互联网应用来说,如果每个用户的请求都创建一个线程,那会非常得多,服务器也是难于承受,再说了,众多的线程去竞争CPU,不断切换,也会让CPU调度不堪重负,很多线程将不得不等待。所以前辈们的思路就是(1)用少量的线程 (2) 让线程保持忙碌,就是说只创建一定数量的线程,让这些线程去处理所有的任务,任务执行完了以后,线程并不结束,而是回到线程池中去,等待接受下一个任务。

jdk5开始新新增了一个executors工厂类来产生线程池,有如下方法:

  1. public static ExecutorService newCachedThreadPool():根据任务的数量来创建线程对应的线程个数
  2. public static ExecutorService newFixedThreadPool(int nThreads):固定初始化几个线程
  3. public static ExecutorService newSingleThreadExecutor():初始化一个线程的线程池
  • 使用runnable的方法
    创建线程池对象
    创建Runnable实例
    提交Runnable实例 submit方法
    关闭线程池

代码实现:

public class Myrunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("这个线程执行了");
    }
}
public class Demo {
    public static void main(String[] args) {
        Myrunnable myrunnable = new Myrunnable();
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.submit(myrunnable);
    }
}
  • 使用callable的方法(可以获取返回值)

    ​ 创建线程池对象
    ​ 创建Callable实例
    ​ 提交Callable实例 submit方法
    ​ 关闭线程池

代码实现

public class Mycallable implements Callable {
    String s="你好";
    @Override
    public Object call() throws Exception {
        System.out.println("这是一个线程");
        return s;
    }
}
public class Demo {
    public static void main(String[] args) {
        Mycallable mycallable = new Mycallable();
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.submit(mycallable);
    }
}

定时器

概述

定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。

Timer和TimerTask

public void schedule(TimerTask task, long delay)延长多长时间后执行

public void schedule(TimerTask task,long delay,long period);往后延长多长时间后间隔period时长重复执行
public void schedule(TimerTask task, Date time);延长到日期后执行

public void schedule(TimerTask task, Date firstTime, long period):延长到日期后执行间隔period时长重复执行

public abstract void run()
public boolean cancel()取消执行任务

案例演示:

public class Mytimertask1 extends TimerTask {
    Timer timer;
    public Mytimertask1(Timer timer) {
        this.timer = timer;
    }
    @Override
    public void run() {
        System.out.println("这条语句执行了");
        timer.cancel();
    }
}
public class Test {
    public static void main(String[] args) throws ParseException {
        Timer timer = new Timer();
        String str="2019-07-30 14:56:00";
        Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(str);
        Mytimertask1 mytimertask1 = new Mytimertask1(timer);
        timer.schedule(mytimertask1,date);
    }
}

设计模式:

单例设计模式
  1. 饿汉式:
    public class Student {
        //饿汉式
        private static Student student=new Student();
        //创建一个静态的对象,静态的对象只能调用一次
        private Student(){};
        //私有化无参构造
        public static Student getStudent(){
            //创建一个静态的方法
            return student;
        }
    }
    public class TEst {
        public static void main(String[] args) {
            Student student = Student.getStudent();
        }
    }
    
  2. 懒汉式

public class Teacher {
         //懒汉式   
    private Teacher teacher=null;
    private Teacher() {};
        //私有化无参构造
    public synchronized Teacher getTeacher(){
        //乐观锁可以防止多线程产生的问题
        if (teacher==null){
            Teacher teacher = new Teacher();
        }
        return teacher;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值