Java多线程基础

多线程

1、线程中的概念

1.1、什么是进程、什么进程

进程是一个应用进程(1个进程就是一个软件)

线程是一个进程中的执行场景、执行单元

一个进程可以启动多个线程。

1.2、进程和线程的关系

在java语言中:

​ 线程A和线程B,堆内存和方法区内存共享。

​ 但是栈内存独立,一个线程一个栈。

java中之所以有多线程机制,目的就是为了提高程序的处理效率。

image-20211023152711578

2、Java中的多线程

java支持多线程机制,并且java已经将多线程实现了,我们只需要继承就行了。

第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法。

package com.caopeng.java.thread;

/**
 * @author Crescent_P
 * @date 2021-10-23 15:40
 * 实现线程的第一种方式:
 *      编写一个类,直接继承java.lang.Thread,重写run方法
 *
 * 怎么创建线程对象? new
 * 怎么启动线程?      调用线程对象的start()方法
 *
 * 方法体重的代码永远都是自上而下的顺序依次执行
 */
public class ThreadTest01 {
    public static void main(String[] args) {
        // 这里是main方法,这里的代码属于主线程,在主栈中运行
        // 新建一个分支线程对象
        MyThread myThread = new MyThread();
        // 启动线程
        // start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了
        // 这段代码的任务只是开启了一个新的栈空间,只要新的栈空间开出来,start()方法就结束了,线程就启动成功了
        // 启动成功的线程会自动调用run方法,并且run方法在分支栈的底部(压栈)
        // run方法在分支栈的底部,main方法在主栈的底部,run和main是平级的
//        myThread.run();不会启动线程,不会分配新的分支栈(这种方式就是单线程)
        myThread.start();
        for(int i = 0;i < 1000;i++) System.out.println("主线程--->" + i);
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
//        super.run();
        // 编写程序,这段程序运行在分支线程中(分支栈)
        for(int i = 0;i < 1000;i++) System.out.println("分支线程--->" + i);
    }
}

image-20211023154813917

第二种方式:编写一个类实现java.lang.Runnable接口

package com.caopeng.java.thread;

/**
 * @author Crescent_P
 * @date 2021-10-23 16:06
 * 实现线程的第二种方式,编写一个类实现java.lang.Runnable接口
 */
public class ThreadTest02 {
    public static void main(String[] args) {
        // 创建一个可运行的对象
//        MyRunnable myRunnable = new MyRunnable();
        // 将可运行的对象封装成一个线程对象
//        Thread thread = new Thread(myRunnable);
        // 合并代码
        Thread thread = new Thread(new MyRunnable());
        // 启动线程
        thread.start();
        for(int i = 0;i < 1000;i++) System.out.println("主线程--->" + i);
    }
}

// 这并不是一个线程类,只是一个可运行的类,它号不是一个线程。
class MyRunnable implements Runnable{

    @Override
    public void run() {
        for(int i = 0;i < 1000;i++) System.out.println("分支线程--->" + i);
    }
}

image-20211023161055126

使用匿名内部类:

package com.caopeng.java.thread;

/**
 * @author Crescent_P
 * @date 2021-10-23 16:12
 * 采用匿名内部类
 */
public class ThreadTest03 {
    public static void main(String[] args) {
        // 创建线程对象,采用匿名内部类的方法
        // 直接new 接口
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 0;i < 1000;i++) System.out.println("分支线程--->" + i);
            }
        });
        // 启动线程
        thread.start();
        for(int i = 0;i < 1000;i++) System.out.println("主线程--->" + i);
    }
}

image-20211023161511404

线程生命周期

image-20211023162605688

获取线程对象

package com.caopeng.java.thread;

/**
 * @author Crescent_P
 * @date 2021-10-23 16:50
 * 1.获取当前线程对象
 *  Threat threat = static Thread.currentThreat()
 * 2.获取线程对象的名字
 *  String name = 线程对象.getName()
 * 3.修改线程对象的名字
 *      线程对象.setName(String name)
 * 4. 当线程没有设置名字的时候,默认的名字是
 *          Thread-0
 *          Thread-1
 *          Thread-2
 *          Thread-...
 */
public class ThreadTest05 {
    public static void main(String[] args) {

        // currentThreat就是当前线程对象
        // 这个对象出现再main方法中,所以当前线程就是主线程
        Thread currentThread = Thread.currentThread();
        System.out.println(currentThread.getName());    // main

        // 创建线程对象
        MyThread2 myThread1 = new MyThread2();
        System.out.println(myThread1.getName());    // Thread-0
        // 设置线程对象的名字
        myThread1.setName("t2");
        // 获取线程的名字
        System.out.println(myThread1.getName());    // t2
        // 再创建一个新的线程
        MyThread2 myThread2 = new MyThread2();
        System.out.println(myThread2.getName());    // Thread-1
        // 启动线程
        myThread1.start();                          // t2
        myThread2.start();                          // Thread-1
    }
}


class MyThread2 extends Thread {
    @Override
    public void run() {
        // 当前线程,谁执行run方法,谁就是currentThread
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName());
//        for(int i = 0;i < 100;i++) System.out.println("分支线程-->" + i);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ne0bDgYK-1635059132255)(C:\Users\Crescent_P\AppData\Roaming\Typora\typora-user-images\image-20211023170447329.png)]

线程的sleep()方法

    package com.caopeng.java.thread;

    /**
     * @author Crescent_P
     * @date 2021-10-23 17:05
     * 关于线程的sleep方法:
     *      static void sleep(long millis)
     *      1. 静态方法     Thread.sleep(1000);
     *      2.参数是毫秒
     *      3.作用:让当前线程进入休眠,进入“阻塞状态“,放弃占有CPU时间片,让给其他线程使用
     *          这行代码出现在A线程中,A进程就会进入休眠
     *          这行代码出现在B线程中,B进程就会进入休眠
     *      4.Thread.sleep()方法,可以做到这种效果:
     *          间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。
     */
    public class ThreadTest06 {
        public static void main(String[] args) {

            // 让当前线程进入休眠,睡眠5秒
            // 当前线程是主线程
    //        try {
    //            Thread.sleep(1000 * 5);
    //        } catch (InterruptedException e) {
    //            e.printStackTrace();
    //        }
            // 五秒后执行
    //        System.out.println("Hello World");

            for(int i = 1;i <= 10;i++){
                // 打印一次,休息一秒
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                try {
                    // 睡眠一秒
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

image-20211023171148867

package com.caopeng.java.thread;

/**
 * @author Crescent_P
 * @date 2021-10-23 17:12
 */
public class ThreatTest07 {
    public static void main(String[] args) {
        // 创建线程对象
        Thread t = new MyThreat3();
        t.setName("t");
        t.start();

        // 调用sleep方法
        try {
            // 这行代码会让线程t进入休眠状态吗?
            t.sleep(1000 * 5);      // 不会,在执行的时候还是会转化成 Thread.sleep(1000 * 5)
                                            // 这行代码的作用是:让当前代码进入休眠,也就是main线程进入休眠
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(("Hello World"));
    }
}

class MyThreat3 extends Thread{
    @Override
    public void run() {
//        super.run();
        for (int i = 0;i < 1000;i++) System.out.println(Thread.currentThread().getName() + "--->" + i);
    }
}
package com.caopeng.java.thread;

/**
 * @author Crescent_P
 * @date 2021-10-23 17:18
 * sleep睡眠太久了,怎么叫醒一个正在睡眠的线程?
 *      不是中断线程的执行,是终止线程的睡眠
 */
public class ThreatTest08 {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable2());
        thread.setName("t");
        thread.start();

        // 希望5秒之后,t线程醒来
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 终端t线程的睡眠,这种中断睡眠的方式依靠了java的异常处理机制。
        thread.interrupt(); // 干扰
    }
}

class MyRunnable2 implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "---> begin");
        // 子类重写不能抛出更多异常
        // run()在父类中没有抛出任何异常,子类重写不能比父类抛出更多异常
        try {
            Thread.sleep(1000 * 60 * 60 * 24 * 365);    // interrupt会使这里报异常
        } catch (InterruptedException e) {
            // 打印异常信息
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "--->end");
    }
}

image-20211023172854887

强制终止线程

package com.caopeng.java.thread;

/**
 * @author Crescent_P
 * @date 2021-10-23 18:50
 * 在java中强行终止一个线程的执行
 * 这种方式存在很大的缺点,容易丢数据,因为这种方式直接将线程杀死了
 * 线程没有保存的数据会丢失,不建议使用
 */
public class ThreadTest09 {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable3());
        thread.setName("t");
        thread.start();

        // 模拟5秒
        try {
            Thread.currentThread().sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 5秒收强制终止t线程
        thread.stop();      // 容易损坏数据
    }
}

class MyRunnable3 implements Runnable{

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

合理终止线程

package com.caopeng.java.thread;

/**
 * @author Crescent_P
 * @date 2021-10-23 18:57
 * 合理的终止一个线程的执行
 */
public class ThreadTest10 {
    public static void main(String[] args) {
        MyRunnable4 myRunnable4 = new MyRunnable4();
        Thread thread = new Thread(myRunnable4);
        thread.setName("t");
        thread.start();
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 终止线程
        // 你想要什么时候终止t的执行,那么你就吧标记修改为false,就结束了
        myRunnable4.run = false;
    }
}

class MyRunnable4 implements Runnable {
    // 打一个布尔标记
    boolean run = true;
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if(run){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else{
                // 终止当前进程
                // 在这保存数据
                return;
            }
        }
    }
}

image-20211023190335071

Java中线程调度方法

实例方法:

void setPriority(int newPriority)		// 设置线程的优先级
int getPriority()						// 获取线程优先级
最低优先级	1
默认优先级	5
最高优先级	10
void join()								// 合并线程
class MyThread1 extends Thread{
    public void dosome(){
        Mythread2 t = new Mythread2();
        t.join();	// 当前进程进入阻塞,t线程执行,直到t线程结束,当前线程才可以运行
    }
}
class Mythread2 extends Thread{}

静态方法:

static void yield()			// 让位方法

暂停当前正在执行的线程方法,并执行其它线程

yield()方法不是阻塞方法,让当前线程让位,让给其它线程使用

yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”

image-20211023191224877

package com.caopeng.java.thread;

/**
 * @author Crescent_P
 * @date 2021-10-23 19:15
 */
public class ThreadTest11 {
    public static void main(String[] args) {
//        System.out.println("最高优先级: " + Thread.MAX_PRIORITY);
//        System.out.println("最低优先级: " + Thread.MIN_PRIORITY);
//        System.out.println("默认优先级: " + Thread.NORM_PRIORITY);
//        最高优先级: 10
//        最低优先级: 1
//        默认优先级: 5

        // 获得当前线程对象,获取当前线程优先级
//        Thread thread = Thread.currentThread();
//        System.out.println(thread.getName() + "线程的默认优先级是: " + thread.getPriority());
        // main线程的默认优先级是: 5

//        Thread thread1 = new Thread(new MyRunnable5());
//        thread1.start();
        // Thread-0线程的默认优先级是: 5

        // 设置主线程的优先级为1
        Thread.currentThread().setPriority(1);
        Thread thread1 = new Thread(new MyRunnable5());
        thread1.setPriority(10);
        thread1.start();
        for (int i = 0; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }

    }
}

class MyRunnable5 implements Runnable {
    @Override
    public void run() {
        // 获取线程的优先级
//        System.out.println(Thread.currentThread().getName() + "线程的默认优先级是: " + Thread.currentThread().getPriority());
        for (int i = 0; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

image-20211023192341935

package com.caopeng.java.thread;

/**
 * @author Crescent_P
 * @date 2021-10-23 19:24
 * 让位,当前线程暂停,回到就绪状态,让给其它线程
 * 静态方法:Thread.yield()
 */
public class TreadTest12 {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable6());
        thread.setName("t");
        thread.start();

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

class MyRunnable6 implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 10000; i++) {
            // 每100个让位1次
            if(i % 100 == 0) Thread.yield();
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

image-20211023192941890

package com.caopeng.java.thread;

/**
 * @author Crescent_P
 * @date 2021-10-23 19:30
 * 线程合并
 */
public class ThreadTest13 {
    public static void main(String[] args) {
        System.out.println("main begin...");

        Thread thread = new Thread(new MyRunnable7());
        thread.setName("t");
        thread.start();

        // 合并线程
        try {
            thread.join();  // t合并到当前线程,当前线程受阻塞,t线程执行,直到结束
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("main over....");
    }
}

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

image-20211023193405242

线程安全

什么时候存在线程安全问题呢?

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

满足以上三个条件,就会存在线程安全问题

怎么解决?

线程排队执行,不能并发。称为:线程同步机制

会牺牲一定的效率,但是安全。

模拟取钱出错

Account.java

package com.caopeng.java.thread.threadsafe;

/**
 * @author Crescent_P
 * @date 2021-10-23 19:46
 * 银行账户类
 */
public class Account {
    // 账户
    private String accountId;
    // 余额
    private double balance;

    public Account() {
    }

    public Account(String accountId, double balance) {
        this.accountId = accountId;
        this.balance = balance;
    }

    public String getAccountId() {
        return accountId;
    }

    public void setAccountId(String accountId) {
        this.accountId = accountId;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    // 取款方法
    public void withDraw(double money){
        // 取款前的余额
        double before = this.balance;
        // 取款后的余额
        double after = before - money;
        try {
            // 模拟网络延迟
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 跟新余额
        this.setBalance(after);
    }
}

AccountThread.java

package com.caopeng.java.thread.threadsafe;

/**
 * @author Crescent_P
 * @date 2021-10-23 19:52
 */
public class AccountThread extends Thread{
    // 两个线程共享一个账户对象
    private Account account;

    // 通过构造方法传递过来账户信息
    public AccountThread(Account account) {
        this.account = account;
    }

    // 取款
    @Override
    public void run() {
//        super.run();
        // 假设取款5000
        double money = 5000;
        // 取款
        account.withDraw(money);
        System.out.println(Thread.currentThread().getName() + "对账户" + account.getAccountId() + "取款: "+ money+ " 成功,余额为:" + account.getBalance());
    }
}

Test.java

package com.caopeng.java.thread.threadsafe;

/**
 * @author Crescent_P
 * @date 2021-10-23 19:55
 */
public class Test {
    public static void main(String[] args) {
        // 创建账户对象
        Account account = new Account("act-001",10000);
        // 两个线程共享一个账户
        AccountThread thread1 = new AccountThread(account);
        AccountThread thread2 = new AccountThread(account);
        // 启动取款
        thread1.start();
        thread2.start();
    }
}

image-20211023201019660

解决

使用Synchronized关键字

package com.caopeng.java.thread.threadsafe2;

/**
 * @author Crescent_P
 * @date 2021-10-23 19:46
 * 银行账户类
 */
public class Account {
    // 账户
    private String accountId;
    // 余额
    private double balance;

    public Account() {
    }

    public Account(String accountId, double balance) {
        this.accountId = accountId;
        this.balance = balance;
    }

    public String getAccountId() {
        return accountId;
    }

    public void setAccountId(String accountId) {
        this.accountId = accountId;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    /*
    线程同步机制的语法是:
        synchronized(){
            // 线程同步代码块
        }
        synchronized后面的小括号中传的这个”数据“是相当重要的
        这个数据必须是多线程共享的数据,才能到达多线排队
        ()写什么?
            那要看你想要哪些进程同步
            假设t1、t2、t3、t4、t5,有5个线程
            你只希望 t123排队,t45不需要排队
            那你在()中填写一个t1、t2、t3共享的对象,而这个对象对于t4 t5不是共享的
        这里的共享对象是账户对象,

        在java中,任何对象都有一把锁,对象锁。
        以下代码的执行原理:
            1、假设t1和t2并发,开始执行以下代码的时候,肯定有一个先有一个后,
            2、假设t1先执行了,遇到了synchronized,这个时候会去找”共享对象的对象锁“
                找到之后,会占有这把锁,然后执行同步代码块的中的代码,在程序执行过程中,
                一直都是占有这把锁的,直到同步代码块执行结束,才会放弃这把锁
            3、假设t1已经占有这把锁了,此时t2也遇到了synchronized关键字,也会去占有后面共享对象的这把锁
                结果这把锁已经被t1占有了,t2只能在同步代码块外面等待t1的结束
                直到t1把同步代码块执行完了之后,t1会归还这把锁,此时t2等到了这把锁,
                然后t2就会占有这把锁,并执行代码块中的内容

            这样就达到了线程排队执行
            这里需要注意的是:这个共享对象一定要选好了,这个共享对象一定是你需要排队执行的这些线程所共享的。
     */
    // 取款方法
    public void withDraw(double money){
        // 以下几行代码必须是线程排队,不能并发
        // 一个线程把这里的代码执行完,另一个线程才能进来
        synchronized(this){
            double before = this.balance;
            double after = before - money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBalance(after);
        }

    }
}

package com.caopeng.java.thread.threadsafe2;

/**
 * @author Crescent_P
 * @date 2021-10-23 19:52
 */
public class AccountThread extends Thread{
    // 两个线程共享一个账户对象
    private Account account;

    // 通过构造方法传递过来账户信息
    public AccountThread(Account account) {
        this.account = account;
    }

    // 取款
    @Override
    public void run() {
//        super.run();
        // 假设取款5000
        double money = 5000;
        // 取款
        account.withDraw(money);
        System.out.println(Thread.currentThread().getName() + "对账户" + account.getAccountId() + "取款: "+ money+ " 成功,余额为:" + account.getBalance());
    }
}

package com.caopeng.java.thread.threadsafe2;

/**
 * @author Crescent_P
 * @date 2021-10-23 19:55
 */
public class Test {
    public static void main(String[] args) {
        // 创建账户对象
        Account account = new Account("act-001",10000);
        // 两个线程共享一个账户
        AccountThread thread1 = new AccountThread(account);
        AccountThread thread2 = new AccountThread(account);
        // 启动取款
        thread1.start();
        thread2.start();
    }
}

image-20211023202035970

image-20211023203425719

image-20211023203352925

有线程安全问题的变量

Java中有三大变量。

实例变量:堆

静态变量:方法区

局部变量:栈

局部变量一定没有线程安全问题,因为局部变量不共享,一个线程一个栈。

synchronized出现在实例方法上
package com.caopeng.java.thread.threadsafe3;

/**
 * @author Crescent_P
 * @date 2021-10-23 19:46
 * 银行账户类
 */
public class Account {
    // 账户
    private String accountId;
    // 余额
    private double balance;

    public Account() {
    }

    public Account(String accountId, double balance) {
        this.accountId = accountId;
        this.balance = balance;
    }

    public String getAccountId() {
        return accountId;
    }

    public void setAccountId(String accountId) {
        this.accountId = accountId;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    // 取款方法

    /*
    在实例方法上是可以使用 synchronized 的,那此时锁的一定是this
    没得挑,因此不灵活

    另外,synchronized出现在实例方法上
    表示整个方法体都需要同步,看你会无故扩大同步的范围
    导致程序的执行效率降低,所以这种方式不常用
    
    使用在实例方法上的优点:代码简洁
    如果共享的对象就是this,并且整个方法体都需要同步
    建议使用这个
     */
    public synchronized void withDraw(double money){
        double before = this.balance;
        double after = before - money;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setBalance(after);
    }
}

synchronized的三种写法:

  • 同步代码块

    synchronized(线程共享对象){
        同步代码块
    }
    
  • 实例方法上使用synchronized,表示共享对象一定是this,并且同步代码块是整个方法体

  • 在静态方法上使用synchronized,表示找类锁,类锁只有一把,不管创建了多少个对象,只有一把类锁

Synchronized面试题
题一:
package com.caopeng.java.thread.exam1;

/**
 * @author Crescent_P
 * @date 2021-10-24 12:04
 *  doOther方法的执行需要等 doSome 方法的结束
 */
public class Exam01 {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        Thread thread1 = new MyThread(myClass);
        Thread thread2 = new MyThread(myClass);

        thread1.setName("t1");
        thread2.setName("t2");

        thread1.start();

        try {
            // 保证thread1先执行
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        thread2.start();
    }
}

class MyThread extends Thread {
    private MyClass myClass;

    public MyThread(MyClass myClass) {
        this.myClass = myClass;
    }

    @Override
    public void run() {
        if(Thread.currentThread().getName().equals("t1")){
            myClass.doSome();
        }
        if(Thread.currentThread().getName().equals("t2")){
            myClass.doOther();
        }
    }
}

class MyClass{
    public synchronized void doSome(){
        System.out.println("doSome begin...");
        try {
            Thread.sleep(1000 * 10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over...");
    }

    public void doOther(){
        System.out.println("doOther begin...");
        System.out.println("doOther over...");
    }
}
// 不需要,因为 doOther 没有 synchronized 不需要对象锁

image-20211024121208450

题二:
package com.caopeng.java.thread.exam2;

/**
 * @author Crescent_P
 * @date 2021-10-24 12:04
 *  doOther方法的执行需要等 doSome 方法的结束
 */
public class Exam01 {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        Thread thread1 = new MyThread(myClass);
        Thread thread2 = new MyThread(myClass);

        thread1.setName("t1");
        thread2.setName("t2");

        thread1.start();

        try {
            // 保证thread1先执行
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        thread2.start();
    }
}

class MyThread extends Thread {
    private MyClass myClass;

    public MyThread(MyClass myClass) {
        this.myClass = myClass;
    }

    @Override
    public void run() {
        if(Thread.currentThread().getName().equals("t1")){
            myClass.doSome();
        }
        if(Thread.currentThread().getName().equals("t2")){
            myClass.doOther();
        }
    }
}

class MyClass{
    public synchronized void doSome(){
        System.out.println("doSome begin...");
        try {
            Thread.sleep(1000 * 10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over...");
    }

    public synchronized void doOther(){
        System.out.println("doOther begin...");
        System.out.println("doOther over...");
    }
}
// 需要,因为 doOther 有 synchronized 需要对象锁,但是对象锁此时已经被 thread1拿走了
// 需要thread1执行完doSome方法,才能拿到对象锁

image-20211024121442657

题三:
package com.caopeng.java.thread.exam2;

/**
 * @author Crescent_P
 * @date 2021-10-24 12:04
 *  doOther方法的执行需要等 doSome 方法的结束
 */
public class Exam01 {
    public static void main(String[] args) {
        MyClass myClass1 = new MyClass();
        MyClass myClass2 = new MyClass();
        Thread thread1 = new MyThread(myClass1);
        Thread thread2 = new MyThread(myClass2);

        thread1.setName("t1");
        thread2.setName("t2");

        thread1.start();

        try {
            // 保证thread1先执行
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        thread2.start();
    }
}

class MyThread extends Thread {
    private MyClass myClass;

    public MyThread(MyClass myClass) {
        this.myClass = myClass;
    }

    @Override
    public void run() {
        if(Thread.currentThread().getName().equals("t1")){
            myClass.doSome();
        }
        if(Thread.currentThread().getName().equals("t2")){
            myClass.doOther();
        }
    }
}

class MyClass{
    public synchronized void doSome(){
        System.out.println("doSome begin...");
        try {
            Thread.sleep(1000 * 10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over...");
    }

    public synchronized void doOther(){
        System.out.println("doOther begin...");
        System.out.println("doOther over...");
    }
}
// 不需要,即使两个方法都有 synchronized 修饰,但是两个线程占有的对象不一样
// 有两个 MyClass 类型的对象,thread1拿走的是myClass1这个对象的对象锁,不影响thread2占有myClass2的对象锁

image-20211024121713871

题四:
package com.caopeng.java.thread.exam2;

/**
 * @author Crescent_P
 * @date 2021-10-24 12:04
 *  doOther方法的执行需要等 doSome 方法的结束
 */
public class Exam01 {
    public static void main(String[] args) {
        MyClass myClass1 = new MyClass();
        MyClass myClass2 = new MyClass();
        Thread thread1 = new MyThread(myClass1);
        Thread thread2 = new MyThread(myClass2);

        thread1.setName("t1");
        thread2.setName("t2");

        thread1.start();

        try {
            // 保证thread1先执行
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        thread2.start();
    }
}

class MyThread extends Thread {
    private MyClass myClass;

    public MyThread(MyClass myClass) {
        this.myClass = myClass;
    }

    @Override
    public void run() {
        if(Thread.currentThread().getName().equals("t1")){
            myClass.doSome();
        }
        if(Thread.currentThread().getName().equals("t2")){
            myClass.doOther();
        }
    }
}

class MyClass{
    public synchronized static void doSome(){
        System.out.println("doSome begin...");
        try {
            Thread.sleep(1000 * 10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over...");
    }

    public synchronized void doOther(){
        System.out.println("doOther begin...");
        System.out.println("doOther over...");
    }
}
// 需要,因为 synchronized 出现在静态方法上,此时锁的是类锁
// 不管有几个对象,MyClass这个类的类锁只有这一个

image-20211024121921704

死锁

package com.caopeng.java.thread.deadlock;

/**
 * @author Crescent_P
 * @date 2021-10-24 12:22
 */
public class DeadLock {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();

        // 两个线程共享o1,o2
        Thread myThread1 = new MyThread1(o1,o2);
        Thread myThread2 = new MyThread2(o1,o2);

        myThread1.start();
        myThread2.start();
    }
}

class MyThread1 extends Thread {
    Object o1;
    Object o2;

    public MyThread1(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){

            }
        }
    }
}

class MyThread2 extends Thread {
    Object o1;
    Object o2;

    public MyThread2(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){

            }
        }
    }
}

守护线程

java语言中线程分为两大类:

  • 用户线程
  • 守护线程(后台线程)
    • 垃圾回收线程

守护线程的特点:一般用户线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。

主线程main就是一个用户线程。

守护线程一般用在什么地方呢?

​ 每天00:00的时候系统数据自动备份

​ 这个需要使用到定时器,并且我们可以将定时器设置为守护线程。

​ 一直在那看着,每到00:00的时候就自动备份一份,所有的用户线程如果结束了,守护线程自动退出,没必要进行数据备份了。

package com.caopeng.java.thread;

/**
 * @author Crescent_P
 * @date 2021-10-24 13:55
 * 守护线程
 */
public class ThreadTest14 {
    public static void main(String[] args) {
        Thread thread = new DateThread();
        thread.setName("备份数据的线程");
        // 启动线程之前,将线程设置成守护线程
        thread.setDaemon(true);
        thread.start();

        // 主线程,主线程是用户线程
        for(int i = 0;i < 10;i++){
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

class DateThread extends Thread {
    @Override
    public void run() {
        int i = 0;
        // 即使是死循环,但由于该进程是守护进程,当用户进程结束,守护线程自动终止
        while(true){
            System.out.println(Thread.currentThread().getName() + "--->" + (++i));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

image-20211024140021029

定时器

定时器的作用:

​ 每隔特定的时间,执行特定的程序

package com.caopeng.java.thread;

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

/**
 * @author Crescent_P
 * @date 2021-10-24 14:05
 */
public class TimerTest {
    public static void main(String[] args) throws ParseException {
        // 创建定时器对象
        Timer timer = new Timer();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstDate = sdf.parse("2021-10-24 14:09:00");
        // 指定定时任务
        timer.schedule(new LogTimerTask(),firstDate,1000 * 10);
    }
}

// 编写一个定时任务类
class LogTimerTask extends TimerTask {

    @Override
    public void run() {
        // 需要执行的任务
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String time = sdf.format(new Date());
        System.out.println(time+" 完成了一次备份");
    }
}

image-20211024141218632

实现线程的第三种方式

这种方式可以获取到线程返回值,之前的两种方式无法获得到线程的返回值。

实现Callable接口

package com.caopeng.java.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask; // JUC包下,属于Java的并发包,Java新特性

/**
 * @author Crescent_P
 * @date 2021-10-24 14:13
 * 实现Callable接口
 */
public class ThreadTest15 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 第一步:创建一个"未来任务类"对象
        FutureTask futureTask = new FutureTask(new Callable(){
            @Override
            public Object call() throws Exception {     // call()方法相当于run方法,只不过这个有返回时
                // 线程执行一个任务,执行之后可能会一有一个执行结果
                // 模拟执行
                System.out.println("call method begin");
                Thread.sleep(1000 * 10);
                System.out.println("call method end");
                int a = 100;
                int b = 200;
                return a+b; // 自动装箱
            }
        });

        // 创建一个线程对象
        Thread thread = new Thread(futureTask);

        // 启动线程
        thread.start();

        // 这里是main方法,这是在主线程
        // 在主线程中,怎么获取到t线程的返回结果?
        // get()方法的执行,会导致当前线程的阻塞.
        Object o = futureTask.get();        // 会导致当前线程阻塞,到call()方法执行完后,才能拿到执行的执行结果的返回值
        System.out.println(o);
    }
}

image-20211024142151496

关于Object类中的wait方法和notify方法

  1. wait方法和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方法是Object类自带的

  2. wait方法作用

    Object o = new Object();
    o.wait();
    表示:让正在o对象上活动的线程进入等待,无期限等待,知道被唤醒为止
    

    image-20211024142810596

  3. notify方法的作用

    唤醒正在o对象上等待的线程
    
生产者消费者模式

image-20211024143628469

package com.caopeng.java.thread;

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

/**
 * @author Crescent_P
 * @date 2021-10-24 14:37
 *      1.使用wait方法和notify方法,实现生产者和消费者模式
 *
 *      2.wait和notify方法不是线程对象的方法,是普通java对象都有的方法
 *
 *      3.wait和notify方法建立在线程同步的基础上,因为多线程要同时操作一个仓库,有线程安全问题
 *
 *      4.wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放t线程之前占有的o对象的锁
 *
 *      5.notify方法作用:o.notify()方法让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁
 *
 *      6.模拟这样一个需求:
 *          仓库采用List集合
 *          List集合中只能存储1个元素
 *          1个元素就表示仓库满了,
 *          如果List集合中元素个数是0,就表示仓库空了
 *          保证List集合中永远最多存储1个元素
 *
 *          生产一个消费1个
 */
public class ThreadTest16 {
    public static void main(String[] args) {
        // 共享一个仓库
        List list = new ArrayList();
        // 创建两个线程
        // 生产者线程
        Thread producer = new Thread(new Producer(list));
        // 消费者线程
        Thread consumer = new Thread(new Consumer(list));

        producer.setName("producer");
        consumer.setName("consumer");
        producer.start();
        consumer.start();
    }
}

// 生产线程
class Producer implements Runnable {
    private List list;

    public Producer(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        // 一直生产
        while(true){
            // 给list加锁
            synchronized (list) {
                // 不为空
                if(!list.isEmpty()){
                    //进入等待状态,当前线程进入等待状态,并且释放 List集合的锁
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                // 仓库为空
                }else{
                    Object o = new Object();
                    list.add(o);
                    System.out.println(Thread.currentThread().getName() + "--->" + o);
                    // 唤醒消费者进行消费
                    list.notify();
                }
            }
        }
    }
}

// 消费线程
class Consumer implements Runnable{
    private List list;

    public Consumer(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        // 一直消费
        while(true){
            synchronized(list){
                // 仓库空了,消费者进程进行等待
                if(list.isEmpty()){
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    // 可以消费
                    Object o = list.remove(0);
                    System.out.println(Thread.currentThread().getName() + "--->" + o);
                    list.notify();
                }
            }
        }
    }
}

image-20211024150415934

一直生产
while(true){
// 给list加锁
synchronized (list) {
// 不为空
if(!list.isEmpty()){
//进入等待状态,当前线程进入等待状态,并且释放 List集合的锁
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 仓库为空
}else{
Object o = new Object();
list.add(o);
System.out.println(Thread.currentThread().getName() + “—>” + o);
// 唤醒消费者进行消费
list.notify();
}
}
}
}
}

// 消费线程
class Consumer implements Runnable{
private List list;

public Consumer(List list) {
    this.list = list;
}

@Override
public void run() {
    // 一直消费
    while(true){
        synchronized(list){
            // 仓库空了,消费者进程进行等待
            if(list.isEmpty()){
                try {
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else{
                // 可以消费
                Object o = list.remove(0);
                System.out.println(Thread.currentThread().getName() + "--->" + o);
                list.notify();
            }
        }
    }
}

}


[外链图片转存中...(img-YQndGccp-1635059132268)]





















































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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值