Java多线程基础

创建线程的方式

继承Thread类

  • 创建一个类继承Thread类
  • 重写Thread中的run方法
  • 实例化该类并
  • 调用start方法即可开启该线程
package com.wcy.code01;

/**
 * 继承的方式创建线程
 */
public class ThreadTest01 {
    public static void main(String[] args) {
        //实例化线程
        MyThread t = new MyThread();
        t.start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程->" + i);
        }
    }
}

/**
 * 继承Thread类
 * 并且重写run方法
 * run方法体中的代码就是该线程需要执行的代码
 */
class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("子线程->" + i);
        }
    }
}

实现Runnable接口

  • 创建一个类实现Runnable接口
  • 实现run方法
  • 实例化一个类
  • 将该实例传入Thread构造函数
package com.wcy.code01;

public class ThreadTest02 {
    public static void main(String[] args) {
        RunnableClass r = new RunnableClass();
        //将Runnable对象传入Thread中实例化一个线程
        Thread t = new Thread(r);
        t.start();
        
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程->" + i);
        }
        
    }
}

/**
 * 实现Runnable接口
 * 实现run方法
 * 方法体中的语句是该线程需要执行的语句
 */
class RunnableClass implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("子线程->" + i);
        }
    }
}
  • 匿名内部类方式
  • 使用匿名内部类的方式实例化一个线程
package com.wcy.code01;

/**
 * 匿名内部类方式
 */
public class ThreadTest03 {
    public static void main(String[] args) {
        //实例化一个线程
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("子线程->" + i);
                }
            }
        });
        
        //开辟线程
        t.start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程->" + i);
        }
    }
}

实现Callable接口

  • jdk8后可以使用
  • 该方法可以获取线程的返回值
  • 前面的方法不可获取线程的返回值
  • 获取线程的返回值时,效率会降低
package com.wcy.code04;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadTest01 {
    public static void main(String[] args) {
        FutureTask futureTask = new FutureTask<>(new CallableClass());
        Thread thread = new Thread(futureTask);
        thread.start();

        try {
            //获取线程的返回值
            //该方法会阻塞
            //会一直等到该线程结束 获取到返回值后才能继续往下执行
            Object o = futureTask.get();
            System.out.println(o);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        System.out.println("main 结束");
    }

}


class CallableClass implements Callable{
    @Override
    public Object call() throws Exception {
        System.out.println("Callable begin");
        Thread.sleep(1000*5);
        System.out.println("Callable end");
        return 300;
    }
}

在这里插入图片描述

package com.wcy.code04;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadTest01 {
    public static void main(String[] args) {
        FutureTask futureTask = new FutureTask<>(new Callable(){
			@Override
		    public Object call() throws Exception {
		        System.out.println("Callable begin");
		        Thread.sleep(1000*5);
		        System.out.println("Callable end");
		        return 300;
		    }
		});
        Thread thread = new Thread(futureTask);
        thread.start();

        try {
            //获取线程的返回值
            //该方法会阻塞
            //会一直等到该线程结束 获取到返回值后才能继续往下执行
            Object o = futureTask.get();
            System.out.println(o);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        System.out.println("main 结束");
    }

}

线程的生命周期

线程的五种状态

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

  • 就绪状态
    一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。
    处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序来调度的。

  • 运行状态(running)
    当线程获得CPU时间片(执行权)后,它才进入运行状态,真正开始执行run()方法。

  • 阻塞状态(blocked)
    线程运行过程中,可能由于各种原因进入阻塞状态:
    ①线程通过调用sleep方法进入睡眠状态;
    ②线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
    ③线程试图得到一个锁,而该锁正被其他线程持有;
    ④线程在等待某个触发条件;
    所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。
    线程被堵塞可能是由下述五方面的原因造成的:
    (1) 调用sleep(毫秒数),使线程进入"睡眠"状态。在规定的时间内,这个线程是不会运行的。
    (2) 用suspend()暂停了线程的执行。除非线程收到resume()消息,否则不会返回"可运行"状态。
    (3) 用wait()暂停了线程的执行。除非线程收到nofify()或者notifyAll()消息,否则不会变成"可运行"(是的,这看起来同原因2非常相象,但有一个明显的区别是我们马上要揭示的)。
    (4) 线程正在等候一些IO(输入输出)操作完成。
    (5) 线程试图调用另一个对象的"同步"方法,但那个对象处于锁定状态,暂时无法使用。

  • 死亡状态(dead)
    有两个原因会导致线程死亡:
    ①run方法正常退出而自然死亡;
    ②一个未捕获的异常终止了run方法而使线程猝死;
    为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法,如果是可运行或被阻塞,这个方法返回true;如果线程仍旧是new状态且不是可运行的,或者线程死亡了,则返回false。
    线程的生命周期图

相关方法

方法作用
void setName(String name)设置该线程的名字 不设置默认的名字为Thread-x
String getName()获取该线程的名字
static Thread currentThread()返回对当前正在执行的线程对象的引用。 这是一个静态方法,该方法在哪个位置引用就返回当前的线程对象
package com.wcy.code01;

public class ThreadTest04 {
    public static void main(String[] args) {
        MyThread01 t1 = new MyThread01();
        //设置名字
        t1.setName("t1");
        System.out.println(t1.getName());
        t1.start();

        MyThread01 t2 = new MyThread01();
        t2.setName("t2");
        System.out.println(t2.getName());
        t2.start();

        MyThread01 t3 = new MyThread01();
        //Thread-2
        System.out.println(t3.getName());
        t3.start();

        //获取当前线程对象
        Thread currentThread = Thread.currentThread();
        //main方法线程对象的名字就是main
        System.out.println(currentThread.getName());
    }
}


class MyThread01 extends Thread {
    @Override
    public void run() {
        Thread currentThread = Thread.currentThread();
        System.out.println("当前线程为->" + currentThread.getName());
    }
}

在这里插入图片描述

线程的中断

  • static void sleep(long millis)
    使当前正在执行的线程以指定的毫秒数暂停,进入睡眠状态
    放弃当前线程的执行权让其他线程使用
    这行代码出现在哪个进程中,哪个进程就会休眠!!!
package com.wcy.code01;


public class ThreadTest05 {
    public static void main(String[] args) {
        long t1Start = System.currentTimeMillis();
        MyThread02 t1 = new MyThread02();
        t1.setName("t1");
        t1.start();


        //虽然是t1调用的sleep
        //但是sleep是一个静态方法
        //最终t1.sleep()-->Thread.sleep()
        //所以这里会让main线程睡眠 并不会让t1线程睡眠
        //所以sleep方法在哪个线程出现就让哪个线程睡眠
        try {
            t1.sleep(1000 * 10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //10秒之后才会打印该信息
        System.out.println("hello");
        long t1End = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName() + "执行时长" + (t1End - t1Start) / 1000 + "s");
    }
}

class MyThread02 extends Thread {
    @Override
    public void run() {
        long t1Start = System.currentTimeMillis();
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
            try {
                //睡眠1秒 每秒打印一次
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        long t1End = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName() + "执行时长" + (t1End - t1Start) / 1000 + "s");

    }
}

只需记得sleep在哪调用,该线程就睡眠!!!
在这里插入图片描述

  • void interrupt() 中断线程睡眠
    触发InterruptedException异常达到中断睡眠的目的
package com.wcy.code01;

/**
 * 终止线程的睡眠
 */
public class ThreadTest06 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"->begin");
                //这里的异常只能用try catch捕捉 不能用throw抛出
                //因为子类抛出的异常不能比父类的多
                try {
                    Thread.sleep(1000*1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"->end");
            }
        });
        t1.setName("t1");
        t1.start();

        // 五秒后中断t1的睡眠
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //采用异常机制来中断睡眠
        //调用该方法后会触发t1中的InterruptedException异常达到中断的目的
        t1.interrupt();
    }
}

线程的终止

终止线程的方式
1 stop()方法,不过该方法已经弃用,不推荐使用,该方法会直接杀死该线程,可能会造成数据丢失
2 设置运行标志位 这是通用的方法

package com.wcy.code02;


/**
 * 线程终止
 */
public class ThreadTest07 {
    public static void main(String[] args) {
        RunnableClass r1 = new RunnableClass();
        Thread t1 = new Thread(r1);
        t1.setName("t1");
        t1.start();


        //五秒后终止t1线程
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //终止线程的方式
        //1 stop()方法,不过该方法已经弃用,不推荐使用,该方法会直接杀死该线程,可能会造成数据丢失
        //2 设置运行标志位 这是通用的方法
        //t1.stop();
        r1.isRun = false;
    }
}


class RunnableClass implements Runnable {

    //运行标志位
    boolean isRun = true;

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (isRun) {
                //每秒打印一次
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "-->" + i);
            } else {
                //可以在这里执行保存数据等操作
                System.out.println("数据保存完毕!");
                //直接结束
                return;
            }
        }

    }
}

线程调度

线程调度模型

  • 均分式调度模型
    是指让所有的线程轮流获得cpu的使用权,并且平均分配每个线程占用的CPU的时间片
  • 抢占式调度模型
    是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃CPU。

java中使用的是抢占式调度模型
java中线程的最高优先级10,默认优先级5,和最低优先级1

package com.wcy.code02;


/**
 * 线程调度
 * 线程调度模型
 * 1.抢占式调度模型:优先级不同
 * 2.均分式调度模型:优先级相同
 */
public class ThreadTest08 {
    public static void main(String[] args) {
        //java中线程的最高优先级10,默认优先级5,和最低优先级1
        //优先级高的线程会有更多的机会抢占cpu时间片
        //并不是优先级高就先完全执行完毕
        //优先级高的线程会大部分先执行完 cpu会分配更多的执行资源用于执行该线程
        System.out.println("最高优先级:" + Thread.MAX_PRIORITY);
        System.out.println("默认优先级:" + Thread.MIN_PRIORITY);
        System.out.println("最低优先级:" + Thread.NORM_PRIORITY);


        Thread t1 = new Thread(new RunnableClass01());
        t1.setName("t1");
        t1.setPriority(Thread.MAX_PRIORITY);
        //main 5
        System.out.println(Thread.currentThread().getName() + "线程的优先级为" + Thread.currentThread().getPriority());
        //t1 10
        System.out.println(t1.getName() + "线程的优先级为" + t1.getPriority());

        t1.start();


        for (int i = 0; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }

    }
}

class RunnableClass01 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

线程让位

  • static void yield()
  • 这是一个非阻塞的方法,放弃当前线程的时间片给其他线程使用
  • 该方法只是让当前线程的状态从运行状态回到就绪状态
  • 回到就绪状态后可以重新抢占时间片
package com.wcy.code02;

/**
 * 线程让位 yield
 * static void yield()
 * 这是一个非阻塞的方法,放弃当前线程的时间片给其他线程使用
 * 该方法只是让当前线程的状态从运行状态回到就绪状态
 * 回到就绪状态后可以重新抢占时间片
 */
public class ThreadTest09 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    //每运行100次进行一次线性让位
                    if (i % 100 == 0) {
                        Thread.yield();
                    }
                    System.out.println(Thread.currentThread().getName() + "-->" + i);
                }
            }
        });
        t1.setName("t1");
        t1.start();

        for (int i = 0; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }

    }
}

线程的合并

  • void join()
  • 调用这个方法的就会将该线程合并到当前运行的线程中
  • 直到这个线程运行完毕后才会继续向下执行
package com.wcy.code02;

/**
 * 线程合并 join
 * 调用这个方法的就会将该线程合并到当前运行的线程中
 * 直到这个线程运行完毕后才会继续向下执行
 */
public class ThreadTest10 {
    public static void main(String[] args) {
        System.out.println("main start");
        MyThread t1 = new MyThread();
        t1.setName("t1");
        t1.start();

        try {
            //合并t1线程到当前线程中
            //直到t1线程执行完毕才会继续往下执行
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
        System.out.println("main end");
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}

线程安全

出现线程安全问题的条件

  • 多线程并发
  • 有共享数据
  • 共享数据有修改的行为
    满足以上的条件就会存在线程安全的问题

java的三大变量的线程安全问题

  • 实例变量
    实例变量会有线程安全的问题,实例变量存储在堆内存中,堆内存只有一个,有可能多个线程会同时引用实例变量
  • 静态变量
  • 实例变量会有线程安全的问题,实例变量存储在方法区内存中,方法区内存只有一个,有可能多个线程会同时引用静态变量
  • 局部变量
    局部变量不会存在线程安全问题,因为局部变量存储在栈中,每个线程都有自己的栈,所以局部变量不共享

java线程安全的解决方法

  • 线程同步机制
    让线程排队执行,不能并发,牺牲效率保护数据的安全

异步编程模型和同步编程模型

  • 异步编程模型
    线程之间互不干扰,线程并发执行
  • 同步编程模型
    线程之间会发生等待关系,线程需要排队执行

异步并发,同步排队

线程同步机制

  • 语法
    synchronized(共享对象){
    同步代码块
    }
    共享对象:多个线程共享的对象,如果多个线程都会操作这个对象,此时的这个对象就是共享对象
    如果说想要t1,t2,t3线程同步,并且这三个线程都是操作对象o1,那么就需要j将o1放入共享对象位置上

  • synchronized的执行原理

    synchronized(o1){
    //执行的代码
    }
    

    在java的每个对象中都会有一把锁,锁是一个标记,有多少个对象就会有多少把锁
    每个类会有一个类锁,不论怎么样都只有一个类锁
    线程执行synchronized中的代码需要有共享对象的对象锁

    同步机制执行过程:

    1.t1和t2线程并发执行,开始执行代码到synchronized处的代码

    2.假设t1首先执行,遇到了synchronized关键字,这时会t1寻找共享对象的对象锁,并且占有它,然后执行同步代码块,在程序执行中一直都会占有共享对象的对象锁。知道t1执行结束,才会释放对象锁。

    3.当t2后来执行遇到了synchronized关键字,此时也会也寻找共享对象的对象锁,但是此时t1正在执行并且占有了对象锁,所以t2此时找不到对象锁,只有等t1执行完毕后,t2才会找到对象锁,执行同步代码。

    简而言之:当线程并发操作共享对象,遇到synchronized关键字,下一个线程只有等上一个线程执行完毕之后才会执行。

  • synchronized可以出现在实例方法上

public synchronized void doSome(){
//同步代码块
}

这时共享对象一定是当前对象this,并且不可改变

  • synchronized可以出现在静态方法上
public synchronized static void doSome(){
//同步代码块
}

此时共享的对象是静态方法所在的类,线程找的是类锁,因为类锁只有一个,所以当其他线程调用的是该类中的其他synchronized修饰静态方法时,也要等到该静态方法结束。

模拟账户取钱案例

账户类

class Account {
	//用户名
    private String name;
    //余额
    private int money;

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

    public String getName() {
        return name;
    }

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

    public int getMoney() {
        return money;
    }

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

    /**
     * 模拟取钱机制
     *
     * @param num:取出的金额
     */
    public void drawMoney(int num) {
        //取出后更改余额
        int currentMoney = this.getMoney() - num;
        this.setMoney(currentMoney);
        //打印取款信息
        System.out.println(
           Thread.currentThread().getName()+"-->"+this.getName() + "取出" + num + ",余额" + this.getMoney());
    }
}

执行取钱操作的线程类

class MyThread extends Thread {

    Account account;

    public MyThread(Account account) {
        this.account = account;
    }

    @Override
    public void run() {
        //取钱5000
        this.account.drawMoney(5000);
    }
}
  • 首先不考虑线程安全的问题,执行一个账户在多个线程中取钱的操作
public class TheadSafeTest01 {
    public static void main(String[] args) {
        Account zs = new Account("张三", 10000);
        MyThread t1 = new MyThread(zs);
        MyThread t2 = new MyThread(zs);
        t1.setName("t1");
        t2.setName("t2");

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

如果不考虑线程安全,这里执行后会出现几种情况

  1. t1先取出了5000,然后还没有更新余额,此时t2进来了,在没有t1取出5000还更改余额的基础上,取出了5000元,然后t1才更改余额,接着t2也在10000的基础上更改余额,此时取出来了10000,但是余额还有5000.
    在这里插入图片描述
  2. 正常情况,t1取完了之后,t2才接着取,余额为零
    在这里插入图片描述
    3.取钱的流程正常,打印结果不正常。在t1取完钱并设置余额位5000后,还没有显示取款信息,然后此时t2也执行了上面的操作将余额设置为0,然后t1才去查询余额打印,所以两次余额都是0.
    在这里插入图片描述
    其中第一种情况是最要命的,这是完全不正确的逻辑,而且当在取完钱还没有更改余额之前发生了网络延迟,那么一定会发生这种情况,模拟就是这样
public void drawMoney(int num) {
        //取出
        int currentMoney = this.getMoney() - num;
        
        //模拟延迟
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        //更改余额
        this.setMoney(currentMoney);
        System.out.println(
           Thread.currentThread().getName()+"-->"+this.getName() + "取出" + num + ",余额" + this.getMoney());
    }

所以说这是一种完全不正确的逻辑,必须杜绝,所以接下来我们使用java解决线程安全的线程同步机制来解决这种问题。

将取钱的方法改为如下形式,线程共享对象是this,所以是当前的账户对象,两个线程同时使用,当一个线程先到来之后,会占有对象锁,后面来的线程就必须要等到前面的线程执行完毕才能执行,从而避免了上面的问题。

public void drawMoney(int num) {
        //线程同步机制
        synchronized (this) {
            //取出
            int currentMoney = this.getMoney() - num;

            //模拟延迟
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //更改余额
            this.setMoney(currentMoney);
            System.out.println(
                Thread.currentThread().getName() + "-->" + this.getName() + "取出" + num + ",余额" + this.getMoney()
            );
        }
    }

也可以是如下形式

public synchronized void drawMoney(int num) {
        //线程同步机制
        //取出
        int currentMoney = this.getMoney() - num;

        //模拟延迟
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //更改余额
        this.setMoney(currentMoney);
        System.out.println(
                Thread.currentThread().getName() + "-->" + this.getName() + "取出" + num + ",余额" + this.getMoney()
        );
    }

死锁

synchnized嵌套使用不注意会引发死锁,难以调试,尽量避免使用。

  • 死锁实现
    代码中由于t1先线程占用了对象o1的对象锁然后睡眠,此时t2也占用了对象o2的对象锁,当睡眠醒来后,t1需要占用o2的对象锁,t2需要占用o1的对象锁,但是两个对象的对象锁已经被互相占用,此时两个线程都会一直无休止等待,程序并不会报错。这就是死锁。
package com.wcy.code03;

public class DeadLock {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();

        MyThread01 t1 = new MyThread01(o1, o2);
        MyThread02 t2 = new MyThread02(o1, o2);

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

class MyThread01 extends Thread{
    Object o1, o2;

    public MyThread01(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }

    @Override
    public void run() {
        synchronized (o2){
            //确保死锁发生
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1){

            }
        }
    }
}

class MyThread02 extends Thread{
    Object o1, o2;

    public MyThread02(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }

    @Override
    public void run() {
        synchronized (o1) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o2) {

            }
        }
    }
}

实际中解决线程安全的方案

  • 尽量使用局部变量代替实例变量
  • 必须要使用实例变量,就创建多个对象
  • 如果必须只创建一个对象,再使用synchronized

守护线程

java中线程的分类

  1. 用户线程
  2. 守护线程(后台线程)
    守护线程的特点:守护线程是一个无限循环,当用户线程结束之后,守护线程也会结束,java中的垃圾回收机制就是一个守护线程。
  • 实现守护线程
package com.wcy.code04;

public class DaemonThread {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.setName("t1");

        //将线程设置为守护线程
        //用户进程结束后会自动结束守护线程
        t1.setDaemon(true);
        t1.start();
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + (i));
        }
    }
}


class MyThread extends Thread {
    @Override
    public void run() {
        int i = 0;
        while (true) {
            System.out.println(Thread.currentThread().getName() + "-->" + (++i));
        }
    }
}

定时器

  • 每间隔一段时间执行一件特定的事情
  • 定时器的实现
    1. 使用sleep()实现,这种方式比较麻烦,需要自己手写
    2. 使用util工具包中定义的Timer类实现

定时器Timer的用法

  • 构造方法
    Timer()
    创建一个新的计时器。
    Timer(boolean isDaemon)
    创建一个新的定时器,其相关线程可以指定为守护线程 。
    Timer(String name)
    创建一个新的定时器,其相关线程具有指定的名称。
    Timer(String name, boolean isDaemon)
    创建一个新的定时器,其相关线程具有指定的名称,可以指定为守护线程 。
  • 相关方法
    void cancel()
    终止此计时器,丢弃任何当前计划的任务。
    int purge()
    从该计时器的任务队列中删除所有取消的任务。
    void schedule(TimerTask task, Date time)
    在指定的时间安排指定的任务执行。
    void schedule(TimerTask task, Date firstTime, long period)
    从指定 的时间开始 ,对指定的任务执行重复的 固定延迟执行 。
    void schedule(TimerTask task, long delay)
    在指定的延迟之后安排指定的任务执行。
    void schedule(TimerTask task, long delay, long period)
    在指定 的延迟之后开始 ,重新执行 固定延迟执行的指定任务。
    void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
    从指定的时间 开始 ,对指定的任务执行重复的 固定速率执行 。
    void scheduleAtFixedRate(TimerTask task, long delay, long period)
    在指定的延迟之后 开始 ,重新执行 固定速率的指定任务。

方法主要了解void schedule(TimerTask task, Date firstTime, long period)
TimerTask task :一个TimerTask任务
Date firstTime:第一次执行时间
long period:间隔时间

Timer实现定时器任务

package com.wcy.code04;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class TimerTest {
    public static void main(String[] args) {
        Timer timer = new Timer();

        //设置一个开始时间
        Date firstTime = new Date();
        //日期模板
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        try {
        	//将该时间解析为Date类型
            firstTime = sdf.parse("2021-06-28 22:22:50");
        } catch (ParseException e) {
            e.printStackTrace();
        }

        //设置定时器任务 间隔10s
        timer.schedule(new MyTimeTask(), firstTime, 1000*10);
    }
}

/**
 * 定时任务类
 */
class MyTimeTask extends TimerTask{
    @Override
    public void run() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String date = sdf.format(new Date());
        System.out.println(date+"执行了一次任务!");
    }
}

在这里插入图片描述

wait和notify方法

Object类中的wait和notify方法,所以继承Object的类中都有这两个方法,这两个方法都不是通过线程来调用的,是通过对象调用

  • wait()
Object o = new Object();
o.wait()

该方法的作用是让在o对象上活动的线程进入无限期等待状态,直到被唤醒为止

  • notify()
Object o = new Object();
o.notify()

该方法的作用是唤醒o对象上处于等待状态的线程

  • notifyAll()
Object o = new Object();
o.notifyAll()

该方法的作用是唤醒o对象上所有处于等待的线程

重点:
wait和notify方法都是建立在synchronized线程同步的基础之上的
o.wait()方法调用后会然正在o上活动的线程进入等待的状态,并且放弃o的对象锁
o.notify()方法调用后只会通知等待的线程醒过来,并不会放弃o的对象锁

消费者和生产者模式

  • 生产者模式和消费者模式是专门为了解决某个问题而产生的
  • 最终达到的目的是要使消费和生产达到平衡状态

举个例子来说明:
有一个生产者和一个消费者,共同使用一个仓库。
生产者只负责生产产品放入仓库中
消费者只负责从仓库中取出产品

如果消费者和生产者是不同的两个线程,仓库是这两个线程共同操作的对象
那么根据线程安全的条件

  • 多线程并发
  • 有共享数据
  • 共享数据有修改的行为

这两个线程必须要使用synchroized线程同步机制,来保证数据的安全。

当仓库里没有产品了,生产者必须生产,而消费者就必须要停止消费
此时生产者线程必须工作,而消费者线程就必须等待,直到仓库中有产品了,才会唤醒消费者线程。
停止消费线程的过程:当消费者线程抢到执行权后,占据仓库的对象锁,然后判读仓库此时的容量,如果容量为空,就用仓库调用wait方法,此时消费者线程就释放对象锁,进入等待状态。当生产者线程生产完毕后会调用notify方法来唤醒沉睡的消费者线程,继续抢夺执行权

如果仓库存满了,那么生产者就必须停止生产,消费者必须消费产品
此时生产者线程必须停止工作,而消费者线程就必须消费产品,直到仓库中有剩余容量了,才会唤醒生产者线程。
停止生产线程的过程:当生产者线程抢到执行权后,占据仓库的对象锁,然后判读仓库此时的容量,如果容量满了,就用仓库调用wait方法,此时生产者线程就释放对象锁,进入等待状态。当消费者线程消费完毕后会调用notify方法来唤醒沉睡的生产者线程,继续抢夺执行权。

实现生产者和消费者模式的两个例子

  • 上述仓库的例子
package com.wcy.code05;

import java.util.ArrayList;

/**
 * 生产者和消费者模式
 * 仓库实例
 * 仓库空了不能消费
 * 仓库满了不能生产
 */
public class ThreadTest02 {
    public static void main(String[] args) {
        //使用数组来模拟仓库
        //仓库的容量设定为1, 所以生产一次就消费一次
        ArrayList<Object> list = new ArrayList<>();

        //消费者和生产者线程
        Production production = new Production(list);
        Consumer consumer = new Consumer(list);

        production.setName("生产者");
        consumer.setName("消费者");

        production.start();
        consumer.start();
    }
}

/**
 * 生产者
 */
class Production extends Thread {
    ArrayList<Object> list;

    public Production(ArrayList<Object> list) {
        this.list = list;
    }

    @Override
    public void run() {
        //记录一下产品的编号
        int count = 1;
        while (true) {
            synchronized (list) {
                //如果仓库满了 生产者停止生产 进入等待状态
                if (list.size() != 0) {
                    try {
                        //等待
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //如果仓库没有满 就生产产品 添加到仓库中
                String s = "产品" + count;
                list.add(s);
                System.out.println(getName() + "生产了-->" + s);
                count += 1;
                //唤醒沉睡的消费者者线程
                list.notifyAll();
            }
        }
    }
}

/**
 * 消费者
 */
class Consumer extends Thread {
    ArrayList<Object> list;

    public Consumer(ArrayList<Object> list) {
        this.list = list;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (list) {
                //仓库容量为0 消费者停止 生产者开始
                if (list.size() == 0) {
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(getName()+"消费了-->"+ list.get(0));
                list.remove(0);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //唤醒生产者线程
                list.notifyAll();
            }
        }
    }
}

在这里插入图片描述

  • 交替打印奇数和偶数
package com.wcy.code05;

import java.util.ArrayList;

/**
 * 生产者和消费者模式实现
 * 要求:
 * 用两个线程来实现交替输出奇数和偶数
 * 一个线程输出奇数
 * 另一个线程输出偶数
 * 必须交替输出奇数和偶数
 */
public class ThreadTest01 {
    public static void main(String[] args) {
        ArrayList<Object> num = new ArrayList<>();
        num.add(1);

        PrintOddNumber odd = new PrintOddNumber(num);
        PrintEvenNumber even = new PrintEvenNumber(num);
        odd.setName("奇数线程");
        even.setName("偶数线程");
        odd.start();
        even.start();
    }
}

/**
 * 奇数线程
 */
class PrintOddNumber extends Thread {
    ArrayList<Object> num;

    public PrintOddNumber(ArrayList<Object> num) {
        this.num = num;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //使用同步机制锁住num对象
            synchronized (num) {
                //判断当前的是不是奇数
                int currentNum = (int) num.get(0);
                if (currentNum % 2 == 0) {
                    try {
                        //不是奇数 就放弃时间片 进入等待状态
                        num.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //输出并加1
                System.out.println(currentThread().getName() + "-->" + num.get(0));
                int newNum = (int) num.get(0) + 1;
                num.set(0, newNum);

                //执行结束后唤醒等待的线程
                num.notifyAll();
            }
        }
    }
}

/**
 * 偶数线程
 */
class PrintEvenNumber extends Thread {
    ArrayList<Object> num;

    public PrintEvenNumber(ArrayList<Object> num) {
        this.num = num;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //使用同步机制
            synchronized (num) {
                //判断是否为偶数
                int currentNum = (int) num.get(0);
                if (currentNum % 2 != 0) {
                    try {
                        //不是偶数 就放弃时间片 进入等待
                        num.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(currentThread().getName() + "-->" + num.get(0));
                int newNum = (int) num.get(0) + 1;
                num.set(0, newNum);
                //结束后唤唤醒等待的线程
                num.notifyAll();
            }
        }
    }
}

在这里插入图片描述
码子真不容易~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值