①,线程概念:
进程(处理器)和线程(线程)区别的
进程:启动一个LOL.exe就叫一个进程接着又启动一个DOTA.exe,这叫两个进程。
线程:线程是在进程内部同时做的事情,比如在LOL里,有很多事情要同时做,比如“盖伦”击杀“提莫”,同时“赏金猎人”又在击杀“盲僧”,这就是由多线程来实现的。
②,继承线程类:
自定义一个实体类,继承主题,重写运行并且方法
启动线程办法:实例化一个KillThread对象,并且调用其启动方法
③,实现可运行接口:
创建类战役,实现了可运行接口
启动的时候,首先创建一个战对象,然后再根据该战役对象创建一个线程对象,并启动
战斗之战1 =新战役(gareen,teemo);
new Thread(battle1).start();
battle1对象实现了可运行接口,所以有运行方法,但是直接调用运行方法,并不会启动一个新的线程。
必须,借助一个线程对象的开始()方法,才会启动一个新的线程。
所以,在创建线程对象的时候,把battle1作为构造方法的参数传递进去,这个线程启动的时候,就会去执行battle1.run()方法了。
④,匿名类:
使用匿名来自类,继承主题,重写运行方法,直接在运行中方法写业务代码
匿名来自类的一个好处的英文可以很网求方便的访问外部的局部变量。
前提是外部的局部变量需要被声明为最终的决定。(JDK7以后就不需要了)
//匿名类
Thread t1=
new
Thread(){
public
void
run(){
//匿名类中用到外部的局部变量teemo,必须把teemo声明为final
//但是在JDK7以后,就不是必须加final的了
while
(!teemo.isDead()){
gareen.attackHero(teemo);
}
}
};
t1.start();
①,当前线程暂停
了Thread.sleep(1000); 表示当前线程暂停1000毫秒,其他线程不受影响
Thread.sleep(1000); 会抛出InterruptedException中断异常,因为当前线程sleep的时候,有可能被停止,这时就会抛出InterruptedException
Thread t1= new Thread(){
public void run(){
int seconds =0;
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.printf("已经玩了LOL %d 秒%n", seconds++);
}
}
};
t1.start();
②,加入到当前线程中:所有进程,至少会有一个线程即主线程,即主要方法开始执行,就会有一个看不见的主线程存在。
Thread t1= new Thread(){
public void run(){
while(!teemo.isDead()){
gareen.attackHero(teemo);
}
}
};
t1.start();
//代码执行到这里,一直是main线程在运行
try {
//t1线程加入到main线程中来,只有t1线程运行结束,才会继续往下走
t1.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Thread t2= new Thread(){
public void run(){
while(!leesin.isDead()){
bh.attackHero(leesin);
}
}
};
//会观察到盖伦把提莫杀掉后,才运行t2线程
t2.start();
当线程处于竞争关系的时候,优先级高的线程会有更大的几率获得CPU资源
③,线程优先级的优先级
当线程处于竞争关系的时候,优先级高的线程会有更大的几率获得CPU资源
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
④,临时暂停:当前线程,临时暂停,使得其他线程可以有更多的机会占用CPU资源
Thread t2= new Thread(){
public void run(){
while(!leesin.isDead()){
//临时暂停,使得t1可以占用CPU资源
Thread.yield();
bh.attackHero(leesin);
}
}
};
t1.setPriority(5);
t2.setPriority(5);
t1.start();
t2.start();
⑤,守护线程:
守护线程的概念是:当一个进程里,所有的线程都是守护线程的时候,结束当前进程。
就好像一个公司有销售部,生产部这些和业务挂钩的部门。
除此之外,还有后勤,行政等这些支持部门。
如果一家公司销售部,生产部都解散了,那么只剩下后勤和行政,那么这家公司也可以解散了。
守护线程就相当于那些支持部门,如果一个进程只剩下守护线程,那么进程就会自动结束。
守护线程通常会被用来做日志,性能统计等工作。
Thread t1= new Thread(){
public void run(){
int seconds =0;
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.printf("已经玩了LOL %d 秒%n", seconds++);
}
}
};
t1.setDaemon(true);
t1.start();
①,同步的关键字意义
如下代码:
Object someObject = new Object();
synchronized(someObject){
//此处的代码只有占有了someObject后才可以执行
}
同步表示当前线程,独占对象someObject
当前线程独占了对象someObject,如果有其他线程试图占有对象someObject,就会等待,直到当前线程释放对someObject的占用。
someObject又叫同步对象,所有的对象,都可以作为同步对象
为了达到
同步的效果,必须使用同一个同步对象释放同步对象的方式:synchronized block自然结束,或者有异常抛出
②,使用同步解决同步问题:同一时间,同一个同步对象只能被一个线程占有
所有需要修改hp的地方,有要建立在占有someObject的基础上。
而对象someObject在同一时间,只能被一个线程占有。间接地,导致同一时间,hp只能被一个线程修改。
public class TestThread {
public static void main(String[] args) {
final Hero gareen = new Hero();
gareen.name = "盖伦";
gareen.hp = 10000;
int n = 10000;
Thread[] addThreads = new Thread[n];
Thread[] reduceThreads = new Thread[n];
for (int i = 0; i < n; i++) {
Thread t = new Thread(){
public void run(){
//使用gareen作为synchronized
synchronized (gareen) {
gareen.recover();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
t.start();
addThreads[i] = t;
}
for (int i = 0; i < n; i++) {
Thread t = new Thread(){
public void run(){
//使用gareen作为synchronized
//在方法hurt中有synchronized(this)
gareen.hurt();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
t.start();
reduceThreads[i] = t;
}
for (Thread t : addThreads) {
try {
t.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for (Thread t : reduceThreads) {
try {
t.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.printf("%d个增加线程和%d个减少线程结束后%n盖伦的血量是 %.0f%n", n,n,gareen.hp);
}
public class Hero{
public String name;
public float hp;
public int damage;
//回血
public void recover(){
hp=hp+1;
}
//掉血
public void hurt(){
//使用this作为同步对象
synchronized (this) {
hp=hp-1;
}
}
public void attackHero(Hero h) {
h.hp-=damage;
System.out.format("%s 正在攻击 %s, %s的血变成了 %.0f%n",name,h.name,h.name,h.hp);
if(h.isDead())
System.out.println(h.name +"死了!");
}
public boolean isDead() {
return 0>=hp?true:false;
}
}
③,在方法前,加上修饰符同步
在恢复前,直接加上同步,其所对应的同步对象,就是这
和伤方法达到的效果是一样
外部线程访问gareen的方法,就不需要额外使用synchronized了
public class Hero{
public String name;
public float hp;
public int damage;
//回血
//直接在方法前加上修饰符synchronized
//其所对应的同步对象,就是this
//和hurt方法达到的效果一样
public synchronized void recover(){
hp=hp+1;
}
//掉血
public void hurt(){
//使用this作为同步对象
synchronized (this) {
hp=hp-1;
}
}
public void attackHero(Hero h) {
h.hp-=damage;
System.out.format("%s 正在攻击 %s, %s的血变成了 %.0f%n",name,h.name,h.name,h.hp);
if(h.isDead())
System.out.println(h.name +"死了!");
}
public boolean isDead() {
return 0>=hp?true:false;
}
public class TestThread {
public static void main(String[] args) {
final Hero gareen = new Hero();
gareen.name = "盖伦";
gareen.hp = 10000;
int n = 10000;
Thread[] addThreads = new Thread[n];
Thread[] reduceThreads = new Thread[n];
for (int i = 0; i < n; i++) {
Thread t = new Thread(){
public void run(){
//recover自带synchronized
gareen.recover();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
t.start();
addThreads[i] = t;
}
for (int i = 0; i < n; i++) {
Thread t = new Thread(){
public void run(){
//hurt自带synchronized
gareen.hurt();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
t.start();
reduceThreads[i] = t;
}
for (Thread t : addThreads) {
try {
t.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for (Thread t : reduceThreads) {
try {
t.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.printf("%d个增加线程和%d个减少线程结束后%n盖伦的血量是 %.0f%n", n,n,gareen.hp);
}
如果一个类,其方法都是有synchronized修饰的,那么该类就叫做线程安全的类
同一时间,只有一个线程能够进入这种类的一个实例的去修改数据,进而保证了这个实例中的数据的安全(不会同时被多线程修改而变成脏数据)
比如StringBuffer的和的StringBuilder的区别
的StringBuffer的方法都是有同步修饰的,StringBuffer的叫做就线程安全的类
而StringBuilder的就不是线程安全的类
①,HashMap中和哈希表的区别
HashMap和Hashtable都实现了地图接口,都是键值对保存数据的方式
区别1:
HashMap可以存放null
Hashtable不能存放null
区别2:
HashMap不是线程安全的类
Hashtable是线程安全的类
②,StringBuffer的和的StringBuilder的区别
StringBuffer是线程安全的
StringBuilder是非线程安全的
所以当进行大量字符串拼接操作的时候,如果是单线程就用StringBuilder会更快些,如果是多线程,就需要用StringBuffer保证数据的安全性
非线程安全的为什么会比线程安全的快?因为不需要同步嘛,省略了些时间
③,ArrayList的和矢量的区别:
ArrayList的类的声明:
public class ArrayList
实现List
Vector类的声明:
公共类Vector
实现List
一模一样的〜
他们的区别也在于,Vector是线程安全的类,而ArrayList是非线程安全的。
④,把非线程安全的集合转换为线程安全
ArrayList的是非线程安全的,换句话说,多个线程可以同时进入一个ArrayList的对象的添加方法
借助Collections.synchronizedList,可以把ArrayList的转换为线程安全的列表。
与此类似的,还有HashSet的,链表,HashMap的等等非线程安全的类,通过都工具类类别转换为线程安全的
List
new
ArrayList<>();
List
5,死锁:双方同时各自占有,又试图占有对方
6,交互:
①,不好的解决方式:
故意设计减血线程频率更高,盖伦的血量迟早会到达1
减血线程中使用而循环判断是否是1,如果是1就不停的循环,加直到血线程回复主了血量
这是不好的解决方式,因为会大量占用CPU,拖慢性能
②,使用等待和通知进行线程交互:
在英雄类中:伤害()减血方法:当HP = 1的时候,执行this.wait()。
this.wait()表示让占有此的线程等待,并临时释放
占有进入伤方法的线程必然是减血线程,this.wait()会让减血线程临时释放对此的占有。这样加血线程,就有机会进入recover()加血方法
了.recovery()加血方法:增加了血量,执行this.notify();
this.notify()表示通知那些等待在这的线程,可以苏醒过来了。等待在这的线程,恰恰就是减血线程。一旦recover()结束,加血线程释放了这,减血线程,就可以重新占有这一点,并执行后面的减血工作。
public class Hero {
public String name;
public float hp;
public int damage;
public synchronized void recover() {
hp = hp + 1;
System.out.printf("%s 回血1点,增加血后,%s的血量是%.0f%n", name, name, hp);
// 通知那些等待在this对象上的线程,可以醒过来了,如第20行,等待着的减血线程,苏醒过来
this.notify();
}
public synchronized void hurt() {
if (hp == 1) {
try {
// 让占有this的减血线程,暂时释放对this的占有,并等待
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
hp = hp - 1;
System.out.printf("%s 减血1点,减少血后,%s的血量是%.0f%n", name, name, hp);
}
public void attackHero(Hero h) {
h.hp -= damage;
System.out.format("%s 正在攻击 %s, %s的血变成了 %.0f%n", name, h.name, h.name, h.hp);
if (h.isDead())
System.out.println(h.name + "死了!");
}
public boolean isDead() {
return 0 >= hp ? true : false;
}
③,关于等待,通知和notifyAll的
等待方法和通知方法,并不是线程线程上的方法,它们是目标上的方法。
因为所有的对象都可以被用来作为同步对象,所以准确的讲,等待和通知是同步对象上的方法。
等待()的意思是:让占用了这个同步对象的线程,临时释放当前的占用,并且等待。所以调用wait是有前提条件的,一定是在synchronized block里,否则就会出错.infote
()的意思你是通过一个等待在这个同步
对象上的线程了,有机会重新占用当前对象了。
7,线程池:线程池的模式很像生产者消费者模式,消费的对象是一个一个的能够运行的任务
每一个线程的启动和结束都是比较消耗时间和占用资源的。
在整个过程中,都不需要创建新的线程,而是循环使用这些已经存在的线程
①,自定义一个线程池
public class ThreadPool {
// 线程池大小
int threadPoolSize;
// 任务容器
LinkedList tasks = new LinkedList();
// 试图消费任务的线程
public ThreadPool() {
threadPoolSize = 10;
// 启动10个任务消费者线程
synchronized (tasks) {
for (int i = 0; i < threadPoolSize; i++) {
new TaskConsumeThread("任务消费者线程 " + i).start();
}
}
}
public void add(Runnable r) {
synchronized (tasks) {
tasks.add(r);
// 唤醒等待的任务消费者线程
tasks.notifyAll();
}
}
class TaskConsumeThread extends Thread {
public TaskConsumeThread(String name) {
super(name);
}
Runnable task;
public void run() {
System.out.println("启动: " + this.getName());
while (true) {
synchronized (tasks) {
while (tasks.isEmpty()) {
try {
tasks.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
task = tasks.removeLast();
// 允许添加任务的线程可以继续添加任务
tasks.notifyAll();
}
System.out.println(this.getName() + " 获取到任务,并执行");
task.run();
}
}
}
打开五个线程:
public static void main(String[] args) {
ThreadPool pool = new ThreadPool();
for (int i = 0; i < 5; i++) {
Runnable task = new Runnable() {
@Override
public void run() {
//System.out.println("执行任务");
//任务可能是打印一句话
//可能是访问文件
//可能是做排序
}
};
pool.add(task);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
②,使用的Java自带线程池
java的提供自带的线程池,而不需要自己去开发一个自定义线程池了。
线程池类的ThreadPoolExecutor在包java.util.concurrent中下
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10,15,60,TimeUnit.SECONDS,new LinkedBlockingQueue
第一个参数10表示这个线程池初始化了10个线程在里面工作
第二个参数15表示如果10个线程不够用了,就会自动增加到最多15个线程
第三个参数60结合第四个参数TimeUnit.SECONDS,表示经过60秒,多出来的线程还没有接到活儿,就会回收,最后保持池子里就10个
第四个参数TimeUnit.SECONDS如上
第五个参数new LinkedBlockingQueue()用来放任务的集合
执行方法用于添加新的任务
public class TestThread {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor threadPool= new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new LinkedBlockingQueue());
threadPool.execute(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("任务1");
}
});
}
如图8所示,锁定对象:与同步类似的,锁也能够达到同步的效果
当一个线程占用synchronized同步对象,其他线程就不能占用了,直到释放这个同步对象为止Thread.sleep(
5000
);
锁是一个接口,为了使用一个锁定对象,需要用到
锁定=新的ReentrantLock();
与synchronized(someObject)类似的,lock()方法,表示当前线程占用lock对象,一旦占用,其他线程就不能占用了。
与synchronized不同的是,一旦synchronized块结束,就会自动释放对someObject的占用。 lock却必须调用解锁方法进行手动释放,为了保证释放的执行,往往会把unlock()放在finally中进行。
public class TestThread {
public static String now() {
return new SimpleDateFormat("HH:mm:ss").format(new Date());
}
public static void log(String msg) {
System.out.printf("%s %s %s %n", now() , Thread.currentThread().getName() , msg);
}
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Thread t1 = new Thread() {
public void run() {
try {
log("线程启动");
log("试图占有对象:lock");
lock.lock();
log("占有对象:lock");
log("进行5秒的业务操作");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
log("释放对象:lock");
lock.unlock();
}
log("线程结束");
}
};
t1.setName("t1");
t1.start();
try {
//先让t1飞2秒
Thread.sleep(2000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
Thread t2 = new Thread() {
public void run() {
try {
log("线程启动");
log("试图占有对象:lock");
lock.lock();
log("占有对象:lock");
log("进行5秒的业务操作");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
log("释放对象:lock");
lock.unlock();
}
log("线程结束");
}
};
t2.setName("t2");
t2.start();
}
的tryLock方法
synchronized是不占用到手不罢休的,会一直试图占用下去。
与synchronized的钻牛角尖不一样,Lock接口还提供了一个trylock方法
.trylock会在指定时间范围内试图占用,占成功了,就啪啪啪。如果时间到了,还占用不成功,扭头就走〜
注意:因为使用trylock有可能成功,有可能失败,所以后面解锁释放锁的时候,需要判断是否占用成功了,如果没占用成功也解锁,就会抛出异常
public class TestThread {
public static String now() {
return new SimpleDateFormat("HH:mm:ss").format(new Date());
}
public static void log(String msg) {
System.out.printf("%s %s %s %n", now() , Thread.currentThread().getName() , msg);
}
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Thread t1 = new Thread() {
public void run() {
boolean locked = false;
try {
log("线程启动");
log("试图占有对象:lock");
locked = lock.tryLock(1,TimeUnit.SECONDS);
if(locked){
log("占有对象:lock");
log("进行5秒的业务操作");
Thread.sleep(5000);
}
else{
log("经过1秒钟的努力,还没有占有对象,放弃占有");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if(locked){
log("释放对象:lock");
lock.unlock();
}
}
log("线程结束");
}
};
t1.setName("t1");
t1.start();
try {
//先让t1飞2秒
Thread.sleep(2000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
Thread t2 = new Thread() {
public void run() {
boolean locked = false;
try {
log("线程启动");
log("试图占有对象:lock");
locked = lock.tryLock(1,TimeUnit.SECONDS);
if(locked){
log("占有对象:lock");
log("进行5秒的业务操作");
Thread.sleep(5000);
}
else{
log("经过1秒钟的努力,还没有占有对象,放弃占有");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if(locked){
log("释放对象:lock");
lock.unlock();
}
}
log("线程结束");
}
};
t2.setName("t2");
t2.start();
}
线程交互:
使用同步方式进行线程交互,用到的是同步对象的wait,notify和notifyAll方法
Lock也提供了类似的解决办法,首先通过lock对象得到一个条件对象,然后分别调用这个条件对象的:await,signal, signalAll方法
public class TestThread {
public static String now() {
return new SimpleDateFormat("HH:mm:ss").format(new Date());
}
public static void log(String msg) {
System.out.printf("%s %s %s %n", now() , Thread.currentThread().getName() , msg);
}
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Thread t1 = new Thread() {
public void run() {
try {
log("线程启动");
log("试图占有对象:lock");
lock.lock();
log("占有对象:lock");
log("进行5秒的业务操作");
Thread.sleep(5000);
log("临时释放对象 lock, 并等待");
condition.await();
log("重新占有对象 lock,并进行5秒的业务操作");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
log("释放对象:lock");
lock.unlock();
}
log("线程结束");
}
};
t1.setName("t1");
t1.start();
try {
//先让t1飞2秒
Thread.sleep(2000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
Thread t2 = new Thread() {
public void run() {
try {
log("线程启动");
log("试图占有对象:lock");
lock.lock();
log("占有对象:lock");
log("进行5秒的业务操作");
Thread.sleep(5000);
log("唤醒等待中的线程");
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
log("释放对象:lock");
lock.unlock();
}
log("线程结束");
}
};
t2.setName("t2");
t2.start();
}
总结锁和同步的区别:
1. Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,Lock是代码层面的实现.2。Lock
可以选择性的获取锁,如果一段时间获取不到,可以放弃。 synchronized不行,会一根筋一直获取下去。借助Lock的这个特性,就能够规避死锁,synchronized必须通过谨慎和良好的设计,才能减少死锁的发生.3。synchronized
在发生异常和同步块结束的时候,会自动释放锁。而Lock必须手动释放,所以如果忘记了释放锁,一样会造成死锁。
①,原子性操作概念:
所谓的原子性操作即不可中断的操作,比如赋值操作
int i = 5;
原子性操作本身是线程安全的
但是i ++这个行为,事实上是有3个原子性操作组成的。
步骤1.取i的值
步骤2. i + 1
步骤3.把新的值我
这三个步骤,每一步都是一个原子操作,但是合在一起,就不是原子操作。就不是线程安全的。换句话说
,一个线程在步骤1取我的值结束后,还没有来得及进行步骤2,另一个线程也可以取i的值了。
这也是分析同步问题产生的原因 中的原理
.i ++,i--,i = i + 1这些都是非原子性操作。
只有int i = 1 ,这个赋值操作是原子性的。
②,的AtomicInteger
JDK6以后,新增加了一个包java.util.concurrent.atomic,里面有各种原子类,比如AtomicInteger。
而AtomicInteger提供了各种自增,自减等方法,这些方法都是原子性的。话说,自增方法incrementAndGet是线程安全的,同一个时间,只有一个线程可以调用这个方法。
public static void main(String[] args) throws InterruptedException {
AtomicInteger atomicI =new AtomicInteger();
int i = atomicI.decrementAndGet();
int j = atomicI.incrementAndGet();
int k = atomicI.addAndGet(3);
}
③,同步测试:分别使用基本变量的非原子性的++运算符和原子性的AtomicInteger对象的incrementAndGet进行多线程测试。
public class TestThread {
private static int value = 0;
private static AtomicInteger atomicValue =new AtomicInteger();
public static void main(String[] args) {
int number = 100000;
Thread[] ts1 = new Thread[number];
for (int i = 0; i < number; i++) {
Thread t =new Thread(){
public void run(){
value++;
}
};
t.start();
ts1[i] = t;
}
//等待这些线程全部结束
for (Thread t : ts1) {
try {
t.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.printf("%d个线程进行value++后,value的值变成:%d%n", number,value);
Thread[] ts2 = new Thread[number];
for (int i = 0; i < number; i++) {
Thread t =new Thread(){
public void run(){
atomicValue.incrementAndGet();
}
};
t.start();
ts2[i] = t;
}
//等待这些线程全部结束
for (Thread t : ts2) {
try {
t.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.printf("%d个线程进行atomicValue.incrementAndGet();后,atomicValue的值变成:%d%n", number,atomicValue.intValue());
}
1,基本使用:
①,为项目导入的MySQL-JDBC的罐包
导包步骤:右键项目 - > property-> java build path-> libaries->添加外部jar
②,初始化驱动
通过Class.forName(“com.mysql.jdbc.Driver”);
初始化驱动类com.mysql.jdbc.Driver
就在mysql-connector-java-5.0.8-bin.jar中
如果忘记了第一个步骤的导包,就会抛出ClassNotFoundException
Class.forName是把这个类加载到JVM中,加载的时候,就会执行其中的静态初始化块,完成驱动的初始化的相关工作。
③,建立与数据库的连接
Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8",
"root"
,
"admin"
);
④,创建声明:声明是用于执行SQL语句的,比如增加,删除
// 注意:使用的是 java.sql.Statement
// 不要不小心使用到: com.mysql.jdbc.Statement;
Statement s = c.createStatement();
⑤,执行SQL语句:s.execute执行sql语句执行成功后,用mysql-front进行查看,明确插入成功
// 准备sql语句
// 注意: 字符串要用单引号'
String sql =
"insert into hero values(null,"
+
"'提莫'"
+
","
+
313
.0f+
","
+
50
+
")"
;
s.execute(sql);
⑥,关闭连接
先关闭声明
后关闭连接
public static void main(String[] args) {
Connection c = null;
Statement s = null;
try {
Class.forName("com.mysql.jdbc.Driver");
c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8", "root",
"admin");
s = c.createStatement();
String sql = "insert into hero values(null," + "'提莫'" + "," + 313.0f + "," + 50 + ")";
s.execute(sql);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
// 数据库的连接时有限资源,相关操作结束后,养成关闭数据库的好习惯
// 先关闭Statement
if (s != null)
try {
s.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 后关闭Connection
if (c != null)
try {
c.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
⑦,使用尝试,与资源的方式自动关闭连接:如果觉得上一步的关闭连接的方式很麻烦,可以参考关闭流 的方式,使用尝试,与资源的方式自动关闭连接,因为连接和声明都实现了AutoCloseable接口
public class TestJDBC {
public static void main(String[] args) {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try (
Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8",
"root", "admin");
Statement s = c.createStatement();
)
{
String sql = "insert into hero values(null," + "'提莫'" + "," + 313.0f + "," + 50 + ")";
s.execute(sql);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
2,CRUD:即增删改查
C增加(创建)
R读取查询(检索)
更新(更新)
D删除(删除)
在JDBC中增加,删除,修改的操作都很类似,只是传递不同的SQL语句就行了。
查询因为要返回数据,所以和上面的不一样
String sql =
"insert into hero values(null,"
+
"'提莫'"
+
","
+
313
.0f +
","
+
50
+
")"
;
String sql =
"delete from hero where id = 5"
;
String sql =
"update hero set name = 'name 5' where id = 3"
;
String sql =
"select * from hero"
;
executeQuery execute行SQL查询语句
注意:在取第二列的数据的时候,用的是rs.get(2),而不是get(1)。这个是整个的Java自带的API里唯二的地方,使用基1的,即2就代表第二个。
另一个地方是在PreparedStatement的
// 执行查询语句,并把结果集返回给ResultSet
ResultSet rs = s.executeQuery(sql);
while
(rs.next()) {
int
id = rs.getInt(
"id"
);
// 可以使用字段名
String name = rs.getString(
2
);
// 也可以使用字段的顺序
float
hp = rs.getFloat(
"hp"
);
int
damage = rs.getInt(
4
);
System.out.printf(
"%d\t%s\t%f\t%d%n"
, id, name, hp, damage);
}
// 不一定要在这里关闭ReultSet,因为Statement关闭的时候,会自动关闭ResultSet
// rs.close();
SQL语句判断账号密码是否正确:
判断账号密码的正确方式是根据账号和密码到表中去找数据,如果有数据,就表明密码正确了,如果没数据,就表明密码错误。
public class TestJDBC {
public static void main(String[] args) {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try (Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8",
"root", "admin");
Statement s = c.createStatement();
) {
String name = "dashen";
//正确的密码是:thisispassword
String password = "thisispassword1";
String sql = "select * from user where name = '" + name +"' and password = '" + password+"'";
// 执行查询语句,并把结果集返回给ResultSet
ResultSet rs = s.executeQuery(sql);
if(rs.next())
System.out.println("账号密码正确");
else
System.out.println("账号密码错误");
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
获取总数:
执行的SQL语句为
从英雄中选择计数(*)
然后通过ResultSet中获取出来
public class TestJDBC {
public static void main(String[] args) {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try (Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8",
"root", "admin"); Statement s = c.createStatement();) {
String sql = "select count(*) from hero";
ResultSet rs = s.executeQuery(sql);
int total = 0;
while (rs.next()) {
total = rs.getInt(1);
}
System.out.println("表Hero中总共有:" + total+" 条数据");
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
MySQL的分页查询:
/**
* 设计一个方法,进行分页查询
*
* public static void list(int start, int count) start 表示开始页数,count表示一页显示的总数
* list(0,5) 表示第一页,一共显示5条数据 list(10,5) 表示第三页,一共显示5条数据 进行分页查询用到的SQL语句参考 : 查询数据
*
* @author litte
*
*/
public class JdbcPracticeRetrieve03 {
static String url = "jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8";
static String user = "root";
static String password = "password";
public static void main(String[] args) {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
list(5, 3);
}
public static void list(int start, int count) {
try (Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8",
"root", "admin"); Statement s = c.createStatement();) {
// int page = start % 4;// 显示的页数
String sql = "select * from hero limit " + start + "," + Math.abs(start - count);
System.out.println(sql);
ResultSet rs = s.executeQuery(sql);
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
float hp = rs.getFloat("hp");
int damage = rs.getInt("damage");
System.out.printf("%d\t%s\t%.0f\t%d%n", id, name, hp, damage);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
PreparedStatement:和 Statement一样,PreparedStatement也是用来执行sql语句的
与创建Statement不同的是,需要根据sql语句创建PreparedStatement
除此之外,还能够通过设置参数,指定相应的值,而不是Statement那样使用字符串拼接
注: 这是JAVA里唯二的基1的地方,另一个是查询语句中的ResultSet也是基1的。
public static void main(String[] args) {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
String sql = "insert into hero values(null,?,?,?)";
try (Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8","root", "admin");
// 根据sql语句创建PreparedStatement
PreparedStatement ps = c.prepareStatement(sql);
) {
// 设置参数
ps.setString(1, "提莫");
ps.setFloat(2, 313.0f);
ps.setInt(3, 50);
// 执行
ps.execute();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
PreparedStatement优点:
①、参数设置
Statement 需要进行字符串拼接,可读性和维护性比较差
String sql = "insert into hero values(null,"+"'提莫'"+","+313.0f+","+50+")";
PreparedStatement 使用参数设置,可读性好,不易犯错
String sql = "insert into hero values(null,?,?,?)";
②、性能表现更快:
PreparedStatement有预编译机制,性能比Statement更快
// Statement执行10次,需要10次把SQL语句传输到数据库端
// 数据库要对每一次来的SQL语句进行编译处理
// PreparedStatement 执行10次,只需要1次把SQL语句传输到数据库端
// 数据库对带?的SQL进行预编译
// 每次执行,只需要传输参数到数据库端
// 1. 网络传输量比Statement更小
// 2. 数据库不需要再进行编译,响应更快
③、防止SQL注入式攻击:
假设name是用户提交来的数据
String name = "'盖伦' OR 1=1";
使用Statement就需要进行字符串拼接
拼接出来的语句是:
select * from hero where name = '盖伦' OR 1=1
因为有OR 1=1,这是恒成立的
那么就会把所有的英雄都查出来,而不只是盖伦
如果Hero表里的数据时海量的,比如几百万条,把这个表里的数据全部查出来
会让数据库负载变高,CPU100%,内存消耗光,响应变得极其缓慢
而PreparedStatement使用的是参数设置,就不会有这个问题
execute与executeUpdate的相同点:都可以执行增加,删除,修改
不同1:
execute可以执行查询语句
然后通过getResultSet,把结果集取出来
executeUpdate不能执行查询语句
不同2:
execute返回boolean类型,true表示执行的是查询语句,false表示执行的是insert,delete,update等等
executeUpdate返回的是int,表示有多少条数据受到了影响
6、特殊操作:获取自增长id和获取表的元数据
①、获取自增长id:
无论是execute还是executeUpdate都不会返回这个自增长id是多少。需要通过Statement的getGeneratedKeys获取该id
PreparedStatement ps = c.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
ResultSet rs = ps.getGeneratedKeys();
if
(rs.next()) {
int
id = rs.getInt(
1
);
System.out.println(id);
}
②、获取表的元数据:
元数据概念:
和数据库服务器相关的数据,比如数据库版本,有哪些表,表有哪些字段,字段类型是什么等等。
public static void main(String[] args) throws Exception {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try (Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8","root", "admin");) {
// 查看数据库层面的元数据
// 即数据库服务器版本,驱动版本,都有哪些数据库等等
DatabaseMetaData dbmd = c.getMetaData();
// 获取数据库服务器产品名称
System.out.println("数据库产品名称:\t"+dbmd.getDatabaseProductName());
// 获取数据库服务器产品版本号
System.out.println("数据库产品版本:\t"+dbmd.getDatabaseProductVersion());
// 获取数据库服务器用作类别和表名之间的分隔符 如test.user
System.out.println("数据库和表分隔符:\t"+dbmd.getCatalogSeparator());
// 获取驱动版本
System.out.println("驱动版本:\t"+dbmd.getDriverVersion());
System.out.println("可用的数据库列表:");
// 获取数据库名称
ResultSet rs = dbmd.getCatalogs();
while (rs.next()) {
System.out.println("数据库名称:\t"+rs.getString(1));
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
7、事务:
在事务中的多个操作,要么都成功,要么都失败
通过 c.setAutoCommit(false);关闭自动提交
使用 c.commit();进行手动提交
MYSQL 表的类型必须是INNODB才支持事务:
在Mysql中,只有当表的类型是INNODB的时候,才支持事务,所以需要把表的类型设置为INNODB,否则无法观察到事务.
修改表的类型为INNODB的SQL:
alter table hero ENGINE = innodb;
查看表的类型的SQL
show table status from how2java;
不过有个前提,就是当前的MYSQL服务器本身要支持INNODB,如果不支持,请看 开启MYSQL INNODB的办法
设计一个代码,删除表中前10条数据,但是删除前会在控制台弹出一个提示:
是否要删除数据(Y/N)
如果用户输入Y,则删除
如果输入N则不删除。
如果输入的既不是Y也不是N,则重复提示
/*
当c.setAutoCommit(false);时,事务是不会提交的
只有执行使用 c.commit(); 才会提交进行
设计一个代码,删除表中前10条数据,但是删除前会在控制台弹出一个提示:
是否要删除数据(Y/N)
如果用户输入Y,则删除
如果输入N则不删除。
如果输入的既不是Y也不是N,则重复提示
*/
public class Test6 {
public static void main(String[] args) {
Connection c = null;
Statement s = null;
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Scanner scanner = new Scanner(System.in);
try {
c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8", "root",
"123456");
s = c.createStatement();
c.setAutoCommit(false);
for (int i = 2; i < 12; i++) {
ResultSet rs = s.executeQuery("select * from hero where id = " + i);
while(rs.next()){
String name = rs.getString("name");
float hp = rs.getFloat("hp");
int damage = rs.getInt("damage");
System.out.println("試圖刪除id="+i+"的數據"+"\t"+name+"\t"+hp+"\t"+damage+"\n");
}
}
System.out.println("是否刪除 : Y/N");
if(scanner.nextLine().equals("Y")){
for(int i=2;i<12;i++){
s.execute("delete from hero where id = " + i);
}
}
c.commit();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
if(s != null){
try {
s.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(c != null){
try {
c.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
8、ORM:Object Relationship Database Mapping(对象和关系数据库的映射)(简说:一个对象,对应数据库中的一条数据)
根据id返回一个Hero对象:
public class TestJDBC {
public static Hero get(int id) {
Hero hero = null;
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try (Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8","root", "admin");
Statement s = c.createStatement();) {
String sql = "select * from hero where id = " + id;
ResultSet rs = s.executeQuery(sql);
// 因为id是唯一的,ResultSet最多只能有一条记录
// 所以使用if代替while
if (rs.next()) {
hero = new Hero();
String name = rs.getString(2);
float hp = rs.getFloat("hp");
int damage = rs.getInt(4);
hero.name = name;
hero.hp = hp;
hero.damage = damage;
hero.id = id;
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return hero;
}
public static void main(String[] args) {
Hero h = get(22);
System.out.println(h.name);
}
}
步骤 2 : 练习-ORM
ORM的增删改查: CDUR
//Hero类
package LearnJDBC;
public class Hero {
//注意这些修饰符,至少为protect,如果为private,则外部类,无法访问
public int id;
public String name;
public float hp;
public int damage;
public Hero(int id,String name,float hp,int damage) {
this.id=id;
this.name=name;
this.hp=hp;
this.damage=damage;
}
public Hero() {}
}
//4个函数的代码
package LearnJDBC;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedList;
import java.util.List;
/*
* 数据库的hero表创建:create table hero(id int,name varchar(20),hp float,damage int);
* 注意id字段不能设置为自增,否则后面的增加删除函数不能演示运行效果
*/
public class TestORM {
//从数据库的特定id字段下的记录所有值,在jvm上生成一个对象,该对象拥有该记录的有所值
public static Hero get(int id) {
//初始化一个Hero对象
Hero hero=new Hero();
//初始化驱动
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//连接数据库,执行sql语句
try(Connection c=DriverManager.getConnection("jdbc:mysql://localhost:3306/grid?useSSL=false","root","yueli");
Statement s=c.createStatement()){
String sql="select * from hero where id="+id;
//查询结果返回一个set集合
ResultSet rs=s.executeQuery(sql);
if(rs.next()) {
//jvm获得数据库特定记录下的所有值,并修改对象的成员变量
String name=rs.getString(2);
Float hp=rs.getFloat("hp");
int damage=rs.getInt(4);
hero.name=name;
hero.hp=hp;
hero.damage=damage;
hero.id=id;
}
}catch(SQLException e) {
e.printStackTrace();
}
return hero;
}
public static void add(Hero h) {
int id=h.id;
String name=h.name;
float hp=h.hp;
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//关键这个,将sql语句与后面的设置参数分开写,否则拼接字符串看不懂
String sql="insert into hero values(?,?,?,?)";
try(Connection c=DriverManager.getConnection("jdbc:mysql://localhost:3306/grid?useSSL=false","root","yueli");
PreparedStatement ps=c.prepareStatement(sql);){
// 设置参数并执行
ps.setInt(1, id);
ps.setString(2, name);
ps.setFloat(3, hp);
ps.setInt(4, h.damage);
ps.execute();
}catch(SQLException e) {
e.printStackTrace();
}
}
public static void delete(Hero h) {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String sql="delete from hero where id="+h.id;
try(Connection c=DriverManager.getConnection("jdbc:mysql://localhost:3306/grid?useSSL=false","root","yueli");
PreparedStatement ps=c.prepareStatement(sql);){
ps.execute();
}catch(SQLException e) {
e.printStackTrace();
}
}
public static void update(Hero h) {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try(Connection c=DriverManager.getConnection("jdbc:mysql://localhost:3306/grid?useSSL=false","root","yueli");
Statement s=c.createStatement();){
String sql="select * from hero where id="+h.id;
ResultSet rs=s.executeQuery(sql);
if(rs.next()) {
int id=rs.getInt(1);
String name=rs.getString(2);
float hp=rs.getFloat(3);
int damage=rs.getInt(4);
h.id=id;
h.name=name;
h.hp=hp;
h.damage=damage;
}
}catch(SQLException e) {
e.printStackTrace();
}
}
public static List list(){
List list=new LinkedList<>();
String sql="select * from hero";
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try(Connection c=DriverManager.getConnection("jdbc:mysql://localhost:3306/grid?useSSL=false","root","yueli");
Statement s=c.createStatement()){
ResultSet rs=s.executeQuery(sql);
//类似循环的,游标查询每个记录
while(rs.next()) {
//从当前记录,jvm获得数据
int id=rs.getInt(1);
String name=rs.getString(2);
float hp=rs.getFloat(3);
int damage=rs.getInt(4);
//创建一个Hero对象,把获得数据修改对象的成员变量
Hero h=new Hero();
h.id=id;
h.name=name;
h.hp=hp;
h.damage=damage;
//把这个对象添加到集合
list.add(h);
}
}catch(SQLException e) {
e.printStackTrace();
}
return list;
}
public static void main(String[] args) {
// Hero h=get(101);
// System.out.println(h.name);
// Hero h=new Hero(120,"带帽子的提莫",500,48);
// add(h);
// delete(h);
// Hero h=new Hero(1,"带巨人腰带的提莫",700,50);
// update(h);
//下面应输出hero表id=1的记录下的teemo
// System.out.println(h.name);
List resultlist=list();
for(Hero h:resultlist) {
System.out.println(h.id);
}
}
}
CRUD:增差更删
9、DAO:Data Access Object数据库访问对象
DAO接口:
package jdbc;
import java.util.List;
import charactor.Hero;
public interface DAO{
//增加
public void add(Hero hero);
//修改
public void update(Hero hero);
//删除
public void delete(int id);
//获取
public Hero get(int id);
//查询
public List list();
//分页查询
public List list(int start, int count);
}
DAO实现类:
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import charactor.Hero;
public class HeroDAO implements DAO{
public HeroDAO() {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8", "root",
"admin");
}
public int getTotal() {
int total = 0;
try (Connection c = getConnection(); Statement s = c.createStatement();) {
String sql = "select count(*) from hero";
ResultSet rs = s.executeQuery(sql);
while (rs.next()) {
total = rs.getInt(1);
}
System.out.println("total:" + total);
} catch (SQLException e) {
e.printStackTrace();
}
return total;
}
public void add(Hero hero) {
String sql = "insert into hero values(null,?,?,?)";
try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) {
ps.setString(1, hero.name);
ps.setFloat(2, hero.hp);
ps.setInt(3, hero.damage);
ps.execute();
ResultSet rs = ps.getGeneratedKeys();
if (rs.next()) {
int id = rs.getInt(1);
hero.id = id;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public void update(Hero hero) {
String sql = "update hero set name= ?, hp = ? , damage = ? where id = ?";
try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) {
ps.setString(1, hero.name);
ps.setFloat(2, hero.hp);
ps.setInt(3, hero.damage);
ps.setInt(4, hero.id);
ps.execute();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void delete(int id) {
try (Connection c = getConnection(); Statement s = c.createStatement();) {
String sql = "delete from hero where id = " + id;
s.execute(sql);
} catch (SQLException e) {
e.printStackTrace();
}
}
public Hero get(int id) {
Hero hero = null;
try (Connection c = getConnection(); Statement s = c.createStatement();) {
String sql = "select * from hero where id = " + id;
ResultSet rs = s.executeQuery(sql);
if (rs.next()) {
hero = new Hero();
String name = rs.getString(2);
float hp = rs.getFloat("hp");
int damage = rs.getInt(4);
hero.name = name;
hero.hp = hp;
hero.damage = damage;
hero.id = id;
}
} catch (SQLException e) {
e.printStackTrace();
}
return hero;
}
public List list() {
return list(0, Short.MAX_VALUE);
}
public List list(int start, int count) {
List heros = new ArrayList();
String sql = "select * from hero order by id desc limit ?,? ";
try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) {
ps.setInt(1, start);
ps.setInt(2, count);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
Hero hero = new Hero();
int id = rs.getInt(1);
String name = rs.getString(2);
float hp = rs.getFloat("hp");
int damage = rs.getInt(4);
hero.id = id;
hero.name = name;
hero.hp = hp;
hero.damage = damage;
heros.add(hero);
}
} catch (SQLException e) {
e.printStackTrace();
}
return heros;
}
}
10、数据库连接池:
①、传统方式(原理):
当有多个线程,每个线程都需要连接数据库执行SQL语句的话,那么每个线程都会创建一个连接,并且在使用完毕后,关闭连接。
创建连接和关闭连接的过程也是比较消耗时间的,当多线程并发的时候,系统就会变得很卡顿。
同时,一个数据库同时支持的连接总数也是有限的,如果多线程并发量很大,那么数据库连接的总数就会被消耗光,后续线程发起的数据库连接就会失败。
②、使用池(原理):
与传统方式不同,连接池在使用之前,就会创建好一定数量的连接。
如果有任何线程需要使用连接,那么就从连接池里面借用,而不是自己重新创建.
使用完毕后,又把这个连接归还给连接池供下一次或者其他线程使用。
倘若发生多线程并发情况,连接池里的连接被借用光了,那么其他线程就会临时等待,直到有连接被归还回来,再继续使用。
整个过程,这些连接都不会被关闭,而是不断的被循环使用,从而节约了启动和关闭连接的时间。
③、ConnectionPool构造方法和初始化:
1. ConnectionPool()构造方法约定了这个连接池一共有多少连接
2。在init()初始化方法中,创建了size条连接。注意,这里不能使用try-with-resource这种自动关闭连接的方式,因为连接恰恰需要保持不关闭状态,供后续循环使用
3。getConnection,判断是否为空,如果是空的就等等待,否则就借用一条连接出去
4。returnConnection,在使用完毕后,归还这个连接到连接池,并且在归还完毕后,调用notifyAll,通知那些等待的线程,有新的连接可以借用了。
注:连接池设计用到了多线程的wait和notifyAll
自定义连接池:
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class ConnectionPool {
List cs = new ArrayList();
int size;
public ConnectionPool(int size) {
this.size = size;
init();
}
public void init() {
//这里恰恰不能使用try-with-resource的方式,因为这些连接都需要是"活"的,不要被自动关闭了
try {
Class.forName("com.mysql.jdbc.Driver");
for (int i = 0; i < size; i++) {
Connection c = DriverManager
.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8", "root", "admin");
cs.add(c);
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public synchronized Connection getConnection() {
while (cs.isEmpty()) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Connection c = cs.remove(0);
return c;
}
public synchronized void returnConnection(Connection c) {
cs.add(c);
this.notifyAll();
}
}