华杉研发九学习日记24 线程同步 死锁 线程通信 线程池 Callable接口

华杉研发九学习日记24

java多线程

一,线程同步

1.1 线程同步问题

多个线程同时操作一个全局数据或静态数据时可能会造成数据冲突

解决:

  • synchronized同步代码块
  • synchronized同步方法
  • 使用锁对象加锁解锁
//  火车站卖车票  --  100张
public class Train implements Runnable{
    int count = 100;    //一百张火车票,全局变量
    private boolean flag = true;
    @Override
    public void run() {
        while(flag){    //  永久开启窗口售卖火车票
            sale();
        }
    }
    public void sale(){
        //  设置一个停止售卖的条件
        if(count<=0){
            flag = false;
            return;
        }
        System.out.println(Thread.currentThread().getName()+ "窗口售出了第"+count+"张火车票!");
        count--;
    }
}
public class TestTrain {
    public static void main(String[] args) {
        //  创建火车票对象
        Train train = new Train();
        //  启动线程
        new Thread(train,"窗口一").start();
        new Thread(train,"窗口二").start();
        new Thread(train,"窗口三").start();
    }
}
//	测试结果卖出票会出现重复和顺序混乱等问题

1.2 同步代码块

Java中采用锁来保护共享资源,防止数据不一致的情况产生。下面我们就来介绍Java中的同步机制以及synchronized关键字。

在Java中,每个对象都拥有一个"互斥锁标记",这就好比是我们说的挂锁。这个锁标记,可以用来分给不同的线程。之所以说这个锁标记是"互斥的",因为这个锁标记同时只能分配给一个线程。

光有锁标记还不行,还要利用synchronized 关键字进行加锁的操作。

synchronized 关键字有两种用法

第一种: synchronized+代码块。

synchronized(obj){

	代码块;

}

synchronized关键字后面跟一个圆括号,括号中的是某一个引用,这个引用应当指向某一个对象。后面紧跟一个代码块,这个代码块被称为同步代码块""。
这种语法的含义是,如果某一个线程想要执行代码块中的代码,必须要先获得obj所指向对象的互斥锁标记。也就是说,如果有一个线程t1要想进入同步代码块,必须要获得obj对象的锁标记;而如果t1线程正在同步代码块中运行,这意味着t1有着obj对象的互斥锁标记;而这个时候如果有一个t2线程想要访问同步代码块,会因为拿不到obj 对象的锁标记而无法继续运行下去。

public void sale(){
    synchronized (object){//  设置一个停止售卖的条件
        if (count <= 0) {
            flag = false;
            return;
        }
        System.out.println(Thread.currentThread().getName() + "窗口售出了第" + count + "张火车票!");
        count--;
    }
}

1.3 同步方法

public synchronized void sale(){
    if (count <= 0) {
            flag = false;
            return;
        }
        System.out.println(Thread.currentThread().getName() + "窗口售出了第" + count + "张火车票!");
        count--;
}

1.4 使用锁对象

    private Lock lock = new ReentrantLock();//	创建锁对象
//  使用锁对象上锁
public void sale(){
    lock.lock();    //  上锁
    if (count <= 0) {
        flag = false;
        lock.unlock();  //  解锁
        return;
    }
    System.out.println(Thread.currentThread().getName() + "窗口售出了第" + count + "张火车票!");
    count--;
    lock.unlock();  //  解锁
}

二,死锁

如果同步代码块包裹多层时,可能会产生新的问题

(a等待b释放,b等待a释放)

synchronized(a){
    synchronized(b){
        代码块;
    }
}
synchronized(b){
    synchronized(a){
        代码块;
    }
}

解决死锁:

在Java中。采用了wait和notify这两个方法,来解决死锁机制。
首先,在Java中,每一个对象都有两个方法: wait和notify方法。这两个方法是定义在Object类中的方法。对某个对象调用wait(方法,表明让线程暂时释放该对象的锁标记。

synchronized(a){
    a.wait();
    synchronized(b){
        代码块;
    }
}
synchronized(b){
    synchronized(a){
        代码块;
        a.notify();
    }
}

在这里插入图片描述

由于可能有多个线程先后调用a对象 wait方法,因此在a对象等待状态中的线程可能有多个。而调用a.notify()方法,会从a对象等待状态中的多个线程里挑选一个线程进行唤醒。与之对应的,有一个notifyAll)方法,调用a.notifyAll会把a对象等待状态中的所有线程都唤醒。

wait作用是让当前线程阻塞,阻塞多久,取决于有没有其他线程唤醒它。notify作用是唤醒处于wait状态的线程。必须是同一个监视器下的线程。
notifyAll作用是唤醒所有处于wait状态的线程。必须是同一个监视器下的线程。

三,线程通信

不同线程之间可以相互的发信号。这就是线程通信。之所以需要进行线程通信,是因为有些时候,一个线程的执行需要依赖另外一个线程的执行结果。在结果到来之前,让线程等待(wait),有了结果只之后再进行后续的操作。对于另外一个线程而言,计算完结果,通知(notify)一下处于等待状态的线程.

线程通信借助的是Object类的wait,notify,notifyAll方法。

wait作用是让当前线程阻塞,阻塞多久,取决于有没有其他线程唤醒它。

notify作用是唤醒处于wait状态的线程。必须是同一个监视器下的线程。

notifyAll作用是唤醒所有处于wait状态的线程。必须是同一个监视器下的线程。

一般情况下,多线程里会出现线程同步的问题,我们不但要进行线程通信,还要解决线程同步的问题。

wait 与notify应用:生产者-消费者模式
这是一个比较经典的多线程场景。有商品的时候,消费者才可以消费,没有商品的时候,消费者等待。商品库存充足的时候,生产者等待,库存不满的时候,生产者生产商品。

四,线程池

什么是线程池?

水池:存放水的池子。线程池:存放线程的池子。

Java中的线程池:是一个管理线程的池子。可以在需要的时候开辟线程,可以控制最大开辟的线程个数,可以在不需要的时候关闭线程,可以让任务排队执行。这些管理过程不需要我们干预,线程池能帮我们完成。我们所要做的就是往线程池中放任务。
为什么要有线程池?

多线程解决了任务并发问题,但是开辟和关闭线程很消耗系统的性能,开辟和关闭一个线程要处理很多细节,频繁的开辟和关闭线程会给系统增加很多开销。

线程池使用了重用的概念,可以控制线程开辟的数量,复用这些线程执行任务。这样就不用频繁的开辟和关闭线程了。

线程池使用场景及优势
线程池适合处理的任务:执行时间短、工作内容较为单一。

合理使用线程池带来的好处:
1)降低资源消耗:重复利用已创建的线程降低线程创建和销毁造成的开销

​ 2)提高响应速度:当任务到达时。任务可以不用等待线程创建就能立即执行

​ 3)提高线程的可管理性:可以统一对线程进行分配、调优和监控

​ 4)提供更多强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池SchedulerThreadPoolExecutor,允许任务延期执行或定期执行

线程池的作用包括:
1):利用线程池管理并复用线程、控制最大并发数等

​ 2):实现任务线程队列缓存策略和拒绝机制

​ 3):实现某些与时间相关的功能。如定时执行、周期执行

​ 4)∶隔离线程环境。通过配置两个或多个线程池,将一台服务器上较慢的服务和其他服务隔离开,避免各服务线程相互影响。

三种常见的线程池

  • 固定线程个数的线程池
  • 不限线程个数的线程池
  • 单个线程的线程池(串行任务池)
public static void main(String[] args) {
    //  创建线程池
    ExecutorService es = Executors.newFixedThreadPool(2);
    //  提交任务    --  把任务提交给线程池,让其从线程池中获取一个线程,并执行其当前任务
    es.submit(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"你好");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    });
    es.submit(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"哟西");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    });
    es.submit(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"你好");
        }
    });
    //  关闭资源
    es.shutdown();
}

拷贝:

public class TestCopy {
    public static void main(String[] args) {
        //  1.创建线程池
        ExecutorService es = Executors.newFixedThreadPool(3);
        //  2.找到其文件夹遍历其中的文件。如果是文件,则把拷贝的任务提交给战程法
        File file = new File("C:\\Users\\86155\\Videos\\Counter-strike 2");
        File[] files = file.listFiles();
        for (File f : files) {
            if (f.isFile()) {
                //  把拷贝的任务交给线程池
                es.submit(new Runnable() {
                    @Override
                    public void run() {
                        try(BufferedInputStream fis = new BufferedInputStream(new FileInputStream(f));
                            BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream("C:\\Users\\86155\\Desktop\\张晓岚\\aa\\"+f.getName()))
                        ) {
                            byte[] buffer = new byte[1024];
                            int len;
                            while ((len = fis.read(buffer)) != -1) {
                                fos.write(buffer, 0, len);
                            }

                        } catch (FileNotFoundException e) {
                            throw new RuntimeException(e);
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }
                });
            }
        }
        es.shutdown();

    }
}

五,Callable接口

创建步骤;
1.创建一个子类,实现Callable接口

​ 2.重写其中的call方法

​ 3.创建EgtrueTask对象,封装Callable对象

​ 4.创建Thread对象,封装FutureTask对象

​ 5.使用start来启动线程

public class MyCall implements Callable {
    @Override
    public Object call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println("------"+i);
        }
        return 100;
    }
}
public class TestMyCall {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //  实现Callable接口方式
        MyCall myCall = new MyCall();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(myCall);
        new Thread(futureTask).start();
        System.out.println(futureTask.get());// 打印线程执行后返回的结果数据

        //  主线程
        for (int i = 0; i < 100; i++) {
            System.out.println("====="+i);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小孤鸡

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

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

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

打赏作者

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

抵扣说明:

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

余额充值