zoukankan      html  css  js  c++  java
  • Java线程

    创建线程的方法

    1、继承Thread类

     格式如下:

    public class 类名 extends Thread {
        // 重写run()方法 (不要重写其他方法,不然运行程序会出bug)
        @Override
    	public void run() {
    		// TODO Auto-generated method stub
    	}
    }
    

    2、实现Runnable接口

     Runnable接口源码:

    package java.lang;
    /**
     * @author  Arthur van Hoff
     * @see     java.lang.Thread
     * @see     java.util.concurrent.Callable
     * @since   JDK1.0
     */
    @FunctionalInterface	// 函数式接口(可以使用lambda)
    public interface Runnable {
        public abstract void run();
    }
    

     格式如下:

    public class TestRunnable implements Runnable {
        // 必须重写run()方法
    	@Override
    	public void run() {
    		// TODO Auto-generated method stub
    	}
    }
    
    // 使用匿名内部内创建
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 返回线程ID
                Thread.currentThread().getId()
                // 返回线程名称
    			Thread.currentThread().getName();
                // 返回线程状态
                Thread.currentThread().getState()
                // 返回线程的优先级 (默认优先级:5  最小优先级:1  最大优先级:10)
                Thread.currentThread().getPriority()
    		}
    	}, "线程名");
    }
    
    // 使用lambda实现
    public static void main(String[] args) {
    	new Thread(()->{
    		// 方法体
    	}, "线程名");
    }
    

    线程优先级

      MAX_PRIORITY // 最优先级 10
      MIN_PRIORITY // 最优先级 1
      NORM_PRIORITY // 默认优先级 5

    threadName.setPriority(Thread.MAX_PRIORITY);	// 设置最大优先级  10
    threadName.setPriority(Thread.MIN_PRIORITY);	// 设置最小优先级  1
    threadName.setPriority(Thread.MIN_PRIORITY);	// 设置默认优先级  5
    

    线程状态

      1. 新建状态(New)
      2. 就绪状态(Runnable)
      3. 运行状态(Running)
      4. 阻塞状态(Blocked)
      5. 死亡状态(Dead)

     线程状态转换:

    image

    包含了等待状态的线程状态转换图:

    image

    线程阻塞的方法:sleep()join()

    sleep() 方法

      让线程进入休眠状态。
      sleep(毫秒数):使当前正在执行的线程以指定的毫秒数暂停。(设置线程休眠必须抛异常)

    public static void main(String[] args) {
    	new Thread(new Runnable() {
    		@Override
    		public void run() {
    			try {
    				for (int i = 0; i < 15; i++) {
    					System.out.println(Thread.currentThread().getName() + "第:" + i + "次");
    					// sleep(毫秒):线程休眠
    					Thread.sleep(1000); // 设置当前线程休眠1秒
    				}
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}, "线程1").start();// start():开始执行此线程; Java虚拟机调用此线程的run方法。
    }
    

    join() 方法

      join():等待这个线程死亡。

         主线程中有多个子线程,等子线程执行完后,在结束主线程。

         通常用在main()方法内。(使用join()方法必须抛异常)

      主线程和一个A线程,如果A线程调用了join()方法,就会将A线程何如到主线程中,首先执行主线程,主线程执行过程中会启动A线程,A线程在运行过程中,主线程进入阻塞状态,等到A线程运行完毕,在执行主线程。

    例:

    public static void main(String[] args) {
    	System.out.println("Main start");
    	Thread threadOne = new Thread(new Runnable() {
    		@Override
    		public void run() {
    			for (int i = 0; i < 5; i++) {
    				System.out.println(Thread.currentThread().getName() + "第:" + i + "次");
    			}
    			
    		}
    	}, "线程1");
    	
    	Thread threadTwo = new Thread(()->{
    		for (int i = 0; i < 5; i++) {
    			System.out.println(Thread.currentThread().getName() + "第:" + i + "次");
    		}
    	}, "线程2");
    	
    	threadOne.start();	// 执行线程1
    	threadTwo.start();	// 执行线程2
    	
    	try {
    		threadOne.join();
    		threadTwo.join();
    	} catch (InterruptedException e) {
    		e.printStackTrace();
    	}
    	
    	System.out.println("Main end");
    }
    

    线程Lambda表达式

     Runnable接口是一个函数式接口,可以为Rannable接口编写匿名函数

    public class TestLambda {
    	public static void main(String[] args) {
    		for(int i=0;i<4;i++) {
    			new Thread(()->{
    				for(int j=1;j<=10;j++) {
    					System.out.println(Thread.currentThread().getName()+":::"+j);
    				}
    			}, "递增线程"+(i+1)).start();
    		}
    	}
    }
    

    如何停止一个线程

    1. stop():强制停止线程,工作中不要使用

    2. 使用定时器去停止正在执行的线程,定时器是一个监听者,监听线程。

     定时器:设定一个周期,按照指定的周期重复的执行任务。

     定时器有一个定时任务(实现了Runnable接口)。

    import java.util.Date;
    import java.util.Timer;
    import java.util.TimerTask;
    
    /**
     * 定时器每隔1秒钟打印一次系统当前时间
     */
    public class TestTimerr {
    	public static void main(String[] args) {
    		Timer timer = new Timer();
    		// 参数1:定时器里面的定时任务;
            // 参数2:delay延迟多长时间执行
    		/*timer.schedule(new TimerTask() {
    			@Override
    			public void run() {
    				System.out.println(new Date());
    			}
    		}, 1000); */
            
    		// Java的定时器:指定一个周期重复执行
    		// 参数1:定时器里面的定时任务
    		// 参数2:delay延迟多长时间执行(此时表示当前时间之后的1秒钟开始执行定时任务)
    		// 参数3:每次任务执行的间隔周期(此时表示每个2秒执行一次定时任务)
    		timer.schedule(new TimerTask() {
    			@Override
    			public void run() {
    				System.out.println(new Date());
    			}
    		}, 1000,2000);
    	}
    }
    

      场景:main线程中启动一个ThreadA,同时开启一个定时任务去监听ThreadA,定时任务每间隔5秒钟扫描当前工程有没有stop文件,如果有将ThreadA线程停止掉。

    import java.io.File;
    import java.util.Timer;
    import java.util.TimerTask;
    
    public class TestStop {
    	public static void main(String[] args) {
    		StopTask stop = new StopTask(true);
    		Thread stopThread = new Thread(stop);
    		stopThread.start();
    		// 创建定时器,定时器包含一个定时任务,每个5秒钟检查当前工程下面有没有stop文件,如果有停止线程
    		Timer timer = new Timer();
            // schedule()方法调度定时任务的执行
    		timer.schedule(new TimerTask() {
    			@Override
    			public void run() {
    				File file = new File("stop");
    				// 条件成立:表示stop文件存在,停止线程
    				if(file.exists()) {
    					stop.setFlag(false);
    					// StopTask任务停止,立马删除stop文件
    					file.delete();
    					// 取消定时任务
    					timer.cancel();
    				}
    			}
    		}, 1000,5000);
    	}
    }
    
    class StopTask implements Runnable{
    	private boolean flag ;
    
    	public void setFlag(boolean flag) {
    		this.flag = flag;
    	}
    	public StopTask(boolean flag){
    		this.flag = flag;
    	}
    
    	@Override
    	public void run() {
    		long index=0;
    		for(;flag;) {
    			index++;
    			if(index%500000000==0) {
    				System.out.println(Thread.currentThread().getName()+":::"+index);
    			}
    		}
    	}	
    }
    

    image

    守护线程

    线程:执行线程(用户线程)、守护线程(守护执行线程的,一旦执行线程结束了,守护线程也会结束)

    通常JVMGC线程就是一个守护线程

    // 设置t1线程为守护线程
    t1.setDaemon(true);
    

    同步

    线程安全和非线程安全

      非线程安全:多个线程同一时刻访问一个共享资源,造成共享资源的数据出现脏数据,不安全(数组下标越界)

      线程安全:需要对共享资源上一把锁,确保同一时刻最多只能有一个线程访问共享资源,其他线程在外面等待,不会出现脏数据。

    synchronized 关键字

      同步:对共享资源进行同步,确保同一时刻最多只能有一个线程访问共享资源

      共享资源同步特征:确保共享资源在每个线程使用过程中数据的有效性和一致性,避免脏数据

      同步机制:为共享资源上锁

      synchronize就是为共享资源上锁,确保同一时刻最多只能有一个线程访问共享资源

    小结

    • 工作中共享资源必须加锁

    • 为了提供程序在内存中运行的效率,尽量不要锁住整个方法,而是锁代码块

    • 那些代码块需要上锁?

        first:某个共享资源的数据经常会发生改变

        second:被多个线程使用

    • 使用synchronize修饰某个块代码,叫做同步块

    • 被synchronize修饰的代码叫做临界区(临界区的代码块最多只能有一个线程访问)

    • synchronize是一个JVM级别的互斥锁(排它锁)

        JVM帮你加锁和解锁(不是人为加锁和解锁)

        缺点:一旦出现异常可能无法解锁,因为不是你手工加锁

    • 同步块括号不能少,括号里面的对象表示你要锁住的共享资源,最好使用final修饰,因为不可改变的

    • synchronize括号里面的对象不支持基本类型

    Lock 重入锁

     @SinceJDK1.5,重入锁可以进行加锁和解锁,比synchronize更加友好,一旦出现异常可以人工解锁

       特征:有一个公平机制,等待时间最长的线程优先进入共享资源

     创建格式如下:

    //true:启动公平锁  false:非公平锁  默认false
    private Lock lck = new ReentrantLock(true);
    

     使用Lock格式如下:

    // 加锁
    lck.lock();
    try{
       
    }finally{
        // 解锁
        lck.unlock();
    }
    // 好处:一旦try块出现异常执行finally,将锁解掉
    // 解锁操作工作中一定要放在finally块中,出现异常立马解锁。
    // 工作中如果使用Lock,一定先编写整体(try...finally块),再编写局部(try块里面的内容)
    

    重入锁功能详解 https://www.cnblogs.com/takumicx/p/9338983.html

    读写锁

      由读锁(如果你为多个方法加了读锁,操作多个读方法的线程可以同时进入临界区)和写锁(如果多个方法加上了写锁,最多只能有一个线程进入临界区)组成。

      :并行,一旦操作共享资源多个线程的读方法进入临界区,所有的操作写方法的线程必须在外面等待

      :串行,一旦某个线程进入了贡献在资源的写方法临界区,所有的读方法在外面等待,其他的操作写方法的线程也将在外面等待。

    一般而言:只是访问共享资源的数据添加读锁,改变共享资源的数据添加写锁。

    一旦某个读方法进入临界区,其他的读方法也可以进入,所有的写方法在临界区外面(锁池)等待

    一旦某个写方法进入临界区,其他的写方法在临界区外等待,所有的读方法也在临界区外面等待

    读写锁效率高于重入锁(Lock)

      wait() notify() notifyAll():必须配合synchronized或者Lock一起使用,如果没有synchronized或者Lock会在程序运行时抛出IllegalMonitorStateException。因为wait()不能多个线程同时进入,一旦多个线程同时进入wait()就会出现死锁的可能,JVM会抛出IllegalMonitorStateException。

      阻塞状态:wait()/notify()/notifyAll() sleep() join()

      死锁:所有的线程都进入了wait() 阻塞状态,无法唤醒对方

      正确情况:生产者唤醒消费者,消费者也能唤醒生产者

    常见面试题:

      解释线程的死锁?工作中你遇到过线程死锁码?什么情况会发生线程死锁

      IllegalMonitorStateException是什么异常?工作中有没有遇到过该异常?如何避免该异常

      sleep()方法和wait()方法区别?

        sleep()方法不会释放共享资源的锁,wait()会释放共享资源的锁

    Lock如何代替synchronized?

    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     *  店员是一个共享资源 :可以服务生产者和消费者
     *  店员后面有一个餐台:只能放一个产品 -1 表示餐台空  其他表示餐台非空
     */
    public class Clerk {
    
    	/**
    	 * 默认餐台为空:生产者开始生产,消费者等待
    	 */
    	private int product = -1;
    	private Lock lck = new ReentrantLock(true);
    	
    	/**
    	 * 锁的条件
    	 * condition对象必须配合lck一起使用
    	 */
    	private Condition condition = lck.newCondition();
    		// wait --->condition.await();
    		// notify---->condition.signal();
    		// notifyAll---->condition.signalAll();
    	/**
    	 * 生产者调用方法
    	 * product:表示生产者生产的产品
    	 */
    	public void setProduct(int product)throws Exception{
    		lck.lock();
    		try {
    			// 条件成立:餐台非空生产者等待
    			while(this.product!=-1) {
    				condition.await();
    			}
    			// 生产者开始生产产品
    			this.product = product;
    			// 生产完毕,通知(唤醒)消费者线程,拿走产品
    			condition.signalAll();
    			System.out.println("生产者线程生产了第"+this.product+"个产品...");
    		} finally {
    			lck.unlock();
    		}
    	}
    	
    	/**
    	 * 消费者调用的方法
    	 * @throws Exception
    	 * IllegalMonitorStateException: wait()  notifyAll()必须配合synchronized一起使用
    	 * 否则就会出现上面的异常
    	 */
    	public int  getProduct()throws Exception{
    		lck.lock();
    		try {
    			//条件成立:表示餐台为空,消费者等待
    			while(this.product==-1) {
    				condition.await();
    			}
    			//消费者取走产品
    			int tmp = this.product;
    			//将餐台设置为空
    			this.product =-1;
    			//通知(唤醒)生产者开始生产
    			condition.signalAll();
    			System.out.println("消费者线程拿走了第"+tmp+"个产品...");
    			return tmp;
    		} finally {
    			lck.unlock();
    		}
    	}
    }
    
    

    无界队列:LinkedBlockingQueue

      基于链表结构的阻塞式队列,也就是说它是建立在LinkedList基础上的

    Blocking 阻塞:当集合中的元素已经放满了,还继续往里面添加就会阻塞

           当集合中的元素已经空了,还继续获取元素也会阻塞

    特征:FIFO First-In-First-Out 先进先出

      无界限:容量可以是Integer的最大值 2^31-1

    import java.util.concurrent.LinkedBlockingQueue;
    
    public class TestLinkedBlockQueue {
    	public static void main(String[] args) {
    		// 队列最大容量为3
    		LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>(5);
    		queue.add(1);
    		queue.add(10);
    		queue.add(100);
    		// 超过最大容量,还在网里面添加元素就会抛出IllegalStateException: Queue full
    		// queue.add(1000);
    		try {
    			// 队列元素已经到达上限(满了),还往里面放元素就会阻塞,InterruptedException在阻塞的过程中可能会发生中断异常
    			queue.put(1000);
    			System.out.println(queue.size());
    			// 获取元素首部元素 结果:1
    			Integer num = queue.peek();
    			System.out.println(num);
    			// size:4 仅仅获取元素没有删除(弹出)元素
    			// System.out.println(queue.size());
    			// 删除队列中的首部元素并返回删除(弹出)的结果
    			// Integer num2 =queue.poll();
    			// 队列中只有3个元素,循环执行poll()方法5次,既没有抛出异常,也没有阻塞,返回null
    			// for(int i=0;i<5;i++) {
    			// Integer num3=queue.poll();
    			// System.out.println(num3);
    			// }
    			// System.out.println(queue.size());
    			// take():获取队列首部元素,并将首部元素弹出(删除),一旦队列中没有元素了还在调用take()方法获取元素,程序就会阻塞
    			for (int i = 0; i < 5; i++) {
    				// 循环体执行第5次会阻塞
    				Integer num6 = queue.take();
    				System.out.println(num6);
    			}
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    	}
    }
    

    往队列添加元素

      add:队列满了会抛出IllegalStateException异常

      offer:队列满了,拒绝处理(新元素无法添加到集合中),不会抛出异常也不会阻塞

         成功添加返回ture ,添加失败返回false

      put:队列满了程序进入阻塞状态,不会抛出异常

    向队列获取元素

      peek:会获取首部元素,不会弹出(删除)元素

      poll:获取首部元素,并弹出(删除)元素

      take:获取首部元素,并弹出(删除)元素 , 如果队列为empty,程序进入阻塞

    线程常见面试题

    线程和进程有什么区别?

      线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。每个线程都拥有单独的栈内存用来存储本地数据。

    start() 和 run() 方法有什么区别?

      start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。

    线程优先级的级别有哪些?

      高优先级10 、低优先级1、普通优先级5(默认)。

    笔记过于简单,如需多多学习,详见:Java线程详解

  • 相关阅读:
    路飞学城系列:第2章 数据类型&文件操作-学习笔记【2.1-2.34-列表细讲、元组、字符串细讲、字典、集合、二进制与字符编码、16进制、HASH、为何dict字典查询速度快、用py操作文件、file类的其它必用功能等】
    路飞学城系列:第1章 Python基础语法入门-学习笔记(4)-补充【变量的创建、身份运算、空值None、三元运算】
    路飞学城系列:第2章 数据类型&文件操作-练习(3)【模拟登陆-等待优化ing-读写不能同时进行ing】
    路飞学城系列:第2章 数据类型&文件操作-练习(2)【文件操作:全局替换程序】
    路飞学城系列:第2章 数据类型&文件操作-练习(1)【列表index()方法的操作练习】
    路飞学城系列:第1章 Python基础语法入门-作业(1)【双色球选购-中文-自由发挥】
    Java泛型的理解
    LeetCode 力扣 99. 恢复二叉搜索树
    深入理解Java虚拟机---栈,堆,方法区
    java中API补充总结
  • 原文地址:https://www.cnblogs.com/lyang-a/p/15077398.html
Copyright © 2011-2022 走看看