多线程简单整理

多线程简单整理

 仅仅整理了一点后续还会继续整理


创建线程的两种方法

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数据库具有一致性读的概念,比如:当一个客户在9select的时候,读取需要10分钟,而恰好在905分钟的时候有另一个客户在update,这个时候,读取的数据是9点的,而不是update之后的数据。oracle数据库中有undo的概念,相当于日志,当进行DML操作的时候,为了防止数据操作失败,用来进行事务回滚。在select时发现数据有变动了,就会在undo中找以前的旧值读取,如果找不到就会抛出异常SnapShotTooOld(快照太旧)。也就是说什么时候进行数据读取,你读的就是什么时候数据,而不是修改过后的数据。