一、为什么学习线程
1. 应付面试:必问题
——线程的实现方式,解决线程完全问题,线程池
2. 并发编程:多个用户同时执行相同的操作
——实际工作中,项目一定要解决并发问题,保证多个用户可以同时执行操作
——实际开发过程中,很少写线程的代码,这部分代码已经被封装了起来
——使用的多,写得少,必须掌握
二、进程和线程
1. Ip
比如某台计算机连接了某个网络,一台计算机可以有多个ip地址(win+R打开命令提示符,输入ipconfig)
2. 进程
计算机上正在运行的某一个应用程序,是资源(内存,CPU等)分配的基本单位,当在计算机上运行某个应用程序时,操作系统会给当前的应用程序,分配资源,并且创建对应的进程,将进程放入进程队列中,进程调度器会为当前进程分配对应的CPU时间,运行程序。
一个进程可以有多个线程
3. 线程
线程是一条执行线路,是程序执行的最小单位,是进程的一个执行流,是CPU调度和分流的最基本的单位,一个进程可以有多个线程,线程之间共享进程的所有资源,每个线程有自己的内存空间,线程由CPU调度执行,允许多个线程同时运行。
4. 并发
当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。这种方式我们称之为并发(Concurrent)。(同一时间,不能同时执行)
5. 并行
当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。(同一时间,同时执行)
6. 区别
并发和并行是即相似又有区别的两个概念,并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。在多道程序环境下,并发性是指在一段时间内宏观上有多个程序在同时运行,但在单处理机系统中,每一时刻却仅能有一道程序执行,故微观上这些程序只能是分时地交替执行。倘若在计算机系统中有多个处理机,则这些可以并发执行的程序便可被分配到多个处理机上,实现并行执行,即利用每个处理机来处理一个可并发执行的程序,这样,多个程序便可以同时执行。
7. 高并发
登录(同一时间多个人同时登录),商品浏览,订单...(同一时间多个人同时操作)
系统需要在同一时间处理不同的操作,多个操作需要同时执行
高并发(High Concurrency)是现在互联网设计系统中需要考虑的一个重要因素之一,通常来说,就是通过严谨的设计来保证系统能够同时并行处理很多的请求。这就是大家常说的「 高并发 」。
高并发是指多个执行单元同时、并行被执行,而并发的执行单位对于共享资源(硬件资源和软件上的全局变量、静态变量等)的访问很容易导致竞态(race conditions)
8. 进程和线程关系
进程:计算机上正在运行的某一个应用程序
线程:内存空间分配,线程是一条执行线路,是程序执行的最小单位
一个执行的应用程序就是一个进程(QQ),一个程序中可以包含多个线程(语音+信息)
一个线程一定属于某一个进程,一个进程可以有多个线程,线程是进程执行的最小单位
进程分配内存空间,同一进程下的所有线程共享进程的所有资源,同一进程中的所有线程共享代码/数据
真正运行的是线程
三、多线程
Thread类
特点:
继承
可以选择性的重写父类的fun()方法
run()方法没有返回值,不能抛出异常
创建的类对象即为线程对象
this对象是thread对象,可以调用thread类提供的方法
使用new类()创建对象
无法实现资源共享
代码
创建一个普通java项目即可
public class MyThread extends Thread{ private int tickect=5; @Override public void run() { while (tickect>0){ System.out.println(Thread.currentThread().getName()+"卖出去一张票,还剩"+tickect--); } } }
public class MyTest { public static void main(String[] args) { // 创建的类对象即为线程对象 MyThread myThread1=new MyThread(); //setName 给线程起别名 myThread1.setName("eee"); // 使用start()方法启动线程 myThread1.start(); MyThread myThread2=new MyThread(); myThread2.setName("abc"); myThread2.start(); } }
这里用两个人卖车票举例,代码中将MyThread重建了两次,也就是现在有两个线程对象(两个人)进行MyThread类中的卖车票循环,这里先看运行结果
由于是建了两个对象,就相当于是a执行一遍,b又执行了一遍。他们两人各自都卖了五张票,顺序问题不用管,因为是线程在抢运行资源,谁抢到谁运行,所以才会出现这种情况
Runnable接口
特点:
必须实现run()方法
run()方法没有返回值,不能抛出异常
创建的对象就是类对象,不是线程,要使用线程,需要先new Thread(runnable)
this对象类对象,不可以直接调用Thread类提供的方法
先创建类对象,再new Thread(类对象)
创建线程时,类对象可以是同一个,实现资源共享
代码:
public class MyThread implements Runnable{ private int tickect=5; @Override public void run() { while (tickect>0){ System.out.println(Thread.currentThread().getName()+"卖出去一张票,还剩"+tickect--); } } }
public class MyTest { public static void main(String[] args) { MyThread myThread1=new MyThread(); Thread thread1=new Thread(myThread1,"test1"); Thread thread2=new Thread(myThread1,"test2"); thread1.start(); thread2.start(); } }
运行结果:
这里就是他们两人共卖五张票,这个运行结果每次运行出来的都不一样
Callable接口
特点:
必须实现call()方法
call()方法有返回值Object,能够抛出异常
可以添加泛型,定义call()方法的返回值
先创建类对象,再创建FutureTask对象,再创建Thread(FutureTask)对象
start()启动线程,自动调用run()方法,run()方法再调用call()方法,对于同一个FutureTask对象,call()方法只调用一次
代码:
public class MyThread implements Callable { private int tickect=5; @Override public Object call() throws Exception { while (tickect>0){ System.out.println(Thread.currentThread().getName()+"卖出去一张票,还剩"+tickect--); } return "ok"; } }
public class MyTest { public static void main(String[] args) { MyThread myThread3=new MyThread(); FutureTask<String> futureTask1=new FutureTask<>(myThread3); FutureTask<String> futureTask2=new FutureTask<>(myThread3); Thread thread1=new Thread(futureTask1); Thread thread2=new Thread(futureTask2); thread1.start(); thread2.start(); //获取返回值 String s=null; try { s=futureTask1.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println(s); } }
运行结果如下:
这里没有起别名,Thread-0是线程默认的名字
四、Thread类的常用方法
方法 | 作用 |
---|---|
setPriority/getPriority | 设置优先级,优先级越高,线程优先执行 |
currentThread() | 获取当前线程对象 |
setName/getName | 设置线程名字 |
yield() | 礼让,如果有其他同步执行的线程,礼让其他线程先执行,当前线程后执行;是Thread类的方法,Thread.yield() |
sleep(毫秒) | 休眠,当前线程停止运行,进入休眠状态,到达休眠时长,线程恢复正常运行。;是Thread类的方法,Thread.sleep(1000) |
wait() | 等待,必须在同步代码块中使用,当前线程停止运行,进入等待状态,是Object类的方法,this.sleep(1000) |
wait(5000) | 到达等待时长,线程会自动唤醒,继续向下运行 |
notify() | 唤醒,唤醒之前进入等待状态的线程,只唤醒第一个,使线程重新恢复到运行状态 |
notifyAll() | 唤醒,唤醒之前进入等待状态的所有线程,使线程重新恢复到运行状态 |
join() | 加入,将当前线程加入到父线程中,加入后,父线程会进入wait状态,一直到子线程运行结束后,父线程会继续恢复运行 |
interrupt() | 中断线程,检查线程状态,如果线程处于中断状态返回true,如果线程处于非中断状态返回false,并且清除中断状态(将中断状态设置为false) |
setPriority()
优先级测试:可以设置1-10不同的优先级,优先级高的线程,有更多的机会(更高的概率)获取到CPU的时间片
public class MyTest { public static void main(String[] args) { //new 一次 MyThread myThread1=new MyThread(); Thread thread1=new Thread(myThread1,"test1"); //MAX_PRIORITY 最大值 thread1.setPriority(thread1.MAX_PRIORITY); Thread thread2=new Thread(myThread1,"test2"); //MIN_PRIORITY 最小值 thread2.setPriority(thread2.MIN_PRIORITY); thread1.start(); thread2.start(); } }
public class MyThread implements Runnable{ private int tickect=5; @Override public void run() { while (tickect>0){ System.out.println(Thread.currentThread().getName()+"卖出去一张票,还剩"+tickect--); } } }
Sleep()
线程休眠,会让当前线程处于阻塞状态,指定时间过后,线程就绪状态
public class MyThread implements Runnable{ private int tickect=5; @Override public void run() { while (tickect>0){ //Thread.sleep(1000); 睡眠一秒然后运行,这里需要抛出异常 try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName()+"卖出去一张票,还剩"+tickect--); } } }
public class MyTest { public static void main(String[] args) { //new 一次 MyThread myThread1=new MyThread(); Thread thread1=new Thread(myThread1,"test1"); Thread thread2=new Thread(myThread1,"test2"); thread1.start(); thread2.start(); } }
yield()
让步,一旦调用该方法,当前线程的状态就会从运行状态,变成就绪状态,和别的线程重新抢CPU时间片。
public class MyThread implements Runnable{ private int tickect=5; @Override public void run() { while (tickect>0){ String name=Thread.currentThread().getName(); if(name.equals("test1")){ Thread.yield(); } System.out.println(Thread.currentThread().getName()+"卖出去一张票,还剩"+tickect--); } } }
public class MyTest { public static void main(String[] args) { //new 一次 MyThread myThread1=new MyThread(); Thread thread1=new Thread(myThread1,"test1"); Thread thread2=new Thread(myThread1,"test2"); thread1.start(); thread2.start(); } }
join()
方法,排队,join()方法会使主线程进入等待池并等待t线程执行完毕后才会被唤醒
将a方法设置一个join后运行的话,b就不会再抢运行,直到a运行完以后b才运行
public class MyThread extends Thread{ private int tickect=5; @Override public void run() { while (tickect>0){ System.out.println(Thread.currentThread().getName()+"卖出去一张票,还剩"+tickect--); } } }
public class MyTest { public static void main(String[] args) { MyThread myThread1=new MyThread(); myThread1.setName("ned"); myThread1.start(); try { // join 插队 myThread1.join(); } catch (InterruptedException e) { e.printStackTrace(); } MyThread myThread2=new MyThread(); myThread2.setName("abc"); myThread2.start(); } }
interrupt()
中断线程,仅仅发送了一个中断的信号,当碰到wait(),sleep方法时,清除中断标记,抛出异常。
public class MyTest { public static void main(String[] args) { MyThread myThread1 = new MyThread(); myThread1.setName("ndt"); MyThread myThread2 = new MyThread(); myThread2.setName("test"); // 第一个线程 myThread1.start(); myThread1.interrupt(); // 第一个线程 myThread2.start(); } }
public class MyTest { public static void main(String[] args) { // MyThread myThread1 = new MyThread(); myThread1.setName("ndt"); MyThread myThread2 = new MyThread(); myThread2.setName("test"); // 第一个线程 myThread1.start(); //中断 myThread1.interrupt(); // 第二个线程 myThread2.start(); } }
setDaemon()
设置线程为后台(守护)线程。
Java中有两类线程:
用户线程(User Thread)
守护线程(Daemon Thread)
守护线程 是指程序运行的时候在后台提供了一种通用服务的线程,比如GC垃圾回收线程,这个线程具有最低的优先级,用于为系统中的其它对象和线程提供服务。
两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:
如果用户线程全部退出离开,只剩下守护线程,虚拟机就会退出。
如果还有至少一个用户线程,那么虚拟机就不会退出。
public class Shouhu extends Thread{ @Override public void run(){ try { Thread.sleep(2000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("守护线程"); } }
public class MyTest6 { public static void main(String[] args) { Shouhu shouhu=new Shouhu();//线程的实例 shouhu.setDaemon(true);//成为守护线程 //守护线程是 main(主线程) shouhu.start(); try { Thread.sleep(6000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("-----------"); } }
wait()/notifyAll()/notify()
wait() 是针对已经获取对象锁的线程进行操作
当线程获取对象锁后,调用 wait() 主动释放对象锁,同时该线程休眠
直到其他线程调用 notify() 唤醒该线程,才继续获取对象锁,并执行
调用 notify() 唤醒线程并不是实时的,而是等相应的 synchronized 语句块执行结束,自动释放对象锁
再由 JVM 选取休眠的线程赋予对象锁,并执行,从而实现线程间同步、唤醒的操作
public class WaitTest{ private static Object object=new Object(); static class Wait extends Thread{ @Override public void run(){ synchronized (object){ System.out.println("当前线程开始等待:"+this.getName()); try { object.wait(10000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(this.getName()+"等待结束"); } } } static class Notify extends Thread{ @Override public void run(){ synchronized (object){ System.out.println("开始唤醒线程:"+Thread.currentThread().getName()); //唤醒 object.notifyAll();//唤醒所有处于等待中的线程 System.out.println("唤醒结束"); } } } public static void main(String[] args) throws InterruptedException { //创建处于等待中的线程 Wait wait1=new Wait(); wait1.setName("wait1"); Wait wait2=new Wait(); wait2.setName("wait2"); wait1.start(); wait2.start(); //唤醒线程 // Thread.sleep(10000); Notify notify=new Notify(); notify.start(); } }
wait()和sleep()
sleep必须添加时间,wait可以定义也可以不定义等待时间
都会中断线程的执行,sleep到达休眠时间后,线程会自动恢复运行状态,wait设置了等待时间,到达等待时间自动恢复运行,未设置需要调用notify方法唤醒线程
sleep可以直接使用,wait必须和同步代码块一起使用
wait() 和 sleep() 都可以通过 interrupt() 打断线程的暂停状态,从而使线程立刻抛出InterruptedException
通过 interrupt() 打断线程时,只需在 catch() 中捕获到异常即可安全结束线程
InterruptedException 是线程内部抛出,并不是 interrupt() 抛出
当线程执行普通代码时调用 interrupt() 并不会抛出异常,只有当线程进入 wait() / sleep() / join() 后才会立即抛出
sleep() 是 Thread 的静态方法,wait() 是 Object 的一般方法
notify()和interrupt()
interrupt:中断线程,会检查线程状态,清除线程的中断状态(sleep,wait),使线程恢复运行
notify:唤醒线程,将处于等待状态的线程(wait),使线程恢复运行