多线程简单整理
多线程简单整理
仅仅整理了一点后续还会继续整理
创建线程的两种方法:
1.继承Thread类,(当然Thread类底层也是继承的Runnable接口)
class Thread1 extends Thread{
private String name;
public Thread1(String name) {
this.name=name;
}
2.实现Runnable接口
class Thread2 implements Runnable{
private String name;
public Thread2(String name) {
this.name=name;
}
两种方法的比较:
1.继承Thread类不适合资源共享,而Runnable则很容易实现资源共享
@继承Thread不能实现资源共享
class Thread1 extends Thread{
private int count=5;
private String name;
public Thread1(String name) {
this.name=name;
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + "运行 count= " + count--);
try {
sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
Thread1 mTh1=new Thread1("A");
Thread1 mTh2=new Thread1("B");
mTh1.start();
mTh2.start();
}
}
输出:
B运行 count= 5
A运行 count= 5
B运行 count= 4
B运行 count= 3
B运行 count= 2
B运行 count= 1
A运行 count= 4
A运行 count= 3
A运行 count= 2
A运行 count= 1
@实现Runnable接口,则很容易实现资源共享
package com.multithread.runnable;
class Thread2 implements Runnable{
private int count=15;
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "运行 count= " + count--);
try {
Thread.sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
Thread2 my = new Thread2();
new Thread(my, "C").start();//同一个mt,但是在Thread中就不可以,如果用同一个实例化对象mt,就会出现异常
new Thread(my, "D").start();
new Thread(my, "E").start();
}
}
输出:
C运行 count= 15
D运行 count= 14
E运行 count= 13
D运行 count=
12 / /你会发现这里的12有两个,既然是共享数据为什么会重复呢,这个后面会讲到
D运行 count= 10
D运行 count= 9
D运行 count= 8
C运行 count= 11
E运行 count=
12
C运行 count= 7
E运行 count= 6
C运行 count= 5
E运行 count= 4
C运行 count= 3
E运行 count= 2
当然有人会说Thread也能够实现资源共享如下代码:
输出:
这里虽然也是实现了资源的共享,但是,这个方式和runnable的实现原理本质都是一个样子的,都是把一个对象给了多个线程去构造,一个对象构造出来的线程当然是共享的了。
注意:
这里的每个线程都是实例化的同一个对象,如果不是同一个对象,就喝继承Thread的例子一样
提醒一下大家:
main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。
在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程(也就是GC机制)。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实际就是在操作系统中启动了一个进程。
总结:
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
线程安全:多个线程共享同一个类(对象或者方法),这个类(对象或者方法)无论什么时候都能做出正确的反应(个人理解)
线程的同步、异步、互斥、死锁:
为什么提出线程同步:线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏
线程的同步:多线程同步是指多线程通过特定的东西(如互斥量)来控制线程之间的执行顺序来访问同一个资源,
输出:
线程的互斥:多个线程共享同一份资源哪那个抢到了cpu的执行权就去访问资源,是无序的
输出:
互斥线程的执行过程如下:
1. 获得同步锁;
2. 清空工作内存;
3. 从主内存拷贝对象副本到工作内存;
4. 执行代码(计算或者输出等);
5. 刷新主内存数据;
6. 释放同步锁。
所以,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。
线程的异步:多线程访问共有资源,其中一个线程访问资源时,占用了锁,其他得线程需要等待这个线程释放锁,在等待的同时去执行其他的进程。在什么情况下用呢?当需要处理的工作很费时或者遇到需要等待的IO时
异步的好处:高相应性,高性能
线程的死锁:两个线程都握着对方想要的锁,都在等着对方释放锁,就形成了死锁
线程的通信:
Wait():
将线程挂起并释放自己的锁,等待另一个线程唤醒通知的方式是notify()或者notifyAll()方法(例子中有)
Notify():唤醒一个等待当前对象锁的线程,只能随机唤醒一个(例子中有)
wait()和notify()方法要求在调用时线程已经获得了对象的锁,因此对这两个方法的调用需要放在synchronized方法或synchronized块中。
notifyAll():唤醒所有等待当前对象锁的线程
Sleep():让线程进入休眠状态,并不释放自己的锁(例子中有)
Yiled(): 是一个Thread类的静态方法.作用是,让出CPU,给其他线程执行的机会
Join(): 让线程暂停,等待其他的线程执行完再执行
线程的生命周期:
脏读:脏读又称无效数据的读出,是指在数据库访问中,事务T1将某一值修改,然后事务T2读取该值,此后T1因为某种原因撤销对该值的修改,这就导致了T2所读取到的数据是无效的。
要注意线程的同步,避免脏读
关系型数据库中如何保持数据的一致性(oracle):
oracle数据库具有一致性读的概念,比如:当一个客户在9点select的时候,读取需要10分钟,而恰好在9:05分钟的时候有另一个客户在update,这个时候,读取的数据是9点的,而不是update之后的数据。oracle数据库中有undo的概念,相当于日志,当进行DML操作的时候,为了防止数据操作失败,用来进行事务回滚。在select时发现数据有变动了,就会在undo中找以前的旧值读取,如果找不到就会抛出异常SnapShotTooOld(快照太旧)。也就是说什么时候进行数据读取,你读的就是什么时候数据,而不是修改过后的数据。