多线程详解

多线程的概述

  1. 什么是进程?什么时线程?

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

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

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

  2. 进程和线程是什么关系?

    阿里巴巴:进程

    ​ 公司的每个员工就是阿里巴巴的每个线程

    京东:进程

    ​ 强东:京东的一个进程

    ​ 妹妹:京东的一个线程

    进程可以看作是现实生活当中的公司。

    线程可以看作是公司当中的某个员工。

    注意:

    进程A和进程B的内存独立不共享

    ​ 进程A和进程B在java语言中

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

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

    ​ j假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。

    火车站可以看作是一个进程。

    火车站中的每一个售票窗口可以看作是一个线程。

    我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你,所以多线程并发可以提高效率。

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

多线程并发

什么是真正的多线程并发?

t1线程执行t1的。

t2线程执行t2的。

t1不会影响t2,t2也不会影响t1,这就叫做多线程并发。

单核CPU可以做到真正的多线程并发吗?

对于多核的CPU电脑来说,真正的多线程并发是没问题的,

4核CPU表示同一个时间点,可以真正的有4个进程并发执行。

单核CPU表示只有一个大脑:

​ 不能够做到真正的多线程并发, 但是可以做到给人一种“多线程并发”的感觉

对于单核的CPU来说,在某个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,给人的感觉是:多个事情在同时做。

java实现多线程有两种方式

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

public class User extends  Thread {


    @Override
    public void run() {
        //编写程序,这段程序运行在分支线程中(分支栈)
        for (int i = 0; i <100 ; i++) {

            System.out.println("分支线程"+i);
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        //这里是main方法,这里的代码属于主线程,在主栈中运行
        //新建一个分支对象
        User user=new User();
        //启动线程
        /*
         *  start()的作用:启动一个分支线程,在jvm中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了
         * 这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了,
         * 线程就启动成功了。启动成功的线程会自动调用run方法,并且在分支栈的栈底部(压栈)。
         * run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。
         *  */
        user.start();  //并发执行
        for (int i = 0; i < 100; i++) {
            System.out.println("主线程+"+i);
        }
    }
}

注意:方法体中当中的代码永远都是自上而下的顺序依次逐行执行的。

run和start的区别

线程的run

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-11uoEsCO-1596290691138)(D:\pic\Typora-pic\image-20200731122253393.png)]

线程的start

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kOXUxqpM-1596290691139)(D:\pic\Typora-pic\image-20200731123128388.png)]

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

public class RunTest {
    public static void main(String[] args) {
        //创建一个可运行的对象
        Test01 test01=new Test01();
        //将可运行的对象封装成一个线程对象
        Thread thread=new Thread(test01);
        //启动线程
         thread.start();

        for (int i = 0; i <100 ; i++) {
            System.out.println("主"+i);
        }
    }
}
//这并不是一个线程类,是一个可执行的类。它还不是一个线程
class  Test01 implements  Runnable {
    @Override
    public void run() {
        for (int i = 0; i <100 ; i++) {
            System.out.println("Runnable"+i);
        }
    }
}

第二种方式比较常用:因为是面向接口编程,一个类实现了接口还可以继承。

线程的生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F3j1zxFY-1596290691140)(D:\pic\Typora-pic\image-20200731145940702.png)]

关于线程对象的生命周期?

新建状态

就绪状态

运行状态

阻塞状态

死亡状态

线程的方法

获取当前线程对象

获取线程对象的名字

设置线程对象的名字

默认线程的名字

package com.aaa.thread.xiancheng;

/*
*  1. 怎么获取当前线程对象
*       static Thread  currentThread()  返回对当前正在执行的线程对象的引用。
*  2. 获取线程对象的名字    线程对象.getName();
*  3. 修改线程对象的名字   线程对象.setName("线程一");
*  4. 当线程没有设置名字时,默认的名字的规律?
*       Thread-0
*       Thread-1
* */
public class ThreadTest01 {
    public static void main(String[] args) {

        MyThread myThread = new MyThread();
        //设置线程对象的名字
        myThread.setName("分支线程1");
        //获取线程对象的名称
        String name = myThread.getName();
        //System.out.println(name);
        //currentThread()当前线程
        //这个代码出现在main方法中,所以当前线程就是主线程
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName());
        //启动线程
        myThread.start();

        MyThread thread1=new MyThread();
        thread1.setName("分支2");
        thread1.start();


    }
}
//线程对象
class  MyThread extends  Thread{
    @Override
    public void run() {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName());

    }
}

线程的 sleep方法

静态方法

参数是毫秒

作用:让当前线程进入休眠,进入阻塞状态,放弃 占有CPU时间片,让给其他线程使用。

package com.aaa.thread.xiancheng;

/*
*  线程的阻塞状态
* */
public class ThreadTest02 {
    public static void main(String[] args) {

        MyThread2 t2=new MyThread2();
        t2.setName("test2");
        //获取线程的名字
        System.out.println(t2.getName());
        //启动线程
        t2.start();
        //使当前线程阻塞
        try {
           Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //获取当前线程对象
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName());
    }

}
class  MyThread2 extends  Thread{

    @Override
    public synchronized void start() {
        System.out.println("分支线程");
    }
}

sleep的面试题

package com.aaa.thread.xiancheng;

/*
*  sleep的面试题
* */
public class ThreadTest03 {
    public static void main(String[] args) throws InterruptedException {
        //创建线程对象
        Thread t3=new MyThread3();
        t3.setName("T");
        t3.start();

        //调用sleep
        //问题:这行代码会让线程T进入休眠状态吗? 不会
        t3.sleep(1000*5);
        //5秒之后
        System.out.println("Hello World");
    }

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

​ 终止线程的睡眠

package com.aaa.thread.xiancheng;

/*
*  sleep睡眠太久了,如果希望提前醒来, 如何提前终止睡眠?
*
*
* */
public class ThreadTest04 {
    public static void main(String[] args) {

        Thread thread=new Thread(new MyThread4());

        thread.setName("t");
        thread.start();
        //希望5秒之后,t线程醒来(5秒之后主线程的开始执行)
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //中断  t线程的睡眠  中断这个线程。(这种中断方式是依靠了java的异常处理机制
        // (InterruptedException(中断异常)))
        thread.interrupt();
    }
}
class  MyThread4 implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-->"+"begin");
        //子类不能抛出比父类更宽泛的异常
        try {
            Thread.sleep(1000*60*60*24);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"-->"+"end");
    }
}

​ 怎么合理的终止一个线程的执行。这种方式很常用

package com.aaa.thread.xiancheng;

/*
*  强行终止进程
* */
public class ThreadTest05 {
    public static void main(String[] args) {
        MyThread5 thread5=new MyThread5();

        Thread thread=new Thread(thread5);
        thread.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread5.run=false;
    }
}
class  MyThread5 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;
            }

        }
    }
}

线程安全(最重要)

​ 关于多线程并发环境下,数据的安全问题。

以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了,这些代码不需要我们编写


最重要的是:你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的时这些数据在多线程并发下是否是安全的。

线程不安全的情况?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qNzN8mrA-1596290691142)(D:\pic\Typora-pic\image-20200731182229804.png)]

三个条件:

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

满足以上3个条件之后,就会窜在线程安全问题。

怎么解决线程安全问题?

线程排队执行。(不能并发)

用排队执行解决线程安全问题。

这种机制被称为:线程同步机制(线程排队)。

线程同步就是线程排队了,线程排队就会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事。

同步和异步

同步编程模型:

线程a和线程b,在线程a执行的时候,必须等待b执行结束,或者说在b执行的时候,必须等待a执行结束,两个线程之间发生了等待关系,这就是同步编程模型。效率较低。线程排队执行。

异步编程模型:

线程a和线程b,各自执行各自的,线程a不管b,b也不管a,谁也不需要等谁,这种编程模型叫做:异步编程模型,

其实就是:多线程并发(效率高)

代码实现例子(异步编程模型)(异步就是并发)

package com.aaa.thread.threadlist;

public class Account {

    private  String actno;
    private  double balance;

    public Account() {
    }

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

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public double getBalance() {
        return balance;
    }

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

    public  void  withdraw(double  balance){
        //取款前的余额
         double before=this.getBalance();

        //取款后的余额
        double after=before-balance;

        //更新
        this.setBalance(after);

    }

}

package com.aaa.thread.threadlist;

public class AccountThread implements  Runnable {

    private  Account account;

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

    @Override
    public  void run() {

        account.withdraw(5000);

        System.out.println("用户"+account.getActno()+"余额为:"+account.getBalance());


    }
}

测试

package com.aaa.thread.threadlist;

public class Test {
    public static void main(String[] args) {

        Account account=new Account("acc-001",10000);

        Runnable at=new AccountThread(account);
        Runnable at1=new AccountThread(account);

         Thread thread=new Thread(at);
         Thread thread1=new Thread(at1);

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

         thread.start();
         thread1.start();

    }
}

同步线程机制

​ 线程同步机制的语法是:

synchronized() {

//线程同步代码块

}

synchronized后面小括号中传的这个数据是非常关键的。

这个数据必须是多线程共享的数据。才能达到多线程排队。

()中写什么?

​ 看你想让哪些线程同步。

假设你有a b c d 4个线程,

你只希望a b c 排队,d不需要排队。怎么办?

你一定要在()中写一个a b c 共享的对象。而这个对象对于d来说不是共享的。

package com.aaa.thread.threadlist;

public class Account {

    private  String actno;
    private  double balance;

    public Account() {
    }

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

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public double getBalance() {
        return balance;
    }

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

    public    void  withdraw(double  balance){
         synchronized (this){
            //取款前的余额
            double before=this.getBalance();
             //取款后的余额
             double after=before-balance;

             try {
                 Thread.sleep(2000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }

            //更新
            this.setBalance(after);
        }

    }

}
package com.aaa.thread.threadlist;

public class AccountThread implements  Runnable {

    private  Account account;

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

    @Override
    public  void run() {

        account.withdraw(5000);

        System.out.println(Thread.currentThread().getName()+"用户"+account.getActno()+"余额为:"+account.getBalance());


    }
}
package com.aaa.thread.threadlist;

public class Test {
    public static void main(String[] args) {

        Account account=new Account("acc-001",10000);

        Runnable at=new AccountThread(account);
        Runnable at1=new AccountThread(account);

         Thread thread=new Thread(at);
         Thread thread1=new Thread(at1);

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

         thread.start();
         thread1.start();

    }
}
java中的三大变量

实例变量:在堆中

静态变量:在方法区中

局部变量:在栈中

以上三大变量中:

​ 局部变量永远都不会存在线程安全问题。

​ 因为局部变量不共享(一个线程一个栈)。

​ 局部变量在栈中。所以局部变量永远都不会共享。

实例变量在堆中,堆只有一个。

静态变量在方法区中,方法区中只有一个。

堆和方法区都是多线程共享的,所以可能存在线程安全问题。

synchronized的三种写法:

第一种:同步代码块

灵活

synchronized(线程共享对象){

同步代码块

}

第二种:在实例方法上使用synchronized

表示共享对象一定是this

并且同步代码块是整个方法

第三种:在静态方法上使用synchronized

表示找类锁

类锁永远只有一把。

就算创建了100个对象,那类锁也只有一把。

对象锁:1个对象1把锁,1000个对象100把锁。

类锁:100个对象,也可能只是一把类锁

守护线程

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

一类是:用户线程

一类是:守护线程(后台线程)

其中具有代表性的就是垃圾回收线程(守护线程)。


# 守护线程的特点:
 - 一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束
 `注意:主线程main方法是一个用户线程`

## 守护线程用在什么地方呢?
- 每天00:00的时候系统数据自动备份。
- 这个需要使用到定时器,并且我们可以将定时器设值为守护线程。一直在那里看着,每到00:00的时候就备份一次。
- 如果用户线程结束了,守护线程自动退出,没有必要进行数据备份了
### 设置 setDaemon(true)将此线程标记为守护线程
setDaemon(boolean on) 
将此线程标记为 守护线程
即当用户线程一结束那个守护进程无论是不是死循环都会结束。

定时器

​ 定时器的作用:

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

## 在实际开发中,每隔多久执行一段特定的程序,这种需要很常见,在java中可以采用多种方式实现。
- 可以使用sleep方法,睡眠,设置睡眠时间,每到这个时间点醒来,执行任务。这种方式是最原始定时器。
`在java的类库中已经写好了定时器:java.util.Timer可以直接拿来用。`
不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。

在实际的开发中,目前使用较多的spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务

基本实现

package com.aaa;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class Ding {
    public static void main(String[] args) {
        //创建定时器
        Timer timer = new Timer();
        TimerTasks timerTasks = new TimerTasks();
        timer.schedule(timerTasks,new Date(),10*1000);
    }
}

class  TimerTasks extends  TimerTask{

    @Override
    public void run() {
        System.out.println("运行");
    }
}

实现线程的第三种方式

实现Callable接口。(jdk8新特性)

这种方式实现的线程可以获取线程的返回值(一开始的那两种不能获取返回值)

缺点:效率低

package com.aaa;

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

public class FutureTaskTest {
    public static void main(String[] args) {
        //创建一个未来任务类

        FutureTask futureTask=new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {
                int a =100;
                int b=200;
                Thread.sleep(1000);//睡觉了
                return  a+b;
            }
        });

       //创建线程对象
        Thread th=new Thread(futureTask);
        th.start();
        //获取线程的返回结果
        try {
            Object o = futureTask.get();
            System.out.println(o);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        //main方法这里的线程要想执行必须等待get()方法的执行结束。
        System.out.println("ttt");
    }
}

关于Object类中的wait和notify方法。(生产者和消费者模式)

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

wait方法和notify方法不是通过线程对象调用的。


## wait()方法的作用?
​```java
Object o=new Object();
o.wait();
表示:
	让正在o对象上活动的线程进入等待状态,无期限等待,
	直到被唤醒为止。
​```
## notify()方法作用?
Object  o=new Object();
o.notify();
表示:
	唤醒正在o对象上等待的 线程
还有一个notifyAll()方法:
	这个方法是唤醒o对象上处于等待的所有线程
package com.aaa.production;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/*
*
* 实现生产和消费者
*   一个仓库当作共享对象
*   生产者和消费者必须是线程安全的,生产出的东西需要和消费的东西的个数一致
*   有一个生产者和一个消费者
*   生产者和消费者共享一个对象
*        使用List集合当作仓库
*           仓库的数量为1
*           当仓库的数量为1时仓库就满了
*           当仓库的数量为0时仓库为空
* */
@SuppressWarnings("all")
public class Test01 {
    public static void main(String[] args) {

        //创建list集合
        List list=new ArrayList();
        //创建线程对象  两个线程共享一个list集合
        Thread thread=new Thread(new Product(list));
        Thread thread1=new Thread(new Consumption(list));
        //为线程对象设置名称
        thread.setName("生产者线程");
        thread1.setName("消费者线程");
        //启动线程
        thread.start();
        thread1.start();;

    }
}

/*
* 生产者  一直生产
* */
@SuppressWarnings("all")
class  Product implements  Runnable{

    List list;

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

    @Override
    public void run() {

            while (true) {
                synchronized (list) {
                //判断仓库的数量是否为空
                // 如果仓库的数量不为空则代表仓库满了
                // 我们就需要等待被消费
                if (list.size() > 0) {
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //如果仓库没有满,那么就进行给仓库增加
                Object object = new Object();
                list.add(object);
                    System.out.println(Thread.currentThread().getName()+object);
                //增加完之后就需要唤醒等待的线程

                list.notifyAll();
            }
        }
    }
}


/*
* 消费者  一直消费
* */
@SuppressWarnings("all")
class Consumption implements  Runnable{

    List list;

    public Consumption (List list){
        this.list=list;
    }
    @Override
    public void run() {

            while (true) {
                synchronized (list) {
                if (list.size() == 0) {
                    //仓库中已经空了
                    //消费者的线程等待,释放list集合的锁
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                    Object remove = list.remove(0);
                    System.out.println(Thread.currentThread().getName()+remove);
                    list.notifyAll();
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值