大数据进阶26-Lock死锁线程间通信线程组线程池,以及定时器

Posted 啊帅和和。

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了大数据进阶26-Lock死锁线程间通信线程组线程池,以及定时器相关的知识,希望对你有一定的参考价值。

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

这一部分的内容,如果想搞得比较明白,最好先看一下上一篇大数据进阶25的内容。地址如下:
大数据进阶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) {
        //创建一个线程池对象&#

以上是关于大数据进阶26-Lock死锁线程间通信线程组线程池,以及定时器的主要内容,如果未能解决你的问题,请参考以下文章

Java程序设计多线程进阶

死锁Lock锁等待唤醒机制线程组线程池定时器单例设计模式_DAY24

线程的几个主要概念----线程间通信;线程死锁;线程控制:挂起停止和恢复(线程同步的5种方式)

Java多线程

java并发线程池

线程间的通信 与 线程池