Java学习-多线程

多线程

任务(Task)、进程(Process)、线程(Thread)

进程的创建

1.继承Thread类

  • 自定义线程类继承Thread类

  • 重写run()方法,编写线程执行体

  • 创建线程对象,调用start()方法启动线程

public class DemoThread1 extends Thread {
    @Override
    public void run() {
        //run方法线程提
        for (int i = 0; i < 1000; i++) {
            System.out.println("子线程第"+i+"个");
        }
    }
    //main线程,主线程
    public static void main(String[] args) {
        //创建一个线程对象
        DemoThread1 demoThread1 = new DemoThread1();
        //调用start()方法
        demoThread1.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程第-"+i+"-个");
        }
    }
}
//多线程下载图片
public class DemoThread2 extends Thread {
    private String url;
    private String name;

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

    @Override
    public void run() {
        //run方法线程提
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url, name);
        System.out.println("下载了文件:" + name);
    }
    //main线程,主线程
    public static void main(String[] args) {
        //创建一个线程对象
        DemoThread2 demoThread1 = new DemoThread2("https://commons.apache.org/proper/commons-io/images/commons-logo.png", "1.png");
        DemoThread2 demoThread2 = new DemoThread2("https://commons.apache.org/proper/commons-io/images/commons-logo.png", "2.png");
        DemoThread2 demoThread3 = new DemoThread2("https://commons.apache.org/proper/commons-io/images/commons-logo.png", "3.png");
        //调用start()方法
        demoThread1.start();
        demoThread2.start();
        demoThread3.start();
    }
}

class WebDownloader {
    public void downloader(String url, String name) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常");
        }
    }
}

不建议使用,避免OPP单继承的局限性

2.实现Runnable接口(推荐)

  • 自定义类实现Runnable接口
  • 实现run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程
public class DemoRunneralbe implements Runnable {
    @Override
    public void run() {
        //run方法线程提
        for (int i = 0; i < 1000; i++) {
            System.out.println("子线程第"+i+"个");
        }
    }
    //main线程,主线程
    public static void main(String[] args) {
        //创建一个线程对象
        DemoRunneralbe demoRunneralbe = new DemoRunneralbe();
        //创建线程对象,代理
        new Thread(demoRunneralbe).start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程第-"+i+"-个");
        }
    }
}

3.实现Callable接口

public class DemoCallable implements Callable<Boolean> {

    private String url;
    private String name;

    public DemoCallable(String url, String name) {
        this.url = url;
        this.name = name;
    }
    @Override
    public Boolean call() throws Exception {
        //call方法线程提
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url, name);
        System.out.println("下载了文件:" + name);
        return true;
    }
    //main线程,主线程
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建一个线程对象
        DemoCallable demoThread1 = new DemoCallable("https://commons.apache.org/proper/commons-io/images/commons-logo.png", "1.png");
        DemoCallable demoThread2 = new DemoCallable("https://commons.apache.org/proper/commons-io/images/commons-logo.png", "2.png");
        DemoCallable demoThread3 = new DemoCallable("https://commons.apache.org/proper/commons-io/images/commons-logo.png", "3.png");

        //创建执行服务创建池子,里面有3个线程
        ExecutorService ser = Executors.newFixedThreadPool(3);
        //提交执行
        Future<Boolean> submit = ser.submit(demoThread1);
        Future<Boolean> submit1 = ser.submit(demoThread2);
        Future<Boolean> submit2 = ser.submit(demoThread3);
        //获取结果
        Boolean aBoolean1 = submit.get();
        Boolean aBoolean2 = submit1.get();
        Boolean aBoolean3 = submit2.get();
        //关闭服务
        ser.shutdown();

    }
}

callable可以定义返回值,可以定义异常

4、静态代理

真实对象和代理对象都要实现同一个接口

代理对象要代理真实角色

好处:

​ 代理对象可以做很多真实对象做不了的事情

​ 真实对象只要关注自己的事情

public class DemoStacticProxy {
    public static void main(String[] args) {
        WeddingCompany weddingCompany = new WeddingCompany(new You());
        weddingCompany.HappyMarry();
        //同new WeddingCompany(new You()).HappyMarry();
    }
}
interface Marry {
    void HappyMarry();
}
//真实角色
class You implements Marry {
    @Override
    public void HappyMarry() {
        System.out.println("自己结婚");
    }
}
//代理劫色,帮助功能
class WeddingCompany implements Marry {
    private Marry target;
    public WeddingCompany(Marry target) {
        this.target = target;
    }
    @Override
    public void HappyMarry() {
        before();
        this.target.HappyMarry();//真是对象
        after();
    }
    private void before() {
        System.out.println("结婚之前");
    }
    private void after() {
        System.out.println("结婚之后");
    }
}

5、Lamda表达式

避免匿名内部类定义过多

其实质属于函数式编程

(params)->expression[表达式]

(params)->statement[语句]

(params)->{statement}

  • 函数式接口

    • 任何接口,如果只包含唯一一个抽象方法,那么他就是一个函数式接口。

      public interface Runnable{
          public abstract void run();
      }
      
    • 对于函数式接口,我们可以使用lamda表达式来创建该接口的对象

Lamda推导

public class DemoLamda {
    //3静态内部类
    static class Like2 implements ILike {
        @Override
        public void lamda() {
            System.out.println("i like lamda2-静态内部类");
        }
    }

    public static void main(String[] args) {

        ILike like = new Like1();
        like.lamda();

        Like2 like2 = new Like2();
        like2.lamda();

        //4局部内部类
        class Like3 implements ILike {
            @Override
            public void lamda() {
                System.out.println("i like lamda3-局部内部类");
            }
        }
        Like3 like3 = new Like3();
        like3.lamda();

        //5匿名内部类,没有类的名称,必须将借助接口或者父类
        ILike like4 = new ILike() {
            @Override
            public void lamda() {
                System.out.println("i like lamda4-匿名内部类");
            }
        };
        like4.lamda();

        //6用lamda简化
        ILike like5 = () -> {
            System.out.println("i like lamda5-lamda简化");
        };
        like5.lamda();
    }
}

//1定义一个函数式接口
interface ILike {
    void lamda();
}

//2实现类
class Like1 implements ILike {

    @Override
    public void lamda() {
        System.out.println("i like lamda1-实现类");
    }
}

Lamda表达式简化

public class DemoLamda2 {
    public static void main(String[] args) {
        Ilove love = null;
        love = (int a) -> {
            System.out.println("lovelovelovelove" + a);
        };
        //简化1,去掉参数类型
        love = (a) -> {
            System.out.println("lovelovelovelove" + a);
        };
        //简化2,去掉括号,参数个数只能有一个
        love = a -> {
            System.out.println("lovelovelovelove" + a);
        };
        //简化2,去掉花括号,只能有一行代码才能简化
        love = a -> System.out.println("lovelovelovelove" + a);

        love.love(1231);
    }
}

interface Ilove {
    void love(int a);
}

6、线程的状态

创建状态,就绪状态,运行状态,阻塞状态,死亡状态

方法 说明
setPriority(int newPriority) 更改线程的优先级
static void sleep(long milis) 在指定的毫秒数内让当前正在执行的线程休眠
void jion() 等待该线程终止
static void yeild() 暂停当前正在执行的线程对象,并执行其他线程
void interrupt() 中断线程(不推荐,建议使用标志位来停止线程)
boolean isAlive() 测试线程是否处于活动状态

线程的停止

public class DemoStop implements Runnable {
    //标志位
    private boolean flag = true;
    @Override
    public void run() {
        int i = 0;
        while (flag) {
            System.out.println("run---Thread" + i++);
        }
    }
    //设置方法转换标志位
    public void stop() {
        this.flag = false;
    }
    public static void main(String[] args) {
        DemoStop demoStop = new DemoStop();
        new Thread(demoStop).start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("main" + i);
            if (i == 900) {
                demoStop.stop();
                System.out.println("线程停止");
            }
        }
    }
}

线程的休眠

  • sleep(时间)指定当前线程祖代的毫秒数
  • sleep存在异常IntrruptedException
  • sleep时间达到后线程进入就绪状态
  • sleep可以模拟网络延时(放大问题的发生性),倒计时等
  • 每一个对象都有一个锁,sleep不会释放锁

线程礼让

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 礼让不一定成功
public class DemoYield {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();
        new Thread(myYield, "A").start();
        new Thread(myYield, "B").start();
    }
}

class MyYield implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "线程开始执行");
        Thread.yield();
        System.out.println(Thread.currentThread().getName() + "线程结束执行");
    }
}

Join

  • Join合并线程,要等此线程执行完之后,在执行其他线程,其他线程处于阻塞状态
public class DemoJoin {
    public static void main(String[] args) throws InterruptedException {
        MyJoin myJoin = new MyJoin();
        Thread thread = new Thread(myJoin);
        thread.start();
        for (int i = 0; i < 1000; i++) {
            if (i==300) {
                //线程插队
                thread.join();
            }
            System.out.println("main--"+i);
        }
    }
}
class MyJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("MyJoin--"+i);
        }
    }
}

7、线程守护(setDaemon)

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 如后台记录操作日志,监控内存,垃圾回收等

8、线程同步

多个线程操作同一个资源 ,线程同步其实就是一种等待机制,多个线程访问统一资源,就要进入一个对象的等待池,形成队列,等待前面的线程使用完毕,下一个线程再去使用

队列和锁解决安全性。

在访问资源的时候,加入锁机制(synchronized),当一个线程获得资源的排它锁,独占资源,其他的线程就必须等待,使用完成之后释放锁即可。

同步方法

同步方法的同步监视是this,就是则个对象本身,或者是class。锁对象应是变化的量(增删改)

  • synchronized方法(影响效率)
  • synchronized块
    • synchronized(Obj){}
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();

        new Thread(buyTicket, "玛丽").start();
        new Thread(buyTicket, "小二").start();
        new Thread(buyTicket, "小民").start();
    }
}
class BuyTicket implements Runnable {
    private int ticketnum = 10;
    @Override
    public void run() {
        synchronized ((Integer) ticketnum) {
            while (true) {
                if (ticketnum <= 0) {
                    break;
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "买到了第" + ticketnum-- + "票");
            }
        }

    }
}

扩充(CopyOnWriteArrayList)

CopyOnWriteArrayList是的List变成一个线程安全的list

public class DemoJUC {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new
                CopyOnWriteArrayList<String>();
        for (int i = 0; i < 1000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

9、死锁

某一个同步块同时拥有两个以上对象的锁,就可能产生死锁。

Lock(锁)

JDK5开始,JAVA提供了更强大的线程同步机智---通过显示的定义同步锁对象来实现同步,同步锁使用Lock对象充当

java.util.concurrent.locks.Lock接口是控制多个线程对象共享资源进行访问的工具。锁提供了对象共享资源的独占空间,每一次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前就要先获得Lock对象

ReentrantLock类实现了Lock,他拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁。

public class DemoLock {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();
        new Thread(buyTicket, "玛丽").start();
        new Thread(buyTicket, "小二").start();
        new Thread(buyTicket, "小民").start();
    }
}

class BuyTicket1 implements Runnable {
    private int ticketnum = 10;
    //d定义lock锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();
                if (ticketnum <= 0) {
                    break;
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "买到了第" + ticketnum-- + "票");
            } finally {
                lock.unlock();
            }
        }
    }
}

10、线程通信

Java提供了几个方法解决线程之间的通信问题。

方法名 作用
wait() 表示线程一致等待,直到其他线程通知,与sleep不同,会释放锁
wait(long timeout) 指定等待的毫秒数
notify() 唤醒一个处于等待的线程
notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度

均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IIIelgalMonitorStateException

生产者/消费者模式

管程法
//生产者、消费者、产品、缓冲区
public class DemoPC {
    public static void main(String[] args) {
        SynContainer synContainer = new SynContainer();
        new Productor(synContainer).start();
        new Consumer(synContainer).start();
    }
}

//生产者
class Productor extends Thread {
    SynContainer container;

    public Productor(SynContainer container) {
        this.container = container;
    }

    //生产
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            container.push(new Pencel(i));
            System.out.println("生产了" + i + "只笔");
        }
    }
}

//消费者
class Consumer extends Thread {
    SynContainer container;

    public Consumer(SynContainer container) {
        this.container = container;
    }

    //消费
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了第--" + container.pop().id+ "只笔");
        }
    }
}

//产品
class Pencel {
    int id;//产品编号
    public Pencel(int id) {
        this.id = id;
    }
}

//缓冲区
class SynContainer {
    Pencel[] pencels = new Pencel[10];
    int count = 0;

    //生产者放入产品
    public synchronized void push(Pencel pencel) {
        //如果容器满了,就需要等待消费者消费
        if (count == pencels.length) {
            //通知消费者,生产者等待
            try {
                this.wait();//System.out.println("-----------等待消费者------------");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果没有满,就丢入产品
        pencels[count] = pencel;
        count++;
        //通知消费者消费
        this.notifyAll();
    }

    //消费者消费产产品
    public synchronized Pencel pop()  {
        //判断能否消费
        if (count == 0) {
            //等待生产者
            try {
                this.wait();
                //System.out.println("-----------等待生产者------------");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count--;
        Pencel pencel = pencels[count];
        //通知生产者生产
        this.notifyAll();
        return pencel;
    }
}
信号灯法
//生产者、消费者、产品、缓冲区
public class DemoPC2 {
    public static void main(String[] args) {
        TV tv = new TV();
        new Player(tv).start();
        new Watcher(tv).start();
    }
}

//生产者
class Player extends Thread {
    TV tv;

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

    //生产
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i % 2 == 0) {
                this.tv.play("快乐大本营");
            } else {
                this.tv.play("抖音");
            }

        }
    }
}

//消费者
class Watcher extends Thread {
    TV tv;

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

    //消费
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            this.tv.whatch();
        }
    }
}

//产品-->节目
class TV {
    //演员表演,观众等待 T
    //观众观看,演员等待 F
    String voice;
    boolean flag = true;

    //表演
    public synchronized void play(String voice) {
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演员表演了:" + voice);
        //通知观众观看
        this.notifyAll();
        this.voice = voice;
        this.flag = !this.flag;
    }

    //表演
    public synchronized void whatch() {
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观看了:" + voice);
        //通知演员表演
        this.notifyAll();
        this.flag = !this.flag;
    }
}

11、线程池

提前创好多个线程,放入线程池中,使用时直接获取,使用完放回池子中。可以避免频繁的创建销毁对象、实现了重复利用

  • 提高了形影速度(减少了创建新县城的时间)
  • 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
  • 便于线程管理
    • corePollSize:核心池大小
    • maximunPoolSize:最大线程数
    • keepAliveTime:线程没有任务时最多保持多长时间后会终止
  • 线程池相关的API:ExecutorService和Executors
  • ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
    • void execute(Runnable command)执行任务/命令,没有返回值,一般用来执行Runnable
    • Futuresubmit(Callable task)执行任务,有返回值,一般用来执行Callable
    • void shotdown()关闭连接池
  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
public class DemoPool {
    public static void main(String[] args) {
        //创建线程池
        //newFixedThreadPool 参数为线程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);

        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        //关闭
        service.shutdown();
    }
}
class MyThread implements Runnable{
    @Override
    public void run() {
            System.out.println(Thread.currentThread().getName());
    }
}
posted @ 2021-01-18 11:13  DarkerGuo  阅读(132)  评论(0编辑  收藏  举报