Java学也学不明白之多线程机制详解。

简介: 一个进程就是一个应用程序。在操作系统中每启动一个应用程序就会相应的启动一个进程。例如:千千静听进程,魔兽进程,Word 进程,QQ 进程,JVM 启动对应一个进程。线程是进程的一个执行场景。一个进程可以启动多个线程。

一、基本概述


在了解线程之前,我们来了解下什么是进程?


一个进程就是一个应用程序。在操作系统中每启动一个应用程序就会相应的启动一个进程。例如:千千静听进程,魔兽进程,Word 进程,QQ 进程,JVM 启动对应一个进程。


那什么是线程呢?


线程是进程的一个执行场景。一个进程可以启动多个线程。


线程和进程有什么区别呢?


1.进程A和进程B:内存独立不共享。

2.线程A和线程B:堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈。


在java中,每个栈和每个栈之间互不干扰,各自执行各自的,这就是多线程并发。java中之所以有多线程机制,目的就是为了提高程序的处理效率。


系统引入多进程的作用?


最初的计算机是“单进程的”,计算机只能运行一个应用程序,例如第一台计算机只有DOS 窗口。现代的计算机可以满足我们一边听音乐,一边玩游戏。现代的计算给我们人类感觉:多件事情一起运行。感觉是并行的(错觉)

对于单核的计算机来讲,在某一个时间点上只能做一件事情,但是由于计算机的处理速度很高,多个进程之间完成频繁的切换执行,这个切换速度使人类产生了错觉,人类的错觉是:多个进程在同时运行。计算机引入多进程的作用:提高 CPU 的使用率。

进程和进程之间的内存独立。


浅谈java 程序的执行原理:

java 命令执行会启动 JVM,JVM 的启动表示启动一个应用程序,表示启动了一个进程。该进程会自动启动一个“主线程”,然后主线程负责调用某个类的 main 方法。所以 main 方法的执行是在主线程中执行的。然后通过 main 方法代码的执行可以启动其他的“分支线程”。所以,main 方法结束程序不一定结束,因为其他的分支线程有可能还在执行。


二、实现线程


1.继承 Thread 类(重写run方法)


Thread类常用构造方法:

(1)Thread() 分配一个新的 Thread对象。

(2)Thread(String name) 分配一个新的 Thread对象。


Thread类常用方法:

(1)static Thread currentThread() 返回当前正在执行的线程对象的引用

(2)String getName() 返回此线程的名称。

(3)void interrupt() 中断这个线程。

(4)boolean isDaemon() 如果该线程是守护线程。

(5)void setName(String name) 改变该线程的名称等于参数 name。

(6)static void sleep(long millis)当前正在执行的线程休眠(暂停执行)为指定的毫秒数,根据精度和系统定时器和调度的准确性

(7)void start() 导致该线程开始执行;java虚拟机调用这个线程的 run方法。

(8)String toString() 返回此线程的字符串表示形式,包括线程的名称、优先级和线程组。

示例代码(1):


public class ThreadText01 {
    public static void main(String[] args) {
        //currentThread当前线程对象
        //在main方法中,当前线程就是主线程,
        Thread tt=Thread.currentThread();
        String str=tt.getName();//main
        System.out.println(str);
        //创建线程对象
        MyThread2 t=new MyThread2();
        //设置线程名字
        t.setName("t1");
        //获取线程名字
        String s=t.getName();
        System.out.println(s);//Thread-0 默认线程名字
        MyThread2 t2=new MyThread2();
        t2.setName("t2");
        String s1=t2.getName();
        System.out.println(s1);//Thread-1
        //启动线程
        t.start();
        t2.start();
    }
}
class MyThread2 extends Thread{
    public void run() {
        for (int i = 0; i < 10; i++) {
            //获取当前线程t1
            //t2线程执行此方法,当前对象就是t2
            Thread t=Thread.currentThread();
            System.out.println(t.getName()+"线程----->"+i);//t1
             try {
             //休眠一秒
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:


main
t1
t2
t2线程----->0
t1线程----->0
t2线程----->1
t1线程----->1
t2线程----->2
t1线程----->2
t2线程----->3
t1线程----->3
t2线程----->4
t1线程----->4
t2线程----->5
t1线程----->5
t2线程----->6
t1线程----->6
t2线程----->7
t1线程----->7
t2线程----->8
t1线程----->8
t2线程----->9
t1线程----->9
Process finished with exit code 0


2.实现 Runnable 接口


其实 Thread 对象本身就实现了 Runnable 接口,但一般建议直接使用 Runnable 接口来写多线程程序,因为接口会比类带来更多的好处


示例代码(2):


public class ThreadText02 {
    public static void main(String[] args) {
        //创建线程
        Thread t=new Thread(new MyRunnable());
        //启动线程
        t.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程----->"+i);
        }
    }
}
class MyRunnable implements Runnable{
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("分支线程----->"+i);
        }
    }
}

运行结果:


主线程----->0
分支线程----->0
主线程----->1
分支线程----->1
主线程----->2
分支线程----->2
主线程----->3
分支线程----->3
主线程----->4
分支线程----->4
主线程----->5
分支线程----->5
主线程----->6
分支线程----->6
主线程----->7
分支线程----->7
分支线程----->8
分支线程----->9
主线程----->8
主线程----->9

3.实现线程的第三种方式实(现Callable接口。(JDK8新特性。))


这种方式实现的线程可以获取线程的返回值。(效率低)


示例代码:


import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;//JUC包下的,属于java并发包,老jdk没有,新特性
public class ThreadText14 {
    public static void main(String[] args) {
        FutureTask task=new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {//call方法相当于run方法,但是有返回值
                System.out.println("call method begin");
                Thread.sleep(1000*2);
                int a=100;
                int b=200;
                return a+b;//(自动装箱)
            }
        });
        Thread ti=new Thread(task);
        //开始
        ti.start();
        //get方法导致当前线程阻塞!!
        try {
            Object obj=task.get();
            System.out.println("线程执行结果--->"+obj);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("hello world");
    }
}

运行结果:


call method begin
线程执行结果--->300
hello world


三、线程的生命周期


线程的生命周期存在五个状态:新建、就绪、运行、阻塞、死亡


1.png


新建:采用 new语句创建完成

就绪:执行 start 后

运行:占用 CPU 时间

阻塞:执行了 wait 语句、执行了 sleep 语句和等待某个对象锁,等待输入的场合

终止:退出 run()方法


四、多线程并发环境下,数据的安全问题。


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


三个条件:①多线程并发②有数据共享③共享数据有修改的行为。


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


使用线程同步机制:线程排队执行。(不能并发)


线程同步与线程异步的理解


异步编程模型:

 线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,

 谁也不需要等谁,这种编程模型叫做:异步编程模型。

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

 异步就是并发。


同步编程模型:

 线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行

 结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,

 两个线程之间发生了等待关系,这就是同步编程模型。

 效率较低。线程排队执行。

 同步就是排队。


线程同步其实是对对象加锁(给对象加锁使用“synchronized”关键字:)


synchronized有三种写法:


①同步代码块

      synchronized(线程共享对象){
        同步代码块;
      }
      线程共享对象必须是多线程共享的数据。
java中,任何对象都有一把锁,是一个标记,让哪个线程排队,让那个线程共享数据

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


③在静态方法上使用synchronized表示找类锁。类锁永远只有1把。


Java中三大变量:

实例变量:在堆中。


静态变量:在方法区。


局部变量:在栈中。


以上三大变量中:

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

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

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


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

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

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

局部变量+常量:不会有线程安全问题。

成员变量:可能会有线程安全问题。


以后开发中应该怎么解决线程安全问题?

1.尽量使用局部变量代替“实例变量和静态变量”。

2.如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。

3.如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。


Java中怎样终止一个线程


使用布尔标记:


示例代码(3):


public class ThreadText10 {
    public static void main(String[] args) {
        MyRunnable4 r = new MyRunnable4();
        Thread t = new Thread(r);
        t.setName("t");
        t.start();
        try {
            t.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //终止线程
        //想要什么时候终止,把标记修改为false就终止!
        r.run=false;
    }
}
class MyRunnable4 implements Runnable {
    //打一个布尔标记
    Boolean run = true;
    @Override
    public void run() {
        if (run) {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "---->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } else {
            //在这里保存数据!!!
            return;
        }
    }
}

为了预防死锁的出现,面试官说:“请手写一个死锁代码”。


示例代码(4):


public class ThreadText04 {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        Thread t1 = new MyThread01(o1, o2);
        Thread t2 = new MyThread02(o1, o2);
        t1.start();
        t2.start();
    }
}
class MyThread01 extends Thread {
    Object o1;
    Object o2;
    public MyThread01(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 MyThread02 extends Thread {
    Object o1;
    Object o2;
    public MyThread02(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){
            }
        }
    }
}


五、守护线程


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

 一类是:用户线程

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

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


守护线程:所有的用户线程结束生命周期,守护线程才会结束生命周期,只要有一个用户线程存在,那么守护线程就不会结束,例如 java 中著名的垃圾回收器就是一个守护线程,只有应用程序中所有的线程结束,它才会结束。


守护线程的特点:

一般守护线程是一个死循环,所有的用户线程只要结束,

守护线程自动结束。


示例代码(5):


/*
守护线程!!
 */
public class ThreadText05 {
    public static void main(String[] args) {
        Thread t = new BakDataThread();
        //调用setDaemon方法!
        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 {
    int i = 0;
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + "守护线程备份数据" + (i++));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


定时器


定时器的作用:间隔特定的时间,执行特定的程序。

java中其实可以采用多种方式实现:


可以使用sleep方法,睡眠,设置睡眠时间,没到这个时间点醒来,执行任务。这种方式是最原始的定时器。

java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。

在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架


六、生产者和消费者模式


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


wait和notify方法不是线程对象的方法,是java中任何一个java对象

都有的方法


wait()方法作用?


Object o = new Object();

o.wait();


 表示:

  让正在o对象上活动的线程进入等待状态,无期限等待,

  直到被唤醒为止。

  o.wait();方法的调用,会让“当前线程(正在o对象上

  活动的线程)”进入等待状态。


notify()方法作用?

Object o = new Object();

o.notify();


 表示:

  唤醒正在o对象上等待的线程。  

 还有一个notifyAll()方法:

  这个方法是唤醒o对象上处于等待的所有线程。


wait()和notify()是建立在synchronized(线程同步的基础上!)


示例代码(6):


import java.util.ArrayList;
import java.util.List;
public class ThreadText06 {
    public static void main(String[] args) throws Exception {
        List list = new ArrayList();
        Thread t1 = new Thread(new Producer(list));
        Thread t2 = new Thread(new Consumer(list));
        t1.setName("生产者线程");
        t2.setName("消费者线程");
        t1.start();
        t2.start();
    }
}
//生产者
class Producer implements Runnable {
    private List list;
    public Producer(List list) {
        this.list = list;
    }
    public void run() {
        while (true) {
            synchronized (list) {
                if (list.size() > 0) {
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //仓库里面有元素。
                Object o = new Object();
                list.add(o);
                System.out.println(Thread.currentThread().getName() + "---->" + o);
                //唤醒消费者线程开始消费
                list.notifyAll();
            }
        }
    }
}
//消费者
class Consumer implements Runnable {
    private List list;
    public Consumer(List list) {
        this.list = list;
    }
    public void run() {
        while (true) {
            synchronized (list) {
                if (list.size() == 0) {
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                Object o = list.remove(0);
                System.out.println(Thread.currentThread().getName() + "----->" + o);
                //唤醒生产者线程
                list.notifyAll();
            }
        }![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20200727171907257.gif)
    }
}
相关文章
|
10天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
13天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
43 2
|
1天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
1天前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
|
1天前
|
安全 Java 开发者
Java中的多线程编程:从基础到实践
本文深入探讨了Java多线程编程的核心概念和实践技巧,旨在帮助读者理解多线程的工作原理,掌握线程的创建、管理和同步机制。通过具体示例和最佳实践,本文展示了如何在Java应用中有效地利用多线程技术,提高程序性能和响应速度。
16 1
|
9天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
|
9天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
|
8天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
11天前
|
安全 Java 开发者
Java多线程编程中的常见问题与解决方案
本文深入探讨了Java多线程编程中常见的问题,包括线程安全问题、死锁、竞态条件等,并提供了相应的解决策略。文章首先介绍了多线程的基础知识,随后详细分析了每个问题的产生原因和典型场景,最后提出了实用的解决方案,旨在帮助开发者提高多线程程序的稳定性和性能。
|
14天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin