Java基础总结—多线程篇

本文详细介绍了Java中的多线程概念,包括进程与线程的区别、线程的创建(继承Thread、实现Runnable、实现Callable)、线程生命周期、线程安全(同步机制、死锁)以及守护线程和定时器的使用。通过实例代码展示了wait和notify的使用,以及生产者消费者模式的应用。
摘要由CSDN通过智能技术生成

多线程【重点】

一、基础知识

1、进程和线程区别

进程是资源分配的最小单位,线程是cpu调度的最小单位,一个进程中可以包含多个线程

2、线程的三种创建方式

  • 继承Thread类

    package com.sqx;
    /*
    *   创建线程方式一
    */
    public class CreatThread {
        public static void main(String[] args) {
            MyThread thread = new MyThread();
            thread.start();                    //开辟新的占空间,该方法执行完成后瞬间结束!
            for(int i = 0 ; i < 100 ; i++ ){
                System.out.println("当前执行main线程----->" + i);
            }
        }
    }
    class MyThread extends Thread{
        @Override
        public void run() {
            try {
                Thread.sleep(5000);         //当前线程休眠5秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            for(int i = 0 ; i < 100 ; i ++ ) {
                System.out.println("当前执行分支线程------>" + i);
            }
        }
    }
    /*
    输出结果:
        当前执行main线程----->62
        当前执行分支线程------>0
        当前执行分支线程------>1
        当前执行分支线程------>2
        当前执行main线程----->63
    */
    
  • 实现Runnable接口

    /*
     *   创建线程方式二 【常用一些】
     */
    public class CreatThread2 {
        public static void main(String[] args) {
           /* MyRunnable runnable = new MyRunnable();
            Thread thread = new Thread(runnable);*/             //格式2
    
            //Thread thread = new Thread(new MyRunnable());    //格式1
    
            Thread thread = new Thread(new Runnable() {         //匿名内部类
                public void run() {
                    for(int i = 0 ; i < 100 ; i ++ ) {
                        System.out.println("当前执行分支线程------>" + i);
                    }
                    Thread thread = Thread.currentThread();  //静态方法!获取当前线程对象
                    System.out.println("当前线程"+thread);   //得到当前线程对象,当前线程Thread[Thread-0,5,main]
                }
            });
            thread.start();                    //开辟新的占空间,该方法执行完成后瞬间结束!
            for(int i = 0 ; i < 100 ; i++ ){
                System.out.println("当前执行main线程----->" + i);
            }
        }
    }
    
    class MyRunnable implements Runnable{
        public void run() {
            for(int i = 0 ; i < 100 ; i ++ ) {
                System.out.println("当前执行分支线程------>" + i);
            }
    
        }
    }
    /*
     测试结果:
        当前执行main线程----->84
        当前执行分支线程------>33
        当前执行main线程----->85
        当前执行分支线程------>34
        当前执行main线程----->86
    * */
    
  • 实现Callable接口 (jDK8新特性)

    //FutureTask方式,实现Callable接口 ,优点:该线程可以有返回值!
    
    public class CreatThread3 {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            FutureTask task = new FutureTask(new Callable() {
                public Object call() throws Exception {
                    System.out.println("分支线程开始!");
                    return 123;
                }
            });
            Thread thread = new Thread(task);
            thread.start();
            System.out.println(task.get());   //主线程获取分支线程的返回值
        }
    }
    
    

3、线程的生命周期

线程有时间片,才会运行,不然就抢夺时间片!

4、获取当前线程对象

Thread thread = Thread.currentThread();  //静态方法!获取当前线程对象
thread.getName() ; thread.setName() ;

5、线程休眠 Thread.sleep

package com.sqx;
/*
*   线程休眠,中断线程线程休眠
* */
public class ThreadSleep {
    public static void main(String[] args) {
        MyRunnable03 myRunnable03 = new MyRunnable03();
        Thread t = new Thread(myRunnable03);
        t.start();
        System.out.println("main线程开始");
        try {
            Thread.sleep(1000*5);   //main线程休眠5秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

     //   t.interrupt();   //方式一 : 中断分支线程,抛出异常sleep interrupted
     //   t.stop();   //直接杀死线程!栈都回收了,造成丢失数据!(该方法已经过时)


     // 合理终止线程休眠(常用): 打个bool 标价,如果true就继续休眠,否则直接结束
        myRunnable03.isSleep = false ;
    }
}

class MyRunnable03 implements Runnable{
    boolean isSleep = true ;
    public void run() {
        for( int i = 0 ; i < 10 ; i ++ ) {
            if (isSleep == true){
                System.out.println("分支线程 ---- >" + i);
                try {
                    Thread.sleep(1000*1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else{

                System.out.println("分支线程 ---- >" + i);
                return ;
            }
        }
    }
}
//线程休眠会使得当前线程放弃之前抢夺的时间片,从而线程进入就绪状态!

了解

  1. 线程优先级

    public class ThreadPriority {
        public static void main(String[] args) {
            System.out.println(Thread.MAX_PRIORITY);    //线程的最大优先级 10
            System.out.println(Thread.MIN_PRIORITY);    //线程的最小优先级 0 
            System.out.println(Thread.NORM_PRIORITY);   //线程的默认优先级 5
            
            System.out.println(Thread.currentThread().getPriority());   //获取当前线程的优先级
            Thread.currentThread().setPriority(10); //设置当前线程的优先级为10
        }
    }
    
  2. 线程合并

            Thread thread = new Thread(new MyRunnable05());
            thread.start();
            thread.join();    //讲thread线程合并到当前main线程中执行,栈不会改变仅仅是执行顺序!
    
  3. 线程让位

    		Thread.yield();    //当前线程让一下,当前线程此刻不回去抢占时间片,下一刻继续抢占
    

需要注意:Java的线程调度是抢占式调度模型,优先级高的线程抢占时间片的概率大!

二、线程安全 *

线程安全问题的产生需要满足:多线程并发、共享数据、共享数据有修改操作 ,如:两人同时取银行卡里的余额!

怎么解决线程安全问题?

  • 引入“线程同步机制”,也就是让线程排队,不再并发执行
  • 为了安全,可以牺牲一部分效率

了解 异步,同步编程模型

  • 异步编程模型 : 线程t1 ,t2各自执行各自的,t1不管t2,t2不管t1 ;谁也不需要等谁,本质就是多线程并发
  • 同步编程模型: 线程t1 ,t2需要满足 t2执行之前必须等待t1线程执行结束,两线程发生了等待关系 本质:线程排队执行

注意:异步就是并发,同步就是排队

Synchronized 同步代码块

//t1 、t2 两个线程同时向一个共享账户Account发起取款,如何避免线程的并发执行 ?

在我们的取款方法体上添加一层
public void withdraw(int count){
    synchronized (需要排队的线程的共享对象){    //注:一个对象只有一把锁
    		方法体
	}
}    
当我们的t1 、t2线程调用withdraw方法时,先遇到synchronized(共享对象)的线程,会直接拿走锁池(lock)中共享对象的对象锁,从而执行方法体,此时共享对象的锁已经被占用,我们的另外一个线程只能等待共享对象的锁的归还,也就是等待上一个线程线程执行结束该同步代码块,执行结束才会归还锁,我们未执行该方法的线程再去获取锁,去执行方法!

为什么非得传入共享对象?

因为传入一个共享对象,两个线程中共享的这一个对象的对象锁被拿走,另一个对象无法获取,因此无法进入同步代码块!但是如果传入的是非共享对象,则一个线程把这个非共享对象的锁拿走,并不影响另外一个线程任何操作! (共享可以看作,两线程共用这一个)

局部变量是线程安全的!因为存储在栈中,数据不共享,常量不可修改也是线程安全的!

静态变量和实例变量则是分别在方法区和队中,存在数据共享,可能会导致线程不安全问题

Synchrnoized三种写法

方式一:同步代码块

public void withdraw(int count){
    synchronized (需要排队的线程的共享对象){     //优点:灵活,哪里需要加哪里
    		方法体
	}
}  

方式二:实例方法上添加Synchronized

public synchronized void withdraw(int count){   //默认是方法体中的所有都需要同步,而且共享对象为this
    										    //优点:省代码
}  

方式三:在类上添加Synchronized

//表示找类锁,一个类一个锁,即使创建100个对象也仍然只有1个锁!

扩展:对象锁保证的是实例变量的安全、类锁保证的是静态变量的安全!

三、死锁

实现一个死锁,由于死锁不会报错,因此很难调试,我们只有会写死锁,以后才能注意 !

package com.sqx;
/*
*   实现一个死锁!
* */
public class DeadLock {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        MyThread1 thread1 = new MyThread1(o1, o2);
        MyThread2 thread2 = new MyThread2(o1, o2);
        thread1.start();
        thread2.start();
    }
}
class MyThread1 extends Thread{
    Object o1 ;
    Object o2 ;
    public MyThread1(Object o1 , Object o2){
        this.o1 = o1 ;
        this.o2 = o2 ;
    }
    public void run(){
        synchronized (o1){
            synchronized (o2){    //等待o2锁的释放

            }
        }
    }
}
class MyThread2 extends Thread{
    Object o1 ;
    Object o2 ;
    public MyThread2(Object o1, Object o2){
        this.o1 = this.o1;
        this.o2 = this.o2;
    }
    public void run(){
        synchronized (o2){      //等待o1锁的释放
            synchronized (o1){

            }
        }
    }
}

四、守护线程

守护线程一般在默默运行,用户线程全部结束,守护线程也会自动结束!

线程分为两类:

  1. 用户线程
  2. 守护线程

创建一个线程,直接设置为守护线程即可

t.setDeamon(true)   //讲当前t线程设置为守护线程!

五、定时器

实际开发中,每隔多久执行一段特定的程序,这种需求是很常见的!

方式一:Thread.Sleep, 设置睡眠多长时间,执行任务,最原始的方式 ;

方法二:Java类库中写好的一个定时器java.util.Timer,可以直接用,但是用的少,框架一般都带自己的定时器,

方式三 : 使用最多的就是spring框架当中提供的springTask框架,只需简单的配置就可以完成定时器(底层还是方式二);

//方式二的实现原理
public class TimeTest {
    public static void main(String[] args) throws ParseException {
        Timer timer = new Timer();  //创建定时对象
        //Timer timer = new Timer(true); Timer以守护线程的形式运行

        //指定定时任务(要执行的任务,第一次执行时间,间隔多久执行一次)
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date FirstDate = dateFormat.parse("2021-9-28 17:40:00");
        timer.schedule(new LogTask(),FirstDate,1000*10);   //可以改为匿名内部类方式

    }
}
class LogTask extends TimerTask{

    public void run() {
        //定时执行的任务
		System.out.println("到时间了!");
    }
}

六、wait和notify

wait() 和 notify () 是Object 的方法 , 通常结合synchrnoized使用

  • wait()意思是说,我等会儿再用这把锁(对象锁!),CPU也让给你们,我先休息一会儿!
  • notify()意思是说,我用完了,你们谁用?

测试代码:

package com.sqx;

public class WaitTest {
    public static void main(String[] args) {
        Object obj = new Object() ;
        MyThread01 t1 = new MyThread01(obj);
        MyThread02 t2 = new MyThread02(obj);
        t1.start();
        t2.start();
    }
}

class  MyThread01 extends Thread{
    Object object ;

    public MyThread01(Object object) {
        this.object = object ;
    }

    @Override
    public void run() {
        synchronized (object){
            System.out.println("T1线程执行");
            try {
                object.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("T1线程结束!");
        }

    }
}

class  MyThread02 extends Thread{
    Object object ;

    public MyThread02(Object object) {
        this.object = object ;
    }
    @Override
    public void run() {
        synchronized (object){
            System.out.println("T2线程执行");
            object.notify();
            System.out.println("T2线程结束!");

        }

    }
}
/*
结果:
T1线程执行
T2线程执行
T2线程结束!
T1线程结束!
流程:
T1启动,让出锁,让出CPU,T2获得CPU,启动,唤醒使用了object的休眠的线程,
T1被唤醒后等待启动,T2继续执行,T2执行完,T1获得CPU后继续执行。
* */

生产者消费者模式


多线程基础了解到这里即可,我们接下来就是JUC

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Today不上发条

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值