Java多线程详细知识点(吐血级详细)

前言:

今天给大家分享一些自己在学习JavaSE阶段中,学到多线程这个阶段之后,
自己所总结的一些知识点,希望对您有所帮助,并且自己也是个初学者,
有什么地方写错了,也希望您能点出来。另外,自己最近也在开始写
博客,希望在学习的道路上一起进步,也期待您的关注与支持。

文章目录
1、进程和线程
2、什么是真正的多线程并发
3、实现线程的3种方式
4、线程对象的生命周期
5、线程的调度
6、数据的安全性问题
7、异步编程模型和同步编程模型
8、Java语言三大变量与线程安全
9、synchronized关键字
10、守护线程
11、定时器
12、关于Object类中的wait和notify方法(生产者和消费者模式)
13、死锁
14、自己以后遇到线程安全怎么办

一、进程和线程
  1.1、什么是进程?什么是线程?

    进程是一个应用程序(1个进程是一个软件)
    线程是一个程序的执行场景/执行单元
    一个进程可以启动多个线程。

  1.2、进程和线程的关系?举例说明。

    阿里巴巴:可以看成一个进程
    马        云:可以看成一个线程

    京        东:可以看成一个进程
    刘  强   东:可以看成一个线程

    所以:进程可以看做是现实生活中的公司。线程可以看做公司中的某个员工。

    注意:进程A和进程B的内存独立不共享。
              线程A和线程B的方法区内存和堆内存共享,栈内存相互独立。
                Java中之所以有多线程机制,目的就是为了提高程序的处理效率。

二、什么是真正的多线程并发

  t1线程执行t1的。
  t2线程执行t2的。
  t1不会形象t2,t2也不会影响t2的,这才叫真正的多线程并发。

三、实现线程的3种方式

  3.1、第一种方式:
编写一个类直接继承Java.lang.Thread,重写run()方法。

// 定义线程类
public class MyThread extends Thread{
     public void run(){

     }
}
// 创建线程对象
MyThread t = new MyThread();
// 启动线程
t.start();

  3.2、第二种方式:
编写一个类,实现java.lang.Runnable接口,实现run方法。

// 定义一个可运行的类
class MyRun implements Runnable{
    @Override
    public void run() {

    }
}
// 创建线程对象
Thread t = new Thread(new MyRun());
// 启动线程
t.start();

  3.3、第三种方式:
FutureTask方式,实现Callable接口。(JDK8新特性)
这种方式实现的线程可以获取线程的返回值。
之前的那两种方式是无法获取线程的返回值的,因为run方法返回void。

// 第一步:创建一个“未来任务类”对象。
FutureTask task = new FutureTask(new Callable() {
    @Override
    public Object call() throws Exception { // call方法就相当于run方
        return null; 
    }
});

// 创建线程对象
Thread t = new Thread(task);

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

// 获取线程的返回值
Object obj = null;
try {
    obj = task.get();
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}
System.out.println("线程执行结果"+obj);
// main方法这里的程序需要执行到这里必须等待get()方法的结束
// 而get()方法可能需要很久,因为get()方法是为了拿到另一个线程的执行结果
// 另一个线程执行是需要时间的。
// 所以get()可能会导致当前线程进入阻塞状态。
System.out.println("hello");

四、线程对象的生命周期

Java多线程详细知识点(吐血级详细)_第1张图片
五、线程的调度
  5.1、常见的线程调度模型有哪些?

          抢占式调度模型:
          哪个线程的优先级比较高,抢到的CPU时间片的概率就比较高/多一写。
          Java采用的就是这种模型

          均分式调度模型:
          平均分配CPU时间片,每个线程占用的CPU时间片长度一样。
          平均分配,一切平等。
          有一些编程语言,线程调度模型就是采用的这种方式。

5.2、Java提供的哪些方法是和线程调度有关系呢?

实例方法:

void setPriority(int newPriority) // 更改线程的优先级。
int getPriority()  // 返回线程的优先级。
   // 最低优先级 1
   // 最高优先级 10
   // 默认优先级 5

void join()  // 合并线程。
class MyThread extends Thread{
    public void doSome(){
    MyThread2 t = new MyThread();
    t.join(); // 当前线程进入阻塞,t线程执行,直到t线程执行完之后,当前线程才可以执行。
    }
}
class MyThread2 extends Thread{

}

静态方法:

static void yield() // 让位方法
      /*暂停当前正在执行的线程对象,并执行其他线程。
      yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用
      yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
      注意:在回到就绪状态之后,还有可能再次抢到CPU时间片进入运行状态。*/

六、线程的安全性问题

  6.1、线程安全的重要性?

  以后在开发中,我们的项目都是运行在服务器中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了,这些代码我们不需要编写。
最重要的是:你要知道,你编写的程序需要放在一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。(重点*****)

  6.2、什么时候数据在多线程并发的环境下存在安全性问题呢?

三个条件:
条件一:多线程并发
条件二:有数据共享
条件三:共享数据有修改的行为

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

  6.3、怎么解决线程安全问题呢?

当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,
怎么解决这个问题呢?
线程排队执行。(不能并发)
用排队解决线程安全问题。
这种机制被称为:线程同步机制。

专业术语叫:线程同步,实际上就是线程不能并发,线程必须排队执行。

怎么解决线程安全问题呢?
使用“线程同步机制”。

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

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

  异步编程模型:
    线程t1和线程t2,各自执行各自的,t1不用管t2,t2不用管t1,
谁也不需要等谁,这种编程模型叫做:异步编程模型。
其实也就是:多线程并发(效率较高)

    异步就是并发

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

    同步就是排队

八、Java语言三大变量与线程安全

  实例变量:在堆中
  静态变量:在方法区中
  局部变量:在栈中

  以上三大变量中:
     局部变量永远都不会存在线程安全问题。
    因为局部变量永远都不会共享。(一个线程一个栈)
    局部变量在栈中,所以局部变量永远不会共享。

  实例变量和静态变量都可能存在线程安全问题。

  局部变量 + 常量 不会有线程安全问题
  成员变量可能会有线程安全问题

九、synchronized关键字

  9.1、synchronized有三种写法:

   第一种:同步代码块
      灵活
      synchronized(线程共享对象){
      同步代码块;
      }

  第二种:在实例方法上使用synchronized
表示共享对象一定是this,并且同步代码块是整个方法体。

  第三种:在静态方法上使用synchronized
    表示找类锁。
    类锁永远只有一把。
    就算创建了100个对象,哪类锁也只有1把。

    对象锁:1个对象1把锁,100个对象100把锁
    类锁:100个对象,也可能只有1把锁

  9.2、举例说明:
    例子1:

/**
 *   doOther方法的执行需不需要等待doSome方法的结束吗?
 *
 *   不需要,因为doOther方法上没有synchronized,不需要获取对象锁。
 */

public class Exam01 {
    public static void main(String[] args) {
        MyClass mc = new MyClass();

        Thread t1 = new MyThread(mc);
        Thread t2 = new MyThread(mc);

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

        t1.start();
        try {
            Thread.sleep(1000);// 这个睡眠的作用是保证t1线程先执行。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}
class MyThread extends Thread{
    private MyClass mc;
    public MyThread(MyClass mc){
        this.mc = mc;
    }
    public void run() {
        if (Thread.currentThread().getName().equals("t1")){
            mc.doSome();
        }
        if (Thread.currentThread().getName().equals("t2")){
            mc.doOther();
        }
    }
}
class MyClass{
    // synchronized 出现在实例方法上,锁的是this
    public synchronized void doSome(){
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }
    public void doOther(){
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}

    例子2:

/**
 *   doOther方法的执行需不需要等待doSome方法的结束吗?
 *
 *   需要,synchronized在实例方法上锁的是this
 */

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

        MyClass mc = new MyClass();

        Thread t1 = new MyThread(mc);
        Thread t2 = new MyThread(mc);

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

        t1.start();
        try {
            Thread.sleep(1000);// 这个睡眠的作用是保证t1线程先执行。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

class MyThread extends Thread{
    private MyClass mc;
    public MyThread(MyClass mc){
        this.mc = mc;
    }
    public void run() {
        if (Thread.currentThread().getName().equals("t1")){
            mc.doSome();
        }
        if (Thread.currentThread().getName().equals("t2")){
            mc.doOther();
        }
    }
}

class MyClass{
    // synchronized 出现在实例方法上,锁的是this
    public synchronized void doSome(){
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }
    public synchronized void doOther(){
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}

    例子3:

/**
 *   doOther方法的执行需不需要等待doSome方法的结束吗?
 *
 *   不需要,MyClass对象是两个,两把锁。
 */

public class Exam01 {
    public static void main(String[] args) {
        MyClass mc1 = new MyClass();
        MyClass mc2 = new MyClass();

        Thread t1 = new MyThread(mc1);
        Thread t2 = new MyThread(mc2);

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

        t1.start();
        try {
            Thread.sleep(1000);// 这个睡眠的作用是保证t1线程先执行。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

class MyThread extends Thread{
    private MyClass mc;
    public MyThread(MyClass mc){
        this.mc = mc;
    }
    public void run() {
        if (Thread.currentThread().getName().equals("t1")){
            mc.doSome();
        }
        if (Thread.currentThread().getName().equals("t2")){
            mc.doOther();
        }
    }
}

class MyClass{
    // synchronized 出现在实例方法上,锁的是this
    public synchronized void doSome(){
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000 * 5);
        } 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方法的执行需不需要等待doSome方法的结束吗?
 *
 *   需要,静态方法是类锁,类锁不管创建了几个对象,类锁只有一把。
 */

public class Exam01 {
    public static void main(String[] args) {
        MyClass mc1 = new MyClass();
        MyClass mc2 = new MyClass();

        Thread t1 = new MyThread(mc1);
        Thread t2 = new MyThread(mc2);

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

        t1.start();
        try {
            Thread.sleep(1000);// 这个睡眠的作用是保证t1线程先执行。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

class MyThread extends Thread{
    private MyClass mc;
    public MyThread(MyClass mc){
        this.mc = mc;
    }
    public void run() {
        if (Thread.currentThread().getName().equals("t1")){
            mc.doSome();
        }
        if (Thread.currentThread().getName().equals("t2")){
            mc.doOther();
        }
    }
}

class MyClass{
    // synchronized 出现在静态方法上,锁的是类
    public synchronized static void doSome(){
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }
    public synchronized static void doOther(){
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}

十、守护线程
  10.1、Java语言中线程分为两大类:
      一类是: 用户线程
      另一类是:守护线程(后台线程)
      其中具有代表性的是:垃圾回收线程(守护线程)

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

  10.3、注意:主线程main是一个用户线程。

  10.4、语法:

 // 线程对象.setDaemon(true);

  10.4、举例子:

/**
 * 守护线程
 *    t.setDaemon(true);
 */
public class ThreadTest14 {
    public static void main(String[] args) {
        Thread t = new BakDataThread();

        t.setName("备份线程");

        // 在启动线程之前,将线程设置为守护线程
        // 即使是死循环,但由于该线程被设置为守护线程,当用户线程结束,守护线程自动终止。
        t.setDaemon(true);

        t.start();

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

运行结果:

main---->0
备份线程---->1
main---->1
备份线程---->2
main---->2
备份线程---->3
备份线程---->4
main---->3
main---->4
备份线程---->5
main---->5
备份线程---->6
main---->6
备份线程---->7
main---->7
备份线程---->8
备份线程---->9
main---->8
main---->9
备份线程---->10
备份线程---->11

Process finished with exit code 0

十一、定时器
  11.1、定时器的作用是什么:
间隔特定的时间去执行特定的程序。
比如:每天进行银行账户的总账操作。
每天进行数据的备份操作等。

  11.2、实现方式:
    可以使用sleep方法,睡眠,设置睡眠时间,每到某个时间点醒来,执行任务
。这种方法是最原始的定时器。(比较low)

    在Java的类库已经写好一个定时器:java.util.Timer,可以拿来直接用。
不过,这种方式在目前的开发中也很少用,因为现在有好多高级框架都是支持
定时任务的。

    在实际的开发中,目前使用的最多是Spring框架的SpringTask框架,
这个框架只需要简单的配置,就可以完成定时器的任务。

  11.3、举例子:

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();
        // Timer timer = new Timer(true); // 守护线程的方式

        // 指定定时任务
        // timer.schedule(定时任务,第一次执行的时间,间隔多久执行一次);

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstTime = null;
        try {
            firstTime = sdf.parse("2021-1-22 21:29:50");
        } catch (ParseException e) {
            e.printStackTrace();
        }
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                String strTime = sdf.format(new Date());
                System.out.println(strTime + "-->完成一次数据备份!");
            }
        }, firstTime, 1000 * 10);
    }
}

运行结果:

2021-01-22 21:29:07-->完成一次数据备份!
2021-01-22 21:29:17-->完成一次数据备份!
2021-01-22 21:29:27-->完成一次数据备份!

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

  12.1、wait和notify方法不是线程对象的方法,是Java中任何一个java对象都有的方法,因为着两个方式是Object类中自带的。
  wait方法和notify方法不是通过线程对象调用。

  12.2、wait()的作用?

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

/*
表示:
   让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。
   o.wait();方法的调用,会让当前线程进入等待状态。
   */

  12.3、notify()的作用?

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

/*
表示:
    唤醒正在o对象上等待的线程。
*/

/*
还有一个o.notifyAll();方法:
   这个方法是唤醒o对象上处于等待的所有线程。
*/

  12.4、举例子:

/*
*模拟这样一个需求:
*      仓库采用List集合。
*      1个元素就代表仓库满了。
*      如果List集合中个数为0,表示仓库空了。
*      保证List集合永远最多储存1个元素。
*
*      必须做到这种效果:生产一个消费一个。
*/
public class ThreadTest16 {
    public static void main(String[] args) {
        List list = new ArrayList();

        Thread t1 = new Thread(new Produce(list));
        Thread t2 = new Thread(new Consumer(list));

        t1.setName("生产者线程生产");
        t2.setName("消费者线程消费");


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

    }
}
// 生产线程
class Produce implements Runnable{

    // 仓库
    private List list;

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

    @Override
    public void run() {
        while (true){ // 使用死循环来一直生产
            synchronized (list){ // 给仓库对象list加锁
                if (list.size() > 0){ // 大于0,说明仓库中已经有1个元素了
                    try {
                        // 当前线程进入等待状态,并且释放Produce之前占有的list对象锁
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 程序执行到这里,说明可以生产
                Object obj = new Object();
                list.add(obj);
                System.out.println(MyThread3.currentThread().getName()+"--->"+obj);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 唤醒消费者线程进行消费
                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.size() == 0){ // 仓库空了
                    try {
                        // 消费线程进行等待,释放掉list集合占有的锁
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 程序执行到这里说明仓库中有数据,可以进行消费。
                Object obj = list.remove(0);
                System.out.println(Thread.currentThread().getName()+"--->"+obj);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 唤醒生产者线程进行生产
                list.notify();
            }
        }
    }
}

  12.5、运行结果:

生产者线程生产--->java.lang.Object@528ff726
消费者线程消费--->java.lang.Object@528ff726
生产者线程生产--->java.lang.Object@5c481307
消费者线程消费--->java.lang.Object@5c481307
生产者线程生产--->java.lang.Object@58779c56
消费者线程消费--->java.lang.Object@58779c56
生产者线程生产--->java.lang.Object@1d75193c
消费者线程消费--->java.lang.Object@1d75193c
生产者线程生产--->java.lang.Object@30eeeb73
消费者线程消费--->java.lang.Object@30eeeb73
生产者线程生产--->java.lang.Object@2c220642
消费者线程消费--->java.lang.Object@2c220642
生产者线程生产--->java.lang.Object@4853aabc
消费者线程消费--->java.lang.Object@4853aabc

十三、死锁
  13.1、代码

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

        Thread t1 = new MyThread1(o1,o2);
        Thread t2 = new MyThread2(o1,o2);

        t1.start();
        t2.start();
    }
}
class MyThread1 extends Thread{
    Object o1;
    Object o2;

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


    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;
    }


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

            }
        }
    }
}

  死锁:死锁这种情况下,程序不会报错,不会产生任何错误,程序会一直僵持在那里。所以遇到这种情况,程序非常难调试。通过以上的代码,我们可以得出一个结论:synchronized在开发中最好不要嵌套使用,因为一不小心就有可能造成死锁的现象。

14、自己以后遇到线程安全怎么办

  是一上来就选择线程同步机制吗?synchronized
不是,synchronized会让程序的执行效率降低,用户体验不好。
系统的用户吞吐量/并发量降低。用户体验差。在不得已的情况下在选择
线程同步机制。

  第一种方案:尽量使用局部变量代替“实例变量和静态变量”

  第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样
实例变量的内存就不共享了。(一个线程对应一个对象,100个
线程对应100个对象,对象不共享就没有数据安全问题)

  第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择
synchronized,线程同步机制。

最后

希望在学习的道路上一起进步,也期待您的关注与支持。

你可能感兴趣的:(java,多线程)