推荐阅读:http://www.iteye.com/topic/806990

一、起手式——基本概念

1.什么叫线程

  进程:进行中的程序;作为资源分配的单位。

  线程:轻量级的进程;程序里的顺序控制流,可以理解为程序里不同的执行路径;作为调度和执行的单位

      多个线程可以共享内存,共享地址。相互间的通信十分迅速

        线程体为run()方法(直接调用run()视为普通方法),启动线程为start()方法

    这里方法run()称为线程体, 它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止, 而CPU再运行其它线程, 而如果直接用Run方法, 这只是调用一个方法而已, 程序中依然只有主线程--这一个线程, 其程序执行路径还是只有一条, 这样就没有达到写线程的目的。

  真正的多线程是指有多个CPU,而我们自己用的电脑,实际上是模拟多线程,来多个线程之间来回切换,然而由于切换的时间非常之短,所以我们所看到的就好像同时进行多个线程一样。

2.如何创建线程

  1.继承Thread类,重写run()方法

  线程类:Rabbit(兔子)类与Tortoise(乌龟)类:

package com.jiangbei.thread;

/**
* 兔子类
* @author Administrator
* @date 2017/8/30
**/
public class Rabbit extends Thread { @Override
public void run() {
//线程体
for (int i =0; i < 100; i++){
System.out.println("兔子跑了"+i+"步");
}
}
} /**
* 乌龟类
*/
class Tortoise extends Thread { @Override
public void run() {
//线程体
for (int i =0; i < 100; i++){
System.out.println("乌龟跑了"+i+"步");
}
}
}

  应用线程类方法:线程体是run()方法,启动线程是start()方法,直接调用run()是普通方法,并且启动线程并不等于运行了,具体还需要等带CPU调度

package com.jiangbei.thread;

/**
* 兔子类应用类
* @author Administrator
* @date 2017/8/30
**/
public class RabbitApp {
public static void main(String[] args) {
//创建子类对象
Rabbit rabbit = new Rabbit();
Tortoise tortoise = new Tortoise(); //调用start()方法,启动线程
rabbit.start();
tortoise.start(); for(int i =0; i < 10; i++){
System.out.println("main>>>>>>");
}
}
}

  运行结果:

  

  2.实现Runnable接口,实现run方法(推荐)

    优点:避免单继承;方便共享资源

    原理是静态代理模式,将在设计模式篇中补充

  实现接口的线程类:

package com.jiangbei.thread;

/**
* 实现Runnable接口,创建线程的类
* 真实角色类
* @author Administrator
* @date 2017/8/30
**/
public class Programer implements Runnable{
@Override
public void run() {
for(int i = 0; i < 100; i++){
System.out.println("一边敲HelloWorld");
} }
}

  应用类:(步骤见代码注释)

package com.jiangbei.thread;

/**
* Programer应用类
* @author Administrator
* @date 2017/8/30
**/
public class ProgramerApp {
public static void main(String[] args) {
//创建真实角色
Programer p = new Programer();
//创建代理角色+持有真实角色引用
Thread proxy = new Thread(p);
//是用代理角色启动线程
proxy.start(); for (int i = 0; i < 100; i++) {
System.out.println("一边聊QQ");
} }
}

  结果:

  

  匿名内部类的写法:

  new Thread(new Runnable() {
@Override
public void run() {
Counter.inc();
}
}).start();

  应用:实现接口方式模仿12306购票(方便共享资源:)

package com.jiangbei.thread.web12306;

/**
* 模拟Web12306抢票系统
* @author Administrator
* @date 2017/8/30
**/
public class Web12306 implements Runnable{
private int num = 50; @Override
public void run() {
while (true) {
if(num < 0){
break;
}
System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
}
} public static void main(String[] args) {
//创建真实对象
Web12306 web12306 = new Web12306();
//创建代理对象
Thread t1 = new Thread(web12306,"黄牛甲");
Thread t2 = new Thread(web12306,"黄牛乙");
Thread t3 = new Thread(web12306,"黄牛丙");
//启动线程
t1.start();
t2.start();
t3.start(); }
}

  结果:

  

  是用可以有返回值的 Callable 接口,暂不此展开...

3.线程的五种状态

  

  详细状态:

  

  创建:两种方式 继承Thread类,重写run()方法;实现Runnable接口,实现run()方法。——new出一个线程

  就绪:调用Thread的start()方法(第二种实现接口方式使用静态代理)

  运行:得到时间片,开始运行

  阻塞:遇到阻塞事件——解除阻塞回到就绪状态

  终止:线程死亡,严禁使用stop()方法,而应该定义一个boolean flag,并在run()方法中判断标志进行合理结束run()方法进而结束线程。

    通过外部干预实例:

package com.jiangbei.thread;

/**
* 测试线程状态类
*
* @author Administrator
* @date 2017/8/30
**/
public class ThreadStatusTest {
public static void main(String[] args) {
Study study = new Study();
//创建匿名代理
new Thread(study).start();
for (int i =0; i < 500; i++) {
if (450 == i) {
study.stop();
}
System.out.println("main>>>>>>>>>");
}
}
}
class Study implements Runnable {
private boolean flag = true;
@Override
public void run() {
while (flag){
System.out.println("Study...");
}
}
//对外提供停止方法
public void stop () {
this.flag = false;
}
}

4.线程常用方法

  Thread.sleep():线程睡眠,参数为当前线程的睡眠毫秒数。(静态方法)。抱着锁睡觉

  join():线程合并,将当前线程与线程合并,等待线程终止;

    示例:

package com.jiangbei.thread;

/**
* 测试Thread的常用方法
*
* @author Administrator
* @date 2017/8/30
**/
public class ThreadMethod extends Thread{
public static void main(String[] args) throws InterruptedException {
ThreadMethod tm = new ThreadMethod();
Thread t = new Thread(tm);
t.start();
for(int i = 0; i < 100; i++){
t.join();//等待线程执行完毕
System.out.println("main...."+i);
}
} @Override
public void run() {
for(int i = 0; i < 100; i++){
System.out.println("join...."+i);
}
}
}

    结果:join执行完了再走

    

  yield() static:线程让步,当前线程让出CPU——高风亮节,牺牲自己

    示例:

package com.jiangbei.thread;

/**
* 测试Thread的常用方法
*
* @author Administrator
* @date 2017/8/30
**/
public class ThreadMethod extends Thread{
public static void main(String[] args) throws InterruptedException {
ThreadMethod tm = new ThreadMethod();
Thread t = new Thread(tm);
t.start();
for(int i = 0; i < 100; i++){
if (i%20 == 0) { //被20整除
Thread.yield();//暂停本线程,也就是main
}
System.out.println("main...."+i);
}
} @Override
public void run() {
for(int i = 0; i < 100; i++){
System.out.println("yield...."+i);
}
}
}

    结果:没有真正的暂停,如果CPU又调度掉了它,那么它将继续执行,取决于CPU的算法

    

  sleep() static:线程休眠,暂停当前线程,休眠多少秒。——抱着锁睡觉(排它锁)

           用于倒计时与模拟延时等

    示例:模拟网络延时的时候可能会存在数据不准确(资源冲突)

 public static void main(String[] args) throws InterruptedException {
int num = 10;
while (true) {
System.out.println(num--);
Thread.sleep(1000); //睡眠1秒,主线程睡眠
if(num <= 0){
break;
}
}
}

  wait():线程等待,当前线程进入wait pool 线程等待池。放弃锁等待

  notify()/notifyAll():线程唤醒,唤醒等待池中的一个/所有线程

  currentThread()  getName()  isAlive()   NORM_PRIORITY   setPriority(int newPriority)

  详细介绍,请参见 JDK-API

5.线程同步

  多线程的访问的情况下,由于出现多条路径,就可能会出现访问同一份资源的线程安全问题——对共享资源的访问控制

 以之前的12306抢票为例:

    线程不安全

package com.jiangbei.thread.web12306;

/**
* 模拟Web12306抢票系统
* @author Administrator
* @date 2017/8/30
**/
public class Web12306 implements Runnable{
private int num = 50;
private boolean flag = true;
@Override
public void run() {
while (flag) {
test1();
// test2();
}
} //测试方法1:线程不安全
public void test1() {
if(num <= 0){
flag = false;
return;
}
try {
Thread.sleep(200);//睡眠1秒,存在线程不安全
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
}
//测试方法2:线程安全
public synchronized void test2 (){
if(num <= 0){
flag = false;
return;
}
try {
Thread.sleep(200);//睡眠1秒,存在线程不安全
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
}
public static void main(String[] args) {
//创建真实对象
Web12306 web12306 = new Web12306();
//创建代理对象
Thread t1 = new Thread(web12306,"黄牛甲");
Thread t2 = new Thread(web12306,"黄牛乙");
Thread t3 = new Thread(web12306,"黄牛丙");
//启动线程
t1.start();
t2.start();
t3.start(); }
}

    结果:3个代理黄牛,导致结果不准确!

    

    线程安全同步方法 :运行上述代码test2()

    结果:加入同步方法,牺牲了效率,换来了安全

    

    同步块:只能锁包装类(例如this),同步块必须谨慎选择同步范围

 //测试方法3:利用同步块
public void test3 (){
synchronized(this){
if(num <= 0){
flag = false;
return;
}
try {
Thread.sleep(200);//睡眠1秒,存在线程不安全
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
}
}

    典型应用:单例模式-doublechecking——更多的介绍,将会在设计模式篇进一步讲解

  懒汉式:

package com.jiangbei.thread;

/**
* 线程同步Demo,单例模式运用
* 作者: Administrator
* 日期: 2017/8/30
**/
public class SynDemo {
public static void main(String[] args) {
Jvm jvm1 = Jvm.getIntance();
Jvm jvm2 = Jvm.getIntance();
System.out.println(jvm1);
System.out.println(jvm2);
}
}
/*
单例模式-懒汉式
1.构造器私有化,避免外部创建对象
2.声明私有静态变量
3.创建公有静态方法,对外提供对静态变量的访问
*/
class Jvm { //声明私有静态变量
private static Jvm instance = null;
//构造器私有化
private Jvm(){ }
//对外提供公有静态方法用于获取
public static Jvm getIntance() {
if (null == instance) {
instance = new Jvm();
}
return instance;
}
}

  单线程下,完全没有问题:

  

  稍微修改单例的方法,模拟延时;此时多线程下便会发生线程安全问题,导致单例失效!

  //对外提供公有静态方法用于获取
public static Jvm getIntance() {
if (null == instance) {
try {
Thread.sleep(1000);//模拟延时
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Jvm();
}
return instance;
}

  //可以创建一个线程类,定义方法获取实例,创建两个线程对象进行访问,便可以发现问题,当某个线程延时时,可能另外一个线程也通过了空判断,导致两次创建对象!

  最简单的处理方式是改造为同步方法:

  //对外提供公有静态方法用于获取
public static synchronized Jvm getIntance() {
if (null == instance) {
try {
Thread.sleep(1000);//模拟延时
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Jvm();
}
return instance;
}

  我们这里来使用同步块,由于静态方法里面没有 this,我们只能锁字节码,类.class:

   //对外提供公有静态方法用于获取
public static Jvm getIntance() {
synchronized(Jvm.class) {
if (null == instance) {
try {
Thread.sleep(1000);//模拟延时
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Jvm();
}
return instance;
}
}

  由于上面锁字节码信息任何时候都需要等(即使存在对象),效率非常低,我们加一重判断:

  public static Jvm getIntance() {
if(null == instance) {
synchronized (Jvm.class) {
if (null == instance) {
try {
Thread.sleep(1000);//模拟延时
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Jvm();
} }
}
return instance;
}

  //我们对段代码稍作分析:假设a,b两个线程同时进来,假设都为空,都经过了第一重检查,此时假设a先进入锁区域,创建了对象,释放锁离开

  然后b进来,再次检查时发现对象不为空,对象已经创建,直接离开。

  这就是double check:

public Resource getResource() {
if (resource == null) {
synchronized(this){
if (resource==null) {
resource = new Resource();
}
}
}
return resource;
}

  对象互斥锁:synchronized(obj) 保证同一时刻只能有一个线程访问该对象,从而保证了共享数据的操作完整性。

  同步一般分为两类(注意死锁问题):

      同步方法

        public synchronized void fun1(){}

      同步方法拿的对象的锁,即一个线程进入同步方法A后,其它线程不能进入同步方法B

      同步块

        synchronized(this){}

  解决线程死锁的问题最好只锁定一个对象,不要同时锁定两个对象

经典的生产者消费者模型,请参见:http://www.cnblogs.com/myhomepages/archive/2016/11/03/6028663.html

二、白鹤亮翅——高级线程类与关键字

 1.volatile

  用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。

  基本原理:

    多线程的内存模型:main memory(主存)、working memory(线程栈),在处理数据时,线程会把值从主存load到本地栈,完成操作后再save回去(volatile关键词的作用:每次针对该变量的操作都激发一次load and save)。完整的过程应该是:

read and load 从主存复制变量到当前工作内存
use and assign  执行代码,改变共享变量值 
store and write 用工作内存数据刷新主存相关内容

    本质上,volatile就是不去缓存,直接取值。

  详细的底层原理解析,请参见http://www.importnew.com/18126.html

  注意:

    volatile并不是原子性的,很容易被误用,关于这方面的讲解,请参见http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html

  2.ThreadLocal

   ThreadLocal类,为每个线程维护一个独立的变量副本(应当修正) ——线程本地变量

    用处:保存线程的独立变量。对一个线程类(继承自Thread)
  当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。常用于用户登录控制,如记录session信息。
    深入浅出的讲解,请参见:http://www.iteye.com/topic/103804                
            作者:海子
            出处:http://www.cnblogs.com/dolphin0520/
    几个面试题,请参加:http://www.cnblogs.com/dolphin0520/p/3932934.html

Java基础—线程的更多相关文章

  1. Java基础-线程安全问题汇总

    Java基础-线程安全问题汇总 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.内存泄漏和内存溢出(out of memory)的区别 1>.什么是内存溢出 答:内存溢出指 ...

  2. Java基础-线程操作共享数据的安全问题

    Java基础-线程操作共享数据的安全问题 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.引发线程安全问题 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运 ...

  3. Java基础——线程总结

    Java基础--线程总结 一.线程是什么? 线程:一个程序里不同的运行路径. 二.怎样创建线程? 两种方法创建线程: 第一种 (1)定义详细功能类实现Runnable接口,能够多次调用而实现数据共享 ...

  4. 《Java基础——线程类》

    Java基础--线程类       一.线程的创建之Thread类: 规则: 通过声明一个新类作为子类继承 Thread 类,并复写 run() 方法,就可以启动新线程并执行自己定义的 run()方法 ...

  5. JAVA基础—线程池

    推荐文章java多线程基础 线程池概述 为什么要使用线程池 1.服务器创建和销毁工作线程的开销很大 2.如果频繁的创建和销毁线程会导致频繁的切换线程,因为一个线程被销毁后,必然要把CPU转让给另一个已 ...

  6. java基础—线程(一)

    一.线程的基本概念

  7. java基础—线程(二)

    一.线程的优先级别

  8. Java基础--线程创建方式

    线程的创建主要有两种形式,通过继承Thread或者实现Runnable接口,本质上没有太大区别. /** * @date: 2019/7/16 **/ public class ThreadOne i ...

  9. Java基础-线程与并发1

    线程与并发 Thread 基本概念 程序: 一组计算机能识别和执行的指令 ,是静态的代码. 进程: 程序的一次运行活动, 运行中的程序 . 线程: 进程的组成部分,它代表了一条顺序的执行流. 进程线程 ...

  10. 面试题-Java基础-线程部分

    1.进程和线程的区别是什么? 进程是执行着的应用程序,而线程是进程内部的一个执行序列.一个进程可以有多个线程.线程又叫做轻量级进程. 2.创建线程有几种不同的方式?你喜欢哪一种?为什么? 有三种方式可 ...

随机推荐

  1. iftop 命令

    在Linux中有一个可以实施监控网络流量的一个工具那就是我们这次要说的iftop命令,这个命令不是系统自带的内置命令,在使用之前是需要先进行安装的 安装方式:yum -y install iftop就 ...

  2. JAVA 实现 QQ 邮箱发送验证码功能(不局限于框架)

    JAVA 实现 QQ 邮箱发送验证码功能(不局限于框架) 本来想实现 QQ 登录,有域名一直没用过,还得备案,好麻烦,只能过几天再更新啦. 先把实现的发送邮箱验证码更能更新了. 老规矩,更多内容在注释 ...

  3. Android--自定义弹出框-自定义dialog

    项目要用到弹出框,还要和苹果的样式一样(Android真是没地位),所以就自己定义了一个,不是很像(主要是没图),但是也还可以. 废话不多说了,直接上代码 1.先看布局文件 <?xml vers ...

  4. Expo大作战(十六)--expo结合firebase 一个nosql数据库(本章令我惊讶但又失望!)

    简要:本系列文章讲会对expo进行全面的介绍,本人从2017年6月份接触expo以来,对expo的研究断断续续,一路走来将近10个月,废话不多说,接下来你看到内容,讲全部来与官网 我猜去全部机翻+个人 ...

  5. Jemeter 连接数据库

    1.打开Jmeter添加测试计划,(http://central.maven.org/maven2/mysql/mysql-connector-java/6.0.6/mysql-connector-j ...

  6. 如何将同一云服务下的虚拟机从经典部署模型迁移到 Azure Resource Manager

    适用场景 用户希望将特定云服务下的所有虚拟机从经典部署模型(以下简称:ASM)迁移到 Azure Resource Manager(以下简称:ARM). Note 如果云服务下使用 VNET 也希望将 ...

  7. Angular 过滤器的简单使用

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. Linux 补丁生成与使用

    我们在升级Linux 内核的时候,难免会接触到补丁的知识.下面对如何生成补丁和如何打补丁作讲解. 生成补丁: 制作 hello.c 和 hello_new.c 两个文件如如下所示. ➜ diff ls ...

  9. python基础学习9----深浅拷贝

    数据类型有可变类型和不可变类型 不可变类型:整型,长整型,浮点数,复数,布尔,字符串,元组 可变类型:列表,字典 浅拷贝 简单说只对第一层进行拷贝,如下对于列表中的列表的数据进行改变,list1和li ...

  10. Jenkins 角色 项目权限管理

    插件名称: Role-based Authorization Strategy 新建 两用户 配置项目安全策略  在系统管理页面点击Manage and Assign Roles进入角色管理页面: 进 ...