一文精通Java 多线程之全方位解读

目录
  • 并行和并发
  • 线程基础概念
    • 线程和进程
    • 多线程的好处
    • 线程的状态
  • 实现多线程的两种方式
    • 继承Thread类
    • 实现Runnable接口
  • 线程的安全性和原子性
  • 锁的概念和使用
  • 生产消费者模型
    • 生产消费者模型中的类–存储类
    • 生产消费者模型中的类–生产者
    • 生产消费者模型中的类–消费者
    • 测试类
    • 效果
  • volatile变量
  • 线程池的概念和使用

并行和并发

并行:多个CPU实例或是多台机器同时执行一段处理逻辑,是真正的同时。
并发:一个CPU或一台机器,通过CPU调度算法,让用户看上去同时去执行,实际上从CPU操作层面并不是真正的同时。并发往往需要公共的资源,对公共资源的处理和线程之间的协调是并发的难点。

线程基础概念

线程和进程

进程就是程序,有独立的运行内存空间,比如应用和后台服务,windows是一个支持多进程的操作系统。内存越大能同时运行的程序越多,在Java里一个进程指的是一个运行在独立JVM的程序。
线程:一个程序里运行的多个任务,每个任务就是一个线程,线程是共享内存的在QQ、微信、钉钉等软件中每个聊天窗口都是一个线程,可以同时接收消息和发送消息,但只有一个内存占用。

多线程的好处

◆ 同时运行多个任务,提升CPU的使用效率
◆ 共享内存,占用资源更少,线程间可以通信
◆ 异步调用,避免阻塞
◆ 用户体验感更好

线程的状态

线程包括5种状态:
1、新建(New):线程对象被创建时,它只会短暂地处于这种状态。此时它已经分配了必须的系统资源,并执行了初始化。例如,Thread thread = new Thread()。
2、就绪(Runnable):称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3、运行(Running):线程获取CPU权限进行执行。注意:线程只能从就绪状态进入运行状态。
4、阻塞(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分为三种:
(1)等待阻塞:通过调用线程的wait()方法,让线程等待某工作的完成。
(2)同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态。
(3)其他阻塞:通过调用线程的sleep()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或是超时。或是I/O处理完毕时,线程重新转入就绪状态。
5.死亡(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

实现多线程的两种方式

继承Thread类

第一步 继承Therad父类
第二步 重写run方法
第三步 实例化线程类
第四步 启动线程

public class ThreadDemo extends Thread{

	/**
	 * 继承Thread
	 *
	 * @author gavin
	 *
	 */

	@Override
	public void run() {
		// TODO 自动生成的方法存根
		//打印10次世界
			try {
				for(int i=0;i<10;i++) {
					System.out.print("世界");
					Thread.sleep(200);//阻塞态 休息200毫秒
				}
		} catch (InterruptedException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
		System.out.println("线程执行完成!");
	}

	public static void main(String[] args) {

		//初始化线程t1 t2
		ThreadDemo t1 = new ThreadDemo();
		ThreadDemo t2 = new ThreadDemo();
		//启动线程
		t1.start();
		t2.start();
		//注意不是调用run方法 而是启动线程 调用run方法只是执行了run方法但不是多线程执行

		//打印200次你好
		for(int i = 0;i<20;i++) {
			System.out.print("你好");
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
		}

		//结束态
		System.out.println("程序运行完成");

	}

}

注意:多线程每次运行得到的结果是不相同的
Thread.sleep();方法可以使线程阻塞
调用start();方法才能启动多线程
调用run方法和普通方法效果相同

实现Runnable接口

第一步 实现Runnable接口
第二步 重写run方法
第三步 实例化当前类(任务类)
第四步 将任务类对象作为参数传入Thread中进行实列化
第五步 启动多线程

/**
 * 实现二: 实现Runnable接口
 * 当类本身有父类的时候 使用实现Runnable接口
 * @author gavin
 *
 */
public class RunnableDemo implements Runnable {

	@Override
	public void run() {
		// TODO 自动生成的方法存根
		//打印10次世界
		try {
			for(int i=0;i<10;i++) {
				System.out.print(Thread.currentThread().getName()+"世界\t");
				if(i%3==0) {
					System.out.println();
				}
				Thread.sleep(200);//阻塞态 休息200毫秒
			}
	} catch (InterruptedException e) {
		// TODO 自动生成的 catch 块
		e.printStackTrace();
	}
	System.out.println(Thread.currentThread().getName()+"线程执行完成!");			

	}

	public static void main(String[] args) {

		//实列化当前类
		RunnableDemo runnableDemo1 = new RunnableDemo();

		//创建线程 传入任务类
		Thread t1 = new Thread(runnableDemo1);
		Thread t2 = new Thread(runnableDemo1);
		Thread t3 = new Thread(runnableDemo1);

		//设置线程优先级
		t1.setPriority(Thread.MAX_PRIORITY);//最高优先级
		t2.setPriority(Thread.MIN_PRIORITY);//最低优先级
		t3.setPriority(5);//设置默认优先级 优先级为5 优先级从0-10最高为10

		//启动线程
		t1.start();
		t2.start();
		t3.start();

	}	

}

注意 setPriority 可以设置线程的优先级
但并不代表线程一定优先执行完

线程的安全性和原子性

由于线程之间可以共享内存,则某个对象(变量)是可以被多个线程共享的,是可以被多个线程同时访问的。当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。
举个栗子:甲乙两个人,甲负责向筐里放苹果,乙负责从筐里数苹果,甲乙同时进行,问乙如何操作才能正确?
不幸的是,以上代码不是线程安全的,因为count++并非是原子操作,实际上,它包含了三个独立的操作:读取count的值,将值加1,然后将计算结果写入count。如果线程A读到count为10,马上线程B读到count也为10,线程A加1写入后为11,线程B由于已经读过count值为10,执行加1写入后依然为11,这样就丢失了一次计数。在并发编程中,这种由于不恰当的执行时序而出现不正确的结果是一种非常重要的情况,它有一个正式的名字:
竞态条件(Race Condition)。

Java 内存模型中的可见性、原子性和有序性。
可见性:当多个线程访问同一个变量x时,线程1修改了变量x的值,线程1、线程2…线程n能够立即读
取到线程1修改后的值。
有序性:即程序执行时按照代码书写的先后顺序执行。在Java内存模型中,允许编译器和处理器对指
令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
原子性:原子性通常指多个操作不存在只执行一部分的情况,要么全部执行、要么全部不执行。

锁的概念和使用

竟态条件会使运行结果变得不可靠,程序的运行结果取决于方法的调用顺序,将方法以串行的
方式来访问,我们称这种方式为同步锁(synchronized)。
Java实现同步锁的方式有:
▶同步方法synchronized method
▶ 同步代码块 synchronized(Lock)
▶ 等待与唤醒 wait 和 notify
▶ 使用特殊域变量(volatile)实现线程同步
▶ 使用重入锁实现线程同步ReentrantLock
▶ 使用局部变量实现线程同步ThreadLocal
synchronized
synchronized是Java 的内置锁机制,是在JVM上的
可以同步代码块,也可以同步方法
//同步代码块

synchronized(object){
}

//同步方法

public synchronized void method() {
// do something
}

注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法。

ReentrantLock
可重入锁,是一种显示锁,在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。
ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同
的基本行为和语义,并且扩展了其能力。
ReentrantLock() : 创建一个ReentrantLock实例
lock() : 获得锁
unlock() : 释放锁
可重入: 甲获得锁后释放锁或锁失效,乙可继续获得这个锁

生产消费者模型

生产消费者模型是一个非常典型的多线程并发处理的模型,在实际的生产应用中也有非常广泛的使用。

生产消费者模型中的类–存储类

import java.util.LinkedList;

public class Store {

	/*
	 * 存储 队列实现
	 *
	 * @author gavin
	 * */

	//创建队列
	LinkedList<Integer> list = new LinkedList<Integer>();

	//设置最大存储值
	int max = 10;

	//生产者生产 放入队尾
	public void push(int n) {

		synchronized(list) {
			try {
				if(list.size()>=max){
						System.out.println("存满了");
					    //线程挂起
						list.wait();
					}else {
						//队列没有存满 继续存
						System.out.println("存入:"+n);
						list.add(n);
						//放完之后必须有 因此唤醒取的线程
						list.notifyAll();
					}

			}catch (Exception e) {
					// TODO 自动生成的 catch 块
					e.printStackTrace();
				}
		}

	}

	//消费者消费 从队头取出
	public int pop() {
		try {
			synchronized(list){
				if(list.size()<=0) {
					System.out.println("队列空了。。。。");
					//空了之后就不能取了 因此线程挂起
					list.wait();
				}else {
					//从对头取出
					int n = list.poll();
					System.out.println("取出:"+n);
					//取出了就一定不会满 因此要唤醒线程
					list.notifyAll();
					return n;

				}
			}

		}catch (Exception e) {
			// TODO: handle exception
		}	

		return 0;
	}

}

生产消费者模型中的类–生产者

/**
 * 生产者
 */

public class Producer  implements Runnable{

	private Store store;

	public Producer(Store store) {
		// TODO 自动生成的构造函数存根
		this.store = store;
	}

	@Override
	public void run() {
		// TODO 自动生成的方法存根
		try {
			//生产需要事件 休息100毫秒再生产
			Thread.sleep(100);
			//产生随机数字
			store.push((int)(Math.random()*100));
		}catch (Exception e) {
			// TODO: handle exception

		}

	}

}

生产消费者模型中的类–消费者

public class Customer implements Runnable{

	private Store store;

	public Customer(Store store) {
		// TODO 自动生成的构造函数存根
		this.store = store;
	}

	@Override
	public void run() {
		// TODO 自动生成的方法存根
		try {
			//消费者消费需要时间 休息200毫秒
			Thread.sleep(200);
			//从队头取出
			store.pop();
		}catch (Exception e) {
			// TODO: handle exception
		}
	}

}

测试类

package com.qingsu.pcm;

public class TestPcm {

	public static void main(String[] args) {
		Store store = new Store();

		while(true) {
			Producer producer = new Producer(store);
			Customer customer = new Customer(store);

			Thread t1 = new Thread(producer);
			Thread t2 = new Thread(customer);

			t1.start();
			t2.start();

		}

	}

}

效果

volatile变量

volatile具有可见性、有序性,不具备原子性。
我们了解到synchronized是阻塞式同步,称为重量级锁。
而volatile是非阻塞式同步,称为轻量级锁。
被volatile修饰的变量能够保证每个线程能够获取该变量的最新值,
从而避免出现数据脏读的现象。

线程池的概念和使用

线程的创建是比较消耗内存的,所以我们要事先创建若干个可执行的线程放进一个“池(容器)”里面,需要的时候就直接从池里面取出来不需要自己创建,使用完毕也不需要销毁而是放进“池”中,从而减少了创建和销毁对象所产生的开销。

ExecutorService:线程池接口
ExecutorService pool(池名称) = Executors.常用线程池名;

常用线程池:
newsingleThreadExecutor :单个线程的线程池,即线程池中每次只有一个线程在工作,单线程串行执行任务
newfixedThreadExecutor(n):固定数量的线程池,每提交一个任务就是一个线程,直到达到线程池的最大数量,然后在后面等待队列前面的线程执行或者销毁()。该方式一般会使线程具有一定的执行顺序
newCacheThreadExecutor:一个可缓存的线程池。当线程池超过了处理任务所需要的线程数,那么就会回收部分闲置线程(一般是闲置60s)。当有任务来时而线程不够时,线程池又会创建新的线程,当线程够时就调用池中线程。适
用于大量的耗时较少的线程任务。
newScheduleThreadExecutor:一个大小无限的线程池,该线程池多用于执行延迟任务或者固定周期的任务。

示例

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestPcmTwo {

/*
 * 利用线程池创建线程
*/

	public static void main(String[] args) {
		Store store = new Store();

		//创建10个线程的线程池
		ExecutorService service = Executors.newFixedThreadPool(10);
		ExecutorService serviceTwo = Executors.newFixedThreadPool(10);

		//创建可缓存的线程池
		//可缓存的
		//ExecutorService service = Executors.newCachedThreadPool();
		//ExecutorService serviceTwo = Executors.newCachedThreadPool();

		for(int i =0 ;i<11;i++) {
			service.execute(new Producer(store));

		}
		for(int i =0 ;i<11;i++) {
			serviceTwo.execute(new Customer(store));
		}

		//关闭线程池
			service.shutdown();
			serviceTwo.shutdown();
	}

}

到此这篇关于一文精通Java 多线程之全方位解读的文章就介绍到这了,更多相关Java 多线程内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java基础之多线程方法状态和创建方法

    目录 Java之线程的五大状态及其常用方法(六个状态还有timed_wating超时等待) 1.线程的五大状态及其转换 2.设置或获取多线程的线程名称的方法 3.线程休眠------sleep()方法 4.线程让步------yield()方法 5. 等待线程终止------join()方法 6. 线程停止 7. 线程等待------wait()方法 8. 线程唤醒-------notify()方法 9. notifyAll()方法 JAVA多线程有哪几种实现方式? 1. 继承Thread类 2

  • Java多线程之线程状态详解

    目录 线程状态 停止线程 线程休眠 模拟网络延迟(放大问题的发生性) 模拟计时 线程礼让 插队(线程强制执行) 线程状态观测 线程优先级 守护线程 总结 线程状态 五个状态:新生.就绪.运行.死亡.阻塞 停止线程 不推荐使用JDK提供的stop().destroy()方法[已弃用] 推荐线程自己停止 建议用一个标志位进行终止变量,到flag=false,则终止线程运行 public class StopDemo implements Runnable { // 设置一个标志位 boolean f

  • 一文彻底搞懂java多线程和线程池

    目录 什么是线程 一. Java实现线程的三种方式 1.1.继承Thread类 1.2.实现Runnable接口,并覆写run方法 二. Callable接口 2.1 Callable接口 2.2 Future接口 2.3 Future实现类是FutureTask. 三. Java线程池 3.1.背景 3.2.作用 3.3.应用范围 四. Java 线程池框架Executor 4.1.类图: 4.2 核心类ThreadPoolExecutor: 4.3 ThreadPoolExecutor逻辑结

  • Java多线程学习笔记

    目录 多任务.多线程 程序.进程.线程 学着看jdk文档 线程的创建 1.继承Thread类 2.实现Runable接口 理解并发的场景 龟兔赛跑场景 实现callable接口 理解函数式接口 理解线程的状态 线程停止 线程休眠sleep 1.网路延迟 2.倒计时等 线程礼让yield 线程强制执行 观察线程状态 线程的优先级 守护线程 线程同步机制 1.synchronized 同步方法 2.同步块synchronized(Obj){} lock synchronized与lock 多任务.多

  • Java多线程基本概念以及避坑指南

    目录 前言 1. 多线程基本概念 1.1 轻量级进程 1.2 JMM 1.3 Java中常见的线程同步方式 2. 避坑指南 2.1. 线程池打爆机器 2.2. 锁要关闭 2.3. wait要包两层 2.4. 不要覆盖锁对象 2.5. 处理循环中的异常 2.6. HashMap正确用法 2.7. 线程安全的保护范围 2.8. volatile作用有限 2.9. 日期处理要小心 2.10. 不要在构造函数中启动线程 End 前言 多核的机器,现在已经非常常见了.即使是一块手机,也都配备了强劲的多核处

  • 一文精通Java 多线程之全方位解读

    目录 并行和并发 线程基础概念 线程和进程 多线程的好处 线程的状态 实现多线程的两种方式 继承Thread类 实现Runnable接口 线程的安全性和原子性 锁的概念和使用 生产消费者模型 生产消费者模型中的类–存储类 生产消费者模型中的类–生产者 生产消费者模型中的类–消费者 测试类 效果 volatile变量 线程池的概念和使用 并行和并发 并行:多个CPU实例或是多台机器同时执行一段处理逻辑,是真正的同时. 并发:一个CPU或一台机器,通过CPU调度算法,让用户看上去同时去执行,实际上从

  • 一文精通Java中的volatile关键字

    前言 在一些开源的框架的源码当中时不时都可以看到volatile这个关键字,最近特意学习一下volatile关键字的使用方法. volatile 关键字:当多个线程进行操作共享数据时,可以保证内存中的数据可见. 相较于 synchronized 是一种较为轻量级的同步策略. 缺点: 1. volatile 不具备"互斥性" 2. volatile 不能保证变量的"原子性" 很多资料中是这样介绍volatile关键字的: volatile是轻量级的synchroniz

  • java多线程从入门到精通看这篇就够了

    目录 一.认识线程及线程的创建 1.线程的概念 2.线程的特性 3.线程的创建方式 <1>继承Thread类 <2>实现Runnable接口 <3>实现Callable接口 二.线程的常用方法 1.构造方法和属性的获取方法 2.常用方法 <1>run()和start() <2>interrupt()方法 <3>join方法 <4>获取当前线程的引用currentThread();方法 <5>休眠当前线程slee

  • 详细解读JAVA多线程实现的三种方式

    最近在做代码优化时学习和研究了下JAVA多线程的使用,看了菜鸟们的见解后做了下总结. 1.继承Thread类实现多线程 继承Thread类的方法尽管被我列为一种多线程实现方式,但Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start()实例方法.start()方法是一个native方法,它将启动一个新线程,并执行run()方法.这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run(

  • JAVA多线程和并发基础面试问答(翻译)

    Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用.而线程是在进程中执行的一个任务.Java运行环境是一个包含了不同的类和程序的单一进程.线程可以被称为轻量级进程.线程需要较少的资源来创建和驻留在进程中,并且可以共享进程中的资源. 2. 多线程编程的好处是什么? 在多线程程序中,多个线程被并发的执行以提高程序的效率,CPU不会因为某个线程需要等待资源而进入空闲状态.多个线程共享堆内存(heap

  • Java多线程和并发基础面试题(问答形式)

    本文帮助大家掌握Java多线程基础知识来对应日后碰到的问题,具体内容如下 一.Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用.而线程是在进程中执行的一个任务.Java运行环境是一个包含了不同的类和程序的单一进程.线程可以被称为轻量级进程.线程需要较少的资源来创建和驻留在进程中,并且可以共享进程中的资源. 2. 多线程编程的好处是什么? 在多线程程序中,多个线程被并发的执行以提高程序的效率,C

  • Java Proxy机制详细解读

    动态代理其实就是java.lang.reflect.Proxy类动态的根据您指定的所有接口生成一个class byte,该class会继承Proxy类,并实现所有你指定的接口(您在参数中传入的接口数组):然后再利用您指定的classloader将 class byte加载进系统,最后生成这样一个类的对象,并初始化该对象的一些值,如invocationHandler,以即所有的接口对应的Method成员. 初始化之后将对象返回给调用的客户端.这样客户端拿到的就是一个实现你所有的接口的Proxy对象

  • Java 多线程学习详细总结

    目录(?)[-] 一扩展javalangThread类 二实现javalangRunnable接口 三Thread和Runnable的区别 四线程状态转换 五线程调度 六常用函数说明 使用方式 为什么要用join方法 七常见线程名词解释 八线程同步 九线程数据传递 本文主要讲了java中多线程的使用方法.线程同步.线程数据传递.线程状态及相应的一些线程函数用法.概述等. 首先讲一下进程和线程的区别: 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1

  • Java多线程及分布式爬虫架构原理解析

    这是 Java 爬虫系列博文的第五篇,在上一篇Java 爬虫服务器被屏蔽的解决方案中,我们简单的聊反爬虫策略和反反爬虫方法,主要针对的是 IP 被封及其对应办法.前面几篇文章我们把爬虫相关的基本知识都讲的差不多啦.这一篇我们来聊一聊爬虫架构相关的内容. 前面几章内容我们的爬虫程序都是单线程,在我们调试爬虫程序的时候,单线程爬虫没什么问题,但是当我们在线上环境使用单线程爬虫程序去采集网页时,单线程就暴露出了两个致命的问题: 采集效率特别慢,单线程之间都是串行的,下一个执行动作需要等上一个执行完才能

随机推荐