Java多线程中相关术语:

  一、Java多线程原则

    1.原子性:一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。一般使用线程同步或者Lock锁来确保。

    2.可见性(Java内存模型):当多个线程同时访问一个变量时,其中某个线程改变改变量值,其他线程需要立即查询到。

    3.有序性(涉及JVM对代码的重排序问题):编译器和处理器对不影响逻辑的代码进行重排序(执行顺序),以便提高运行速率(涉及Java的内存屏障,可以使用volatile来禁止重排序)。

备注:指令重排序时不能把后面的指令重排序到内存屏障之前的位置。

   二、Java内存模型(JMM)

    1.JMM决定了一个线程对共享变量进行操作后,能否对其他线程可见。

    2.JMM为每个线程抽象出一个本地内存概念,让一个线程对共享变量进行修改时,先修改本地内存中的值,然后在将本地内存中的值刷新到主线程中供其他线程读取。

       其中volatile关键字修饰的变量,会被立即更新到主内存中,供其他线程所调用。

       备注(volatite的作用域):在Java中为了加快程序的运行效率,对变量的操作是在寄存器或者CPU上,之后在同步到主内存中,使用volatile修饰后会立即同步至主内存中。   

Java 中的两种线程:

  1. 守护线程 : 主线程停止后,守护线程会立即停止( 例如:GC 线程) ,Java 中使用setDaemon(true) 方法来设置为守护线程。
  2. 用户线程 : 用户自定义的线程,当主线程停止后,用户不会立即停止。

Java 中多线程的几个相关状态:

  新建状态、就绪状态 ( 调用start 方法后) 、运行状态、阻塞状态及死亡状态。

Java 多线程中常用的方法:

  join(): 将执行权让给被调用方。

  setPriority(): 设置获取CPU 调度优先级。

  yield():放弃当前CUP 执行权, 和其他线程在一次竞争CPU 使用权。

  wait():停止当前执行的线程,释放锁让其他线程获取该锁。

  notify():唤醒因锁而阻塞的线程。

  注意:wait和notify要与synchronized一起使用,sleep方法不会释放锁。

使用wait和notify实现消费者和生产者(同步方式)[代码示例:wait和notify实现]

Java多线程中锁的分类:

  重入锁:同一个线程中,函数之间调用或者递归调用,锁是可传递的,也称递归锁。特点:1.当程序进入同步代码快时自动获取锁,程序执行完毕后或者有异常抛出时自动释放锁。2.该锁是互斥锁。3.使用synchronized修饰[代码示例1-1]。

synchronized是重量级锁,Lock中ReentrantLock是轻量级锁,使用时需要手动释放锁。

  注意:1.同步方法使用的是this锁。2.静态同步函数采用的锁是字节码文件对象(类.class)。

  读写锁:允许多个线程同时读同一个共享资源时,一旦有写操作时将禁止其他线程读取(读-读可共存,读-写不共存,写-写不共存),适用于对共享资源写操作少。

      ReentrantReadWriteLock[代码示例:读写锁实现]

  乐观锁:思想是默认认为不会发生并发问题,每次取数据时认为没有其他线程对数据进行修改过,但在更新数据时使用版本号或者CAS方式判断在自己保存之前是否有数据被更改过,如果没有则保存,若果已经被修改则重试。

            版本号(version)方式:在获取数据的表中添加一个version字段,标注当前数据被更新的次数,在更新数据之前先查询version值,在保存时再次查询version值是否被更改,一直重试到当前version和数据库一致才能保存。

eg: update table set x = x +1, version = version + 1 where x = #{x} and version = #{version};

       CAS(compare and swap)方式:有3个关键词,主内存值(V):,预期值:本地内存(E),新值:更新后的值(N)

                   当V=E(主内存值等于本地内存值),表示数据没有被修改过,此时可以进行变更。

                   当V!=E,表示数据被其他线程修改过,此时重新将主内存中的值刷新到本地内存中,然后再重新比较。 

                   jdk底层源码案例[CAS源码] 

    CAS缺点:无法解决ABA问题,即一个线程先将值由A改成了B,然后里一个线程在由B改成了A,此时C线程会当做A没有被修改过。可以使用AtomicStampedReference类解决(原理是用时间戳记录).

    jdk提供常用的实现了CAS的类:AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference等

  悲观锁:在操作数据时,认为每次都会有其他线程去修改,所以读取都会被加锁。

  分布式锁:缓存实现(Redis)、Zookeeper实现等。

Java多线程解决方案:

  ThreadLocal:为每个线程提供一个自己的局部变量(隔离共享变量)。

ThreadLocal方法简介:

void set(Object value) 设置当前线程的线程局部变量的值。

  Object get() 返回当前线程所对应的线程局部变量。

void remove()将当前线程局部变量的值删除(线程执行完毕后,jdk将自动回收ThreadLoacl的局部变量)[代码示例2-1]。

ThreadLocal实现原理:底层采用Map进行封装,key为当前线程,value为需要保存的值。

=================================================================================

多线程使用不当导致的问题集锦:

  1.wait和notify实现。

class Resource {

    public String name;
public Integer sex;
public boolean isCustomer = false;
}
// 生产者
class Produces extends Thread { private Resource resource; Produces(Resource resource) {
this.resource = resource;
} @Override
public void run() {
int count = 0;
while(true) {
synchronized (resource) {
if(resource.isCustomer) {
try {
resource.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(count == 0) {
resource.name = "produce";
resource.sex = 0;
} else {
resource.name = "cusoumer";
resource.sex = 1;
}
count = (count + 1) % 2;
resource.isCustomer = true;
resource.notify();
}
}
}
}
// 消费者
class Cusoumers extends Thread { private Resource resource; Cusoumers(Resource resource) {
this.resource = resource;
} @Override
public void run() {
while(true) {
synchronized (resource) {
try {
if(!resource.isCustomer) {
resource.wait();
}
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前消费:" + resource.name + " " + resource.sex);
resource.isCustomer = false;
resource.notify();
}
}
}
} public class WaitAndNotify { public static void main(String[] args) { Resource resource = new Resource(); Produces produce = new Produces(resource);
Cusoumers cusoumer = new Cusoumers(resource);
produce.start();
cusoumer.start(); } }

2.当在使用多线程时,线程间同时对某个共享的全局变量或者静态变量进行写操作时,将会发生数据冲突问题。

eg(1-1):

 @SuppressWarnings("static-access")
public class MultiThread implements Runnable { private Integer count = 100; @Override
public void run() { while(count > 0) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
sale();
}
} private void sale() {
if(count > 0) {
System.out.println("第" + (100 - count + 1) + "张票售出");
count--;
}
} public static void main(String[] args) { MultiThread multiThread = new MultiThread(); Thread m1 = new Thread(multiThread, "1号窗口");
Thread m2 = new Thread(multiThread, "2号窗口"); m1.start();
m2.start();
} }

使用内置锁来处理(不推荐,性能比较低)

 private void sale() {
synchronized (this) {
if(count > 0) {
System.out.println("第" + (100 - count + 1) + "张票售出");
count--;
}
}
}

2.ThreadLocal

eg(2-1):

class Produce{

    private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() { return 0;
}
}; public Integer getCount() { Integer nextCount = threadLocal.get() + 1;
threadLocal.set(nextCount); return nextCount;
} } public class ThreadLocalTest extends Thread { private Produce produce; ThreadLocalTest(Produce produce) {
this.produce = produce;
} @Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "___" + produce.getCount());
}
} public static void main(String[] args) { Produce produce = new Produce();
ThreadLocalTest t1 = new ThreadLocalTest(produce);
ThreadLocalTest t2 = new ThreadLocalTest(produce);
ThreadLocalTest t3 = new ThreadLocalTest(produce);
t1.start();
t2.start();
t3.start(); } }

编译器对代码进行重排序

class Reorder {

    int a = 0;
boolean flag = false; public void writer() {
a = 1; // 当线程执行是a=1和flag=true时,编译器可能会先执行flag=true,然后在执行a=1
flag = true;
} public void reader() {
int i = 0;
if(flag) {// 当线程执行是a=1和flag=true时,编译器可能会先执行i=a*a,然后在执行if(flag)
i = a * a;
System.out.println(i);
}
}
} class ReorderThread1 extends Thread { private Reorder reorder; ReorderThread1(Reorder reorder) {
this.reorder = reorder;
} @Override
public void run() {
reorder.writer();
} } public class ReorderThread extends Thread { private Reorder reorder; ReorderThread(Reorder reorder) {
this.reorder = reorder;
} @Override
public void run() {
reorder.reader();
} public static void main(String[] args) { Reorder reorder = new Reorder();
ReorderThread t1 = new ReorderThread(reorder);
ReorderThread1 t2 = new ReorderThread1(reorder); t1.start();
t2.start(); }
}

3.读写锁

 public class ReadWriteLockTest {

     public static Map<Integer, Integer> maps = new HashMap<>();
public static ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public static Lock readLock = readWriteLock.readLock();
public static Lock writeLick = readWriteLock.writeLock(); public static Integer getResult(Integer key) { readLock.lock();
Integer result = null;
try {
System.out.println("=======读=======");
Thread.currentThread().sleep(50);
result = maps.get(key);
System.out.println(key);
System.out.println("=======读结束=======");
} catch (Exception e) {
e.printStackTrace();
} finally {
readLock.unlock();
} return result;
} public static void putResult(Integer key, Integer value) { writeLick.lock();
try {
System.out.println("=======写=======");
Thread.currentThread().sleep(50);
maps.put(key, value);
System.out.println(key + "_" + value);
System.out.println("=======写结束=======");
} catch (Exception e) {
e.printStackTrace();
} finally {
writeLick.unlock();
} } public static void main(String[] args) { new Thread(new Runnable() { @Override
public void run() {
for (int i = 0; i < 100; i++) {
putResult(i, i);
}
}
}).start(); new Thread(new Runnable() { @Override
public void run() {
for (int i = 0; i < 100; i++) {
getResult(i);
}
}
}).start();
}
}

若屏蔽掉读写锁,如下

3. CAS源码

 public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
} /**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
for (;;) {
//获取当前值
int current = get();
//设置期望值
int next = current + 1;
//调用Native方法compareAndSet,执行CAS操作
if (compareAndSet(current, next))
//成功后才会返回期望值,否则无线循环
return next;
}
}

Java多线中基础知识整理的更多相关文章

  1. java部分基础知识整理----百度脑图版

    近期发现,通过百度脑图可以很好的归纳总结和整理知识点,本着学习和复习的目的,梳理了一下java部分的知识点,不定期更新,若有不恰之处,请指正,谢谢! 脑图链接如下:java部分基础知识整理----百度 ...

  2. Kali Linux渗透基础知识整理(二)漏洞扫描

    Kali Linux渗透基础知识整理系列文章回顾 漏洞扫描 网络流量 Nmap Hping3 Nessus whatweb DirBuster joomscan WPScan 网络流量 网络流量就是网 ...

  3. 《Java核心技术·卷Ⅰ:基础知识(原版10》学习笔记 第5章 继承

    <Java核心技术·卷Ⅰ:基础知识(原版10>学习笔记 第5章 继承 目录 <Java核心技术·卷Ⅰ:基础知识(原版10>学习笔记 第5章 继承 5.1 类.超类和子类 5.1 ...

  4. Kali Linux渗透基础知识整理(四):维持访问

    Kali Linux渗透基础知识整理系列文章回顾 维持访问 在获得了目标系统的访问权之后,攻击者需要进一步维持这一访问权限.使用木马程序.后门程序和rootkit来达到这一目的.维持访问是一种艺术形式 ...

  5. 【OGG】OGG基础知识整理

    [OGG]OGG基础知识整理 一.GoldenGate介绍 GoldenGate软件是一种基于日志的结构化数据复制软件.GoldenGate 能够实现大量交易数据的实时捕捉.变换和投递,实现源数据库与 ...

  6. wifi基础知识整理

    转自 :http://blog.chinaunix.net/uid-9525959-id-3326047.html WIFI基本知识整理 这里对wifi的802.11协议中比较常见的知识做一个基本的总 ...

  7. JavaScript基础知识整理

    只整理基础知识中关键技术,旨在系统性的学习和备忘. 1.在 JScript 中 null 和 undefined 的主要区别是 null 的操作象数字 0,而 undefined 的操作象特殊值NaN ...

  8. 如何看K线图基础知识

    在日K线图中一般白线.黄线.紫线.绿线依次分别表示:5.10.20.60日移动平均线,但这并不是固定的,会根据设置的不同而不同,比如你也可以在系统里把它们设为5.15.30.60均线. 你看K线图的上 ...

  9. Java学习之旅基础知识篇:数据类型及流程控制

    经过开篇对Java运行机制及相关环境搭建,本篇主要讨论Java程序开发的基础知识点,我简单的梳理一下.在讲解数据类型之前,我顺便提及一下Java注释:单行注释.多行注释以及文档注释,这里重点强调文档注 ...

随机推荐

  1. linux 乌班图 xshell链接不上服务器

    输入  ps -e |grep ssh ,如果没有任何反应则是没有安装 命令 sudo apt-get install openssh-server 然后出现是否同意-Y,同意进行自动下载 如果提示  ...

  2. Linux系统xinetd服务启动不了

    Linux系统xinetd服务启动不了 xinetd服务时发现xinetd服务启动不了,并出现错误提示xinetd:unrecognized service,当出现这个错误提示的时候说明系统未安装xi ...

  3. 《剑指offer》把数组排成最小的数

    本题来自<剑指offer> 反转链表 题目: 思路: C++ Code: Python Code: 总结:

  4. TortoiseGit安装使用简单教程

    一.简介 TortoiseGit是Tortoise基于git的可视化管理工具.本文即将介绍这个工具的安装和简单使用教程(本文均是基于Windows 64位操作系统). git的管理工具有很多.Tort ...

  5. 使用 RAII 完成线程等待

    当使用 std::thread 对象执行线程时,必须要调用 join() (或者 detach(),由于 detach() 可以立即调用,所以这里只考虑 join()) #include <io ...

  6. kvm-virsh管理工具

    virsh  可以进入命令行交互界面 Virsh  list   显示所有虚拟机实例 #virt-manager  &   启动图形界面来创建 Virsh   start  c1  --con ...

  7. python实现Hbase

    1. 下载thrift 作用:翻译python语言为hbase语言的工具 2. 运行时先启动hbase 再启动thrift,最后在pycharm中通过happybase包连接hbase 在hbase目 ...

  8. mybatis代码生成器——MyBatis Generator

    1.maven依赖 a.加入依赖 <!-- mybatis生成工具 --> <dependency> <groupId>org.mybatis.generator& ...

  9. ARC 103

    目录 官方题解 C 官方题解 C 这道题教会了我怎样正确统计众数和第二众数........... 我之前的方法是错的 #include <bits/stdc++.h> using name ...

  10. SSH(Spring Struts2 Hibernate)框架整合(注解版)

    案例描述:使用SSH整合框架实现部门的添加功能 工程: Maven 数据库:Oracle 框架:Spring Struts2  Hibernate 案例架构: 1.依赖jar包 pom.xml < ...