线程和进程概念
进程(Process)是操作系统分配资源的基本单位,一个进程拥有的资源有自己的堆、栈、虚存空间(页表)、文件描述符等信息。从编程的角度来理解进程,可以把它看作是一个类或一个 PCB(Process Control Block)进程控制块的结构体
线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程,同一进程中的多个线程之间可以并发执行。
资源利用率更好
程序在某些情况下更简单
程序响应更快
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()代码也能执行,但是就变成了普通方法的调用了,并没有启动线程
New: 新建线程(创建一个线程,但是没有任何可运行的实体)
Runnable 线程就绪(将程序变量实体放入线程中,)
Running 运行(运行放入的程序)
Blocked 阻塞(程序暂停,等待自动唤醒或者被动唤醒)
Dead 结束(程序运行结束或者异常退出)
例如:
一个线程就是一个抽水机抽水,
线程创建可以看成这个抽水机
就绪就是已经架设好的抽水机
运行:抽水机抽水行为
阻塞:就是正在抽水时因为各种原因抽不上水了
结束:抽水结束并将抽水机搬走
采用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