线程调度分为两种:
- 分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
- 抢占调度:优先级高的线程优先使用CPU,优先级相同则随机调度。【JAVA采用该调度方式】
在java程序中,每次程序运行至少启动2个线程,一个是main,一个是GC。
线程状态其中:
- sleep()不会释放锁,它不需要占用锁。
- wait()会释放锁,但调用的前提是当前线程占有锁(代码在synchronized中)。
- 都可以被interrupted()中断。
一、创建线程
创建线程的方式是有多种,但是创建线程只有一种途径new Thread()。
【1】继承Thread类实现多线程
public class MyThread extends Thread {
public MyThread() {
}
public void run() {
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread()+":"+i);
}
}
public static void main(String[] args) {
MyThread mThread1=new MyThread();
MyThread mThread2=new MyThread();
MyThread myThread3=new MyThread();
mThread1.start();
mThread2.start();
myThread3.start();
}
}
【2】实现Runnable()接口
/*Runnable的优势:
*1.适合多个相同的程序代码的线程去共享同一个资源。
*2.避免单继承的局限性。
*3.实现解耦,代码可以被多个线程共享。
*4.线程池只能实现Runnable/Callable类线程,不能实现继承Thread的类。
*/
public class MyThread implements Runnable{
public static int count=20;
public void run() {
while(count>0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-当前剩余票数:"+count--);
}
}
public static void main(String[] args) {
MyThread Thread1=new MyThread();
Thread mThread1=new Thread(Thread1,"线程1");
Thread mThread2=new Thread(Thread1,"线程2");
Thread mThread3=new Thread(Thread1,"线程3");
mThread1.start();
mThread2.start();
myThread3.start();
}
}
综合示例:subThread
package org.example;
public class subThread extends Thread {
public subThread(){
System.out.println("构造方法中:Thread.currentThread()|||||"+Thread.currentThread().getName());
System.out.println("构造方法中:this.getName()|||||"+this.getName());
}
public void run(){
System.out.println("run()方法中Thread.currentThread()-------"+Thread.currentThread().getName());
System.out.println("run()方法中this.getName()"+this.getName());
}
}
App:
package org.example;
public class App {
public static void main(String[] args) {
subThread st= new subThread();
st.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t = new Thread(st);
t.start();
}
}
结果
以上2种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。
如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。
Java 1.5开始,提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。
【3】Callable、Future接口
public class App {
public static void main(String[] args) throws InterruptedException, ExecutionException {
long start = System.currentTimeMillis();
// 等凉菜
Callable ca1 = new Callable() {
@Override
public String call() throws Exception {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "凉菜准备完毕";
}
};
FutureTask<String> ft1 = new FutureTask<String>(ca1);
new Thread(ft1).start();
// 等包子 -- 必须要等待返回的结果,所以要调用join方法
Callable ca2 = new Callable() {
@Override
public Object call() throws Exception {
try {
Thread.sleep(1000 * 3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "包子准备完毕";
}
};
FutureTask<String> ft2 = new FutureTask<String>(ca2);
new Thread(ft2).start();
System.out.println(ft1.get());
System.out.println(ft2.get());
long end = System.currentTimeMillis();
System.out.println("准备完毕时间:" + (end - start));
}
【4】 线程池启动
通过Executor 的工具类可以创建三种类型的普通线程池
- FixThreadPool(int n); 固定大小的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) {
ExecutorService ex=Executors.newFixedThreadPool(5);
//使用线程池中的线程,起了5个线程。
for(int i=0;i<5;i++) {
ex.submit(new Runnable() {
public void run() {
for(int j=0;j<10;j++) {
System.out.println(Thread.currentThread().getName()+j);
}
}
});
}
//正常不建议销毁。
ex.shutdown();
}
}
- SingleThreadPoolExecutor :单线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) {
ExecutorService ex=Executors.newSingleThreadExecutor();
for(int i=0;i<5;i++) {
ex.submit(new Runnable() {
public void run() {
for(int j=0;j<10;j++) {
System.out.println(Thread.currentThread().getName()+j);
}
}
});
}
ex.shutdown();
}
}
- CashedThreadPool(); 缓存线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) {
ExecutorService ex=Executors.newCachedThreadPool();
for(int i=0;i<5;i++) {
ex.submit(new Runnable() {
public void run() {
for(int j=0;j<10;j++) {
System.out.println(Thread.currentThread().getName()+j);
}
}
});
}
ex.shutdown();
}
}
二、线程安全
多线程访问共享数据,导致共享数据错乱的问题,便是线程安全问题。
2.1线程不安全的实例:
主方法:
package com.itheima.demo06.ThreadSafe;
/*
模拟卖票案例
创建3个线程,同时开启,对共享的票进行出售
*/
public class Demo01Ticket {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
//共享的票:run的ticket。
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
//调用start方法开启多线程
t0.start();
t1.start();
t2.start();
}
}
线程方法:
/*
实现卖票案例
*/
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while(true){
//先判断票是否存在
if(ticket>0){
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
2.2线程安全的实例:同步代码块
/*
卖票案例出现了线程安全问题
卖出了不存在的票和重复的票
解决线程安全问题的一种方案:使用同步代码块
格式:
synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:
1.通过代码块中的锁对象,可以使用任意的对象
2.但是必须保证多个线程使用的锁对象是同一个
3.锁对象作用:
把同步代码块锁住,只让一个线程在同步代码块中执行
*/
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
//创建一个锁对象:钥匙对象更加形象。
Object obj = new Object();
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while(true){
//同步代码块
synchronized (obj){
//先判断票是否存在
if(ticket>0){
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
}
2.3线程安全的实例:同步方法
package com.itheima.demo08.Synchronized;
/*
卖票案例出现了线程安全问题
卖出了不存在的票和重复的票
解决线程安全问题的二种方案:使用同步方法
使用步骤:
1.把访问了共享数据的代码抽取出来,放到一个方法中
2.在方法上添加synchronized修饰符
格式:定义方法的格式
修饰符 synchronized 返回值类型 方法名(参数列表){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
*/
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private static int ticket = 100;
//设置线程任务:卖票
@Override
public void run() {
System.out.println("this:"+this);//this:com.itheima.demo08.Synchronized.RunnableImpl@58ceff1
//使用死循环,让卖票操作重复执行
while(true){
payTicketStatic();
}
}
/*
静态的同步方法,锁对象是谁? 不能是this。
this是创建对象之后产生的,静态方法优先于对象
静态方法的锁对象是本类的class属性-->class文件对象(反射)
*/
public static /*synchronized*/ void payTicketStatic(){
synchronized (RunnableImpl.class){
//先判断票是否存在
if(ticket>0){
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
/*
定义一个同步方法,同步方法也会把方法内部的代码锁住,只让一个线程执行。
同步方法的锁对象是谁? 就是实现类对象 new RunnableImpl()也是就是this.
注释掉synchronized是为了在方法内增加同步代码块验证锁对象。
实际要放开,并去掉同步代码块的限制。
*/
public /*synchronized*/ void payTicket(){
synchronized (this){
//先判断票是否存在
if(ticket>0){
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
2.4线程安全的实例:Lock锁:手动上锁,锁由程序员控制。
package com.itheima.demo09.Lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
卖票案例出现了线程安全问题
卖出了不存在的票和重复的票
解决线程安全问题的三种方案:使用Lock锁
java.util.concurrent.locks.Lock接口
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
Lock接口中的方法:
void lock()获取锁。
void unlock() 释放锁。
java.util.concurrent.locks.ReentrantLock implements Lock接口
使用步骤:
1.在成员位置创建一个ReentrantLock对象
2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
*/
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
//1.在成员位置创建一个ReentrantLock对象。
//跟第一种同步代码块中的类似
Lock l = new ReentrantLock();
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while(true){
//2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
l.lock();
//先判断票是否存在
if(ticket>0){
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
l.unlock();//无论程序是否异常,都会把锁释放
}
}
}
}
}
三、线程通信
- 卖票程序: 涉及共享数据,多线程执行动作相同,涉及线程安全,不涉及线程通信。
- 线程waitting状态被唤醒(notify):不涉及共享数据,多线程执行动作不同,只涉及线程通信。
- 生产者和消费者:涉及共享数据,多线程执行动作不同,涉及线程安全和通信。
0.简易的线程通信(不涉及线程安全):
- wait()和notify()必须由同一个锁对象调用。因为对应的锁对象可以通过notify()唤醒使用同一个锁对象的waitting的状态下的另一个线程。
- wait()和notify()是属于Object类的方法。
- wait()和notify()必须在同步代码块或者同步方法中使用(Lock方式除外)。因为必须要通过锁对象调用这两个方法。
package com.itheima.demo10.WaitAndNotify;
/*
进入到TimeWaiting(计时等待)有两种方式
1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态
唤醒的方法:
void notify() 唤醒在此对象监视器上等待的单个线程。
void notifyAll() 唤醒在此对象监视器上等待的所有线程。
*/
public class Demo02WaitAndNotify {
public static void main(String[] args) {
//创建锁对象,保证唯一
Object obj = new Object();
// 创建一个顾客线程(消费者)
new Thread(){
@Override
public void run() {
//一直等着买包子
while(true){
//保证等待和唤醒的线程只能有一个执行,需要使用同步技术
synchronized (obj){
System.out.println("顾客1告知老板要的包子的种类和数量");
//调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
try {
// obj.wait(5000),不需要唤醒,与sleep()一样。
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后执行的代码
System.out.println("包子已经做好了,顾客1开吃!");
System.out.println("---------------------------------------");
}
}
}
}.start();
//创建一个老板线程(生产者)
new Thread(){
@Override
public void run() {
//一直做包子
while (true){
//花了5秒做包子
try {
Thread.sleep(5000);//花5秒钟做包子
} catch (InterruptedException e) {
e.printStackTrace();
}
//保证等待和唤醒的线程只能有一个执行,需要使用同步技术
synchronized (obj){
System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了");
//做好包子之后,调用notify方法,唤醒顾客吃包子
//obj.notify();//如果有多个等待线程,随机唤醒一个
obj.notifyAll();//唤醒所有等待的线程
}
}
}
}.start();
}
}
1.复杂的线程通信(涉及线程安全):同步代码块方式
1.共享数据:包子
public class BaoZi {
String pier ;
String xianer ;
boolean flag = false ;//包子资源 是否存在 包子资源状态 }
2.生产者:包子铺
public class BaoZiPu extends Thread {
private BaoZi bz;
public BaoZiPu(String name, BaoZi bz) {
super(name);
this.bz = bz;
}
@Override
public void run() {
int count = 0;
//造包子
while (true) {
//同步
synchronized (bz) {
if (bz.flag == true) {
//包子资源 存在
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 没有包子 造包子
System.out.println("包子铺开始做包子");
if (count % 2 == 0) {
// 冰皮 五仁
bz.pier = "冰皮";
bz.xianer = "五仁";
} else {
// 薄皮 牛肉大葱
bz.pier = "薄皮";
bz.xianer = "牛肉大葱";
}
count++;
bz.flag = true;
System.out.println("包子造好了:" + bz.pier + bz.xianer);
System.out.println("吃货来吃吧");
// 唤醒等待线程 (吃货)
bz.notify();
}
}
}
}
3.消费者:顾客
public class ChiHuo extends Thread {
private BaoZi bz;
public ChiHuo(String name, BaoZi bz) {
super(name);
}
this.bz =bz;
}
@Override
public void run() {
while (true) {
synchronized (bz) {
if (bz.flag == false) {
//没包子
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("吃货正在吃" + bz.pier + bz.xianer + "包子");
bz.flag = false;
bz.notify();
}
}
}
}
4.测试类:
public class Demo {
public static void main(String[] args) {
//等待唤醒案例
BaoZi bz = new BaoZi();
ChiHuo ch = new ChiHuo("吃货", bz);
BaoZiPu bzp = new BaoZiPu("包子铺", bz);
ch.start();
bzp.start();
}
}
2.复杂的线程通信(涉及线程安全):Lock方式
Lock的特性:
- 1.Lock不是Java语言内置的;
- 2.synchronized是在JVM层面上实现的,如果代码执行出现异常,JVM会自动释放锁,但是Lock不行,要保证锁一定会被释放,就必须将unLock放到finally{}中(手动释放);
- 3.在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetarntLock,但是在很激烈的情况下,synchronized的性能会下降几十倍;
- 4.ReentrantLock增加了锁:
4.1. void lock(); // 无条件的锁;
4.2. void lockInterruptibly throws InterruptedException;//可中断的锁;
4.3. boolean tryLock();//如果获取了锁立即返回true,如果别的线程正持有,立即返回false,不会等待;
4.4. boolean tryLock(long timeout,TimeUnit unit);//如果获取了锁立即返回true,如果别的线程正持有锁,会等待参数给的时间,在等待的过程中,如果获取锁,则返回true,如果等待超时,返回false;
Condition的特性:
- 1.Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法。不同的是,Object中的这些方法是和同步锁捆绑使用的;而Condition是需要与互斥锁/共享锁捆绑使用的。
- 2.Condition它更强大的地方在于:能够更加精细的控制多线程的休眠与唤醒。对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition。
摘自:使用Lock来实现生产者和消费者问题 和线程高级篇-Lock锁和Condition条件
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用Lock来实现生产者和消费者问题
*/
public class ProducerConsumer {
public static void main(String[] args) {
Basket b = new Basket();
Product p = new Product(b);
Consumer c = new Consumer(b);
Consumer c1 = new Consumer(b);
new Thread(p).start();
new Thread(c).start();
new Thread(c1).start();
}
}
//馒头
class ManTou{
int id;
public ManTou(int id) {
this.id = id;
}
@Override
public String toString() {
return "ManTou"+id;
}
}
//装馒头的篮子
class Basket{
int max = 6;
LinkedList<ManTou> manTous = new LinkedList<ManTou>();
Lock lock = new ReentrantLock(); //锁对象
Condition full = lock.newCondition(); //用来监控篮子是否满的Condition实例
Condition empty = lock.newCondition(); //用来监控篮子是否空的Condition实例
//往篮子里面放馒头
public void push(ManTou m){
lock.lock();
try {
while(max == manTous.size()){
System.out.println("篮子是满的,待会儿再生产...");
full.await();
}
manTous.add(m);
empty.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
//往篮子里面取馒头
public ManTou pop(){
ManTou m = null;
lock.lock();
try {
while(manTous.size() == 0){
System.out.println("篮子是空的,待会儿再吃...");
empty.await();
}
m = manTous.removeFirst();
full.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
return m;
}
}
}
//生产者
class Product implements Runnable{
Basket basket;
public Product(Basket basket) {
this.basket = basket;
}
public void run() {
for (int i = 0; i < 40; i++) {
ManTou m = new ManTou(i);
basket.push(m);
System.out.println("生产了"+m);
try {
Thread.sleep((int)(Math.random()*2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者
class Consumer implements Runnable{
Basket basket;
public Consumer(Basket basket) {
this.basket = basket;
}
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep((int)(Math.random()*2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
ManTou m = basket.pop();
System.out.println("消费了"+m);
}
}
}
四、线程封闭ThreadLocal
多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal
是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。没有线程通信。
ThreadLocal的原理
JDK早期版本 JDK现在版本image.png 强引用
弱引用
解析
1.没有使用ThreadLocal的线程不安全场景
资源类:Demo01
public class Demo01 {
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
使用资源:RunnableImpl
public class RunnableImpl implements Runnable{
Demo01 demo01 = new Demo01();
@Override
public void run() {
demo01.setContent(Thread.currentThread().getName()+"的数据");
System.out.println(Thread.currentThread().getName()+"-->"+demo01.getContent());
}
}
测试类:Client
package com.myspring.temp;
public class Client {
public static void main(String[] args) {
RunnableImpl run = new RunnableImpl();
for (int i = 0; i < 1000; i++) {
Thread thread = new Thread(run);
thread.setName("线程"+i);
thread.start();
}
}
}
结果:
结果.png2.使用ThreadLocal的线程安全场景
资源类:Demo01
package com.myspring.temp;
public class Demo01 {
ThreadLocal<String> tl = new ThreadLocal();
private String content;
public String getContent() {
return tl.get();
}
public void setContent(String content) {
tl.set(content);
}
}
结果:
结果.png卖票示例: 共100张票。
1- 使用锁的方式:多个人卖票,从同一个票池取票来卖,同一个时间点只能一个人取票。
2- 使用ThreadLocal:多个人卖票,从多个票池取票来卖,同一个时间点多人同时取票。
加锁也可以解决上述问题,但是存在以下问题:
- 降低效率。
- 使得原本程序变成异步程序(非并发程序)。
转账事务示例1-无事务
C3P0配置文件:
<c3p0-config>
<!-- 使用默认的配置读取连接池对象 -->
<default-config>
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>
<property name="user">root</property>
<property name="password">1234</property>
<!-- 连接池参数 -->
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">10</property>
<property name="checkoutTimeout">3000</property>
</default-config>
</c3p0-config>
工具类 : JdbcUtils
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class JdbcUtils {
// c3p0 数据库连接池对象属性
private static final ComboPooledDataSource ds = new ComboPooledDataSource();
// 获取连接
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//释放资源
public static void release(AutoCloseable... ios){
for (AutoCloseable io : ios) {
if(io != null){
try {
io.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public static void commitAndClose(Connection conn) {
try {
if(conn != null){
//提交事务
conn.commit();
//释放连接
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void rollbackAndClose(Connection conn) {
try {
if(conn != null){
//回滚事务
conn.rollback();
//释放连接
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
AccountService:
import com.itheima.transfer.dao.AccountDao;
import java.sql.SQLException;
public class AccountService {
public boolean transfer(String outUser, String inUser, int money) {
AccountDao ad = new AccountDao();
try {
// 转出
ad.out(outUser, money);
int a= 1/0;
// 转入
ad.in(inUser, money);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
}
AccountDao:
import com.itheima.transfer.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class AccountDao {
public void out(String outUser, int money) throws SQLException {
String sql = "update account set money = money - ? where name = ?";
Connection conn = JdbcUtils.getConnection();
PreparedStatement pstm = conn.prepareStatement(sql);
pstm.setInt(1,money);
pstm.setString(2,outUser);
pstm.executeUpdate();
JdbcUtils.release(pstm,conn);
}
public void in(String inUser, int money) throws SQLException {
String sql = "update account set money = money + ? where name = ?";
Connection conn = JdbcUtils.getConnection();
PreparedStatement pstm = conn.prepareStatement(sql);
pstm.setInt(1,money);
pstm.setString(2,inUser);
pstm.executeUpdate();
JdbcUtils.release(pstm,conn);
}
}
当转出之后报错,因为没有事务会产生脏数据,因此引入事务管理。
转账事务示例2-有事务(传参数保证数据库连接为同一个)
AccountService 类:
import com.itheima.transfer.dao.AccountDao;
import com.itheima.transfer.utils.JdbcUtils;
import java.sql.Connection;
public class AccountService {
public boolean transfer(String outUser, String inUser, int money) {
AccountDao ad = new AccountDao();
//线程并发情况下,为了保证每个线程使用各自的connection,故加锁
synchronized (AccountService.class) {
Connection conn = null;
try {
conn = JdbcUtils.getConnection();
//开启事务
conn.setAutoCommit(false);
// 转出
ad.out(conn, outUser, money);
// 模拟转账过程中的异常
// int i = 1/0;
// 转入
ad.in(conn, inUser, money);
//事务提交
JdbcUtils.commitAndClose(conn);
} catch (Exception e) {
e.printStackTrace();
//事务回滚
JdbcUtils.rollbackAndClose(conn);
return false;
}
return true;
}
}
}
AccountDao 类:
import com.itheima.transfer.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class AccountDao {
public void out(Connection conn, String outUser, int money) throws SQLException{
String sql = "update account set money = money - ? where name = ?";
//注释从连接池获取连接的代码,使用从service中传递过来的connection
// Connection conn = JdbcUtils.getConnection();
PreparedStatement pstm = conn.prepareStatement(sql);
pstm.setInt(1,money);
pstm.setString(2,outUser);
pstm.executeUpdate();
//连接不能在这里释放,service层中还需要使用
// JdbcUtils.release(pstm,conn);
JdbcUtils.release(pstm);
}
public void in(Connection conn, String inUser, int money) throws SQLException {
String sql = "update account set money = money + ? where name = ?";
// Connection conn = JdbcUtils.getConnection();
PreparedStatement pstm = conn.prepareStatement(sql);
pstm.setInt(1,money);
pstm.setString(2,inUser);
pstm.executeUpdate();
// JdbcUtils.release(pstm,conn);
JdbcUtils.release(pstm);
}
}
这样的作法弊端:
- 直接从service层传递connection到dao层, 造成代码耦合度提高。
- 加锁会造成线程失去并发性,程序性能降低。
转账事务示例3-有事务(使用ThreadLocal)
工具类的修改: 加入ThreadLocal
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class JdbcUtils {
//ThreadLocal对象 : 将connection绑定在当前线程中
private static final ThreadLocal<Connection> tl = new ThreadLocal();
// c3p0 数据库连接池对象属性
private static final ComboPooledDataSource ds = new ComboPooledDataSource();
// 获取连接
public static Connection getConnection() throws SQLException {
//取出当前线程绑定的connection对象
Connection conn = tl.get();
if (conn == null) {
//如果没有,则从连接池中取出
conn = ds.getConnection();
//再将connection对象绑定到当前线程中
tl.set(conn);
}
return conn;
}
//释放资源
public static void release(AutoCloseable... ios) {
for (AutoCloseable io : ios) {
if (io != null) {
try {
io.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public static void commitAndClose() {
try {
Connection conn = getConnection();
//提交事务
conn.commit();
//解除绑定
tl.remove();
//释放连接
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void rollbackAndClose() {
try {
Connection conn = getConnection();
//回滚事务
conn.rollback();
//解除绑定
tl.remove();
//释放连接
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
AccountService类的修改:不需要传递connection对象
import com.itheima.transfer.dao.AccountDao;
import com.itheima.transfer.utils.JdbcUtils;
import java.sql.Connection;
public class AccountService {
public boolean transfer(String outUser, String inUser, int money) {
AccountDao ad = new AccountDao();
try {
Connection conn = JdbcUtils.getConnection();
//开启事务
conn.setAutoCommit(false);
// 转出 : 这里不需要传参了 !
ad.out(outUser, money);
// 模拟转账过程中的异常
// int i = 1 / 0;
// 转入
ad.in(inUser, money);
//事务提交
JdbcUtils.commitAndClose();
} catch (Exception e) {
e.printStackTrace();
//事务回滚
JdbcUtils.rollbackAndClose();
return false;
}
return true;
}
}
AccountDao类的修改:照常使用
import com.itheima.transfer.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class AccountDao {
public void out(String outUser, int money) throws SQLException {
String sql = "update account set money = money - ? where name = ?";
Connection conn = JdbcUtils.getConnection();
PreparedStatement pstm = conn.prepareStatement(sql);
pstm.setInt(1,money);
pstm.setString(2,outUser);
pstm.executeUpdate();
//照常使用
// JdbcUtils.release(pstm,conn);
JdbcUtils.release(pstm);
}
public void in(String inUser, int money) throws SQLException {
String sql = "update account set money = money + ? where name = ?";
Connection conn = JdbcUtils.getConnection();
PreparedStatement pstm = conn.prepareStatement(sql);
pstm.setInt(1,money);
pstm.setString(2,inUser);
pstm.executeUpdate();
// JdbcUtils.release(pstm,conn);
JdbcUtils.release(pstm);
}
}
网友评论