Lock、死锁、线程间通信、线程组、线程池,以及定时器

这一块的内容主要是有关死锁、线程间通信、线程组、线程池以及定时器的内容。

这一部分的内容,如果想搞得比较明白,最好先看一下上一篇大数据进阶25的内容。地址如下:
多线程

Lock

在上一篇大数据 进阶25-多线程 里面,虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5之后提供了一个新的锁对象Lock

Lock(接口):
void lock() 获得锁,加锁
void unlock() 释放锁
子类:
ReentrantLock
这也是解决线程同步安全的第二种方式

class SellTick1 implements Runnable{
    //定义票的个数
    private int tickets = 100;

    //定义锁对象
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try{
                lock.lock();
                if (tickets>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"正在出售第"+
                            (tickets--)+"张票");
                }
            }finally {
                //释放锁
                lock.unlock();
            }
        }
    }
}

public class SellTicketDemo1 {
    public static void main(String[] args) {
        SellTick1 s = new SellTick1();

        //创建三个线程窗口
        Thread t1 = new Thread(s, "窗口一");
        Thread t2 = new Thread(s, "窗口二");
        Thread t3 = new Thread(s, "窗口三");

        //启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

死锁

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

//定义两把锁,两个唯一不能被修改的静态锁对象
class MyLock{
    public static final Object lockA = new Object();
    public static final Object lockB = new Object();
}

class DeadLock extends Thread{
    private boolean flag;
    public DeadLock(boolean flag){
        this.flag = flag;
    }

    @Override
    public void run() {
        if(flag){
            synchronized (MyLock.lockA){
                System.out.println("if lockA");
                synchronized (MyLock.lockB){
                    System.out.println("if lockB");
                }
            }
        }else {
            synchronized (MyLock.lockB){
                System.out.println("else lockB");
                synchronized (MyLock.lockA){
                    System.out.println("else lockA");
                }
            }
        }
    }
}

public class DeadLockDemo {
    public static void main(String[] args) {
        DeadLock foreigner = new DeadLock(true);
        DeadLock chinese = new DeadLock(false);

        foreigner.start();
        chinese.start();
    }
}

线程间通信

我们之前写的电影票程序不是特别符合真是情况,所以我们在这之上对其做一个改进,引入线程间通信
在这里插入图片描述
示例代码1:

class Student1{
    String name;
    int age;
}

class SetThread implements Runnable{
    private Student1 s;

    public SetThread(Student1 s){
        this.s = s;
    }

    @Override
    public void run() {
        s.name = "A";
        s.age = 21;
    }
}

class GetThread implements Runnable{
    private Student1 s;

    public GetThread(Student1 s){
        this.s = s;
    }

    @Override
    public void run() {
        System.out.println(s.name+"---"+s.age);
    }
}

/*
学生类:Student
设置学生信息类:SetStudent(生产者)
获取学生信息类:GetStudent(消费者)
测试类

问题1:按照思路去写,发现每次出现的都是null---0
原因:我们在每个线程中都创建了新对象,而我们要求的是设置学生信息和获取学生信息的对象应该是同一个
如何实现:在外界把学生对象创建出来,通过构造方法传递给其他类

 */
public class StudentDemo1 {
    public static void main(String[] args) {
        Student1 s = new Student1();

        SetThread st = new SetThread(s);
        GetThread gt = new GetThread(s);

        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);

        t1.start();
        t2.start();
    }
}

示例代码2:

class Student2{
    String name;
    int age;
}

class SetThread2 implements Runnable{
    private Student2 s;
    private int x = 0;

    public SetThread2(Student2 s){
        this.s = s;
    }

    @Override
    public void run() {
        while (true){
            synchronized (Student2.class){
                if(x%2==0){
                    s.name = "A";
                    s.age = 21;
                }else {
                    s.name = "B";
                    s.age = 12;
                }
                x++;
            }
        }
    }
}

class GetThread2 implements Runnable{
    private Student2 s;

    public GetThread2(Student2 s){
        this.s = s;
    }

    @Override
    public void run() {
        while (true){
            synchronized (Student2.class){
                System.out.println(s.name+"---"+s.age);
            }
        }
    }
}

/*
学生类:Student
设置学生信息类:SetStudent(生产者)
获取学生信息类:GetStudent(消费者)
测试类

问题2:为了数据好看一些,更容易出现结果,我们加入了循环和判断,给出不同的值,但
        又出现了新的问题
        1、同一个数据出现了多次
        2、姓名和年龄不匹配
原因:
        1、同一个数据出现了多次
            CPU的一点点时间片的执行权,就足够执行很多次
        2、姓名和年龄不匹配
            线程运行的随机性
线程安全问题:
        1、是否是多线程环境      是
        2、是否又共享数据       是
        3、是否有多条语句操作共享数据     是
        
解决:
        加锁
        注意:
            1、不同种类的线程都要加锁
            2、不同种类的线程加的锁必须是同一把            
 */
public class StudentDemo2 {
    public static void main(String[] args) {
        Student2 s = new Student2();

        SetThread2 st = new SetThread2(s);
        GetThread2 gt = new GetThread2(s);

        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);

        t1.start();
        t2.start();
    }
}

等待唤醒机制

在这里插入图片描述
问题3:虽然我们的数据是安全的,但是,每一次我们都是一次获取一大片数据,我想依次一个一个的输出,如何实现呢?
通过Java提供的等待唤醒机制解决

等待唤醒:
Object类中提供了三个方法:

  • wait() 等待
  • notify() 唤醒正在等待对象监视器的单个进程
  • notifyAll() 唤醒正在等待对象监视器的所有进程
    为什么这些方法不定义在Thread类中呢?
    这些方法的调用必须由锁对象调用,而我们刚刚使用的是synchronized里面的锁对象与调用wait和notify的对象不一致,会出现异常IllegalMonitorStateException,只要锁对象一致,就不会出错。这个锁对象可以是任意对象,而你不确定是哪一个对象,但是我们知道所有类的父类是Object,所以,这些方法就定义在Object中
class Student3{
    String name;
    int age;
    boolean flag;//默认情况是没有数据的,默认是false,如果是true,说明由数据
}

class SetThread3 implements Runnable{
    private Student3 s;
    private int x = 0;

    public SetThread3(Student3 s){
        this.s = s;
    }

    @Override
    public void run() {
        while (true){
            synchronized (Student3.class){
                //判断有没有数据
                if (s.flag){
                    try {
                        Student3.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if(x%2==0){
                    s.name = "A";//刚走到这里,就被其他线程抢到了执行权
                    s.age = 21;
                }else {
                    s.name = "B";//刚走到这里,又被其他线程抢到了执行权
                    s.age = 12;
                }
                x++;
                s.flag = true;
                Student3.class.notify();
            }
        }
    }
}

class GetThread3 implements Runnable{
    private Student3 s;

    public GetThread3(Student3 s){
        this.s = s;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (Student3.class) {
                if (!s.flag) {
                    try {
                        Student3.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(s.name + "---" + s.age);
                s.flag = false;
                Student3.class.notify();
            }
        }
    }
}

public class StudentDemo3 {
    public static void main(String[] args) {
        Student3 s = new Student3();

        SetThread3 st = new SetThread3(s);
        GetThread3 gt = new GetThread3(s);

        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);

        t1.start();
        t2.start();
    }
}

线程转换的几种情形

在这里插入图片描述

线程组

Java可以使用 ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制

简单来说:线程组就是把多个线程组合到一起

class MyRunnable implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

public class ThreadGroupDemo {
    public static void main(String[] args) {
        fun1();
//        fun2();
    }

    private static void fun2() {
        //创建新的线程组
        //ThreadGroup(String name)
        ThreadGroup tg = new ThreadGroup("第一个线程组");

        MyRunnable my = new MyRunnable();
        Thread t1 = new Thread(my, "A");
        Thread t2 = new Thread(my, "B");

        System.out.println(t1.getThreadGroup().getName());
        System.out.println(t2.getThreadGroup().getName());

        //通过组名名称可以直接将组里的线程都设置为守护线程
        tg.setDaemon(true);

        t1.start();
        t2.start();
    }

    private static void fun1() {
        MyRunnable my = new MyRunnable();
        Thread t1 = new Thread(my, "A");
        Thread t2 = new Thread(my, "B");

        //ThreadGroup getThreadGroup()
        //返回此线程所属的线程组
        ThreadGroup tg1 = t1.getThreadGroup();
        ThreadGroup tg2 = t2.getThreadGroup();

        //String getName()
        //返回此线程组的名称
        System.out.println(tg1.getName());//main
        System.out.println(tg2.getName());//main
        System.out.println(tg1.getMaxPriority());//10
        //通过验证发现,线程默认情况下属于main线程组
    }
}

使用线程组写一个最终代码
1、把Student的成员变量都变成私有的
2、把生产和消费的操作封装成两个方法,并加入同步和唤醒机制
3、生产和消费的线程只需要调用方法即可

class Student4{
    private String name;
    private int age;
    private boolean flag;//默认是false

    //设置数据,相当于生产者,没有数据(false)就生产,有数据(true)就等待
    public synchronized void set(String name,int age){
        if(this.flag){
            try {
                this.wait();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        this.name = name;
        this.age = age;
        this.flag = true;
        this.notify();
    }

    //获取数据,相当于消费者,没有数据(false)就等待(wait),有数据(true)就消费
    public synchronized void get(){
        if(!this.flag){//加感叹号是为了顺利进入循环
            try {
                this.wait();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        System.out.println(this.name+"---"+this.age);
        this.flag = false;
        this.notify();
    }

}

class SetThread4 implements Runnable{
    private Student4 s;
    private int x = 0;

    public SetThread4(Student4 s){
        this.s = s;
    }

    @Override
    public void run() {
        while (true){
            if(x%2==0){
                s.set("A",21);
            }else{
                s.set("B",12);
            }
            x++;
        }
    }
}

class GetThread4 implements Runnable{
    private Student4 s;
    public GetThread4(Student4 s){
        this.s = s;
    }

    @Override
    public void run() {
        while (true){
            s.get();
        }
    }
}

public class StudentDemo4 {
    public static void main(String[] args) {
        Student4 s = new Student4();

        SetThread4 st = new SetThread4(s);
        GetThread4 gt = new GetThread4(s);

        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);

        t1.start();
        t2.start();
    }
}

线程池

线程池的好处:
线程池的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用
如何实现线程池:
1、创建一个线程池对象,控制要创建几个线程对象
public static ExecutorServe newFixedThreadPool(int nThreads)
创建一个线程池,指定线程池大小
2、哪些线程可以放到线程池执行
可以执行Runnable对象或者Callable对象代表的线程
3、如何执行
Future<?> submit(Runnable task)
提交一个可运行的任务执行,并返回一个表示该任务的未来。
Future submit(Callable task)
提交值返回任务以执行,并返回代表任务待处理结果的Future。
4、想结束任务怎么办
void shutdown()
启动有序关闭,其中先前提交的任务将被执行,但是不会接受任何新任务

class MyRunnable2 implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
    }
}

public class ExecutorsDemo {
    public static void main(String[] args) {
        //创建一个线程池对象,控制要创建几个线程对象
        ExecutorService fixpool = Executors.newFixedThreadPool(2);

        //使用匿名内部类的形式将Runnable对象或者Callable对象放进去并执行
        fixpool.submit(new MyRunnable2());
        fixpool.submit(new MyRunnable2());

        fixpool.shutdown();
    }
}

多线程的第三种实现方式

第三种方式:实现Callable接口,必须和线程池结合使用

class MyCallable implements Callable {
    @Override
    public Object call() {
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
        return null;
    }
}

public class CallableDemo {
    public static void main(String[] args) {
        //创建线程池对象
        ExecutorService fixpool = Executors.newFixedThreadPool(2);

        MyCallable c1 = new MyCallable();
        MyCallable c2 = new MyCallable();

        fixpool.submit(c1);
        fixpool.submit(c2);

        fixpool.shutdown();
    }
}

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

匿名内部类的格式:
new 类名或者接口名(){
重写方法
};
本质:该类或者是接口的子类对象

public static void main(String[] args) {
        //继承Thread类来实现多线程
//        new Thread(){
//            @Override
//            public void run() {
//                for(int i=0;i<10;i++){
//                    System.out.println("继承Thread类:"+Thread.currentThread().getName()+
//                    "---"+i);
//                }
//            }
//        }.start();
//
//        //实现Runnable接口
//        new Thread(new Runnable() {
//            @Override
//            public void run() {
//                for(int i=0;i<10;i++){
//                    System.out.println("实现Runnable接口:"+Thread.currentThread().getName()+
//                    "---"+i);
//                }
//            }
//        }).start();

        //执行的是Thread本身的fun方法,
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<10;i++){
                    System.out.println("实现Runnable接口2:"+Thread.currentThread().getName()+
                            "---"+i);
                }
            }
        }){
            @Override
            public void run() {
                for(int i=0;i<10;i++){
                    System.out.println("继承自Thread类2:"+Thread.currentThread().getName()+
                            "---"+i);
                }
            }
        }.start();
    }

定时器

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

Timer:定时
Timer() 创建一个新的计时器
void schedule(TimerTask task,long delay)在指定的延迟之后安排指定的任务执行
void schedule(TimerTask task,long delay,long period)在指定的延迟之后开始,重写执行
void cancel()终止此计数器,丢弃任何当前计划的任务
TimerTask:任务

class MyTask extends TimerTask{
    private Timer timer;

    public MyTask(Timer timer){
        this.timer = timer;
    }

    @Override
    public void run() {
        System.out.println("计时结束" );
        timer.cancel();
    }
}

public class TimerDemo {
    public static void main(String[] args) {
        //创建一个计时器对象
        Timer timer = new Timer();
//        timer.schedule(new MyTask(timer),3000);

        //void schedule(TimerTask task,long delay,long period)
        //在指定的延迟之后开始 ,重新执行固定延迟执行的指定任务。
        timer.schedule(new MyTask(timer),3000);
    }
}

感谢阅读,我是啊帅和和,一位大数据专业即将大四学生,祝你快乐。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啊帅和和。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值