JAVA并发编程---线程基础、线程之间的共享和操作

一、基础概念  

1.什么是进程和线程

进程是操作系统进行资源分配的最小单位,一个进程下是可以拥有多个线程的

线程是CPU调度的最小单位,不能独立于进程存在,可分享进程的资源

2.CPU核心数和线程数的关系

一般情况下——cpu核心数:线程数 = 1:1

intel引用超线程机制后 = 1:2

3.CPU时间轮转机制

并发编程之 CPU 时间片轮转机制 (RR 调度)_Java_谭青海-CSDN博客

4.并行与并发

并行:同时运行的任务数

eg:一台咖啡机能够给一人提供服务,那么并行数就为1,如果两台,那么并行数就为2

并发:一个时间段内能够执行的任务数

eg:一台咖啡机在10分钟内可以产出两杯咖啡,那么我们就可以认为并发数为10分钟内并发数为2

思考:JAVA为什么引入并发编程

1.充分利用CPU资源;

2.加快用户响应的时间;

二、认识JAVA里的线程

JAVA中的线程是协作的并不是抢占的

1.java里的程序天生就是多线程的,那么有几种新启线程的方式?

为什么说java天生就是多线程的,那么我们证明一下

public static void main(String[] args) {

//JAVA虚拟机线程系统管理接口

    ThreadMXBeanthreadMXBean= ManagementFactory.getThreadMXBean();

//仅获得线程和线程堆栈信息

    ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false,false);

for (ThreadInfo threadInfo : threadInfos) {

System.out.println("[" + threadInfo.getThreadId() +"]" + threadInfo.getThreadName());

}

}

运行后我们会发现控制台上为我们打印了很多线程信息(线程信息仅供了解,不是重点)

[8]JDWP Command Reader

[7]JDWP Event Helper Thread

[6]JDWP Transport Listener: dt_socket

[5]Attach Listener   --- dump线程信息线程

[4]Signal Dispatcher

[3]Finalizer        ---  对象资源回收线程(守护线程)

[2]Reference Handler    ---  回收引用处理线程

[1]main --- 主线程main

仅有一个main方法就为我们打印了很多线程,说明我们的JAVA天生就是多线程协作的

几种新启线程的方式

JDK Thread源码注释提供

JDK中告诉我们仅有两种方式  

(1)extends Thread   

(2)implements Runnable

(3)implements Callable     注:Callable 是有返回值的,可以认为和Runnable为一种

以下为代码实现

public class Theadextends Thread {

@Override

    public void run() {

System.out.println("继承自Thread");

}

}

public class Thead1implements Runnable {

@Override

    public void run() {

System.out.println("实现Runnable");

}

}

public class Thead2implements Callable {

@Override

    public Object call()throws Exception {

return "实现Callable";

}

}

那么我们说Thread和Runnable 有什么区别呢?

1.Thead是对线程的抽象,而Runable是对任务的抽象

2.如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

3.main函数,实例化线程对象也有所不同,

extends Thread :t.start();

implements Runnable : new Thread(t).start();

4.使用Runnable,增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

5.线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

2.有开始就有结束,怎样才能让JAVA里的线程安全的停止呢?

stop()还是interrupt()、isInterrupted()、static方法interrupted()  =>我们需要深入了解这些方法

stop()  --- 强制停止当前线程

我们通过Thread的源码中可看到stop方法被标记了废弃标签,这说明JDK告诉我们不推荐使用,因为它带有强制作用,并不能保证线程的安全与资源的回收

interrupt() --- 对线程进行通知中断(其实是设置线程的中断标识位)

我们可以认为是向我们的线程打个招呼,告诉当前线程你该停止了,但并非为强制中断,是否中断由线程自己决定

isInterrupted() --- 判断当前线程是否被中断 

通常用来在线程run方法中进行判定业务是否进行

interrupted() --- 给线程一个中断标记位

也可以用来判断线程是否被中断,但同时将中断标识位由true改为false

*在Runnable 中使用时需执行Thread.currentThread()方法,调取当前线程,Runnable 中并未提供以上方法

*当阻塞方法抛出InterruptedException时,线程是不会被中断的,需我们手动在catch中重新调用interrupt()方法

*处于死锁状态的线程是不会理会中断的

三、对Java里的线程再多一点点认识

线程的生命周期

1.线程常用方法和线程的状态

深入理解run()和start()

run()方法为线程执行业务的方法,执行结束后死亡

start()线程进入一个就绪状态(可执行状态),每个线程仅可调用一次,等待时间片轮转,当cpu分配时间后可进入运行状态

wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)

notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程

wait(long timeout)让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)

Java线程中的Thread.yield( )方法,译为线程让步。顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行,注意是让自己或者其他线程运行,并不是单纯的让给其他线程。

yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行

yield() 的作用仅是让出cpu ,并不会让出锁

join()方法,获取执行权,能够使线程顺序执行,从并行变为串行

setPriority()方法 ,设置优先级,java中优先级分为1-10个级别,默认为5,但优先级并不能够保证线程执行顺序,真正的顺序由cpu决定  

守护线程,与主线程共存亡的一个线程,服务于主线程,例如GC线程,new Thread都为用户线程(非守护线程)

setDaemon(boolean on) 设置守护线程,缺省为false,true为守护线程

*注:守护线程中finally不一定起作用,upu时间片分配到会执行,否则不会执行,完全由操作系统的调度决定

四、线程间的共享

1.synchronized 关键字(内置锁)

线程开始运行,拥有自己的栈空间,就如同一个脚本一样,按照既定的代码 一步一步地执行,直到终止。但是,每个运行中的线程,如果仅仅是孤立地运行, 那么没有一点儿价值,或者说价值很少,如果多个线程能够相互配合完成工作, 包括数据之间的共享,协同处理事情。这将会带来巨大的价值。 Java 支持多个线程同时访问一个对象或者对象的成员变量,关键字 synchronized 可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线 程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量 访问的可见性和排他性,又称为内置锁机制。

对象锁和类锁: 对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态 方法或者一个类的 class 对象上的。我们知道,类的对象实例可以有很多个,但 是每个类只有一个 class 对象,所以不同对象实例的对象锁是互不干扰的,但是 每个类只有一个类锁。 但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存 在的,类锁其实锁的是每个类的对应的 class 对象。类锁和对象锁之间也是互不 干扰的

用处和用法

(1)方法加锁(同步方法)、同步块

public synchronized void incCount2() { count++;}

public void incCount3() { 

 synchronized (this) { 

 count++; 

 }

}

(2)对象加锁

private Objectobj =new Object();//作为一个锁

public void incCount() { 

 synchronized (obj) { 

 count++; 

 }

}

(3)类加锁

public synchronized static void incCount4() { count++;}

*注:synchronized  锁对象必须保证锁的对象不能够发生变化

2.volatile  最 轻 量 的 同 步 机 制

volatile 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某 个变量的值,这新值对其他线程来说是立即可见的

volatile 不能保证数据在多个线程下同时写时的线程安全

volatile 最适用的场景:一个线程写,多个线程读

3.ThreadLocal辨析

(1)与 Synchonized的 比 较

ThreadLocal 和 Synchonized 都用于解决多线程并发访问。可是 ThreadLocal 与 synchronized 有本质的差别。synchronized 是利用锁的机制,使变量或代码块 在某一时该仅仅能被一个线程访问。而 ThreadLocal 为每个线程都提供了变量的 副本,使得每个线程在某一时间访问到的并非同一个对象,这样就隔离了多个线 程对数据的数据共享

Spring 的事务就借助了 ThreadLocal 类。 Spring 会从数据库连接池中获得一个 connection,然会把 connection 放进 ThreadLocal 中,也就和线程绑定了

只要从 ThreadLocal 中拿到 connection 进行操作。为何 Spring 的事务要借助 ThreadLocal 类? 以 JDBC 为例,正常的事务代码可能如下:

 dbc=newDataBaseConnection();//第 1 行 

Connectioncon=dbc.getConnection();//第 2 行

 con.setAutoCommit(false);////第 3 行 

con.executeUpdate(...);//第 4 行 

con.executeUpdate(...);//第 5 行 

con.executeUpdate(...);//第 6 行 

con.commit();////第 7 行 

上述代码,可以分成三个部分: 事务准备阶段:第 1~3 行 业务处理阶段:第 4~6 行 事务提交阶段:第 7 行 可以很明显的看到,不管我们开启事务还是执行具体的 sql 都需要一个具体 的数据库连接。 现在我们开发应用一般都采用三层结构,如果我们控制事务的代码都放在 DAO(DataAccessObject)对象中,在 DAO 对象的每个方法当中去打开事务和关闭 事务,当 Service 对象在调用 DAO 时,如果只调用一个 DAO,那我们这样实现则 效果不错,但往往我们的 Service 会调用一系列的 DAO 对数据库进行多次操作, 那么,这个时候我们就无法控制事务的边界了,因为实际应用当中,我们的Service 调用的 DAO 的个数是不确定的,可根据需求而变化,而且还可能出现 Service 调 用 Service 的情况

(2)ThreadLocal的 使 用

ThreadLocal 类接口很简单,只有 4 个方法,我们先来了解一下: 

• voidset(Objectvalue) 设置当前线程的线程局部变量的值。

 • publicObjectget() 该方法返回当前线程所对应的线程局部变量。

 • publicvoidremove() 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是 JDK 5.0 新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动 被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它 可以加快内存回收的速度。

 • protectedObjectinitialValue() 返回该线程局部变量的初始值,该方法是一个 protected 的方法,显然是为 了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第 1 次调用 get() 或 set(Object)时才执行,并且仅执行 1 次。ThreadLocal 中的缺省实现直接返回一 个 null。

publicfinalstaticThreadLocal<String>RESOURCE=new ThreadLocal<String>();RESOURCE代表一个能够存放String类型的ThreadLocal对象。 此时不论什么一个线程能够并发访问这个变量,对它进行写入、读取操作,都是 线程安全的。

以上内容仅代表个人学习的见解,希望可以为大家提供一个学习参考

发布了18 篇原创文章 · 获赞 4 · 访问量 140

猜你喜欢

转载自blog.csdn.net/weixin_42081445/article/details/104828948