java多线程详解

一、线程简介

1、进程和线程
  • 程序: 开发出来的代码称之为程序。程序就是一堆代码,是一个静态的概念。(车间)
  • 进程(Process): 程序运行起来我们称为进程。进程是程序的一次执行,是一个动态的概念。进程有生命周期,会随着程序的终止而销毁。(运作的车间)
  • 线程(Thread): 线程是进程的运作单位。一个进程包含多个线程。(车间的任意一条流水线)
  • 注意:很多多线程都是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器,如果是模拟出来的多线程,即一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。
2、并发、并行、串行
  • 并发: 同一个对象被多个线程同时操作。(这是一种假并行。即一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉)。
  • 并行: 多个线程同时执行。并行必须有多核才能实现,否则只是并发(假并行)
  • 串行: 多个线程执行完一个再执行下一个。
3、线程生命周期的五种状态

在这里插入图片描述

  • 新建: 线程已经创建,但并未调用start()方法启动。
  • 就绪: 线程启动start()后,等待CPU分配资源。
  • 运行: 当就绪的线程获取CPU资源时,便进入运行状态,run()方法定义了线程池的操作和功能。
  • 阻塞: 在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态。
  • 死亡: 线程完成他的全部工作或线程被提前强制性的中止或异常导致结束。

二、线程实现

1、继承Thread类,重写run方法
public class WindowsThread {
    public static void main(String[] args) {
        WindowThd w1 = new WindowThd();
        WindowThd w2 = new WindowThd();
        WindowThd w3 = new WindowThd();
        w1.setName("线程1");
        w2.setName("线程2");
        w3.setName("线程3");
        w1.start();
        w2.start();
        w3.start();

    }
}
class WindowThd extends Thread{
    private static int num=100;
    public void run(){
        while(true){
            //同步代码块
//            synchronized (WindowThd.class){
//                if(num>0){
//                    System.out.println("---"+Thread.currentThread().getName()+"出售票:"+num+"---");
//                    num--;
//                }else{
//                    break;
//                }
//            }
            show();
        }
    }
    //
    public synchronized void show(){//同步监视器WindowThd.class
        if(num>0){
            System.out.println("---"+Thread.currentThread().getName()+"出售票:"+num+"---");
            num--;
        }
    }
}
2、实现Runnable接口,重写run方法
public class WindowsRunnable {
    public static void main(String[] args) {
        WindowRun w = new WindowRun();
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3= new Thread(w);
        t1.setName("线程1");
        t2.setName("线程2");
        t3.setName("线程3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class WindowRun implements Runnable{
    private int num=100;
    public void run(){
        while(true){
            synchronized (this){
                if(num>0){
                    System.out.println("---"+Thread.currentThread().getName()+"出售票:"+num+"---");
                    num--;
                }else{
                    break;
                }
            }
            show();


        }
    }
    public synchronized void show(){//同步监视器this
        if(num>0){
            System.out.println("---"+Thread.currentThread().getName()+"出售票:"+num+"---");
            num--;
        }
    }

}
3、实现Callnable接口,重写run方法
public class WindowsCallnable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<String> w = new WindowCall();
        Callable<String> callA = new WindowCall();
        Callable<String> callB = new WindowCall();
        Callable<String> callC = new WindowCall();

        FutureTask<String> futureA = new FutureTask<String>(callA);
        FutureTask<String> futureB = new FutureTask<String>(callB);
        FutureTask<String> futureC = new FutureTask<String>(callC);

        new Thread(futureA).start();
        new Thread(futureB).start();
        new Thread(futureC).start();

        System.out.println("A执行返回结果:"  + futureA.get());
        System.out.println("B执行返回结果:"  + futureB.get());
        System.out.println("C执行返回结果:"  + futureC.get());

    }
}
class WindowCall implements Callable<String> {

    @Override
    public String call() throws Exception {
        for(int i = 0 ; i < 100; i++) {
            System.out.println("线程运行, x = " + i);
        }
        return "执行完毕";  // 返回值
    }
}

三、线程常用方法

Thread类api方法

1. start(): 启动当前线程:调用当前线程的run()
2. run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3. currentThread(): 静态方法,返回执行当前代码的线程
4. getName(): 获取当前线程的名字
5. setName(): 设置当前线程的名字
6. yieId(): 释放当前cpu的执行权
7. join(): 在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态
8. stop(): 已过时。当执行此方法时,强制结束当前线程
9. sleep(): 让当前线程“睡眠”指定的millitime毫秒。在指定的时间内,当前线程是阻塞状态
10. isAlive(): 判断当前线程是否存活

通讯方法

1. wait():
2. notify():
1. notifyAll():

四、多线程应用

1、线程安全性问题
例子:创建三个窗口卖票,总票数为100张,使用实现Runnable接口的方式
 1、问题:卖票过程中,出现了重票、错票 --->出现了线程的安全问题
 2、问题出现原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票,同时操作num
           会发生重票和错票的情况。
 3、如何解决:当一个线程a在操作num的时候,其他线程不能参与进来。直到线程操作完成num时,其他线程才可以
           开始操作num。这种情况及时线程a出现了阻塞,也不能被改变。
 4、线程安全性问题
	一、同步代码块
	    synchronized(同步监视器){
	        //需要被同步的代码
	    }
	    说明:
	        1、操作共享数据的代码,即需要被同步的代码。 --->不能包含多了,也不能包含少了
	        2、共享数据:多个线程共同操作的变量。比如num就是共享数据
	        3、同步监视器:俗称,锁。任意一个类的对象,都可以充当锁
	            要求:多个线程必须共用一把锁
	    补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器
	        在继承Thread类创建多线程的方式中,我们可以用类名.class充当同步监视器
	  二、同步方法
	  	//继承Thread类
	 	public static synchronized void show(){//同步监视器WindowThd.class
	        //需要被同步的代码
	    }
	    //实现Runnable接口
	 	public synchronized void show(){//同步监视器this
	        //需要被同步的代码
	    }
	    在需要被同步的方法上加synchronized
	    注意:Thread必须用static修饰方法
     	     1)继承Thread类的同步方法必须加static修饰方法,同步监视器指本类.class
	         2)实现Runnable接口的同步方法不需要加static,同步监视器指本类this
	 
	 备注:
	    同步的方式,解决了线程的安全问题。---好处
	    操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。---局限性
	
	三、lock同步代码块

解决线程安全问题的方式三:Lock---Jdk5.0新增

	1synchronized与lock的对比
	 1)lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放
	 2)lock只有同步代码块锁,synchronized有同步代码块锁和同步方法锁
	 3)使用lock锁,Jvm将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
	 优先使用顺序
	    lock-->同步代码块(已经进入了方法体,分配了相应资源)-->同步方法(在方法体之外)

	2、面试题:synchronizedLock的异同?
	     相同:二者都可以解决线程安全问题   
	     不同:synchronized机制在执行完全相应的同步代码以后,自动的释放同步监视器
	        lock需要手动的启动同步lock()方法,同时结束同步也需要手动的实现unlock()方法
	3、面试题:如何解决线程安全问题?有几种方式
		两种方式
	         synchronized:同步代码块,和同步方法体
	         lock:同步代码块

	
class WindowLk implements Runnable{
    private static int num=100;
    //1、实例化ReentrantLock true是指是否交替执行
    private ReentrantLock lock = new ReentrantLock(true);

    public void run(){
        while(true){
            try{
                //2、调用锁定方法lock()
                lock.lock();

                if(num>0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }

                    System.out.println("---"+Thread.currentThread().getName()+"出售票:"+num+"---");
                    num--;
                }else{
                    break;
                }
            }finally{
                //3.调用解锁方法:unlock()
                lock.unlock();
            }

        }
    }
}

2、线程死锁

1、死锁的理解:
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要同步的同步资源,就形成了线程的死锁
2、说明:

  1. 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
  2. 我们使用同步时,要避免出现死锁

五、多线程常见面试题

1、比较创建线程的俩种方式(Thread和Runnable)

开发时: 优先选择Runnable接口的方式
原因:

  1. 实现的方式没有类的单继承方式的局限性
  2. 实现的方式更适合来处理多线程的共享数据的情况
    Treand共享线程类的全局变量必须加static
    Runnable共享线程类的全局变量不需要加static

联系: public class Thread implements Runnable
相同点: 俩种方式都需要重写run(),将线程要执行的逻辑声明再run()中

2、说说你对IDEA中Project和Module的理解

1、Project:相当于eclipse中的workspace
2、Module:相当于eclipse中的project

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值