项目实际需求:DB交互使用多线程实现
多线程编程基础:1.5 :( (假设总分10)
计划一个半月从头学习梳理Java多线程编程基础以及Oracle数据库交互相关的多线程实现
学习如何通过代码去验证这些结论
Oracle Tutorial Sample: JDBC and Multi-Threading
Some Tips
Multi-threading will improve your performance but there are a couple of things you need to know:
- Each thread needs its own JDBC connection. Connections can't be shared between threads because each connection is also a transaction.
- Upload the data in chunks and
commit
once in a while to avoid accumulating huge rollback/undo tables. - Cut tasks into several work units where each unit does one job.
To elaborate the last point: Currently, you have a task that reads a file, parses it, opens a JDBC connection, does some calculations, sends the data to the database, etc.
What you should do:
- One (!) thread to read the file and create "jobs" out of it. Each job should contains a small, but not too small "unit of work". Push those into a queue
- The next thread(s) wait(s) for jobs in the queue and do the calculations. This can happen while the threads in step #1 wait for the slow hard disk to return the new lines of data. The result of this conversion step goes into the next queue
- One or more threads to upload the data via JDBC.
The first and the last threads are pretty slow because they are I/O bound (hard disks are slow and network connections are even worse). Plus inserting data in a database is a very complex task (allocating space, updating indexes, checking foreign keys)
Using different worker threads gives you lots of advantages:
- It's easy to test each thread separately. Since they don't share data, you need no synchronization. The queues will do that for you
- You can quickly change the number of threads for each step to tweak performance
Other Tech-Blogs
java多线程编程
在java中,一个普通的类要成为一个可被java多线程机制调用的 "线程类" 有两种方式;继承Thread 或者 实现Runnable 或 Callable 接口。
问题:
1. 通过实现 Runnable 接口来创建线程
实例中TestThread类实现如下,则输出满足预期。
1 public class TestThread { 2 3 public static void main(String args[]) { 4 RunnableDemo R1 = new RunnableDemo( "Thread-1"); 5 R1.start(); 6 7 RunnableDemo R2 = new RunnableDemo( "Thread-2"); 8 R2.start(); 9 } 10 }
输出如下:
Creating Thread-1 Starting Thread-1 Creating Thread-2 Starting Thread-2 Running Thread-2 Running Thread-1 Thread: Thread-2, 4 Thread: Thread-1, 4 Thread: Thread-2, 3 Thread: Thread-1, 3 Thread: Thread-1, 2 Thread: Thread-2, 2 Thread: Thread-1, 1 Thread: Thread-2, 1 Thread: Thread-2 exiting. Thread: Thread-1 exiting.
但若实现为如下,则Thread只Run一次后无法继续循环和正常退出。
1 public class ThreadTest { 2 private RunnableDemo R1 = new RunnableDemo( "Thread - 1" ); 3 private RunnableDemo R2 = new RunnableDemo( "Thread - 2" ); 4 5 @Test 6 public void runnableMultiThread(){ 7 R1.start(); 8 R2.start(); 9 } 10 }
输出如下:
Creating Thread - 1 Creating Thread - 2 Starting Thread - 1 Starting Thread - 2 Running Thread - 1 Thread: Thread - 1, 4 Running Thread - 2 Thread: Thread - 2, 4
原因 =====> Junit单元测试不支持多线程
JunitCore类的main函数如下:
public static transient void main(String args[]){
runMainAndExit(new RealSystem(), args);
}
private static transient void runMainAndExit(JUnitSystem system, String args[]){
Result result = (new JUnitCore()).runMain(system, args);
System.exit(result.wasSuccessful() ? 0 : 1);
}
很明显,其调用了System.exit函数,将导致线程结束,而多线程甚至都来不及运行。
Junit单元测试多线程的问题
在TestRunner源码中可以看出,如果是单线程,当测试主线程执行结束后,不管子线程是否结束,都会回调TestResult的wasSuccessful方法,然后判断结果是成功还是失败,最后调用相应的System.exit()方法。大家都知道这个方法是用来结束当前正在运行中的java虚拟机,jvm都自身难保了,所以子线程也就对不住你咧...
解决办法:
1 简单粗暴地让主线程休眠一段时间,然后让子线程能够运行结束。但是这个方法的弊端是,你不知道子线程的运行时间,所以需要看脸=_=
Thread.sleep();
2 使用CountDownLatch工具类,让主线程阻塞,直到子线程运行结束或者阻塞超时,这个方法要比第一个方法好点。
countDownLatch.await(5, TimeUnit.MINUTES);
Junit多线程测试
https://www.ibm.com/developerworks/cn/java/j-lo-test-multithread/
多线程与IO专题
Java多线程1:进程与线程概述
两种多线程实现方式的对比
看一下Thread类的API:
其实Thread类也是实现的Runnable接口。两种实现方式对比的关键就在于extends和implements的对比,当然是后者好。
因为第一,继承只能单继承,实现可以多实现;第二,实现的方式对比继承的方式,也有利于减小程序之间的耦合。
因此,多线程的实现几乎都是使用的Runnable接口的方式。
Java多线程2:Thread中的实例方法
1、this.XXX() 这种调用方式表示的线程是线程实例本身
2、Thread.currentThread.XXX()或Thread.XXX() 这种调用方式表示的线程是正在执行Thread.currentThread.XXX()所在代码块的线程
Thread类中的实例方法
- start() 启动线程但并不是开始执行线程。调用start()方法的顺序不代表线程启动的顺序,线程启动顺序具有不确定性(异步)。
- run() 只有run()而不调用start()启动线程是没有任何意义的。需要注意的是,必须调用线程类的start方法才是启动线程,调用run方法只是相当于调用一个普通的方法。
- isAlive() 从start()开始至线程run()方法执行完毕之间为true。
- getId() 在一个Java应用中,有一个long型的全局唯一的线程ID生成器threadSeqNumber,每new出来一个线程都会把这个自增一次,并赋予线程的tid属性
- getName() new一个线程的时候,可以指定该线程的名字;也可以不指定 - 那么Thread中有一个int型全局唯一的线程初始号生成器threadInitNum,Java先把threadInitNum自增,然后以"Thread-threadInitNum"的方式来命名新生成的线程。
- getPriority() / setPriority(int newPriority) CPU会尽量将执行资源让给优先级比较高的线程。线程默认优先级为5,如果不手动指定,那么线程优先级具有继承性,比如线程A启动线程B,那么线程B的优先级和线程A的优先级相同。
- isDeaMon() / setDaeMon(boolean on) 守护线程是一种特殊的线程,它的作用是为其他线程的运行提供便利的服务,最典型的应用便是GC线程。如果进程中不存在非守护线程了,那么守护线程自动销毁。setDaemon(true)必须在线程start()之前。
- isInterrupted() / interrupt() 在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞状态。换句话说,没有被阻塞的线程,调用interrupt()方法是不起作用的。
- join()
Java多线程8:wait()和notify()/notifyAll()
场景:多个线程间的消息通信
问题:如果可以把这些资源是否占用的轮询时间释放出来,给别的线程用,就好了。
Object对象中的三个方法wait()、notify()、notifyAll()。wait()使线程停止运行,notify()使停止运行的线程继续运行。
- wait() 使当前执行代码的线程进行等待,将当前线程置入"预执行队列"中,并且wait()所在的代码处停止执行,直到接到通知或被中断。在调用wait()之前,线程必须获得该对象的锁,因此只能在同步方法/同步代码块中调用wait()方法。可以使调用该线程的方法释放共享资源的锁,然后从运行状态退出,进入等待队列,直到再次被唤醒。
- notify() 如果有多个线程等待,那么线程规划器随机挑选出一个wait的线程,对其发出通知notify(),并使它等待获取该对象的对象锁。注意"等待获取该对象的对象锁",这意味着,即使收到了通知,wait的线程也不会马上获取对象锁,必须等待notify()方法的线程释放锁才可以。和wait()一样,notify()也要在同步方法/同步代码块中调用。可以随机唤醒等待队列中等待同一共享资源的一个线程,并使得该线程退出等待状态,进入可运行状态。
- notifyAll() 可以使所有正在等待队列中等待同一共享资源的全部线程从等待状态退出,进入可运行状态
- 如果wait()方法和notify()/notifyAll()方法不在同步方法/同步代码块中被调用,那么虚拟机会抛出java.lang.IllegalMonitorStateException,注意一下。
- wait是会释放锁的,而sleep是不会释放锁的!
- notify是先唤醒第一个被阻塞的线程<队列实现>; 而notifyAll则相反,它先唤醒最后一个被阻塞的线程<栈实现>!
- interrupt() 在线程阻塞的时候给线程一个中断标识,表示该线程中断。wait()就是"阻塞的一种场景"。
Java多线程3:Thread中的静态方法
Thread类中的静态方法表示操作的线程是"正在执行静态方法所在的代码块的线程",这样就能对CPU当前正在运行的线程进行操作。
线程类的构造方法、静态块是被main线程调用的,而线程类的run()方法才是应用线程自己调用的。
- currentThread() 返回对当前正在执行线程对象的引用。"this.XXX()"和"Thread.currentThread().XXX()"的区别,当前执行的Thread未必就是Thread本身。构造函数中Thread.currentThread()返回的是main,而this返回的是该类对象;若此类继承了Thread,则其为此线程类对象;线程类中的run()方法中Thread.currentThread()和this等价,均为返回此线程类对象。
- sleep(long millis) 在指定的毫秒内让当前"正在执行的线程"休眠(暂停执行)。这个"正在执行的线程"是关键,指的是Thread.currentThread()返回的线程。根据JDK API的说法,"该线程不丢失任何监视器的所属权",简单说就是sleep代码上下文如果被加锁了,锁依然在,但是CPU资源会让出给其他线程。
- yield() 暂停当前执行的线程对象,并执行其他线程。这个暂停是会放弃CPU资源的,并且放弃CPU的时间长短不确定。
- interrupted() 测试当前线程是否已经中断,执行后会将状态标识清除为false。换句话说,如果连续两次调用该方法,那么返回的必定是false。
Java多线程4:synchronized锁机制
- 脏读:在多线程中,难免会出现在多个线程中对同一个对象的实例变量进行并发访问的情况,如果不做正确的同步处理,那么产生的后果就是"脏读",也就是取到的数据其实是被更改过的。
- 关键字synchronized:取得的锁都是对象锁,而不是把一段代码或方法(函数)当作锁,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁,其他线程都只能呈等待状态。但是这有个前提:既然锁叫做对象锁,那么势必和对象相关,所以多个线程访问的必须是同一个对象。如果多个线程访问的是多个对象,那么Java虚拟机就会创建多个锁。既然多个线程持有的是不同的锁,自然不会受到"等待释放锁"这一行为的制约。
- Thread.holdsLock(this)可以判断当前线程是否持有某个对象锁。
- A线程持有Object对象的Lock锁,B线程可以以异步方式调用Object对象中的非synchronized类型的方法。
- A线程持有Object对象的Lock锁,B线程如果在这时调用Object对象中的synchronized类型的方法则需要等待,也就是同步。
- synchronized锁重入:当一个线程得到一个对象锁后,再次请求此对象锁时时可以再次得到该对象的锁的。也支持在父子类继承的环境中。实例即同步方法中调用同一对象的其它同步方法。
- 当一个线程执行的代码出现异常时,其所持有的锁会自动释放。
Java多线程5:synchronized锁方法块
- 从执行效率的角度考虑,有时候我们未必要把整个方法都加上synchronized,而是可以采取synchronized块的方式,对会引起线程安全问题的那一部分代码进行synchronized就可以了。
- 当A线程访问对象的synchronized代码块的时候,B线程依然可以访问对象方法中其余非synchronized块的部分,第一部分的执行结果证明了这一点
- 当A线程进入对象的synchronized代码块的时候,B线程如果要访问这段synchronized块,那么访问将会被阻塞,第二部分的执行结果证明了这一点
- synchronized(this)同步代码块获得的是一个对象锁,即synchronized块锁定的是整个对象。
- Java还支持对"任意对象"作为对象监视器来实现同步的功能。这个"任意对象"大多数是实例变量及方法的参数。多个线程持有"对象监视器"为同一个对象的前提下,同一时间只能有一个线程可以执行synchronized(非this对象x)代码块中的代码。
- synchronized(非this)对象锁具有一定的优点:如果在一个类中有很多synchronized方法,这时虽然能实现同步,但会受到阻塞,从而影响效率。但如果同步代码块锁的是非this对象,则synchronized(非this对象x)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,大大提高了运行效率。
- synchronized(非this对象x),这个对象如果是实例变量的话,指的是对象的引用,只要对象的引用不变,即使改变了对象的属性,运行结果依然是同步的。
-
synchronized(非this对象x)格式的写法是将x对象本身作为对象监视器,有三个结论得出:
1、当多个线程同时执行synchronized(x){}同步代码块时呈同步效果
2、当其他线程执行x对象中的synchronized同步方法时呈同步效果
3、当其他线程执行x对象方法中的synchronized(this)代码块时也呈同步效果
Java多线程6:synchronized锁定类方法、volatile关键字及其他
同步静态方法:synchronized还可以应用在静态方法上,如果这么写,则代表的是对当前.java文件对应的Class类加锁。
Java中的多线程你只要看这一篇就够了
1.ThreadLocal类
用处:保存线程的独立变量。对一个线程类(继承自Thread)
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。常用于用户登录控制,如记录session信息。
实现:每个Thread都持有一个TreadLocalMap类型的变量(该类是一个轻量级的Map,功能与map一样,区别是桶里放的是entry而不是entry的链表。功能还是一个map。)以本身为key,以目标为value。
主要方法是get()和set(T a),set之后在map里维护一个threadLocal -> a,get时将a返回。ThreadLocal是一个特殊的容器。
多线程的一些基本概念
多任务 / 并行 / 并发
进程 / 线程
线程的生命周期 / 状态转换
线程优先级
线程同步
线程间通信
线程死锁
线程控制:挂起、停止和恢复
多线程上下文切换开销 v.s. 多线程并发提升效率
线程池