写点什么

Javva 基础:多线程重点回顾

  • 2022 年 7 月 08 日
  • 本文字数:7044 字

    阅读完需:约 23 分钟

一、基本概述

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


一个进程就是一个应用程序。在操作系统中每启动一个应用程序就会相应的启动一个进程。例如:千千静听进程,魔兽进程,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(); } } }}
复制代码


运行结果:


maint1t2t2线程----->0t1线程----->0t2线程----->1t1线程----->1t2线程----->2t1线程----->2t2线程----->3t1线程----->3t2线程----->4t1线程----->4t2线程----->5t1线程----->5t2线程----->6t1线程----->6t2线程----->7t1线程----->7t2线程----->8t1线程----->8t2线程----->9t1线程----->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线程执行结果--->300hello world
复制代码

三、线程的生命周期

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


新建:采用 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://img-blog.csdnimg.cn/20200727171907257.gif)
}}
复制代码


发布于: 刚刚阅读数: 3
用户头像

该来的总会来,或迟或早。🎈 2022.06.13 加入

有JAVA方面3年学习经验,csdn/51cto等平台优质作者,华为云云享专家、阿里云受邀专家博主,擅长JAVA,大数据等方面的技术等。

评论

发布
暂无评论
Javva基础:多线程重点回顾_Java_百思不得小赵_InfoQ写作社区