java多线程超级详解(简单,详细,易懂)

1.java中线程的创建方式

1.1.线程的介绍

线程和进程
进程和线程算是操作系统内两个很基本、很重要的概念了,进程是操作系统中进行保护和资源分配的基本单位,操作系统分配资源以进程为基本单位。而线程是进程的组成部分,它代表了一条顺序的执行流。
系统中的进程线程模块是这样的:
在这里插入图片描述1.线程是独立的 堆内存和方法区是共享的
2.栈是相互独立的,一个线程就是一个栈
3.main方法是程序的主线程 main方法结束了不代表程序结束 其他的线程可能还在执行

1.1.线程的创建

创建线程有两种基本的方法,一种是实现Runnable接口,另外一种是继承Thread类

1 继承Thread类

public class ThreadTest  {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        //t.run();
        t.start();
        for (int j=0;j<100;j++){
            System.out.println("主线程"+j);
        }
    }
 static class MyThread extends Thread{
        @Override
        public void run() {
            for (int i=0;i<100;i++){
                System.out.println("线程分支"+i);
            }
        }
    }
}

2 实现Runnable接口

package com.lf.Thread.com.lf.Test;
public class ThreadTest1 {
    public static void main(String[] args) {
        RunThread runThread = new RunThread();
        runThread.run();
        //这一行代码写在那个线程里面的就是那个线程
        Thread thread = Thread.currentThread();
        thread.setName("lo");
        for (int j=0;j<10;j++){
            System.out.println(thread.getName()+j);
        }
    }
}
class RunThread implements Runnable{
    @Override
    public void run() {
        Thread thread = Thread.currentThread();
        thread.setName("LF");
        for (int i =0;i<10;i++){
            System.out.println(thread.getName()+i);
        }
    }
}

3 匿名内部类

package com.lf.Thread.com.lf.Test;
public class ThreadTest1 {
    public static void main(String[] args) {
        RunThread runThread = new RunThread();
        runThread.run();
        //这一行代码写在那个线程里面的就是那个线程
        Thread thread = Thread.currentThread();
        thread.setName("lo");
        for (int j=0;j<10;j++){
            System.out.println(thread.getName()+j);
        }
    }
}
class RunThread implements Runnable{
    @Override
    public void run() {
        Thread thread = Thread.currentThread();
        thread.setName("LF");
        for (int i =0;i<10;i++){
            System.out.println(thread.getName()+i);
        }
    }
}

2.Run方法Start()方法的比较

start()方法是启动线程的方法
start()的作用:启动一个线程,在JVM中开辟一个新的栈空间,这段代码瞬间就结束了,只要新的栈空间开辟出来start()方法就结束了
启动成功的分支线程会主动去调用run()方法,并且run方法在分支栈的底部,main()方法在主栈的底部,run()方法与main()方法是平级的.

public class ThreadTest  {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.run();
        for (int j=0;j<100;j++){
            System.out.println("主线程"+j);
        }
    }
 static class MyThread extends Thread{
        @Override
        public void run() {
            for (int i=0;i<100;i++){
                System.out.println("线程分支"+i);
            }
        }
    }
}

在这里插入图片描述

public class ThreadTest  {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        //t.run();
        t.start();
        for (int j=0;j<100;j++){
            System.out.println("主线程"+j);
        }
    }
 static class MyThread extends Thread{
        @Override
        public void run() {
            for (int i=0;i<100;i++){
                System.out.println("线程分支"+i);
            }
        }
    }
}

并发执行的
在这里插入图片描述在这里插入图片描述

3.线程的生命周期

新建:就是刚使用new方法,new出来的线程;

就绪:就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行;

运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;

阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;

销毁:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;

在这里插入图片描述

4.线程方法的介绍与使用

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

public class Thread2 {
    public static void main(String[] args) throws InterruptedException {
        for (int i=0;i<10;i++){
        System.out.println(Thread.currentThread().getName()+i);
            Thread.sleep(1000);
        }
    }
}
sleep方法是让当前线程进入休眠  与对象没有关系 
比如
public static void main(String[] args){
Thread t =new MyTHread();
t.start();
t.sleep(100);//他这里实际执行的还是Thread.sleep();是让主线程休眠
}

class MyThread extends Thread{
...

}

在这里插入图片描述

public class ThreadTest1 {
    public static void main(String[] args) {
        RunThread runThread = new RunThread();
        runThread.run();
        //这一行代码写在那个线程里面的就是那个线程
        Thread thread = Thread.currentThread();
        thread.setName("lo");
        for (int j=0;j<10;j++){
            System.out.println(thread.getName()+j);
        }
    }
}
class RunThread implements Runnable{
    @Override
    public void run() {
        Thread thread = Thread.currentThread();
        thread.setName("LF");
        for (int i =0;i<10;i++){
            System.out.println(thread.getName()+i);
        }
    }
}
线程睡眠的终止
public class 终止线程的睡眠 {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyThread());
        thread.start();
        try {
            Thread.sleep(5000);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Thread.currentThread().setName("end");
        System.out.println(Thread.currentThread().getName());
    }
}

class MyThread implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(10000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
线程的合理中断
public class 线程的中端 {
    public static void main(String[] args) {
        MyThreadDemo myThreadDemo = new MyThreadDemo();
        Thread thread = new Thread(myThreadDemo);
        thread.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        myThreadDemo.flag=false;
    }
}
class MyThreadDemo extends Thread{
    //通过一个标记来实现现成的中断
    boolean flag=true;
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (flag ) {
                System.out.println(Thread.currentThread().getName() + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                return;
            }
        }
    }
}

5.java中关于线程的调度

常见的调度模型
  抢占式调度模型:那个线程的优先级高一些,抢到的CPU时间片机会机会高一些/多一些,java中采用的就是抢占式调度模型

  均分式调度模型:均匀的分配CPU时间片,每个线程获得的CPU时间片都是相同的

  java中提供的线程调度的方法
  java中线程的优先级是0-10的,java中线程的的默认优先级是
5,最高优先级是10

main()的优先级就是5

实例方法
设置优先级的函数
  void setPriority(int newProority)
获取优先级的函数
  int getPriority()

public class 线程的优先级 {
    public static void main(String[] args) {
        Thread.currentThread().setPriority(1);
        Thread thread = new Thread(new RuableDemo());
        thread.setName("t");
        thread.setPriority(10);
        thread.start();
        for (int i=0;i<1000;i++){
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}
class RuableDemo implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<1000;i++){
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}

  void join( ) 合并线程
   t.join() 当前线程进入阻塞 t线程执行,直到t线程执行结束,当前线程才能够被重新调度,获得CPU时间片

public class 线程的合并 {
    public static void main(String[] args) {
        System.out.println("start");
        Thread thread = new Thread(new Runable2());
        thread.start();
        //join()方法 让thread线程合并到当前线程,当前线程进入阻塞状态,thread执行直到结束
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("end");

    }
}
class Runable2 implements Runnable{

    @Override
    public void run() {
        for (int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}

静态方法
  static void yield()让位方法,暂停当前执行的线程对象,并执行其他线程
  yield()方法不是阻塞,他是暂停当前线程的运行,让他从运行状态回到就绪状态

6.多线程的安全问题

  什么时候数据在多线程并发会产生数据安全问题呢?一般有三个条件
条件1:多线程并发执行
条件2:多线程之间存在共享数据
条件3:共享数据之间存在修改行为
  为了解决线程的安全问题,就会使用线程同步机制
异步编程模型
  线程t1和t2各自执行自己的,彼此之间相互不影响,谁也不需要等谁,也就是并发

同步编程模型
  线程t1和t2在执行的时候发生了等待,在线程t1执行的时候,t2必须进行等待,只有等待t1执行结束之后t2才能够执行,t2执行的时候也是一样的原理,线程排队执行,效率低
异步编程模型会存在安全问题

package com.lf.Thread.线程安全;    
public class Account {
    private String actno;//账户
    private double Banlance;//余额

    public Account() {
    }

    public Account(String actno, double banlance) {
        this.actno = actno;
        Banlance = banlance;
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public double getBanlance() {
        return Banlance;
    }

    public void setBanlance(double banlance) {
        Banlance = banlance;
    }

    @Override
    public String toString() {
        return "Account{" +
                "actno='" + actno + '\'' +
                ", Banlance=" + Banlance +
                '}';
    }
    public void getMoney(double money){
        //取款前
        double  befor=this.getBanlance();
        //取款后
        double after=befor-money;
         //通过睡眠 来模拟网络延迟
        Thread.sleep(1000);
        //跟新余额
        this.setBanlance(after);
    }
}
package com.lf.Thread.线程安全;
public class accountThread extends Thread {
    //两个账户要实现数据共享  所以使用了构造方法的形式
    public accountThread(Account act) {
        this.act = act;
    }

    private Account act;
    @Override
    public void run() {
        act.getMoney(5000);
        System.out.println(Thread.currentThread().getName()+ "对账户"+act.getActno()+"当前余额"+act.getBanlance());
    }
}
public class test {
    public static void main(String[] args) {
        Account act = new Account("a001", 10000);
        //创建两个线程,相当于两个人来取钱
        Thread t1 = new accountThread(act);
        Thread t2 = new accountThread(act);
        t1.setName("李峰");
        t2.setName("薛建伟");
        t1.start();
        t2.start();
    }
}00);
        System.out.println(Thread.currentThread().getName()+ "对账户"+act.getActno()+"当前余额"+act.getBanlance());
    }
}

在这里插入图片描述会出现结果是两个5000的情况
发生的原因分析
public void getMoney(double money){
//取款前
double befor=this.getBanlance();
//取款后
double after=befor-money;
//通过睡眠 来模拟网络延迟
Thread.sleep(1000);
//跟新余额
this.setBanlance(after);
}
  线程1来取款,取完款还没有执行跟新余额方法线程2就进来取款了,共享数据导致的线程安全的问题
解决上面的问题 使用线程同步机制来解决

public void getMoney(double money){
    /*
    *线程同步机制 就是一个代码执行结束了 另外一个才能够执行负责必须等待
    * synchronized(线程同步对象){ 线程同步代码块}
    * ()多线程共享的数据  多线程的共享对象
    *
    * */
    synchronized (this){
        //取款前
        double  befor=this.getBanlance();
        //取款后
        double after=befor-money;
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //跟新余额
        this.setBanlance(after);
    }
}

6.1.线程的同步

1.同步方法
  用synchronized关键字修饰方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

public class Bank {
    private int count = 0;// 账户余额

    // 存钱
    public synchronized void addMoney(int money) {
        count += money;
        System.out.println(System.currentTimeMillis() + "存进:" + money);
    }

    // 取钱
    public synchronized void subMoney(int money) {
        if (count - money < 0) {
            System.out.println("余额不足");
            return;
        }
        count -= money;
        System.out.println(+System.currentTimeMillis() + "取出:" + money);
    }

    // 查询
    public void lookMoney() {
        System.out.println("账户余额:" + count);
    }
}

2.同步代码块
  用synchronized关键字修饰语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

public class Bank {
    private int count = 0;// 账户余额

    // 存钱
    public  void addMoney(int money) {
        synchronized(this){
            count += money;
        }
        
        System.out.println(System.currentTimeMillis() + "存进:" + money);
    }

    // 取钱
    public  void subMoney(int money) {
        synchronized(this){
            if (count - money < 0) {
                System.out.println("余额不足");
                return;
            }
            count -= money;
        }
        
        System.out.println(+System.currentTimeMillis() + "取出:" + money);
    }

    // 查询
    public void lookMoney() {
        System.out.println("账户余额:" + count);
    }
}

  注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可
1.synchronized() 可以写在run()方法上 扩大范围

2.synchronized() 可以写在实例方法上,他锁的就是this,不能是其他的对象了
出现在实例方法上,表示的是整个方法体都需要同步

3.synchronized()出现在静态方法上他就是类锁

3. Volatile
a.volatile关键字为域变量的访问提供了一种免锁机制
b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新
c.因此每次使用该域就要重新计算,而不是使用寄存器中的值
d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量

public class Bank {
    private volatile int count = 0;// 账户余额

    // 存钱
    public  void addMoney(int money) {
        count += money;
        System.out.println(System.currentTimeMillis() + "存进:" + money);
    }

    // 取钱
    public  void subMoney(int money) {
        if (count - money < 0) {
            System.out.println("余额不足");
            return;
        }
        count -= money;
        System.out.println(System.currentTimeMillis() + "取出:" + money);
    }

    // 查询
    public void lookMoney() {
        System.out.println("账户余额:" + count);
    }
}

6.3.java中那些变量会有线程安全的问题

java中的三大变量

1.实例变量保存在堆中的变量

2.静态变量:在方法区中
  上面两个会存在线程安全问题,因为堆中的变量和方法区中的变量是共享数据

3.局部变量在栈中保存的变量
  局部变量是线程安全的,因为每个线程就相当于一个栈,局部变量保存在栈中,不共享所以不存在线程不安全.

4.分析一下常见的
Vector是线安全的
HashTable是线程安全的
Arraylist是线程不安全的
HashMap 和 HashSet 是线程不安全的

6.4.关synchrozied()的面试题来解析

package com.lf.Thread.面试题;
public class synchronized面试题 {
    public static void main(String[] args) {
        myClass mc = new myClass();
        Thread thread=new MyThread(mc);
        Thread thread1=new MyThread(mc);
        thread.setName("t1");
        thread1.setName("t2");
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread1.start();
    }
}
class MyThread extends  Thread{
    private myClass mc;

    MyThread(myClass mc) {
        this.mc = mc;
    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("t1")){
            mc.dosome();
        }else{
            mc.doather();
        }
    }
}
class  myClass{
    public synchronized void  dosome(){
        System.out.println("dosome begin");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("dosome end");
    }
    public void doather(){
        System.out.println("doather begin");
        System.out.println("doather end");
    }
}

在这里插入图片描述doater方法的执行不需要都some方法执行完毕

class  myClass1{
    public synchronized void  dosome(){
        System.out.println("dosome begin");
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("dosome end");
    }
    public synchronized void doather(){
        System.out.println("doather begin");
        System.out.println("doather end");
    }

这里doather的执行就需要等待dosome执行结束把锁释放了才能够执行

class  myClass1{
    public synchronized void  dosome(){
        System.out.println("dosome begin");
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("dosome end");
    }
    public synchronized void doather(){
        System.out.println("doather begin");
        System.out.println("doather end");
    }

........................................................
myClass mc = new myClass();
myClass mc1 = new myClass();
Thread t1=new MyThread(mc);
Thread t2=new MyThread(mc1);
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
t2.start();

这样就不需要等待了,因为有两个对象,t1进来找t1对应的锁t2进来找t2的彼此相互不影响

class  myClass1{
    public synchronized static void  dosome(){
        System.out.println("dosome begin");
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("dosome end");
    }
    public synchronized static void doather(){
        System.out.println("doather begin");
        System.out.println("doather end");
    }

这个则需要等待了,因为静态方法他找到就是类锁了

7.死锁

定义
 线程死锁是指两个或两个以上的线程互相持有对方所需要的资源,由于synchronized的特性,一个线程持有一个资源,或者说获得一个锁,在该线程释放这个锁之前,其它线程是获取不到这个锁的,而且会一直死等下去,因此这便造成了死锁。
死锁产生的条件
  互斥条件:一个资源,或者说一个锁只能被一个线程所占用,当一个线程首先获取到这个锁之后,在该线程释放这个锁之前,其它线程均是无法获取到这个锁的。

  占有且等待:一个线程已经获取到一个锁,再获取另一个锁的过程中,即使获取不到也不会释放已经获得的锁。

  不可剥夺条件:任何一个线程都无法强制获取别的线程已经占有的锁
循环等待条件:线程A拿着线程B的锁,线程B拿着线程A的锁。

package com.lf.Thread.线程安全.死锁;
public class 死锁 {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        Thread01 thread01 = new Thread01(o1, o2);
        Thread02 thread02 = new Thread02(o1,o2);
        thread01.start();
        thread02.start();
    }
}
class Thread01 extends  Thread{
    Object o1;
    Object o2;
    public Thread01(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }
    @Override
    public void run() {
        synchronized (o1){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o2){

            }
        }
    }
}
class Thread02 extends  Thread{
    Object o1;
    Object o2;
    public Thread02(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }
    @Override
    public void run() {
        synchronized (o2){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1){
            }
        }
    }
}

7.守护线程

java中的线程分为两大类

  1. 用户线程
  2. 守护线程(后台线程) (GC垃圾回收)

  守护线程的特点:守护线程一般就是一个死循环,所有用户线程只要只要结束,守护线程自动结束

main()线程是一个用户线程

守护线程的设置 t.setDaemon(true);

package com.lf.Thread.线程安全.守护线程;
public class protectThread {
    public static void main(String[] args) {
        Thread t=new proThread();
        //设置为守护线程 ,等待用户执行完毕,自动结束
        t.setDaemon(true);
        t.start();
        for (int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"---->"+i);
        }

    }
}
class proThread extends  Thread{
    @Override
    public void run() {
        int i=1;
        while(true){
            ++i;
            System.out.println(Thread.currentThread().getName()+"----->"+i);
        }
    }
}
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值