栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java学习总结之多线程

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java学习总结之多线程

多线程概述

1.进程是一个应用程序,线程是一个进程中的执行场景/执行单元。一个进程可以启动多个线程。

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

3.假设启动10个线程,会有10个栈空间,每个栈和每个栈之间互不干扰,各自执行各的,这就是多线程并发。多线程并发可以提高效率,java中之所以有多线程机制,目的就是为了提高程序的处理效率。
注意!进程A和进程B的内存独立不共享

4.什么是真正的多线程?
t1线程执行t1的,t2线程执行t2的,t1不会影响t2,t2也不会影响t1。

5.java语言中,实现线程的方式:
(1)编写一个类,直接继承java.lang.Thread,重写run方法

代码示例:
class MyThread extends Thread{
      public void run(){
      //编写程序,这段程序运行在分支线程当中(分支栈)
      for(int i = 0; i < 1000; i++){
            System.out.println("分支线程------");
         }
      }
public static void main(String[] args) {
      //创建线程对象(在main方法中创建)
      MyThread mythread = new MyThread();
      //启动线程
      myThread.start();//调用线程的start方法
      for(int i = 0; i < 100; i++){
           System.out.println("主线程------" + i);
          }
     }
}

①start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后瞬间就结束了,这段代码的任务是为了开辟一个新的栈空间。只要新的栈空间开出来,start()方法就结束了
②启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的。
③run和start的区别:
run()不会启动线程,不会分配新的分支栈(即单线程)
start()会启动一个分支线程,开辟新的栈空间

(2)编写一个类实现java.lang.Runnable接口

代码示例:
//创建一个可运行的对象
     MyRunnable r = new MyRunnable();
//将可运行的对象封装成一个线程对象
     Thread t = new Thread(r);
//启动线程
     t.start();
class MyRunnable implements Runnable{
        public void run();
     }
//这并不是一个线程类,是一个可运行的类,不是一个线程

注意!第二种实现接口比较常用,因为一个类实现了接口它还可以继承其他类,更灵活。

(3)实现Callable接口(JDK8新特性)
这种方法实现的线程可以获取线程的返回值。

6.线程生命周期

(1)新建状态:刚new出来的线程对象

(2)就绪状态:就绪状态的线程又叫做可运行状态,表示当前线程具有抢夺CPU时间片的权利,CPU时间片就是执行权。当一个线程抢夺到CPU时间片之后,就开始执行run方法,run方法的开始执行标志着程序进入运行状态

(3)运行状态:run方法的开始执行标着这个线程进入运行状态,当之前占有的CPU时间片用完之后,会重新回到就绪状态继续抢夺CPU的时间片,会出现进入run方法接着上一次的代码继续往下执行

(4)阻塞状态:当一个线程遇到阻塞事件,例如接收用户键盘输入,或者sleep方法等,此时线程会进入阻塞状态,阻塞状态的线程会放弃之前占有的CPU时间片

(5)死亡状态:run方法结束

7.获取线程的名字

代码示例:
String name = t,getName();
//获取当前线程对象
Thread currentThread = Thread.currentThread();
//修改线程名字
线程对象.setName("线程名字");

8.线程的sleep方法

static void sleep(long millis)

(1)静态方法:Thread.sleep(1000);
(2)参数是毫秒
(3)作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其他线程使用

9.终止线程的睡眠
注意!这个不是终断线程的执行,是终止线程的睡眠

代码示例:
//终断线程的睡眠
t.interruput();//这种终断睡眠机制依靠了java的异常处理机制

10.强行终止线程的执行

t.stop()
//不建议经常使用,容易丢失数据

合理地终止一个线程的执行(常用):

线程安全(多线程并发环境下数据安全的问题)

1.线程不安全的条件
(1)多线程并发
(2)有共享数据
(3)共享数据有修改的行为

2.如何解决?
线程排队执行(不能并发),用排队执行解决线程安全问题这种机制称为:线程同步机制。
异步编程模型:多线程并发(效率较高)
同步编程模型:线程排队执行(效率较低)

同步代码块synchronized

1.语法:

synchronized( ){ }
//括号中的数据非常关键,必须是多线程共享的数据才能达到多线程排队

2.以上代码块的执行原理
(1)假设t1和t2线程并发,开始执行以下代码时,肯定有一个先有一个后
(2)假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,找到之后并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都占有这把锁,直至同步代码块结束,这把锁才会被释放。
(3)假设t1已经占有这把锁,此时t2也遇到了synchronized关键字,也会去占有后面共享对象的这把锁,结果这把锁也被t1占有,t2只能在同步代码块外面等待t1把同步代码块执行结束,y1会归还这把锁,此时t2终于等到这把锁,然后t2占有这把锁,进入同步代码块执行程序,这样就达到线程排队执行。
==注意!==这个共享对象一定要选好了,这个共享对象一定是你需要排队执行的这些线程对象所共享的。

Java中有三大变量

实例变量:在堆中
静态变量:在方法区中
局部变量:在堆中
以上三大变量中:
局部变量永远都不会共享,所以局部变量永远都不会存在线程安全问题,一个线程一个栈,局部变量在栈中,实例变量在堆中,堆只有一个,静态变量在方法区,方法区只有一个。堆和方法区都是多线程共享的,所以可能会存在线程安全问题。

synchronized有三种写法:
(1)同步代码块:灵活

synchronized( ){ }
//括号中的数据非常关键,必须是多线程共享的数据才能达到多线程排队

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

(3)在静态方法上使用synchronized表示找类锁。类锁永远只有一把,即使是创建了一百个对象也只有一把类锁。

死锁

1.什么是死锁?
 死锁(Deadlock):指多个进程在运行过程中,因争夺资源而造成的一种僵局。当进程处于这种状态时,若无外力作用,它们都将无法再向前推进。

死锁和饥饿的区别:
死锁:指进程之间无休止地互相等待,进程处于阻塞状态
饥饿:指一个进程无休止地等待,进程处于就绪状态

2.产生死锁的原因
资源问题
1.可重用性资源和消耗性资源
2.可抢占性资源和不可抢占性资源
那么,竞争不可抢占性资源和竞争可消耗资源都会引起死锁
总结:
①竞争资源
②进程间推进顺序非法

3.死锁的必要条件和处理方法

形成死锁的四个必要条件:(四个缺一不可)

(1)互斥条件
进程对所分配到的资源进行排他性使用
(2)请求和保持条件
进程已经保持了至少一个资源,又提出新的资源请求,而新请求资源被其他进程占有只能造成自身进程阻塞,但对自己已获得的其他资源保持不放,必然影响其他进程
(3)不可抢占条件
进程已获得的资源未使用完之前不能被剥夺,只能在使用完时由自己释放
(4)循环等待条件
哲学家问题

处理死锁的方法:

(1)预防死锁
破坏四个必要条件的一个或几个
(2)避免死锁
动态资源分配
(3)检测死锁
能精确确定与死锁有关的进程和资源;然后采取适当的措施,从系统中将已发生的死锁清除掉
(4)解除死锁
撤销或挂起一些进程,以便回收一些资源并将他们分配给已阻塞进程
死锁的检测与解除措施,有可能使系统获得较好的资源利用率和吞吐量(死锁几率不一定很高),但在实现上难度也最大

4.预防死锁的方法

由于互斥条件时非共享设备所必备的,故要破坏产生死锁的后三个条件

(1)破坏“保持和请求条件”

①必须一次性的申请其在整个运行过程中所需要的全部资源
算法简单、易于实现且很安全。但缺点是资源浪费严重、或进程延迟运行

②只允许一个进程只获得在运行初期所需的资源后,便开始运行。进程运行过程中再逐步释放已分配给自己的、且已用完的全部资源,然后在请求新的资源

(2)破坏“不可抢占”条件

允许进程先运行,但当提出的新要求不被满足时必须释放它已保持的所有资源,待以后需要时再重新申请。实现比较复杂且付出很大代价。可能会造成前功尽弃,反复申请和释放等情况

(3)破坏“循环等待”条件
对系统所有资源类型进行排序并赋予不同的序号,所有进程对资源的请求必须严格按照资源序号递增的次序提出。例如:输入设备规定较低的序号,输出设备规定较高的序号

前两种策略比较,资源利用率和系统吞吐量都有较明显的改善。
但也存在严重问题:
①资源编号限制新设备的增加;
②应用中的使用设备顺序与规定的顺序并不协调;
③限制了用户编程自由

在实际开发中应该如何解决线程安全问题?

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

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

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

守护线程概述

1.java语言中线程分为两大类
(1)用户线程
(2)守护线程即后台线程,其中具有代表性的是垃圾回收线程

2.守护线程的特点
一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束
==注意!==主线程main方法是一个用户线程

3.实现守护线程
在启动线程之前,将线程设置为守护线程

t.setDaemon(true)

定时器

1.定时器的作用:
间隔特定的时间执行特定的程序

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

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

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

2.wait()方法调用

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

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

3.notify()方法的作用?

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

表示唤醒正在o对象上等待的线程,还有一个notifyAll()方法,表示唤醒o对象上处于等待的所有线程

4.注意!o.wait()方法会让在o对象上活动的当前线程进入等待状态,并且释放之前占有的o对象的锁。notify()只会通知不会释放之前占有的o对象的锁。

生产者和消费者模式

需要用wait()方法和notify()方法实现

代码示例:
package ProduceConsumer;  
  
import java.util.ArrayList;  
  
public class Produce {  
      
    public Object object;  
    public ArrayList list;//用list存放生产之后的数据,最大容量为1  
              
    public Produce(Object object,ArrayList list ){  
        this.object = object;  
        this.list = list;  
    }  
      
    public void produce() {  
          
        synchronized (object) {  
              
            try {  
            while(!list.isEmpty()){  
                    System.out.println("生产者"+Thread.currentThread().getName()+" waiting");  
                    object.wait();  
                }   
            int value = 9999;  
            list.add(value);  
            System.out.println("生产者"+Thread.currentThread().getName()+" Runnable");  
            object.notifyAll();//然后去唤醒因object调用wait方法处于阻塞状态的线程  
        }catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
  
}  

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/345137.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号