Java多线程

2020-03-27  本文已影响0人  忒无聊了叭

线程简介

初识

程序

说起进程,就不得不说程序,程序是指令和数据的有序集合,其本身没有任何意义,是一个静态的概念。

进程

进程就是执行程序的一次过程,他是一个动态的概念,是系统资源分配的单位。

线程

通常在一个进程中,可以包含多个线程,一个进程中至少存在一个线程,不然没有存在的意义,线程是cpu调度和执行的单位。

注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核。

举例:比如说一边玩游戏,一边听音乐是同时进行的吗?
不是。因为单CPU在某一个时间点上只能做一件事情。
而我们在玩游戏,或者听音乐的时候,是CPU在做着程序间的高效切换让我们觉得是同时进行的。

创建线程的三种方式

创建线程的三种方式.png

继承thread类进行实现多线程

package com.szw;

//创建线程方式一:继承thread,重新run()方法,调用start()开启线程
public class Thread01 extends Thread {

    @Override
    public void run() {
        //run方法线程体,重写父类run方法
        for (int i = 0; i < 200; i++) {
            System.out.println("我在看代码~~"+i);
        }
    }
    public static void main(String[] args) {
        //main主线程
        Thread01 thread01 = new Thread01();
        //调用start方法,启动多线程
        thread01.start();
        for (int i = 0; i < 200; i++) {
            System.out.println("我在学习多线程~~"+i);
        }
    }
}

问题:启动线程为什么不直接使用run方法,而是start方法?

Thired类用于描述线程,其中有一个定义存储功能run,用于存储线程要运行的代码,主线程的代码在main中,新线程的代码在run中,start除了开启新线程之外,还有就是启动线程的功能。

实现runnable进行多线程

package com.szw;

//创建线程方式2:实现runnable接口,重新run方法
public class Thread02 implements Runnable {
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i < 200; i++) {
            System.out.println("我在看代码~~" + i);
        }
    }

    public static void main(String[] args) {
        Thread02 thread02 = new Thread02();
        //创建线程对象,通过线程对象来开启我们的线程,代理
        new Thread(thread02).start();

        for (int i = 0; i < 200; i++) {
            System.out.println("我在学习多线程~~" + i);
        }
    }
}

以上两种方式,查看源码可知,thread也是实现了runnable接口,由于java单继承的特性,所以推荐使用runnable进行多线程。

上面两种开启时的区别是:runnable是需要放在Thread中进行start,而thread直接开启即可。

模拟龟兔赛跑

Thread.currentThread表示当前代码段正在被哪个线程调用的相关信息。

package com.szw;

//模拟龟兔赛跑
public class Race implements Runnable{

    //胜利者
    private static String winner;

    @Override
    public void run() {

        //如果是兔子线程,模拟睡觉
        if (Thread.currentThread().getName().equals("兔子")){
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for (int i = 1 ;i <= 100; i++) {
            //判断比赛是否结束
            boolean b = gameOver(i);
            if (b){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
        }
    }

    public boolean gameOver(int steps){
        //判断是否有胜利者
        if (winner!=null){//这里是因为两个进程,一个进程胜利之后,另一个还在跑
            return true;
        }else {
            if (steps>=100){
                winner = Thread.currentThread().getName();
                System.out.println("胜利者是"+winner);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Race race = new Race();
        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }
}

扩展:Lambda表达式

官方定义:Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

使用 Lambda 表达式可以使代码变的更加简洁紧凑。

由于runnable接口就是函数式接口,所以我们这里扩展Lambda可以让代码更简洁。

package com.szw;

//Lambda表达式
public class Lambda {

    //第二种方法:静态内部类
    static class Love implements ILove{
        @Override
        public void ILove(int a) {
            System.out.println("I Love you   序号2"+a);
        }
    }

    public static void main(String[] args) {
        //第三种方法:局部内部类
        class Love implements ILove{
            @Override
            public void ILove(int a) {
                System.out.println("I Love you   序号3"+a);
            }
        }
        //第四种方法:匿名内部类
        ILove love = new Love(){
            @Override
            public void ILove(int a) {
                System.out.println("I Love you   序号4"+a);
            }
        };
        //第五种方法Lambda表达式
        ILove love2 = (int a) -> {
            System.out.println("I Love you   序号5"+a);
        };
        //简化一:简化Lambda表达式
        ILove love3 = (a) -> {
            System.out.println("I Love you   序号6"+a);
        };
        //简化二:去掉a的括号,继续简化Lambda表达式
        ILove love4 = a -> {
            System.out.println("I Love you   序号7"+a);
        };

        //简化三:去掉花括号,(注意这里只是一行代码的情况)继续简化Lambda表达式
        ILove love5 = a -> System.out.println("I Love you   序号8"+a);

        //Lambda表达式:
        //前提:1、只有一行代码,否则使用代码块包裹。
        //2、必须是函数式接口,接口中只有一个方法
        //3、多个参数也可以去掉int,但是要加括号
        love.ILove(0);
        love2.ILove(0);
        love3.ILove(0);
        love4.ILove(0);
        love5.ILove(0);
    }
}

//第一种方法:首先定义一个接口,实现它,然后在主方法中进行调用(普通的方法)
interface ILove{
    void ILove(int a);
}
class Love implements ILove{
    @Override
    public void ILove(int a) {
        System.out.println("I Love you   序号1"+a);
    }
}

线程状态

线程的状态.png

注意:查看源码,可知线程有六种状态!

线程的一些方法:

线程停止

线程停止
1、建议线程停止--->利用次数,不建议死循环
2、建议使用标志位--->设置一个标志位
3、不建议使用stop和destroy等过时的方法,jdk官方不推荐

package com.szw;

//测试线程停止
//1、建议线程停止--->利用次数,不建议死循环
//2、建议使用标志位--->设置一个标志位
//3、不建议使用stop和destroy等过时的方法,jdk官方不推荐
public class ThreadStop 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) {
        ThreadStop threadStop = new ThreadStop();
        new Thread(threadStop).start();//线程开始
        for (int i = 0; i < 200; i++) {
            System.out.println("主线程" + i);
            if (i == 100) {
                //调用stop方法,停止
                threadStop.stop();
                System.out.println("线程停止...");
            }
        }
    }
}

线程休眠

模拟时钟思路:首先获取系统时间,然后进行while循环,输出系统时间,线程休眠1s,然后更新获取系统时间,再输出。。

package com.szw;

import java.text.SimpleDateFormat;
import java.util.Date;

//线程休眠模拟倒计时
public class TestSleep {
    public static void main(String[] args) {
        Date startTime = new Date(System.currentTimeMillis());//获取系统时间
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
            startTime = new Date(System.currentTimeMillis());//更新时间
        }
    }

    //模拟倒计时
    public static void tenDown() throws InterruptedException {
        int num = 10;
        while (true) {
            Thread.sleep(1000);
            System.out.println(num--);
            if (num < 0) {
                break;
            }
        }

    }
}

线程礼让

详细解释

​ yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!

​ 举个例子:一帮朋友在排队上公交车,轮到Yield的时候,他突然说:我不想先上去了,咱们大家来竞赛上公交车。然后所有人就一块冲向公交车,有可能是其他人先上车了,也有可能是Yield先上车了。

线程强制执行

package com.szw;

public class ThreadJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("线程插队"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadJoin threadJoin = new ThreadJoin();
        Thread thread = new Thread(threadJoin);
        thread.start();

        //主线程
        for (int i = 0; i < 500; i++) {
            if (i==200){
                thread.join();//强行让线程执行
                System.out.println("线程执行完毕,主线程才开始~~~~~~~~~~~~~~~~~~~~~~~");
            }
            System.out.println("主线程"+i);
        }
    }
}

线程状态

尚未启动线程处于此状态。

在Java虚拟机中执行的线程处于此状态。

被阻塞监视等待锁定的线程,就处于此状态。

正在等待另一个线程执行特定动作的线程处于此状态。

正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。

已经退出的线程处于此状态;

package com.szw;

//线程状态
public class ThreadState {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{//这里的意思就是让线程运行5s
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("------------");
        });

        //观察状态
        Thread.State state = thread.getState();
        System.out.println("未启动的线程状态"+state);

        //启动以后的状态
        thread.start();
        state = thread.getState();
        System.out.println("启动以后的线程状态"+state);

        while (state!=thread.getState().TERMINATED){//线程状态不是停止的时候
            for (int i = 0; i < 5; i++) {
                thread.sleep(1000);//每隔一秒输出线程的状态
                state = thread.getState();
                System.out.println(state);
            }
        }
        System.out.println("线程停止~~~");
    }
}

线程优先级

优先级具有随机性:一般优先级较高的线程先执行run()方法,但是这个不能说的但肯定,因为线程的优先级具有 “随机性”也就是较高线程不一定每一次都先执行完。

package com.szw;

//线程优先级
public class ThreadPriority {
    public static void main(String[] args) {
        //主线程
        System.out.println("main"+"-->"+Thread.currentThread().getPriority());
        MyPriority myPriority = new MyPriority();
        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);
        t1.setPriority(2);//2
        t2.setPriority(3);//3
        t3.setPriority(Thread.NORM_PRIORITY);//5
        t4.setPriority(Thread.MAX_PRIORITY);//10

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

class MyPriority implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
    }
}

守护线程daemon

通俗易懂的就是说:守护线程是维护用户线程的,等用户线程跑完之后,守护线程也会停止。

package com.szw;

public class ThreadDaemon {
    public static void main(String[] args) {
        Gad gad = new Gad();
        You you = new You();
        Thread thread = new Thread(gad);
        thread.setDaemon(true);
        thread.start();
        //启动你
        new Thread(you).start();

    }
}

class You implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("快乐的每一天~~"+i);
        }
    }
}
class Gad implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("上帝保护你~~");
        }
    }
}

多线程暴露的问题

三人买票问题

使用多线程三人买票,运行输出发现票的数据严重不正确。

package com.szw;

public class Thread03 implements Runnable {

    //定义票数有10张
    private int ticketNums = 10;

    @Override
    public void run() {
        while (true) {
            if (ticketNums <= 0) {
                break;
            }
            //模拟延时
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums + "票");
            ticketNums--;

        }
    }

    public static void main(String[] args) {
        Thread03 thread03 = new Thread03();
        new Thread(thread03, "小明").start();
        new Thread(thread03, "小孩").start();
        new Thread(thread03, "黄牛党").start();
    }
}

1585112453859.png

银行取钱问题

我和妻子同时取钱:出现错误。

package com.szw;

import com.sun.org.apache.bcel.internal.generic.NEW;

public class UnSafeBank {
    public static void main(String[] args) {
        //账户
        Account account = new Account(100,"结婚基金");
        new Drawing(account,100,"你").start();
        new Drawing(account,100,"妻子").start();
    }
}
//账户
class Account{
    int money;
    String name;

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}
//银行
class Drawing extends Thread{
    Account account;//账户
    int drawMoney;//取了多少钱
    int nowMoney; //剩余的钱

    public Drawing(Account account,int drawMoney,String name){
        super(name);
        this.account = account;
        this.drawMoney = drawMoney;
    }

    @Override
    public void run() {
        synchronized(account){
            //判断有没有钱
            if (account.money-drawMoney < 0){
                System.out.println(Thread.currentThread().getName()+"钱不够了,取不了");
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //卡内的余额 = 余额 - 你取的钱
            account.money = account.money - drawMoney;
            //你手里的钱
            nowMoney = nowMoney + drawMoney;

            System.out.println(account.name+"余额为"+account.money);
            System.out.println(this.getName()+"手里的钱"+nowMoney);
            System.out.println(account.name+"余额为"+account.money);
            System.out.println("------------------------------");
        }
    }
}
同时取钱问题.png

线程同步

同修饰私有属性的关键词private一样,线程同步也有修饰词synchronized,加上该修饰词后,只允许一个对象去操作,每个对象有一把锁,当对象访问时,把锁加上,后面的进程就会形成阻塞。

synchronized有synchronized方法,和synchronized块。

缺陷:若将一个大的方法申明为synchronized将会影响效率。

注意:synchronized块的使用方法是:

synchronized(obj){
    //这里的obj是需要锁的对象,为增删改的对象
}

我们在上面的取钱的问题上加上,synchronized修饰,问题就修改了。


取钱修改.png

synchronized方法上使用的是:

private synchronized void buy(){
    
}
//这里不需要指明被锁的对象,默认是this,就是这个方法的对象本身

死锁

解释: 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形.某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。

通俗的讲:就是两个线程运行时,都需要对面的资源进行完成指令,但是都等待对方的资源释放,从而形成停止执行的情况。

package com.szw;


public class DeadLock {
  public static void main(String[] args) {
      Makeup makeup = new Makeup(0,"白雪公主");
      Makeup makeup1 = new Makeup(1,"灰姑娘");
      makeup.start();
      makeup1.start();
  }
}

//口红
class Lipstick{
}

//镜子
class Mirror{
}

class Makeup extends Thread{
  int choice;//选择
  String girlName;//姓名

  Makeup(int choice,String girlName){
      this.choice = choice;
      this.girlName = girlName;
  }

  //化妆需要的镜子和口红都只有一份
  static Lipstick lipstick = new Lipstick();
  static Mirror mirror = new Mirror();

  @Override
  public void run() {
      makeup();
  }

  private void makeup(){
      if (choice == 0){
          synchronized (lipstick){
              System.out.println(this.girlName+"获得了口红");
              synchronized ((mirror)){
                  System.out.println(this.girlName+"获得了镜子");
              }
          }
      }else {
          synchronized (mirror){
              System.out.println(this.girlName+"获得了镜子");
              synchronized ((lipstick)){
                  System.out.println(this.girlName+"获得了口红");
              }
          }
      }
  }
}

可以运行代码,发现两个对象都没有继续获得镜子或者口红,这里修改的方法就是,分别把0获取镜子的线程同步方法分开。1的口红与镜子分开。这样实现了两个对象不会产生抱死的状态。

Lock

和synchronized功能一样。

这里写一下加lock锁以后的取票问题,注意的是用lock锁有加锁解锁的过程,写在try...finally...中

package com.szw;

import java.util.concurrent.locks.ReentrantLock;

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 ReentrantLock lock = new ReentrantLock();
    private int ticketNum = 10;
    boolean flag = true;

    @Override
    public void run(){
        while (true){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    privatevoid buy() throws InterruptedException {
        try {
            lock.lock();//加锁
            //判断是否有票
            if (ticketNum<=0){
                flag = false;
                return;
            }
            //模拟买票
            Thread.sleep(100);
            //拿到票
            System.out.println(Thread.currentThread().getName()+"拿到"+ticketNum--);
        }finally {
            //解锁
            lock.unlock();
        }
    }
}

线程协作

线程通信

生产消费者模式

生产消费者.png
package com.szw;

//测试:生产者消费者模型->利用缓冲区解决:管程法
//生产者、消费者、产品、缓冲区
public class TestPc {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();
        new Producer(container).start();
        new Consumer(container).start();

    }
}

//生产者
class Producer extends Thread {
    //缓冲区   
    SynContainer container;

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

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

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

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

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                System.out.println("消费了-->" + container.pop().id + "");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//产品
class Chicken {
    int id;

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

//缓冲区
class SynContainer {
    //需要一个容器大小
    Chicken[] chickens = new Chicken[10];
    int Count = 0;

    //生产者放入产品
    public synchronized void push(Chicken chicken) throws InterruptedException {
        if (Count == chickens.length) {
            //如果有10只鸡,就先停止生产,让消费者消费
            this.wait();
        }
        chickens[Count] = chicken;
        Count++;
        //通知消费者消费
        this.notifyAll();
    }

    //消费者消费产品
    public synchronized Chicken pop() throws InterruptedException {
        if (Count == 0) {
            //通知生产者生产,先停止消费
            this.wait();
        }
        //有鸡:就进行消费
        Count--;
        Chicken chicken = chickens[Count];
        this.notifyAll();//唤醒全部线程
        return chicken;
    }

}
1585317414956.png
package com.szw;

//测试生产消费者问题2:信号灯法,标志位解决
public class TestPc2 {
    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++) {
            tv.watch();
        }
    }
}

//产品,TV
class TV {
    //演员表演,观众等待
    //观众观看,演员等待
    String voice;
    boolean flag = true;//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 watch() {
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观看了" + voice);
        this.notifyAll();//通知唤醒
        //通知演员表演
        this.flag = !this.flag;
    }
}

线程池

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

好处:

◆提高咆应速度(减少了创建新线程的时间)

◆降低资源消耗(重复利用线程池中线程,不需要每次都创建)

◆便于线程管理

上一篇下一篇

猜你喜欢

热点阅读