线程

一、内容简介:

  本文主要讲述计算机中有关线程的相关内容,以及JAVA中关于线程的基础知识点,为以后的深入学习做铺垫。如果你已经是高手了,那么这篇文章并不适合你。

二、随笔正文:

1、计算机系统组成

  计算机系统由计算机硬件系统软件系统组成。我们今天要说的线程和硬件系统中的cpu中央处理器,及软件系统中的操作系统,进程等有比较紧密的联系。操作系统是软件中比较特殊的存在,与硬件系统直接交互,其他程序(软件)运行在操作系统之上。

2、cpu简单说明

  硬件系统中特别重要的一项就是处理器CPU,与我们所说的线程有非常紧密的联系。cpu中有几项参数,以及如何查看该信息,在下文逐一说明:

块数:民用pc机,基本都是一块物理cpu,每块主板上只能装一块cpu。

核心数:也就是单块物理cpu是由几组处理芯片组,组成的。4核心 8核心等。

线程数:老款cpu都是单线程的,及一组芯片组只能运行一个线程。现款因特尔cpu大多支持超线程技术可支持多个逻辑线程。但是需要操作系统及相关编程语言的支持,JAVA相较C++在多线程方面能表现的更出色。

主频:单位GHZ(hz赫兹 每秒的周期性变动重复次数)在计算机中即高低电平变化一次,可以产生两个不同的电信号0、1。以我的CPU I5-4200M 2.5GHZ 举例,及cpu可以每秒完成25亿次震荡! 也就是说主频越高理论上计算能力越强,处理计算机指令越快。但是并不代表计算机整体运算速度约高,这点通常满足水桶效应,而cpu一直稳居长板地位。

缓存:cpu内置缓存,很小通常为几Mb至十几Mb,和cpu交互更频繁,速度也远高于普通运行内存,提高cpu处理能力的有效手段。

查看cpu参数指令:

:1进入windows管理工具
wmic
:2获取cpu数量及名称
cpu get name
:3cpu核心数量
cpu get numberOfCores
:4获取逻辑线程数量
cpu get numberOfLogicalProcessors

DOS命令

3、关系梳理

  操作系统,程序,进程,线程之间的关系梳理、

  程序:是计算机上的静态代码,指令文件集合,是静态的存在。比如:QQ,LOL等

  进程:程序的执行实体(过程),持有及分配资源的主体。chrome.ext,QQ.exe等执行进程。

  线程:是进程中的劳动力,由进程创建,完成指定任务后结束。

  关系

    操作系统 1 ——> n 程序 1 ——> n 进程 1 ——> n 线程

     平台       集合     资源     干活的

  普通进程创建线程去完成指定的计算机指令,这个时候需要调用系统资源如cpu进行运算,但是用户线程并不能直接驱动硬件,而是通过操作系统去统一分配、控制硬件的使用。

4、进程、线程基本状态

  五个基本状态,创建和终止不说了。计算机中的线程创建之后会进入就绪状态,当cpu为此线程分配时间片时,线程由就绪转为执行状态,开始干活,当时间片结束时回到就绪状态等待下次获取时间片,循环直到任务完成,当任务完成时,线程终止(死亡)。若在执行过程中遇到耗时操作比如IO或者JAVA中的线程休眠等,会进入阻塞状态,阻塞结束会进入就绪状态继续排队等待被分配时间片。

5、JAVA中的线程

  java中提供了两种方式去创建线程,继承类和实现接口。由于java中单继承机制的限制,大多数情况下使用实现Runnable接口的形式创建线程。

1 、继承Thread类

public class MyThread extends Thread {

    //创建类继承Thread,重写run方法
public void run(){
//调用线程start方法,该线程进入就绪状态,等待被分配时间片执行run方法中的代码
}
public static void main(String [] args){
//创建线程对象即可
MyThread mt = new MyThread();
//启动线程
mt.start();
}
}

继承Thread类

2、实现Runnable接口

public class MyRunnable implements Runnable {

    //实现Runnable接口,重写run方法
public void run(){
//调用线程start方法,该线程进入就绪状态,等待被分配时间片执行run方法中的代码
}
public static void main(String [] args){
//创建实现Runnable接口的对象,创建线程对象
MyRunnable mr = new MyRunnable();
Thread thread = new Thread(mr);
//启动线程
mt.start();
/* Thread的构造本质调用的init()
private void init(ThreadGroup g, Runnable target, String name,
long stackSize,AccessControlContext acc) {
//stackSize及acc先不涉及,g为线程组,target为实现Runnable的类,name为线程名
}
*/
}
}

实现Runnable接口

3、线程的常用方法

 start()
//启动线程,调用run方法
sleep()
//线程休眠,进入阻塞状态,让出时间片,但不会让出锁
Thread.currentThread()
//获取当前执行线程
wait/notify()
//仅能存在synchronized代码块中,wait线程休息,让出时间片,进入等待状态,notify()唤醒该线程;该方法存在重载
join()
//等待此线程执行完毕
setDaemon()
//设置守护线程,守护线程是服务线程,当用户线程结束,守护线程自动结束
yield()
//主动让出时间片给其他线程
interrupt()
//中断线程,不推荐
get/setId()
//设置获取线程id
get/setName()
//设置获取线程名
get/setPriority()
//设置获取线程优先级,理论上优先级越高,获取时间片的概率越大,默认是5最高10最小1

4、JAVA中的线程状态图

5、线程练习

简单模拟多线程购票业务

public class Tickets implements Runnable {
private int ticketsCount = 100;
@Override
public void run() {
boolean b = true;
while (b) {
b = sellTickets();
}
}
//售票
private boolean sellTickets() {
if (ticketsCount > 0) {
System.out.println(Thread.currentThread().getName() + "售出第 [" + (ticketsCount--) + "] 张票");
return true;
} else {
System.out.println("已售罄!");
return false;
}
}
public static void main(String[] args) { //创建一个票务系统,共计100张票
Tickets tickets = new Tickets();
//创建多线程,模拟售票窗口
Thread thread1 = new Thread(tickets, "1号售票窗口");
Thread thread2 = new Thread(tickets, "2号售票窗口");
Thread thread3 = new Thread(tickets, "3号售票窗口");
Thread thread4 = new Thread(tickets, "4号售票窗口");
Thread thread5 = new Thread(tickets, "5号售票窗口");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
}
}

售票业务

  在不做任何控制的情况下,出错的几率很小,我反复测试几次,结果基本都正确!分析原因:业务本身相对简单,没有耗时操作,每个时间片基本能保证线程将本次任务执行完毕!也就是将票数减一并打印内容。

  为了模拟在售票前双方的问询阶段及付款阶段的等待,在售票前(后)加入Thread.sleep(ms) 模拟耗时操作。

private boolean sellTickets() {
try {
// 随机休息1~10毫秒,线程进入Timed_Waiting状态让出时间片给其他的线程
Thread.currentThread().sleep((long) (Math.random() * 10));
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticketsCount > 0) {
System.out.println(Thread.currentThread().getName() + "售出第 [" + (ticketsCount--) + "] 张票");
return true;
} else {
System.out.println("已售罄!");
return false;
}
}

加入线程休眠

  改动后系统出现bug同一张票被售出了多次,并且可能将票卖出负数。究其原因:线程之间的数据争用问题,我们将引入JAVA内存模型进行分析(图片来自网络侵删)

  首先我们需要知道几个概念,如下:

  共享变量:主内存中存在被多个线程同时用到的变量,在多个线程中存在相应副本变量。

  可见性:线程对共享变量的值进行修改,能否被其他线程可见。

  变量访问规则:      1、线程对共享变量的操作只能在自己工作内存中的副本。

            2、工作内存中的变量变化需要通过主内存传递。

6、实现同步

  实现同步即实现共享变量可见性,工作内存1 ——> 主内存 ——>工作内存2,在数据传递的两个环节中出现问题都会对同步造成影响,从而影响执行结果。

  synchronized实现同步

  synchronized 可以实现指令的原子性,及共享变量的可见性。

  原子性:synchronize修饰的方法或者代码块会获取互斥锁,保证同一时间只能有一个对象访问该方法或代码块,将其作为一个整体,保证了原子性。

​   可见性:加锁后,先清空工作内存,同步主内存中的共享变量。解锁前,先将工作内存中的变量同步到主内存,再释放锁。

  所以结合上述例子,为sellTickets()方法加锁即可实现同步;或者对核心代码片段加锁;

private synchronized boolean sellTickets() {  //…  省略中间代码  
}
  //或者如下实现
  
synchronized(this){
//this指的是调用sellTickets()的对象
}

7、volatile关键字

  共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

  原理:被volatile修饰的变量所生成汇编代码时有lock前缀,生成"内存屏障"。

    1)保证了不同线程对这个变量进行操作时的可见性,即工作内存中的变量值在修改后会被立即同步到主内存中;

    2)并且使其他线程中的缓存无效,这样当其他线程在访问共享变量时就必须取主内存中获取;

    3)禁止进行指令重排序;

  综上所述,volatile可以保证可见性,但不能保证原子性;

  举例分析:

volatile int number = 0 ;
number ++ ;
/*
操作可以解析成三步: 1.获取number中的值
2.计算加1操作
3.number = 0 + 1;
在一个时间片中,虽然volatile修饰的number一定会被立即同步到主内存中,但不能保证完整执行这三步,所以不能保证++操作的原子性 。
*/

三、总结说明:

  首先感谢各位能看到最后,对本文内容如果存在疑问,欢迎留言交流,若存在错误,也还望斧正!我将不定期对文章进行修改和调整,如发现错误一定及时改正以免误人子弟!

JAVA线程及简单同步实现的原理解析的更多相关文章

  1. Java线程状态及同步锁

    线程的生命历程 线程的五大状态 创建状态:简而言之,当创建线程对象的代码出现的时候,此时线程就进入了创建状态.这时候的线程只是行代码而已.只有调用线程的start()方法时,线程的状态才会改变,进入就 ...

  2. Java线程中的同步

    1.对象与锁 每一个Object类及其子类的实例都拥有一个锁.其中,标量类型int,float等不是对象类型,但是标量类型可以通过其包装类来作为锁.单独的成员变量是不能被标明为同步的.锁只能用在使用了 ...

  3. java线程和多线程同步

    java的线程之间资源共享,所以会出现线程同步问题(即,线程安全) 一.线程创建: 方式①:extends java.lang.Thread,重写run(),run方法里是开启线程后要做的事..sta ...

  4. java线程池系列(1)-ThreadPoolExecutor实现原理

    前言 做java开发的,一般都避免不了要面对java线程池技术,像tomcat之类的容器天然就支持多线程. 即使是做偏后端技术,如处理一些消息,执行一些计算任务,也经常需要用到线程池技术. 鉴于线程池 ...

  5. java线程的简单实现及方法

    java线程: 线程是一个程序内部的顺序控制流. cpu实际上在一个时间点上,只执行一个.只不过我们把cpu分成了多个时间片,由于速度很快,我们看起来像是多个线程.. 就像你的时间分成几片,这样 整体 ...

  6. Java Web每天学之Servlet的原理解析

    Java Web每天学之Servlet的工作原理解析,上海尚学堂Java技术文章Java Web系列之二上一篇文章Java Web每天学之Servlet的工作原理解析是之一,欢迎点击阅读. Servl ...

  7. Java 线程池的介绍以及工作原理

    在什么情况下使用线程池? 1.单个任务处理的时间比较短 2.将需处理的任务的数量大 使用线程池的好处: 1. 降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁造成的消耗.2. 提高响应速度: ...

  8. java 线程的简单理解

    想要实现线程可以继承Thread也可以实现接口runnable,在类中重写 run()方法在主函数调用start方法就可以开辟线程. 对于java对象都有一个wait()和notify().notif ...

  9. 【Java线程安全】 — 常用数据结构及原理(未完结)

    本文主要记录自己对于多线程安全的学习,先来记几个线程安全模型. 首先最重要的当然是volatile和AQS了: 我们知道,整个java.cuncurrent包的核心就是volatile,CAS加自旋悲 ...

随机推荐

  1. ajax接收php返回得到一堆html代码

    ajax接收php返回得到一堆html代码 一.      错误代码: <font size='1'><table class='xdebug-error xe-notice' di ...

  2. RocketMQ源码 — 八、 RocketMQ消息重试

    RocketMQ的消息重试包含了producer发送消息的重试和consumer消息消费的重试. producer发送消息重试 producer在发送消息的时候如果发送失败了,RocketMQ会自动重 ...

  3. Jmeter——HTTP协议的接口压力测试环境搭建

     文章版权由作者小小小丝和博客园共有,若转载请于明显处标明出处:http://rpc.cnblogs.com/metaweblog/xxxs JDK 是整个Java的核心,包括了Java运行环境.Ja ...

  4. java运行机制、Jdk版本及Java环境变量

    一.语言特性 计算机高级语言按程序的执行方式可分为:编译型和解释型两种.编译型的语言是指使用专门的编译器,针对特定的平台(操作系统)一次性翻译成被该平台硬件执行的机器码,并包装成该平台可执行性程序文件 ...

  5. Flask入门之自定义过滤器(匹配器)

    1.  动态路由的匹配器? 不知道这种叫啥名,啥用法,暂且叫做匹配器吧. Flask自带的匹配器可以说有四种吧(保守数字,就我学到的) 动态路由本身,可以传任何参数字符串或者数字,如:<user ...

  6. HiJson(Json格式化工具)64位中文版下载 v2.1.2

    链接:https://pan.baidu.com/s/15gMvig15iUjpqSX7nUZ-5Q 密码:8086

  7. iscsi 挂载网络存储及存储访问

    http://blog.sina.com.cn/s/blog_408764940101ghzi.html 一.Ess3016x设置 登陆admin 密码 888888888888 1.安装硬盘,查看硬 ...

  8. Python《学习手册:第一章-习题》

    人们选择Python的六大主要原因是什么? 软件质量:Python注重可读性.一致性和软件质量. Python代码的设计致力于可读性,因此具备了比传统脚本语言更优秀的可重用性和可维护性. Python ...

  9. RedisTemplate执行Redis脚本

    对于Redis脚本使用过的同学都知道,这个主要是为了防止竞态条件而用的.因为脚本是顺序执行的.(不用担心效率问题)比如我在工作用,用来设置考试最高分. 如果还没有用过的话,先去看Redis脚本的介绍, ...

  10. 附近的人,附近的卖家(geohash+前缀树)

    http://www.cnblogs.com/LBSer/p/3310455.html http://blog.csdn.net/shixiaoguo90/article/details/253137 ...