Java 高并发编程课后总结,Java 程序员必备书籍
SynchronousQueue 没有数量限制。因为他根本不保持这些任务,而是直接交给线程池去执行。当任务数量超过最大线程数时会直接抛异常。
atomicinteger 常用方法
public final int getAndSet(int newValue) //给 AtomicInteger 设置 newValue 并返回加 oldValue
public final boolean compareAndSet(int expect, int update) //如果输入的值和期望值相等就 set 并返回 true/false
public final int getAndIncrement() //对 AtomicInteger 原子的加 1 并返回当前自增前的 value
public final int getAndDecrement() //对 AtomicInteger 原子的减 1 并返回自减之前的的 v
alue
public final int getAndAdd(int delta) //对 AtomicInteger 原子的加上 delta 值并返加之前的 value
public final int incrementAndGet() //对 AtomicInteger 原子的加 1 并返回加 1 后的值
public final int decrementAndGet() //对 AtomicInteger 原子的减 1 并返回减 1 后的值
public final int addAndGet(int delta) //给 AtomicInteger 原子的加上指定的 delta 值并返回加后的值
6、用过 threadlocal 吗?怎么用的?
早在 JDK 1.2 的版本中就提供 java.lang.ThreadLocal,ThreadLocal 为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。
ThreadLocal 很容易让人望文生义,想当然地认为是一个“本地线程”。
其实,ThreadLocal 并不是一个 Thread,而是 Thread 的局部变量,也许把它命名为 ThreadLocalVariable 更容易让人理解一些。
ThreadLocal 为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
ThreadLocal 是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。
class ConnectionManager {
private static Connection connect = null;
public static Connection openConnection() {
if(connect == null){
connect = DriverManager.getConnection();
}
return connect;
}
public static void closeConnection() {
if(connect!=null)
connect.close();
}
}
假设有这样一个数据库链接管理类,这段代码在单线程中使用是没有任何问题的,但是如果在多线程中使用呢?很显然,在多线程中使用会存在线程安全问题:第一,这里面的 2 个方法都没有进行同步,很可能在 openConnection 方法中会多次创建 connect;第二,由于 connect 是共享变量,那么必然在调用 connect 的地方需要使用到同步来保障线程安全,因为很可能一个线程在使用 connect 进行数据库操作,而另外一个线程调用 closeConnection 关闭链接。
所以出于线程安全的考虑,必须将这段代码的两个方法进行同步处理,并且在调用 connect 的地方需要进行同步处理。
这样将会大大影响程序执行效率,因为一个线程在使用 connect 进行数据库操作的时候,其他线程只有等待。
那么大家来仔细分析一下这个问题,这地方到底需不需要将 connect 变量进行共享?事实上,是不需要的。假如每个线程中都有一个 connect 变量,各个线程之间对 connect 变量的访问实际上是没有依赖关系的,即一个线程不需要关心其他线程是否对这个 connect 进行了修改的。
到这里,可能会有朋友想到,既然不需要在线程之间共享这个变量,可以直接这样处理,在每个需要使用数据库连接的方法中具体使用时才创建数据库链接,然后在方法调用完毕再释放这个连接。比如下面这样:
class ConnectionManager {
private Connection connect = null;
public Connection openConnection() {
if(connect == null){
connect = DriverManager.getConnection();
}
return connect;
}
public void closeConnection() {
if(connect!=null)
connect.close();
}
}
class Dao{
public void insert() {
ConnectionManager connectionManager = new ConnectionManager();
Connection connection = connectionManager.openConnection();
//使用 connection 进行操作
connectionManager.closeConnection();
}
}
这样处理确实也没有任何问题,由于每次都是在方法内部创建的连接,那么线程之间自然不存在线程安全问题。但是这样会有一个致命的影响:导致服务器压力非常大,并且严重影响程序执行性能。由于在方法中需要频繁地开启和关闭数据库连接,这样不尽严重影响程序执行效率,还可能导致服务器压力巨大。
那么这种情况下使用 ThreadLocal 是再适合不过的了,因为 ThreadLocal 在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。
但是要注意,虽然 ThreadLocal 能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用 ThreadLocal 要大。
从上面的结构图,我们已经窥见 ThreadLocal 的核心机制:
每个 Thread 线程内部都有一个 Map。
Map 里面存储线程本地对象(key)和线程的变量副本(value)
但是,Thread 内部的 Map 是由 ThreadLocal 维护的,由 ThreadLocal 负责向 map 获取和设置线程的变量值。
所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。
ThreadLocal 类提供如下几个核心方法:
方法名 作用
public T get() get()方法用于获取当前线程的副本变量值
public void set(T value) set()方法用于保存当前线程的副本变量值
public void remove() remove()方法移除当前前程的副本变量值
8、程序、进程、线程的区别是什么? 举个现实的例子说明
程序(Program):是一个指令的集合。程序不能独立执行,只有被加载到内存中,系统为它分配资源后才能执行.
进程(Process):如上所述,一个执行中的程序称为进程。 进程是系统分配资源的独立单位,每个进程占有特定的地址空间。程序是进程的静态文本描述,进程是程序在系统内顺序执行的动态活动。
线程(Thread):是进程的“单一的连续控制流程“。 线程是 CPU 调度和分配的基本单位,是比进程更小的能独立运行的基本单位,也被称为轻量级的进程。线程不能独立存在,必须依附于某个进程。一个进程可以包括多个并行的线程,一个线程肯定属于一个进程。Java 虚拟机允许应用程序并发地执行多个线程。 举例:如一个车间是一个程序,一个正在进行生产任务的车间是一个进程,车间内每个从事不同工作的工人是一个线程。
下面的代码,实际上有几个线程在运行:
public static void main(String[] argc) throws Exception {
Runnable r = new Thread6();
Thread t = new Thread(r, “Name test”);
t.start();
}
两个:线程 t 和 main()方法(主线程)。
9、线程的状态
线程通常有五种状态,创建,就绪,运行、阻塞和死亡状态。
阻塞的情况又分为三种:
等待阻塞:运行的线程执行 wait()方法,该线程会释放占用的所有资源,JVM 会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用 notify()或 notifyAll()方法才能被唤醒,wait 是 object 类的方法
同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入“锁池”中。
其他阻塞:运行的线程执行 sleep()或 join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。sleep 是 Thread 类的方法。
新建状态(New):新创建了一个线程对象。
就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的 start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取 CPU 的使用权。
运行状态(Running):就绪状态的线程获取了 CPU,执行程序代码。
阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃 CPU 使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
死亡状态(Dead):线程执行完了或者因异常退出了 run()方法,该线程结束生命周期。
10、说说:sleep、yield、join、wait 方法的区别
sleep()方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态,该方法既可以让其他同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。但是 sleep()方法不会释放“锁标志”,也就是说如果有 synchronized 同步块,其他线程仍然不能访问共享数据。 作用于线程
Thread.sleep()方法用来暂停线程的执行,将 CPU 放给线程调度器。
Thread.sleep()方法是一个静态方法,它暂停的是当前执行的线程。
Java 有两种 sleep 方法,一个只有一个毫秒参数,另一个有毫秒和纳秒两个参数。
与 wait 方法不同,sleep 方法不会释放锁
如果其他的线程中断了一个休眠的线程,sleep 方法会抛出 Interrupted Exception。
休眠的线程在唤醒之后不保证能获取到 CPU,它会先进入就绪态,与其他线程竞争 CPU。
有一个易错的地方,当调用 t.sleep()的时候,会暂停线程 t。这是不对的,因为 Thread.sleep 是一个静态方法,它会使当前线程而不是线程 t 进入休眠状态。
join(): 当前线程等待,调用此方法的线程执行结束再继续执行。如:在 main 方法中调用 t.join(),那 main 方法在此时进入阻塞状态,一直等 t 线程执行完,main 方法再恢复到就绪状态,准备继续执行。
join 方法必须在线程 start 方法调用之后调用才有意义。这个也很容易理解:如果一个线程都没有 start,那它也就无法同步了。作用于线程
11、wait、notify、notifyAll 是在 Thread 类中定义的方法吗?作用分别是什么?
wait(),notify(),notifyAll()不属于 Thread 类,而是属于 Object 类,也就是说每个对象都有 wait(),notify(),notifyAll()的功能。
因为每个对像都有锁,锁是每个对像的基础,而 wait(),notify(),notifyAll()都是跟锁有关的方法。
评论