Java-多线程篇

Java-多线程篇

进程和线程定义

  • 进程是程序执行的一次过程,是CPU资源分配的基本单位,是动态概念。程序是指令和数据的集合,是静态概念。
  • 进程包含多个线程,一个进程中至少一个线程,是CPU调度和执行的基本单位。

线程的创建

  • 三种创建方式:继承Thread类,实现Runnable接口和实现Callable接口

继承Thread类

  • 自定义类继承Thread类
  • 重写run()方法
  • start()方法启动

多线程实现下载图片

public class DownloadPic  extends  Thread{

    private String url;
    private String name;

    public DownloadPic(String url,String name){
        this.url = url;
        this.name = name;
    }

    @Override
    public void run(){
        WebDownloader download = new WebDownloader();
        download.downloader(this.url,this.name);
     }

    public static void main(String[] args) {
        DownloadPic downloadPic1 = new           DownloadPic("https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1941888822,907972535&fm=26&gp=0.jpg","beautifulGirl1.jpg");
        DownloadPic downloadPic2 = new DownloadPic("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1595331205729&di=f528ef2e10989fb43e7fd28db5bca45e&imgtype=0&src=http%3A%2F%2Fwww.1bie.com%2Fwp-content%2Fuploads%2Fimg%2Fqxhbewud%2F26.jpg","beautifulGirl2.jpg");
        downloadPic1.start();
        downloadPic2.start();
    }
}

注:存在单继承的局限性。

实现Runnable接口(推荐使用)

  • 自定义类实现Runnable接口
  • 重写run方法
  • 创建线程类对象,启动start()方法
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();

注:避免单继承的局限性,方便同一个对象被多个线程同时使用。

龟兔赛跑


//模拟龟兔赛跑
public class Race implements Runnable{
    private String winner;

    @Override
    public void run() {

        for(int step = 1;step<=100;step++){
            //判断比赛是否结束
            boolean gameFlag = justifyGame(step);
            if(gameFlag){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"跑了"+step+"步");
        }
    }

    //判断比赛是否结束
    public boolean justifyGame(int step){
        //龟或者兔到达终点
        if(winner!=null) {
            return true;
        }
        if(step>=100){
            winner = Thread.currentThread().getName();
            System.out.println("Winner is "+winner);
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        Race race = new Race();
        new Thread(race,"tortoise").start();
        new Thread(race,"rabbit").start();
    }

}

实现Callable接口(了解)

  • 实现Callable接口,需要返回值类型
  • 重写Call方法,需要抛出异常
  • 创建目标对象
  • 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
  • 提交执行:Futureresult1 = ser.submit(目标对象);
  • 获取结果:boolean r1 = result1.get();
  • 关闭服务:ser.shutdownNow();

多线程下载图片:

import java.util.concurrent.*;

//实现Callable接口,需要返回值类型
public class TestCallable implements Callable<Boolean> {

        private String url;
        private String name;

        public TestCallable(String url,String name) {
            this.url = url;
            this.name = name;
        }

//重写Call方法,需要抛出异常
    @Override
    public Boolean call(){
        WebDownloader download = new WebDownloader();
        download.downloader(this.url,this.name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建目标对象
        TestCallable downloadPic1 = new TestCallable("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1595345665378&di=c37ce385fb1ca50b919889b2699b71fd&imgtype=0&src=http%3A%2F%2F51modo.cc%2Fupload%2Fkindeditor%2Fimage%2F20150302%2F20150302161834_22804.jpg","beautifulGirl3.jpg");
        TestCallable downloadPic2 = new TestCallable("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1595345753641&di=1180be11e57564fee9e6187451e782a2&imgtype=0&src=http%3A%2F%2F01.minipic.eastday.com%2F20170113%2F20170113174441_4a700387e67e0119e06a111ee2292bf7_10.jpeg","beautifulGirl4.jpg");
        //创建执行服务
        ExecutorService ser = Executors.newFixedThreadPool(2);
        //提交服务执行
        Future<Boolean> result1 = ser.submit(downloadPic1);
        Future<Boolean> result2 = ser.submit(downloadPic2);
        //获取结果
        boolean rs1 = result1.get();
        boolean rs2 = result2.get();
        //关闭服务
        ser.shutdownNow();
    }

}

注:Callable特点-有返回值和可抛异常。

静态代理

  • 真实对象和代理对象都要实现同一个接口
  • 代理对象要代理真实角色(婚庆公司代理结婚)
  • 好处:代理对象可以做真实对象做不了的事情。真实对象专注做自己的事情。
   new Thread(race,"tortoise").start();

注:Thread类似于婚庆公司,race为真实对象。

Lamda表达式()

  • 希腊字母的第十一位字母λ
  • 避免匿名内部类使用太多
  • 其实质属于函数式编程
  • (参数)->语句

函数式接口:接口中只有一个抽象方法

//简化过程:成员内部类->静态内部类->局部内部类->匿名内部类->lambda简化
public class TestLambda{

    public static void main(String[] args) {
        Ilike like = ()->System.out.println("I like you");
        like.lambda();
    }
}
//定义一个函数式接口
interface Ilike{
    void lambda();
}

注:如果lambda语句有多行,需使用花括号。多个参数可以去掉参类型,但不能够去掉括号。

线程状态

  • 五种状态:创建(new)、就绪(start)、执行、阻塞(sleep)、结束

  • 方法:setPriority()–更改线程的优先级

    ​ static void sleep()–线程休眠

    ​ void join()–等待该线程停止

    ​ static void yield()–暂停当前正在执行的线程,并执行其它线程

    ​ void interrupt()–中断线程,不推荐使用

    ​ boolean isAlive()–判断线程是否处于活动状态

  • 停止线程:建议正常停止。1、利用次数。2、设置标志位。不要使用stop()、destroy()

    public class StopThread implements Runnable{
        //设置标志位
        private boolean flag = true;
    
        @Override
        public void run() {
            int i = 0;
            while(flag){
                System.out.println("执行的次数:"+i++);
            }
        }
    
        //转换标志位,结束进行进程
        public void stop(){
            flag = false;
        }
    
        public static void main(String[] args) {
            StopThread stopThread = new StopThread();
            new Thread(stopThread).start();
            for (int i = 0; i < 100;i++) {
                System.out.println("main thread:"+i);
                if(i == 50){
                    stopThread.stop();
                    System.out.println("son thread stop");
                }
            }
        }
    }
    
  • 线程休眠

    • sleep()设置当前进程休眠时间

    • sleep()存在InterruptedException异常

    • sleep()时间到达后,线程进入就绪状态

    • sleep()可以模拟网络延时(放大问题),倒计时

    • 每个对象都有一个锁,sleep不会释放锁

sleep实现计时:

import java.text.SimpleDateFormat;

//模拟计时
public class SleepThread {
    public static void main(String[] args) throws InterruptedException {
        //时间表示形式
        SimpleDateFormat dateFormat= new SimpleDateFormat("hh:mm:ss");
        while(true){
            //Date类获取当前时间
            //Date dateTime = new Date();
            System.out.println(dateFormat.format(System.currentTimeMillis()));
            Thread.sleep(1000);
        }
    }
}
  • 线程礼让(yield())

    • 当前正在执行的线程暂停,但不阻塞,进入就绪状态
    • CPU重新调度执行,礼让不一定成功
    public class YieldThread implements Runnable{
    
        @Override
        public void run() {
            System.out.println("线程"+Thread.currentThread().getName()+"开始执行");
            Thread.yield();
            System.out.println("线程"+Thread.currentThread().getName()+"结束执行");
        }
    
        public static void main(String[] args) {
            YieldThread yieldThread = new YieldThread();
            new Thread(yieldThread,"A").start();
            new Thread(yieldThread,"B").start();
        }
    }
    
  • **线程强制阻塞:**join()

    • 阻塞其它正在执行线程,“插队”执行其它线程,直到线程执行完成,才切换其它线程执行
public class JoinThread implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("线程:"+Thread.currentThread().getName()+"执行"+ i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        JoinThread joinThread  = new JoinThread();
        Thread thread = new Thread(joinThread,"B");
        thread.start();

        for (int i = 0; i < 100; i++) {
            if(i == 50){
               thread.join();
            }
            System.out.println("主线程执行"+i);
        }
    }
}
  • 线程状态规则:Thread.state—NEW,RUNNABLE,BLOCKED,WAITING,TIMED WAITING,TERMINATED

    • Thread.State state = thread.getState();
  • 线程优先级

    • 线程调度器按照线程优先级调度线程
    • 线程优先级用数字表示:Thread.MIN_PRiORITY = 1;Thread.MAX_PRiORITY = 10;Thread.NORM_PRiORITY = 5;数字越大,优先级越高。
    • 获取优先级:getPriority()。设置优先级:setPriority(int xx)
    • 优先级低只是获得调度的概率低。
  • 守护线程:daemon

    • 线程分为用户线程和守护线程
    • 虚拟机需确保用户进程执行完毕,不需要确保守护进程执行完毕
    • 守护进程如后台记录操作日志,垃圾回收和监控内存等
    • thread.setDaemon(true) -设置为守护线程

线程同步

  • 多个线程访问同一个对象,称之为线程并发。这些线程有修改对象的操作,需要使用线程同步解决。线程同步是一种等待机制,多个访问该对象的线程进入对象的等待池形成队列,等待前面线程操作完毕后,下一个线程再使用。
  • 线程同步借助队列和锁(synchronized)。

同步方法

  • 同步方法:synchronized方法和synchronized块。

    ​ public synchronized void method(int args){}

  • synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法必须获得调用该方法对应的锁才能执行,否则线程阻塞。方法一旦执行,就独占该锁,直到该方法执行返回才释放锁,后面被阻塞的线程才能有获取对象锁的可能性,继续执行。

    **缺点:**方法里需要修该的内容才需要上锁,锁的太多,浪费资源。

    同步方法实现买票的安全性

    //模拟买票
    public class BuyTicket implements Runnable {
        private int totalTickets = 10;
        Boolean flag = true;
        @Override
        public void run() {
            //判断票是否有剩余
            while(flag){
                try {
                    buy();
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
         public synchronized void buy() throws InterruptedException {
            //票卖完,转化标志位
            if(totalTickets <= 0){
                flag  = false;
                return;
            }
            //放大问题
            Thread.sleep(1000);
             System.out.println(Thread.currentThread().getName()+"买第"+totalTickets--+"票");
         }
    
        public static void main(String[] args) {
            BuyTicket buyTicket = new BuyTicket();
            new Thread(buyTicket,"我").start();
            new Thread(buyTicket,"黄牛").start();
        }
    }
    

同步块

  • synchronized(Obj){}。Obj为同步监视器,可以是任何对象,推荐使用共享资源作为同步监视器。同步方法无须指定同步监视器,因为同步监视器为this或者是class。
  • 同步监视器执行过程:
    • 第一个线程访问,锁定同步监视器,执行其中代码。
    • 第二个线程,发现同步监视器被锁定,无法访问。
    • 第一个线程访问完毕,解锁同步监视器。
    • 第二个线程访问,发现同步监视器有可获得的锁,然后锁定访问其代码。
//模拟银行取钱
public class TakeMoney {

    public static void main(String[] args) {
        Account account = new Account(100,"freestyle");
        BankThread bankThread = new BankThread(account,100);
        new Thread(bankThread,"husband").start();
        new Thread(bankThread,"wife").start();
    }

}
//卡类
class Account{
    int totalMoney;
    String cardName;

    public Account(int totalMoney,String cardName){
        this.totalMoney = totalMoney;
        this.cardName = cardName;
    }
}

//自定义线程类
class BankThread implements Runnable{

    private Account account;
    private int money;

    public BankThread(Account account,int money){
        this.account = account;
        this.money = money;
    }
    @Override
    public void run() {
        //满足取钱条件
        try {
            takeMoney();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //取钱
    public  void takeMoney() throws InterruptedException {

          synchronized (account){
              if(account.totalMoney - money < 0){
                  System.out.println("卡内余额不足,请重新选择金额");
                  return;
              }
              Thread.sleep(1000);
              int remainMoney = account.totalMoney - money;
              account.totalMoney = remainMoney;
              System.out.println("卡内剩余金额:"+remainMoney);
              System.out.println("取走金额:"+money);
          }
    }
}

JUC

  • CopyOnWriteArrayList集合具有线程安全性。

死锁

  • 多个线程各自占有一些共享资源,需要等待其它线程资源才能运行。某一个同步块同时拥有两个以上对象锁时,就有发生死锁的可能性。
//死锁:一个同步代码块有多个对象锁时,有发生死锁的可能性
public class TestDeadLock {
    public static void main(String[] args) {

        Mirror mirror = new Mirror();
        Lipstick lipstick = new Lipstick();

        DeadLockThread.mirror = mirror;
        DeadLockThread.lipstick = lipstick;

        DeadLockThread deadLockThread1 = new DeadLockThread(1);
        DeadLockThread deadLockThread2 = new DeadLockThread(2);

        new Thread(deadLockThread1,"girl1").start();
        new Thread(deadLockThread2,"girl2").start();
    }

}

//镜子
class Mirror{

}

//口红
class Lipstick{

}

class DeadLockThread implements Runnable{

    //确保唯一资源
    static Mirror mirror;
    static Lipstick lipstick;
    private int choice;


    public DeadLockThread( int choice){
       this.choice = choice;
    }

    @Override
    public void run() {
        //获取资源,开始化妆
        makeup(mirror,lipstick,choice);
    }

    public void makeup(Mirror mirror,Lipstick lipstick,int choice){
        if(choice == 1){
            synchronized (mirror){
                System.out.println(Thread.currentThread().getName()+"获取镜子");
                synchronized (lipstick){
                    System.out.println(Thread.currentThread().getName()+"获取口红");
                }
            }
        }
        else{
            synchronized (lipstick){
                System.out.println(Thread.currentThread().getName()+"获取口红");
                synchronized (mirror){
                    System.out.println(Thread.currentThread().getName()+"获取镜子");
                }
            }
        }

    }
}

Lock锁

  • JDK5.0开始,Java提供更强大的线程同步机制。通过显示定义同步锁对象来实现同步,同步锁使用Lock(接口)对象充当。
  • 线程访问共享资源时,需要先获得Lock对象。
  • ReentrantLock(可重入锁)类实现Lock,可以显示加锁和释放锁。
  • lock.lock()–加锁 lock.unlock()–解锁
import java.util.concurrent.locks.ReentrantLock;

//实现lock锁
public class TestLock implements Runnable{

    private int totalTicket = 10;
    private final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {

        TestLock testLock = new TestLock();
        new Thread(testLock,"boy").start();
        new Thread(testLock,"girl").start();
    }



    @Override
    public void run() {
        while(true){
            try {
                lock.lock();
                if(totalTicket > 0){
                    try {
                        Thread.sleep(1000);
                        System.out.println(Thread.currentThread().getName()+"买到第"+totalTicket--+"票");
                    }
                    catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
                else{
                    break;
                }
            }
            finally {
                lock.unlock();
            }
        }

    }
}

注:synchronized与Lock的对比

  • Lock是显示锁,需手动释放锁。synchronized是隐式锁,代码块运行完,自动释放锁。

  • Lock只有代码块锁,synchronized有代码块锁和方法锁

  • 使用Lock锁,JVM将花费较少时间来调度线程,性能更好,并且具有更好的扩展性(提供更多的子类)。

    优先使用顺序:Lock > 同步代码块 > 同步方法

线程协作-生产者和消费者模式

  • Java处理线程通信问题的几种方法:
    • wait():表示线程一一直等待,直到其它线程通知,与sleep()不同,会释放锁。
    • wait(long timeout):指定等待的毫秒数。
    • notify():唤醒一个处于等待状态的线程。
    • notifyAll():唤醒同一对象所有调用wait()方法的线程,优先级别高的线程优先调度。

注:均是Object类方法,只能在同步方法或者同步块中使用,否则会抛出IIlegalMonitorstateException的异常。

**解决方式一:**管程法,生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据

import java.sql.SQLOutput;

//管程法解决生产者和消费者问题
public class ProducerConsumer {
    public static void main(String[] args) {

        Container container = new Container();

        new Producer(container).start();
        new Consumer(container).start();
    }

}

//资源类
class Chicken{

    int id;

    public Chicken(int id){
        this.id = id;
    }
}

//生产者类
class Producer extends Thread{

    private Container container;

    public Producer(Container container){
        this.container = container;
    }

    @Override
    public void run() {
        //生产者生产100只鸡
        for (int i = 1; i <= 100; i++) {
            Chicken chicken = new Chicken(i);
            try {
                container.push(chicken);
                System.out.println("生产者生产第"+i+"只鸡");
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//消费者类
class Consumer extends  Thread{

    private Container container;

    public Consumer(Container container){
        this.container = container;
    }
    @Override
    public void run() {
        //消费者消费100只鸡
        for (int i = 1; i <= 100; i++) {
            try {
                Chicken chicken = container.pop();
                System.out.println("消费者者消费第"+chicken.id+"只鸡");
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

//缓冲区类
class Container{

    //设置缓冲区容量
    Chicken [] chickenArray = new Chicken[10];
    //资源计数
    private int count;

    //生产者生产资源并放入缓冲区
    public synchronized void push(Chicken chicken) throws InterruptedException {
        //缓冲区满,通知消费者消费,生产者等待
        if(count == chickenArray.length){
            this.wait();
        }
        //缓冲区未满,生产者继续生产资源
        chickenArray[count++] = chicken;
        this.notifyAll();
    }

    public synchronized Chicken pop() throws InterruptedException {
        //缓冲区为空,消费者等待生产者生产
        if(count == 0){
            this.wait();
        }
        //缓冲区未空,消费者开始消费
        Chicken chicken = chickenArray[--count];
        this.notifyAll();
        return chicken;
    }
}

注:synchroized作用生产者和消费者满足互斥条件,不能同时进行。

**解决方法二:**信号灯法,通过标志位。

//模拟观看节目
//演员表演节目,观众等待;观众观看节目,演员等待
public class WatchShow {

    public static void main(String[] args) {
        TV tv = new TV();
        new Actor(tv).start();
        new Audience(tv).start();
    }
}

//演员类
class Actor extends Thread{

    private TV tv;

    public Actor(TV tv){
        this.tv = tv;
    }

    //演员表演节目
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try {
                    tv.play("乘风破浪的姐姐");
                }
            catch (InterruptedException e) {
                    e.printStackTrace();
                }
        }

    }
}

//观众类
class Audience extends Thread{

    private TV tv;

    public Audience(TV tv){
        this.tv = tv;
    }

    //观众观看节目
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try {
                tv.watch();
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

//电视类
class TV{

    private String show;
    private boolean flag = false;

    //演员表演节目
    public synchronized void play(String show) throws InterruptedException {

        //节目还未观看
        if(flag){
            this.wait();
        }
        System.out.println(show+"表演完成");
        this.show = show;
        flag = !flag;
        this.notifyAll();
    }

    //观众观看节目
    public synchronized void watch() throws InterruptedException {
        if(!flag){
            this.wait();
        }
        System.out.println(this.show+"观看完毕");
        flag = !flag;
        this.notifyAll();

    }
}

线程池

  • 线程频繁创建和销毁,对性能影响很大。

  • 提高响应速度,减少创建新线程的时间。

  • 降低资源消耗,重复利用线程池中的线程,不需要每次都创建。

  • 便于管理线程:

    • corePoolSize:核心池的大小
    • maximumPoolSize:最大线程数
    • keepAliveTime:线程没有任务时最多保持多长时间后会终止。
  • 实现步骤:

    • ExecutorService:真正的线程池接口。Executors;创建不同类型的线程池。
    • void execute(Runnable command)。执行任务命令,一般用来执行Runnable。
    • void shoutdown():关闭线程池。
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    //线程池实现
    public class TestThreadPool  {
    
        public static void main(String[] args) {
            //创建服务
            ExecutorService service = Executors.newFixedThreadPool(10);
            //执行任务
            service.execute(new ThreadPool());
            service.execute(new ThreadPool());
    
            //关闭线程池
            service.shutdown();
        }
    
    }
    
    class ThreadPool implements Runnable{
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"执行");
        }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值