多线程二

多线程二

_

线程状态

创建

用new操作符创建一个线程。此时程序还没有开始运行线程中的代码。

就绪

一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。

处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序来调度的。

运行

当线程抢到了CPU时间片段之后,他才进入了运行状态,开始执行run方法

阻塞

线程运行过程中,可能因为各种原因阻塞

  1. 线程通过调用sleep方法进入睡眠状态
  2. 线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者
  3. 线程试图得到一个锁,而该锁正被其他线程持有
  4. 线程在等待某个触发条件

所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。

堵塞状态是前述四种状态中最有趣的,值得我们作进一步的探讨。线程被堵塞可能是由下述五方面的原因造成的:

  1. 调用sleep(毫秒数),使线程进入"睡眠"状态。在规定的时间内,这个线程是不会运行的。
  2. 用suspend()暂停了线程的执行。除非线程收到resume()消息,否则不会返回"可运行"状态
  3. 用wait()暂停了线程的执行。除非线程收到nofify()或者notifyAll()消息,否则不会变成"可运行"(是的,这看起来同原因2非常相象,但有一个明显的区别是我们马上要揭示的)
  4. 线程正在等候一些IO(输入输出)操作完成
  5. 线程试图调用另一个对象的"同步"方法,但那个对象处于锁定状态,暂时无法使用

死亡状态

有两个原因会导致线程死亡:

  • run方法正常退出而自然死亡
  • 一个未捕获的异常终止了run方法而使线程猝死

线程状态图一.jpg

线程状态4.png

线程方法

线程方法.png

停止线程

不推荐使用jdk提供的 stop()、destroy()方法,【已经废弃】

推荐让线程自己停下来

建议使用一个标志位进行终止标量,当flag=false ,则线程终止

例子

/**
 * 使用标志位方法停止线程
 */
public class TestStop implements Runnable{
    //创建标志位
    private Boolean falg = true;
    @Override
    public void run() {
        int i= 1;
        while (falg){
            System.out.println("线程"+Thread.currentThread().getName()+"正在运行----------->"+ i++);
        }
    }

    //写一个stop  停止程序
    public void stop(){
        this.falg=false;
    }

    public static void main(String[] args) {
        TestStop ts = new TestStop();
//        new Thread(ts).start();
        Thread t = new Thread(ts,"TestStop");
        t.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("main"+i);
            //主线程循环到900的时候就停止线程
            if (i==900) {
                ts.stop();
                System.out.println("线程"+t.getName()+"停止了");
            };
        }
    }
}

结果

main899
main900
线程TestStop正在运行----------->267
线程TestStop停止了
main901
main902

线程休眠

  • sleep(时间)指当前线程进入阻塞状态时间(毫秒)
  • sleep存在异常InterruptedException
  • sleep时间达到后线程重新进入就绪状态
  • sleep可以模拟网络延时,倒计时等
  • 每个线程都有一个锁,sleep不会释放锁

奸商.png

案例

模拟倒计时

/**
 * 模拟倒计时
 */
public class TestSleep {
    
    public static void main(String[] args) {
        int num =10;
        while (true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(num--);
            if (num<=0){
                break;
            }
        }
    }
}

每秒打印一次系统时间

/**
 * 获取打印当前时间
 */
public class GetTime {
    public static void main(String[] args) {
        //获取当前系统时间
        Date date = new Date(System.currentTimeMillis());
        while (true){
            System.out.println(new SimpleDateFormat("YYYY-MM-dd HH:mm:ss").format(date));
            date = new Date(System.currentTimeMillis());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程礼让

礼让线程,让当前正在执行的线程暂停,但不阻塞

将线程从运行状态转为就绪状态

让cpu重新调度,礼让并不一定成功,看cpu调度

/**
 * 线程礼让
 */
public class TestYield {

    public static void main(String[] args) {
        MyYield myYield = new MyYield();
        new Thread(myYield,"小白").start();
        new Thread(myYield,"小黑").start();
    }
}

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

礼让成功

礼让成功.png

礼让失败

礼让失败.png

Join(线程强制执行)

Join合并线程,待此线程执行完成后,在执行其他线程,其他线程阻塞(可以想象成插队)

/**
 * 测试线程强制执行(插队)
 */
public class TestJoin {
    public static void main(String[] args) throws InterruptedException {
        VipJoin vipJoin = new VipJoin();
        Thread t = new Thread(vipJoin);
        t.start();

        //主线程执行100次
        for (int i = 0; i < 100; i++) {
            //当主线程执行50次的时候,vip用户直接插队
            if (i==50) {
                t.join();
            };
            System.out.println("main---->"+i);
        }

    }
}

/**
 * vip线程
 */
class VipJoin implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("vip用户------------->"+i);
        }
    }
}

线程状态观测

Thread.State

线程可以处于一下状态:

  • NEW: 尚未启动
  • RUNNABLE: 在java虚拟机中执行
  • **BLOCKED **: 被阻塞等待监视器锁定
  • WAITING: 正在等另一个线程执行特定动作
  • TIME WAIYING: 正在等另一个线程执行动作达到指定等待时间
  • TERMINATED: 已退出的线程

一个线程可以在给定时间点处于一个状态。这些状态是不反应任何操作系统线程状态的虚拟机状态。

/**
 * 监测线程状态
 */
public class TestState {
    public static void main(String[] args) {
        SeeState seeState = new SeeState();
        Thread t = new Thread(seeState);
        //获取线程状态并打印
        Thread.State state = t.getState();
        System.out.println(state);

        t.start();
        //获取启动后的线程状态
        state = t.getState();
        System.out.println(state);

        //只要线程没有终止,就一直输出状态
        while (state!=Thread.State.TERMINATED){
            try {
                //休眠一下,检查线程休眠后的阻塞状态
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //获取线程状态并打印
            state = t.getState();
            System.out.println(state);
        }

    }

}

class SeeState implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
        }
    }
}

结果

NEW
RUNNABLE
TIMED_WAITING
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TIMED_WAITING
TIMED_WAITING
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TIMED_WAITING
TIMED_WAITING
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TIMED_WAITING
TIMED_WAITING
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TIMED_WAITING
TIMED_WAITING
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TERMINATED

线程优先级

Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定先调度那个线程执行。

线程的优先级用数字表示,范围从0-10

使用getPriority()回去线程优先级,使用setPriority()改变优先级

优先级底只是意味着获得调度的概率低,并不是不会被调度,同样的,优先级高也只是增加优先执行的几率,但并不是一定会优先执行,一切都看CPU的调度

守护线程

线程分为用户线程守护线程

虚拟机必须确保用户线程执行完毕

虚拟机不用等待用户线程执行完毕

比如后台记录日志操作、监控内存、垃圾回收等等。

public class TestDanmon {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();
        Thread t = new Thread(god);
        Thread t2 = new Thread(you);

        t.setDaemon(true);
        t.start();
        t2.start();
    }

}

class God implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("上帝一直保佑着你。。。。。");
        }

    }
}

class You implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("你开心的活着...");
        }
        System.out.println("你没了====================================");
    }
}

当用户线程结束了,守护线程也会自动结束。

线程同步

并发同一个对象多个线程同时操作

并发.png

现实生活中,我们会遇到同一资源,多个人都想使用 的问题,比如食堂排队,打饭,每个人都想吃饭,最好的的办法就是排队一个个来。

处理多线程问题时,多个线程访问一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池排队,等前面的线程使用完毕,下一个线程在使用。

锁.png

不安全案例

银行取钱

/**
 * @author c
 * @Package com.qc.testThread
 * @date 2021/2/28 14:38
 *
 * 线程不安全案例 之 银行取钱
 */
public class TakeMoney implements Runnable{

    //银行有小两口的账户
    private Account account;
    //要取的钱
    private int takeMoney;
    //手里有的钱
    private int newMoney;

    public TakeMoney(Account account, int takeMoney) {
        this.account = account;
        this.takeMoney = takeMoney;
    }

    //模拟取钱业务
    @Override
    public void run() {

        System.out.println(Thread.currentThread().getName()+"来取款,卡内现有余额"+account.getMoney());

        if (takeMoney<account.getMoney()){
            System.out.println(Thread.currentThread().getName()+"来取款,余额不足,取款失败");
            return;
        }

        //取款成功  存款减去取出的钱
        account.setMoney(account.getMoney()-takeMoney);
        //手里的钱加上取出来的钱
        newMoney = newMoney + takeMoney;

        System.out.println(Thread.currentThread().getName()+"取款成功,手里现有: "+newMoney);

        System.out.println("卡内现余: "+account.getMoney());

    }

    public static void main(String[] args) {

        Account account = new Account("结婚基金",100);
        TakeMoney you = new TakeMoney(account,100);
        TakeMoney girlfriend = new TakeMoney(account,50);
        new Thread(you,"你").start();
        new Thread(girlfriend,"girlfriend").start();
    }
}

/**
 * 小两口共同的账户
 */
class Account{
    //账户名
    private String name;
    //账户余额
    private int money;

    public String getName() {
        return name;
    }

    public int getMoney() {
        return money;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setMoney(int money) {
        this.money = money;
    }

    public Account(String name, int money) {
        this.name = name;
        this.money = money;
    }
}

线程不安全之 ArrayList

public class TestArrayList {

    public static void main(String[] args) {

        List list = new ArrayList();

        for (int i = 0; i < 10000; i++) {
            new Thread(() ->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
        
    }
}

结果

按理说应该添加10000条,结果只添加了9991条,说明arrayList是线程不安全的

9991

线程同步

由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized 关键字,它包括两种用法:

synchronized方法和synchronized块。

//同步方法
 public synchronized void method(int args){}

synchronized方法控制对对象的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象锁才能执行,或者线程会阻塞,方法一旦执行,就独占该锁,知道方法返回才释放该锁,后面被阻塞的线程才能获得这个锁,继续执行。

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

synchronized方法

public class TestSynchronized implements Runnable{
    @Override
    public synchronized void run() {
            System.out.println("你");
            System.out.println("是");
            System.out.println("最");
            System.out.println("棒");
            System.out.println("的");
            System.out.println("!");
    }
}

class Test2{
    public static void main(String[] args) {
        TestSynchronized ts = new TestSynchronized();
        new Thread(ts,"ts").start();
        new Thread(ts,"ts2").start();

    }
}

没加之前

你
你
是
是
最
最
棒
棒
的
的
!
!

加了之后

你
是
最
棒
的
!
你
是
最
棒
的
!

synchronized块

同步块:synchromized(obj){}

obj称之为同步监视器

  • obj可以是任何对象,但是推荐使用共享资源作为同步监视器
  • 同步方法中无需指定同步监视器,应为同步方法的监视器就是this(对象本身),或者class

同步监视的执行过程:

  1. 第一个线程访问,锁同步监视器,执行其中代码
  2. 第二个线程访问,发现锁同步监视器被锁定,无法访问
  3. 第一个线程访问完毕,解放锁同步件事器
  4. 第二个线程访问,发现同步监视器没有锁,然后锁定访问
public class TestSynchronized implements Runnable{
    @Override
    public void run() {
        synchronized(this) {
//        synchronized (TestSynchronized.class){
            System.out.println("你");
            System.out.println("是");
            System.out.println("最");
            System.out.println("棒");
            System.out.println("的");
            System.out.println("!");
        }
    }
}

class Test2{
    public static void main(String[] args) {
        TestSynchronized ts = new TestSynchronized();
        new Thread(ts,"ts").start();
        new Thread(ts,"ts2").start();

    }
}

死锁

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

案例

/**
 * @author c
 * @Package com.qc.testThread
 * @date 2021/2/28 18:00
 *
 * 死锁
 */
public class TestDeadlock{
    public static void main(String[] args) {
        Makeup makeup = new Makeup(0,"阿花");
        Makeup makeup1 = new Makeup(1,"小婵");
        new Thread(makeup).start();
        new Thread(makeup1).start();
    }
}
/**
 口红
 */
class Lipstick{}

/**
 镜子
 */
class   Mirror{

}

/**
 * 化妆
 */
class Makeup implements Runnable{

    //只有一份资源,用static来保证
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();
    //选择
    int choice;
    //使用化妆品的人
    String name;

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

    @Override
    public void run() {
        //化妆
        makeup();
    }

    //化妆,互相持有对方的锁,需要得到对方的资源
    private void makeup(){
        if (choice==0){
            //获得口红的锁
            synchronized (lipstick){
                System.out.println(this.name+"获得了口红的锁");
                System.out.println(this.name+"想获得镜子的锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (mirror){
                    System.out.println(this.name+"获得镜子的锁");
                }
            }
        }else {
            synchronized (mirror){
                System.out.println(this.name+"获得了镜子的锁");
                System.out.println(this.name+"想获得口红的锁");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lipstick){
                    System.out.println(this.name+"获得口红的锁");
                }
            }
        }
    }
}

结果:阿花和小婵都想获得对方的锁,但都不让步,因此僵持住了

小婵获得了镜子的锁
阿花获得了口红的锁
小婵想获得口红的锁
阿花想获得镜子的锁

lock锁

从jdk5.0开始,java提供了更强大的线程同步机制-------通过显式定义同步锁对象来实现同步,同步锁使用lock对象充当

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

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

/**
 * @author c
 * @Package com.qc.testThread
 * @date 2021/2/28 18:34
 *
 * 测试ReetrantLock加锁
 *
 * //lock锁默认是非公平的 第一次获取锁的对象 第二次获取的概率比较大。
 *
 * 除非改成公平锁,或者让出时间片Thread.yield();
 */
public class TestLock implements Runnable{

    //次数太少发现一个线程就跑完了
    int num = 200;

    private final ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            //lock.lock()要放在try前面
            lock.lock();
            try {
                if (num<=0){
                    break;
                }
                System.out.println(Thread.currentThread().getName()+" "+num--);
//                try {
//                    Thread.sleep(1000);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
            }finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        TestLock testLock = new TestLock();
        new Thread(testLock,"1号").start();
        new Thread(testLock,"2号").start();
        new Thread(testLock,"3号").start();
    }
}

经典之生产者消费者

生产消费.png

生产者

package com.qc.testThread;

import java.util.ArrayList;
import java.util.List;

/**
 * @author c
 * @Package com.qc.testThread
 * @date 2021/2/28 21:44
 *
 * 生产者
 */
public class Producers implements Runnable{

    //生产者的产品列表
    private static List<String> list = new ArrayList();

    static {
        list.add("苹果");
        list.add("香蕉");
        list.add("梨子");
        list.add("桔子");
        list.add("火龙果");
        list.add("樱桃");
        list.add("柚子");
        list.add("木瓜");
        list.add("葡萄");
        list.add("西瓜");
    }

    //生产者为购物中心生产产品
    private Mall mall;

    public Producers(Mall mall) {
        this.mall = mall;
    }

    //重写线程体,让生产者为购物中心生产商品
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        System.out.println(name+" 开始生产了。。。");
        for (int i = 0; i <10; i++) {
            //随机偷懒
            try {
                Thread.sleep((long) (1000*Math.random()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //生产者很任性,随机生产产品
            int ioc = (int) (list.size()* Math.random());
            String goods = list.get(ioc);

            //生产完了,直接送送货车
            mall.setGoods(goods);
        }
        System.out.println(name+"完成了生产任务下班啦,喜大普奔。");
    }
}

消费者

package com.qc.testThread;

import java.util.LinkedList;
import java.util.List;

/**
 * @author c
 * @Package com.qc.testThread
 * @date 2021/2/28 21:46
 *
 * 消费者
 */
public class Consumers implements Runnable{
    //消费者有事没事就去购物中心消费
    private Mall mall;

    public Consumers(Mall mall) {
        this.mall = mall;
    }

    @Override
    public void run() {
        //土豪家的储物柜
        List<String> list = new LinkedList<String>();
        String name = Thread.currentThread().getName();
        for (int i = 0; i <30; i++) {
            //土豪心情不好,休息一下
            try {
                Thread.sleep((long) (1000* Math.random()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //土豪心情不好,决定去购物---->开始购物
            String goods = mall.getGoods();
            //将商品存放到储物柜
            list.add(goods);
        }
        System.out.println(name+"成功把商场买空,老板大气!");
        System.out.println(name+"完成购物了,购买的所有商品如下=========>>>"+list);
    }
}

购物天堂

package com.qc.testThread;

import java.util.LinkedList;
import java.util.List;

/**
 * @author c
 * @Package com.qc.testThread
 * @date 2021/2/28 21:46
 *
 * 购物天堂
 */
public class Mall {

    /**商场有货架*/
    List<String> list = new LinkedList<String>();

    //送货车
    public synchronized void setGoods(String goods){
        String name = Thread.currentThread().getName();

        //判断是否能送货
        while (list.size()==10){
            try {
                System.out.println(name+"试图送货,但货架满了,正在等待消费。。。。。。");
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //将送来的货物放入货架
        list.add(goods);
        System.out.println(name+ "送来了一个"+goods+"到商场,可以消费了");

        System.out.println("商品明细为===========>>>"+list);
        //唤醒消费者
        this.notifyAll();
    }

    //购物车,提供给消费者使用
    public synchronized String getGoods(){
        String name = Thread.currentThread().getName();

        //判断是否可以购物
        while (list.size()==0){
            try {
                System.out.println(name+"试图购物,但商场没货了,正在等待进货.....");
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //土豪在货架随机购买
        int ioc = (int) (list.size()*Math.random());
        String goods = list.get(ioc);

        //商品被购买,从货架移除
        list.remove(ioc);
        System.out.println(name+"购买了"+goods);
        System.out.println("此时商品明细为"+list);

        //唤醒生产者继续生产
        this.notifyAll();

        return goods;
    }

}

测试类

package com.qc.testThread;

/**
 * @author c
 * @Package com.qc.testThread
 * @date 2021/2/28 21:33
 *
 * 金典的生产者消费者模式
 */
public class Classic {
    public static void main(String[] args) {
        //购物中心
        Mall mall = new Mall();
        //工人1、2、3为土豪五福
        Producers producers = new Producers(mall);
        Producers producers2 = new Producers(mall);
        Producers producers3 = new Producers(mall);
        //土豪
        Consumers consumers = new Consumers(mall);

        new Thread(producers,"工人三号").start();
        new Thread(producers2,"工人二号").start();
        new Thread(producers3,"工人一号").start();
        new Thread(consumers,"土豪").start();
    }
}

Docker 常用命令 2021-02-27
配置Https 2021-03-01

评论区