当前位置: 首页 > 编程笔记 >

Java多线程编程相关知识点

公孙弘图
2023-05-05

线程和进程概念

进程(Process)是操作系统分配资源的基本单位,一个进程拥有的资源有自己的堆、栈、虚存空间(页表)、文件描述符等信息。从编程的角度来理解进程,可以把它看作是一个类或一个 PCB(Process Control Block)进程控制块的结构体

线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程,同一进程中的多个线程之间可以并发执行。

1. 多线程优点

  • 资源利用率更好

  • 程序在某些情况下更简单

  • 程序响应更快

2. 创建线程

   1. 实现Runnable接口       

class MyFirstRunnable implements Runnable {
        private int i=0;
        @Override
        public void run() {
                // TODO Auto-generated method stub
                for (i=0;i<10;i++){
                        System.out.println(Thread.currentThread().getName()+" "+ i);
                }
        }
}
new Thread(Runnable).start()


  • 可以避免由于java单继承带来的局限

  • 增强程序的健壮性,代码能够被多个线程共享,代码和数据是=数据是独立的

  • 适合多个相同程序代码的线程区处理同意资源的情况

   2. 继承Thread

1.class MyFirstThread extends Thread {
        private int i = 0;
        @Override
        public void run() {
                // TODO Auto-generated method stub
                for (i=0;i<100;i++) {
                        System.out.println(Thread.currentThread().getName()+" "+i);
                }                
        }
}
2.new MyThread().start()
注意:启动线程的方式必须是start(),若是直接调用Thread.run()代码也能执行,但是就变成了普通方法的调用了,并没有启动线程


3. 线程基本状态(五种)

      New: 新建线程(创建一个线程,但是没有任何可运行的实体)

      Runnable 线程就绪(将程序变量实体放入线程中,)

      Running  运行(运行放入的程序)

      Blocked  阻塞(程序暂停,等待自动唤醒或者被动唤醒)

      Dead  结束(程序运行结束或者异常退出)

例如:

      一个线程就是一个抽水机抽水,

      线程创建可以看成这个抽水机

      就绪就是已经架设好的抽水机

      运行:抽水机抽水行为

      阻塞:就是正在抽水时因为各种原因抽不上水了

      结束:抽水结束并将抽水机搬走

4. synchronised关键字

采用synchronized修饰符实现的同步机制叫做互斥锁机制,每一个对象都有一个monitor(锁标记),只能分配给一个线程。当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池,因此叫做互斥锁,synchronised同时具有可见性和原子性,原子性是因为锁内的操作不可分割,可见性因为 入锁(进入synchronized)会获取主存中变量的最新值和出锁(退出synchronized)会将变量的最新值刷新回主存

1.实例方法的同步与实例方法内的同步块  实例方法内的synchronized是同步在某个实例对象上。即对象锁

public class MyClass {
public synchronized void log1(String msg1, String msg2){
        //...
}
public void log2(String msg1, String msg2){
        synchronized(object){
            //...
        }
}}


2.静态方法的同步与静态方法内的同步块  静态方法内的synchronized是同步在类对象上,锁住的是整个类,即类锁   

public static synchronized void log1(String msg1, String msg2){
        //...
}
public void log2(String msg1, String msg2){
        synchronized(MyClass.class){
            //...
        }
    }
}

使用同步机制获取互斥锁的情况,进行几点说明:

一旦某个锁被某个线程获取,那么其他所有在这个锁(同一个对象或类)上竞争的线程都会阻塞,不管是不是同一个方法或代码块(仔细体会)。以对象锁为例,假如有三个synchronized方法a,b,c,当线程A进入实例对象M中的方法a时,它便获得了该M对象锁,其他的线程会在M的所有的synchronized方法处阻塞,即在方法 a,b,c 处都要阻塞

对象级别锁,锁住的是对象,有上面说的锁的特性.

类级别锁,锁住的是整个类,它用于控制对 static 成员变量以及 static 方法的并发访问。有上面说的锁的特性

互斥是实现同步的一种手段,临界区、互斥量和信号量都是主要的互斥实现方式。synchronized 关键字经过编译后,会在同步块的前后分别形成 monitorenter 和 monitorexit这两个字节码指令。根据虚拟机规范的要求,在执行 monitorenter指令时,首先要尝试获取对象的锁,如果获得了锁,把锁的计数器加 1,相应地,在执行 monitorexit 指令时会将锁计数器减 1,当计数器为 0 时,锁便被释放了。由于synchronized同步块对同一个线程是可重入的,一个线程可以多次获得同一个对象的互斥锁,要释放相应次数的该互斥锁,才能最终释放掉该锁。

Thread.sleep(2000);

    暂停当前线程的执行,暂停时间由方法参数指定,单位为毫秒。注意参数不能为负数,否则程序将会抛出IllegalArgumentException。

   /**
	 * 单线程
	 * @throws wangh
	 */
	public void thread() throws InterruptedException {
		// 一个人每隔两秒搬一块砖
		Thread t = Thread.currentThread();
		t.setName("zhangsan 单例线程");
		// 有1000条数据需要更新,由一个人来完成
		for (int i = 0; i < 1000; i++) {
			// 每隔两秒执行一次, 线程休眠中
			Thread.sleep(2000);
			System.out.println(t.getName() + "正在执行" + i);
		}
	}


创建多个线程(非安全)

public class Count {
    // 公共变量
    private int num;  
    public void count() {  
        for(int i = 1; i <= 10; i++) {  
            num += i;  
        }  
        System.out.println(Thread.currentThread().getName() + "-" + num);  
    }  
}


 在这个类中的count方法计算1一直加到10的和,并输出当前线程名和总和,我们期望的是每个线程都会输出55。

public class ThreadTest {  
    public static void main(String[] args) {  
        Runnable runnable = new Runnable() {  
            Count count = new Count();  
            public void run() {  
                count.count();  
            }  
        };  
        for(int i = 0; i < 10; i++) {  
            new Thread(runnable).start();  
        }  
    }  
}


 这里启动了10个线程,看一下输出结果:

Thread-0-55  
Thread-1-110  
Thread-2-165  
Thread-4-220  
Thread-5-275  
Thread-6-330  
Thread-3-385  
Thread-7-440  
Thread-8-495  
Thread-9-550


只有Thread-0线程输出的结果是我们期望的,而输出的是每次都累加的,要想得到我们期望的结果,有几种解决方案:

   1、将Count类中的成员变量num变成count方法的局部变量;

public class Count {  
    public void count() {  
        int num = 0;  
        for(int i = 1; i <= 10; i++) {  
            num += i;  
        }  
        System.out.println(Thread.currentThread().getName() + ”-“ + num);  
    }  
}


        2、将线程类成员变量拿到run方法中,这时count引用是线程内的局部变量;

public class ThreadTest4 {  
    public static void main(String[] args) {  
        Runnable runnable = new Runnable() {  
            public void run() {  
                Count count = new Count();  
                count.count();  
            }  
        };  
        for(int i = 0; i < 10; i++) {  
            new Thread(runnable).start();  
        }  
    }  
}


存在成员变量的类用于多线程时是不安全的,不安全体现在这个成员变量可能发生非原子性的操作,而变量定义在方法内也就是局部变量是线程安全的

多个线程之间是不能直接传递数据进行交互的,它们之间的交互只能通过共享变量来实现。拿上面的例子来说明,在多个线程之间共享了Count类的一个实例,这个对象是被创建在主内存(堆内存)中,每个线程都有自己的工作内存(线程栈),工作内存存储了主内存count对象的一个副本,当线程操作count对象时,首先从主内存复制count对象到工作内存中,然后执行代码count.count(),改变了num值,最后用工作内存中的count刷新主内存的 count。当一个对象在多个工作内存中都存在副本时,如果一个工作内存刷新了主内存中的共享变量,其它线程也应该能够看到被修改后的值,此为可见性。

   多个线程执行时,CPU对线程的调度是随机的,我们不知道当前程序被执行到哪步就切换到了下一个线程,一个最经典的例子就是银行汇款问题,一个银行账户存款100,这时一个人从该账户取10元,同时另一个人向该账户汇10元,那么余额应该还是100。那么此时可能发生这种情况,A线程负责取款,B线程负责汇款,A从主内存读到100,B从主内存读到100,A执行减10操作,并将数据刷新到主内存,这时主内存数据100-10=90,而B内存执行加10操作,并将数据刷新到主内存,最后主内存数据100+10=110,显然这是一个严重的问题,我们要保证A线程和B线程有序执行,先取款后汇款或者先汇款后取款,此为有序性。

线程的几个主要概念

在多线程编程时,你需要了解以下几个概念:

  • 线程同步

  • 线程间通信

  • 线程死锁

  • 线程控制:挂起、停止和恢复

 类似资料:
  • 本文向大家介绍python3多线程知识点总结,包括了python3多线程知识点总结的使用技巧和注意事项,需要的朋友参考一下 多线程类似于同时执行多个不同程序,多线程运行有如下优点: 使用线程可以把占据长时间的程序中的任务放到后台去处理。 用户界面可以更加吸引人,比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。 程序的运行速度可能加快。 在一些等待的任务实现上如用户输

  • 本文向大家介绍Spring 注解编程模型相关知识详解,包括了Spring 注解编程模型相关知识详解的使用技巧和注意事项,需要的朋友参考一下 Spring 中有一个概念叫「元注解」(Meta-Annotation),通过元注解,实现注解的「派生性」,官方的说法是「Annotation Hierarchy」。 什么是元注解 所谓元注解,即标注在注解上的注解。这种方式所形成的注解层级结构中,元注解在层级

  • Java 相关知识点汇总,包括 Java 基础、Java 容器、Java 并发、JVM、编程规范、数据结构与算法、数据库、系统设计、设计模式、数据通信、网站架构、备战面试、Github 榜单。

  • 本文向大家介绍java多线程编程之InheritableThreadLocal,包括了java多线程编程之InheritableThreadLocal的使用技巧和注意事项,需要的朋友参考一下 InheritableThreadLocal的作用: 当我们需要在子线程中使用父线程中的值得时候我们就可以像使用ThreadLocal那样来使用InheritableThreadLocal了。 首先我们来看一

  • 创建一个易应用程序只需要短短几分钟的时间 - 通过在设计窗口上“绘制”诸如编辑框和按钮等组件来创建用户界面。然后,为窗口和组件设置属性以规定诸如标题、位置、尺寸等的值。最后,编写处理程序将生命真正赋于程序。 组件及事件驱动   组件及其事件驱动是使用易语言在 Windows 环境下编程的基础知识。所谓“组件”,即用作组成用户图形界面的基本成员,譬如:窗口、编辑框、图片框等等。组件按可否容纳其它组件

  • 其实创建线程之后,线程并不是始终保持一个状态的,其状态大概如下: New 创建 Runnable 就绪。等待调度 Running 运行 Blocked 阻塞。阻塞可能在 Wait Locked Sleeping Dead 消亡 线程有着不同的状态,也有不同的类型。大致可分为: 主线程 子线程 守护线程(后台线程) 前台线程 简单了解完这些之后,我们开始看看具体的代码使用了。 1、线程的创建 Pyt