JavaSE——多线程(二)(死锁,等待唤醒机制,内存可见性,CAS算法,sleep()和wait()的异同,线程池,定时器)

1.死锁

(1)概述

是两个或两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象,如果出现了同步嵌套,就容易出现死锁问题。

举例:中国人和美国人一起吃饭
中国人使用的筷子
美国人使用的刀和叉
中国人获取到了美国人的刀
美国人获取到了中国人的一根筷子
那么美国人和中国人都无法将吃饭进行下去

(2)代码演示
package com.westmo3.demo3;
public class MyTest {
    public static void main(String[] args) {
        Mythread1 mythread1 = new Mythread1(true);
        Mythread1 mythread2 = new Mythread1(false);
        mythread1.start();
        mythread2.start();
    }
}
class Mythread1 extends Thread{
   boolean flag=false;
    public Mythread1(boolean fla) {
        this.flag=fla;
    }
    @Override
    public void run() {
        while(true){
            if (flag){
                synchronized (ObjectUtils.objA){
                    System.out.println("objA锁被使用了");
                    synchronized (ObjectUtils.objB){
                        System.out.println("objtB锁被调用了");
                    }
                }
            }else{
                synchronized (ObjectUtils.objB){
                    System.out.println("objB锁被调用了");
                    synchronized (ObjectUtils.objA){
                        System.out.println("objA锁被调用了");
                    }
                }
            }
        }
    }
}
class ObjectUtils{//定义两个锁
    static Object objA=new Object();
    static Object objB=new Object();
}

2.线程间的等待唤醒机制(通信问题)

(1)为什么会有线程间的通信问题?

举例:我们有两个线程,生产者线程和消费者线程。生产者线程,生产了资源,就等着,然后通知消费者线程来消费。消费者线程,消费了资源,就等着,然后通知生产者进行生产。

 void wait ()  在其他线程调用此对象的 notify () 方法或 notifyAll () 方法前,导致当前线程等待。
 void notify () 唤醒在此对象监视器上等待的单个线程。
(2)代码演示
package com.westmo3.demo4;

public class MyDemo3 {
    public static void main(String[] args) {
        //需要实现生产一个资源,消费一个资源
        Student student = new Student();
        setThread st1 = new setThread(student);
        getThread gt1 = new getThread(student);
        st1.start();
        gt1.start();
    }
}
class setThread extends Thread{
    Student student;
    public setThread(Student student) {
        this.student=student;
    }
    int i=0;
    @Override
    public void run() {
        while(true){
            synchronized (student) {
                //先要判断有无资源,有资源就要等待(通知消费者消费),无资源才进行生产
                if (student.flag) {
                    try {
                        student.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                    //生产者生产资源
                    if (i % 2 == 0) {
                        student.setName("张三");
                        student.setAge(23);
                    } else {
                        student.setName("李四");
                        student.setAge(26);
                    }
                    //生产结束,就需要修改资源的状态
                    student.setFlag(true);
                    student.notify();
            }
            i++;
        }
    }
}
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();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //消费者消费资源
                System.out.println(Thread.currentThread().getName() + student.getName() + "=" + student.getAge());
                student.setFlag(false);
                student.notify();
        }
    }
}
class Student{
    private String name;
    private int age;
    boolean flag=false;//设置一个变量表示资源的状态 true代表有资源 false代表无资源
    public Student() {
    }
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

3.内存可见性问题

对于可见性,Java提供了volatile关键字来保证可见性,想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的。

(1)Java内存模型

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

(2)内存可见性问题原因
  • 当一个共享变量被volatile修饰时,它会保证修改的值立即更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
  • 一个线程对共享变量进行读取,赋值等操作时,这些操作都是在工作内存中进行的,操作完成后,需要将值重新写回主内存中,但是什么时候写回主内存是不确定的,当另外一个线程进来去对共享变量进行操作时,就可能还是原来的旧值,这就导致了内存可见性。
  • 而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
(3)synchronized和Lock锁保证可见性与volatile保证关键字的区别
  • synchronized和Lock:也可以保证可见性,它们的原理是能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
  • 当多个线程进行操作共享数据时,可以保证内存中的数据可见。 相较于 synchronized 是一种较为轻量级的同步策略,可以将volatile看做一个轻量级的锁。但是又与锁有些不同:对于多线程,不是一种互斥关系,不能保证变量状态的“原子性操作” 。
(4)代码演示
package com.westmo3.demo4;
/**
 * @Author: ShenMouMou
 * @Company:西部开源教育科技有限公司
 * @Description:简简单单,只为教育。
 */
public class MyTest {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
        while (true){
                if (myRunnable.getFlag()) { //false
                    System.out.println("进来了");
                    break;
                }
        }
    }
}
class MyRunnable implements Runnable{
   boolean flag = false;

    public boolean getFlag() {
        return flag;
    }
    @Override
    public void run() {
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //修改flag的值
        this.flag=true;
        System.out.println("run方法修改了flag的值是"+flag);
    }
}

4.CAS算法

(1)概述
  • CAS (Compare-And-Swap) 是一种硬件对并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问。
  • CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。
  • CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
(2)实现过程

在线程开启的时候,会从主存中给每个线程拷贝一个变量副本到线程各自的运行环境中,CAS算法中包含三个参数(V,E,N),V表示要更新的变量(也就是从主存中拷贝过来的值)、E表示预期的值、N表示新值。
在这里插入图片描述

(3)优点

这个算法相对synchronized是比较“乐观的”,它不会像synchronized一样,当一个线程访问共享数据的时候,别的线程都在阻塞。synchronized不管是否有线程冲突都会进行加锁。由于CAS是非阻塞的,它死锁问题天生免疫,并且线程间的相互影响也非常小,更重要的是,使用无锁的方式完全没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销,所以它要比锁的方式拥有更优越的性能。

(4)缺点
  • 循环时间长开销很大:
    我们可以看到getAndAddInt方法执行时,如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。

  • 只能保证一个共享变量的原子操作:
    当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。

  • 出现ABA问题:
    什么是ABA问题?ABA问题怎么解决?
    如果内存地址V初次读取的值是A,并且在准备赋值的时候检查到它的值仍然为A,那我们就能说它的值没有被其他线程改变过了吗?
    如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。这个漏洞称为CAS操作的“ABA”问题。Java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。因此,在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。

5.sleep()和wait()的区别

  • 这两个方法来自不同的类分别是Thread和Object
  • 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
  • wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)
  • sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
  • sleep是Thread类的静态方法。sleep的作用是让线程休眠指定的时间,在时间到达时恢复,也就是说sleep将在接到时间到达事件恢复线程执行。wait是Object的方法,也就是说可以对任意一个对象调用wait方法,wait方法将会将调用者的线程挂起,直到其他线程调用同一个对象的notify方法才会重新激活调用者。

6.线程池

(1)线程池概述
  • 程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互,而线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
  • 线程池里的每一个线程代码结束后,并不会死亡,而是回到线程池中成为空闲状态,等待下一个对象来使用。
(2)线程池使用概述

在jdk1.5之前,我们必须手动实现自己的线程池,从jdk1.5之后,Java内置支持线程池。它新增了一个Excutors工厂类来生产线程。有如下几个方法:

public static ExecutorService newCachedThreadPool():			根据任务的数量来创建线程对应的线程个数	
public static ExecutorService newFixedThreadPool(int nThreads):	固定初始化几个线程
public static ExecutorService newSingleThreadExecutor():			初始化一个线程的线程池

这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法:

Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T> task)
(3)使用步骤
创建线程池对象
创建Runnable实例
提交Runnable实例
关闭线程池
(4)案例演示
package com.westmo3.demo4;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyDemo {
    public static void main(String[] args) {
        //ExecutorService executorService = Executors.newFixedThreadPool(2);//可以固定初始化几个线程
        ExecutorService executorService = Executors.newSingleThreadExecutor();//初始化一个线程的线程池
        //往线程池提交任务
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务执行了");
            }
        });
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务执行了");
            }
        });
        //关闭线程池
        executorService.shutdown();
    }
}

7.匿名内部类的方式实现多线程程序

new Thread(){代码…}.start();
new Thread(new Runnable(){代码…}).start();

8.定时器

(1)概述

定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后线程的方式执行,在Java中,可以通过Timer和TimerTask类来实现定义调度的功能。

(2)Timer和TimerTask
Timer:
	public Timer():创建定时器
	public void schedule(TimerTask task, long delay):	经过delay毫秒后,执行task任务
	public void schedule(TimerTask task,long delay,long period);等待delay毫秒后,第一次执行task任务,然后间隔period毫秒重复执行任务
	public void schedule(TimerTask task,  Date time):指定日期执行任务
	public void schedule(TimerTask task,  Date firstTime, long period):
TimerTask:定时任务
	public abstract void run()
	public boolean cancel():取消定时任务/定时器
开发中
	Quartz是一个完全由java编写的开源调度框架。
(3)案例演示
package com.练习.定时器;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class Demo {
    public static void main(String[] args) throws ParseException {
        Timer timer = new Timer();
        //timer.schedule(new timertask(),2000);//2s后执行定时任务一次
        timer.schedule(new timertask(),3000,2000);//3s后执行一次,后面每隔2s执行一次
        String str="2020-02-25 15:24:40";
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(str);
        Date parse = simpleDateFormat.parse(str);
        timer.schedule(new timertask(),parse);//在特定的时间完成任务执行
    }
}
class timertask extends TimerTask{
    @Override
    public void run() {
        System.out.println("任务执行");
    }
}
(4)定时删除文件夹
package com.westmo3.demo2;
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Timer;
import java.util.TimerTask;
public class MyTest {
    public static void main(String[] args) throws ParseException {
        //定时删除文件夹
        Timer timer = new Timer();
        //提交定时任务
        DelTask delTask = new DelTask(timer);
        timer.schedule(delTask,new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2020-02-25 16:42:00"));
       // timer.cancel();
    }
}
class DelTask extends TimerTask{
    Timer timer;
    public DelTask(Timer timer) {
        this.timer=timer;
    }
    @Override
    public void run() {
        //删除文件夹
        File file = new File("D:\\123");
        delFolder(file);
        //关闭定时器
        timer.cancel();
    }
    private void delFolder(File folder) {
        File[] files = folder.listFiles();
        for (File f : files) {
            if(f.isFile()){
               f.delete();
            }else{
                delFolder(f);
            }
        }
        folder.delete();
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值