线程(英语:thread)是 *** 作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实 际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程 并行执行不同的任务
注意:有的多线程是模拟出来的,真正的多线程是指有多个 cpu,即多核,如服务器。如果是模拟出来的 多线程,即一个 cpu 的情况下,在同一个时间点,cpu 只能执行一个代码, 因为切换的很快,所以就 有同时执行的错觉。
多线程优缺点
优点 资源利用率更好,程序设计在某些情况下更简单,程序响应更快。 缺点 设计更复杂,虽然有一些多线程应用程序比单线程的应用程序要简单,但其他的一般都更复杂。在 多线程访问共享数据的时候,这部分代码需要特别的注意。线程之间的交互往 往非常复杂。不正确 的线程同步产 生的错误非常难以被发现,并且重现以修复。 上下文切换的开销较大, 当 CPU 从执行一个线程切换到执行另外一个线程的时候,它需要 先存储当 前线程的本地的数据,程序 指针等,然后载入另一个线程的本地数据,程序指针 等,最后才开始 执行。这种切换称为“上下文切换”(“context switch”)。CPU 会在一 个上下文中执行一个线程,然后 切换到另外一个上下文中执 行另外一个线程。上下文切换 并不廉价。如果没有必要,应该减少上 下文切换的发生。
二、创建线程2.1、继承Thread类实现
线程: 执行的顺序流 多线程: 多任务执行,多路径执行 创建线程的方式: 1.继承Thread类,重写run() 2.实现Runnable接口,重写run() 接口可以多实现 实现资源共享 3.实现juc包下Callable接口,重写call()
public class Class001_Thread extends Thread { @Override public void run() { for(int i=1;i<=20;i++){ System.out.println("一边喝水...."); try { Thread.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { //创建线程 Class001_Thread th = new Class001_Thread(); //开启线程 void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法。 th.start(); for(int i=1;i<=20;i++){ System.out.println("一边讲课...."); try { Thread.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } } }
2.2 继承Runnable实现
实现Runnable接口,重写run方法 优点: 接口多实现,类的单继承 资源共享
public class Class002_Thread implements Runnable{ public static void main(String[] args) { //创建线程 Thread th = new Thread(new Class002_Thread()); //开启线程 th.start(); for(int i=1;i<=20;i++){ System.out.println("一边陪女朋友..."); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public void run() { for(int i=1;i<=20;i++){ System.out.println("一边打游戏..."); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } }
练习:
实现12306模拟购票
public class Class003_12306 implements Runnable{ //共享资源: 100张票 int tickets = 100; //ABC @Override public void run() { while(true){ if(tickets<=0){ break; } System.out.println(Thread.currentThread().getName()+"正在购买第"+tickets-- +"张票...."); } } public static void main(String[] args) { //创建3个线程 Class003_12306 web = new Class003_12306(); Thread th1 = new Thread(web,"张三"); Thread th2 = new Thread(web,"李四"); Thread th3 = new Thread(web,"王五"); //开启线程 th1.start(); th2.start(); th3.start(); } }
模拟龟兔赛跑 一共100步,跑完100步就赢了,只要一个参赛者赢了另外一个参赛者就不跑了 乌龟正常跑,兔子每跑10步休息10ms 注意: 可以通过共享的标识控制多线程执行中线程的结束
public class Class004_Racer implements Runnable{ private String winner = null; //存储赢的人的名字 @Override public void run() { for(int i=1;i<=100;i++){ System.out.println(Thread.currentThread().getName()+"正在跑第"+i+"步..."); if("兔子".equals(Thread.currentThread().getName()) && i%10==0){ try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } //检查是否结束 if(checkOver(i)){ break; } } } private boolean checkOver(int steps) { if(steps==100){ winner = Thread.currentThread().getName(); return true; } if(winner!=null){ return true; } return false; } public static void main(String[] args) { Class004_Racer racer = new Class004_Racer(); //创建2个线程 Thread th1 = new Thread(racer,"乌龟"); Thread th2 = new Thread(racer,"兔子"); //开启线程 th1.start(); th2.start(); } }
2.3 collable--了解
测试第三种开启线程方式--> 了解 实现juc包下Callable接口,重写call方法,在call方法中定义线程体 创建与开启:使用线程池实现
优点: 1.call中可以抛出异常
2.call可以存在返回值(线程执行完毕的结果)
ExecutorService : 提供了一些线程池管理线程执行的相关方法 Future表示异步计算的结果。
public class Class005_Racer implements Callable{ //共享资源 : 记录赢的人名字 标识 private String winner = null; @Override public Integer call() { for(int steps =1;steps<=100;steps++){ System.out.println(Thread.currentThread().getName()+"正在跑第"+steps+"几步"); if("pool-1-thread-1".equals(Thread.currentThread().getName())&&steps%10==0){ try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } //检查是否结束 boolean flag = checkOver(steps); if(!flag){ return steps; } } return -1; } private boolean checkOver(int steps) { //已经存在其他参赛者赢了 if(winner!=null){ return false; } //当前线程赢了 if(steps==100){ winner = Thread.currentThread().getName(); return false; } return true; } public static void main(String[] args) throws ExecutionException, InterruptedException { //一场比赛 Class005_Racer racer = new Class005_Racer(); //两个参赛者 //创建执行服务: 线程池 ExecutorService server = Executors.newFixedThreadPool(2); //提交执行 Future future1 = server.submit(racer); Future future2 = server.submit(racer); //获取结果 Integer result1 = future1.get(); Integer result2 = future2.get(); System.out.println(result1); System.out.println(result2); //关闭服务 server.shutdown(); } } //被重写方法上的异常抛出类型要求 >= 重写方法上异常抛出类型 class Fu{ void test() throws FileNotFoundException{} } class Zi extends Fu{ void test() throws FileNotFoundException { throw new FileNotFoundException(); } }
2.4 内部类定义线程体
public class Class006_Thread{ //静态内部类 static class Inner1 implements Runnable{ @Override public void run() { for(int i=1;i<=20;i++){ System.out.println("一边游泳...."); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { //开启线程 new Thread(new Inner1()).start(); //匿名内部类 new Thread(new Runnable() { @Override public void run() { for(int i=1;i<=20;i++){ System.out.println("一边喝水...."); } try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(()->{ for(int i=1;i<=20;i++){ System.out.println("一边吐泡泡...."); } try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } }三、线程状态
3.1线程状态
线程状态: 五种状态 新生状态 : new 就绪状态 : 调用start方法,线程就会进入就绪队列 运行状态 : 当cpu调度就绪的线程,线程开始执行 阻塞状态 : 线程一旦进入阻塞状态,需要等待阻塞解除 终止状态3.IO
当线程进入阻塞状态,阻塞解除之后不会直接恢复到运行状态,会直接进入就绪状态,等待cpu的下一次调度 如果一个线程已经进入终止状态,无法恢复
如何进入终止状态: 1.正常执行完毕 2.stop()已过时不推荐 3.通过添加标识判断-->推荐
如何进入到就绪状态: 1.start() 2.阻塞解除 3.线程切换,cpu调度切换 4.yield()
如何进入到阻塞状态: 1.sleep() 2.join() 3.wait() 4.IO
sleep : 线程休眠睡眠 static void sleep(long millis) 导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数 1s = 1000ms
特点: 一个线程进入到休眠状态,会自动让出cpu资源,cpu可能会调度其他线程 抱着资源睡觉-->资源值得是对象的锁资源
作用: 1.模拟网络延迟 2.方法问题出现的可能性
3.2 yield 礼让线程
yield 礼让线程 让出cpu的资源,直接进入到就绪状态
public class Class002_Yield implements Runnable{ public static void main(String[] args) { Class002_Yield cy = new Class002_Yield(); new Thread(cy,"A").start(); new Thread(cy,"B").start(); } @Override public void run() { System.out.println(Thread.currentThread().getName()+"开始了...."); Thread.yield(); System.out.println(Thread.currentThread().getName()+"结束了...."); } }
3.3 join 插队线程
join() 插队线程 void join() 等待这个线程死亡。 void join(long millis) 此线程最多等待 millis毫秒。 void join(long millis, int nanos) 此线程最多等待 millis毫秒加上 nanos纳秒。
被插队的线程会进入到阻塞状态
注意: 先就绪后插队
public class Class003_Join { public static void main(String[] args) { new Thread(new Father()).start(); } } class Father implements Runnable{ @Override public void run() { System.out.println("想吸烟了。。。。"); System.out.println("给儿子100块。。。让儿子去买烟。。。。"); //创建儿子线程 Thread th = new Thread(new Son()); //就绪 th.start(); //插队 try { th.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("接过烟,吸一口。。。"); } } class Son implements Runnable{ @Override public void run() { System.out.println("接过钱,去买烟。。。。"); System.out.println("路边看到一家游戏厅,进入玩10s钟。。。。"); for(int i=1;i<=10;i++){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(i+"s过去了。。。。。"); } System.out.println("赶紧去买烟。。买一盒煊赫门。。。"); System.out.println("把烟递给老爸。。。。"); } }
3.4 interrupt 中断线程
中断线程 void interrupt() 为该线程添加中断标识 boolean isInterrupted() 判断该线程是否曾经调用interrupt()方法添加过中断标识 static boolean interrupted() 判断该线程是否曾经调用interrupt()方法添加过中断标识,同时会复位这个标识
当线程处于休眠过程(执行sleep()方法),如果线程被添加了中断标识,会遇到此异常
InterruptedException - 如果有任何线程中断了当前线程。 抛出此异常时,将清除当前线程的中断状态 。
public class Class004_Interrupt implements Runnable{ @Override public void run() { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } for(int i = 1;i<=10;i++){ if(Thread.interrupted()){ //复位 System.out.println("添加了中断标识,即将结束线程"); System.out.println(Thread.currentThread().isInterrupted()); //只判断 break; } System.out.println("---------------------"+i+"---------------------"); } } public static void main(String[] args) { Thread th = new Thread(new Class004_Interrupt()); th.start(); System.out.println(th.isInterrupted()); //false //添加中断标识 th.interrupt(); System.out.println(th.isInterrupted()); //true } }
3.4 守护线程
线程的分类: 用户线程 : 创建的新的线程默认为用户线程 守护线程 : 守护用户线程的执行
如果存在多个用户线程,需要所有的用户线程全部执行完毕,JVM才会正常的退出 当所有的用户线程全部执行完毕,守护线程会直接结束退出
守护线程 : setDaemon(true) 垃圾回收机制是一个典型的守护线程的案例
public class Class005_Daemon implements Runnable{ public static void main(String[] args) { Thread th = new Thread(new Class005_Daemon()); //为th线程设置为守护线程 th.setDaemon(true); //开启线程 th.start(); //主线程具体代码 for(int i = 1;i<=20;i++){ try { Thread.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("正在吃第"+i+"根面条..."); } } @Override public void run() { int i = 1; while(true){ try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("守护线程----->"+i++); } } }
3.5 getState()获取进程状态
getState() 获取线程状态 Thread.State 线程状态。 线程可以处于以下状态之一: NEW -> 新生 尚未启动的线程处于此状态。
RUNNABLE -> 就绪|运行 在Java虚拟机中执行的线程处于此状态。
BLOCKED -> 等待获取对象锁资源的过程 被阻塞等待监视器锁定的线程处于此状态。
WAITING -> wait(),join()等 无限期等待另一个线程执行特定 *** 作的线程处于此状态。
TIMED_WAITING -> 与时间相关的等待 sleep(ms) ,wait(ms),join(ms)等 正在等待另一个线程执行最多指定等待时间的 *** 作的线程处于此状态。
TERMINATED -> 终止 已退出的线程处于此状态。
public class Class006_getState implements Runnable{ public static void main(String[] args){ Thread th = new Thread(new Class006_getState()); System.out.println(th.getState()); //NEW th.start(); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } while(true){ try { Thread.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(th.getState()); //如果th线程是终止状态,循环结束 if(th.getState()==Thread.State.TERMINATED){ break; } } } @Override public void run() { for(int i=1;i<=10;i++){ if(i==5){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(i); } } }
3.6 getPriority 编程优先级
线程优先级 getPriority : 优先执行哪一个线程概率高 1~10 1 : 最小 10 : 最大 5 : 默认
void setPriority(int newPriority) 更改此线程的优先级。
int getPriority() 返回此线程的优先级。
static int MAX_PRIORITY 线程可以拥有的最大优先级。 static int MIN_PRIORITY 线程可以拥有的最低优先级。 static int NORM_PRIORITY 分配给线程的默认优先级。
ublic class Class007_Priority implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } public static void main(String[] args) { Class007_Priority cp = new Class007_Priority(); Thread th1 = new Thread(cp,"A"); Thread th2 = new Thread(cp,"B"); Thread th3 = new Thread(cp,"C"); th1.setPriority(1); //th3.setPriority(10); th3.setPriority(Thread.MAX_PRIORITY); System.out.println(th1.getPriority()); System.out.println(th2.getPriority()); System.out.println(th3.getPriority()); th1.start(); th2.start(); th3.start(); } }四、线程安全
线程安全: 多线程同时 *** 作同一份资源,才有可能遇到数据不安全情况
同步锁 : synchronized 使用分为2部分: 同步的条件 : 协调多个线程排队执行的条件(对象的锁资源:默认理解为只有一个) 同步的代码 : 多个线程之间排队执行的代码
4.1 同步方法:
synchronized关键字修饰方法 --> 简单,但是范围可能过大,效率
成员方法 条件: 相当于锁this 范围 : 当前方法体
静态方法 条件: 相当于锁类 范围 : 当前方法体
同步块: 需要多线程排队执行的代码定义在同步块中,满足条件要求才能执行 --> 排队执行的代码范围小,效率高,有可能不安全 synchronized(条件){ 排队执行的代码段; }
测试使用同步方法保证12306案例数据安全:
public class Class001_Synchronized { public static void main(String[] args) { } } class Web01_12306 implements Runnable{ //共享资源: 100张票 int tickets = 100; //ABC @Override public void run() { while(true){ if(buyTicket()){ break; } try { Thread.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized boolean buyTicket(){ if(tickets<=0){ return true; } System.out.println(Thread.currentThread().getName()+"正在购买第"+tickets-- +"张票...."); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } return false; } public static void main(String[] args) { //创建3个线程 Web01_12306 web = new Web01_12306(); Thread th1 = new Thread(web,"张三"); Thread th2 = new Thread(web,"李四"); Thread th3 = new Thread(web,"王五"); //开启线程 th1.start(); th2.start(); th3.start(); } }
4.2 同步块
同步块: 需要多线程排队执行的代码定义在同步块中,满足条件要求才能执行 --> 排队执行的代码范围小,效率高,有可能不安全 synchronized(条件){ 排队执行的代码段; }
条件: this : 默认指代当前调用成员方法的对象 类.class : 指代某一个类型的Class对象 资源 : 成员变量->对象
测试使用同步块保证12306案例数据安全: 锁类.class : 简单,因为一个类的Class对象只有一个,相当于锁了这个类的所有对象,如果存在多个对象,建议直接锁某个对象,使用this 锁this : 锁住当前调用成员方法的对象,相当于锁了当前对象的所有资源(成员),如果只想锁某一个资源,name建议直接锁资源 锁资源 : 锁当前对象的某一个成员,要求这个成员为自定义的引用数据类型的对象地址,保证能够锁住,效率最高,但是容易写错
注意: 锁要锁不变的东西才能锁住,自定义的引用数据类型的地址不变
锁类.class、锁this 例子
public class Class002_Synchronized { public static void main(String[] args) { } } class Web02_12306 implements Runnable{ //共享资源: 100张票 int tickets = 100; @Override public void run() { while(true){ //ABC synchronized (Web02_12306.class){ //synchronized (this){ if(tickets<=0){ break; } System.out.println(Thread.currentThread().getName()+"正在购买第"+tickets-- +"张票...."); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { //创建3个线程 Web02_12306 web = new Web02_12306(); Thread th1 = new Thread(new Web02_12306(),"张三"); Thread th2 = new Thread(new Web02_12306(),"李四"); Thread th3 = new Thread(new Web02_12306(),"王五"); //开启线程 th1.start(); th2.start(); th3.start(); } }
锁资源 例子
public class Class003_Synchronized { public static void main(String[] args) { } } class Web03_12306 implements Runnable{ //共享资源: 100张票 Tickets tickets = new Tickets(); @Override public void run() { while(true){ //ABC synchronized (tickets){ if(tickets.num<=0){ break; } System.out.println(Thread.currentThread().getName()+"正在购买第"+tickets.num-- +"张票...."); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { //创建3个线程 Web03_12306 web = new Web03_12306(); Thread th1 = new Thread(web,"张三"); Thread th2 = new Thread(web,"李四"); Thread th3 = new Thread(web,"王五"); //开启线程 th1.start(); th2.start(); th3.start(); } } class Tickets{ int num = 100; }五、线程通信
线程通信 wait() 等待,一个线程一旦调用wait(),会进入该对象的等待池中进行等待,等待被唤醒,等待的过程中处于等待阻塞的状态 wait-->会释放cpu的资源与对象的锁资源 sleep-->会释放cpu的资源不会释放对象的锁资源 notify() 唤醒,唤醒该对象等待池中正在等待的线程,被唤醒的线程相当于被激活,能够被cpu调用,但是是否能够执行要看是否能获取到对象的所资源--> 满足同步执行的条件 wait,notify都是Object类提供的成员方法,用来处理多线程之间共享数据的存储问题,必须使用在同步环境下,否则会遇到异常java.lang.IllegalMonitorStateException
通过信号灯法实现人车公用街道 -> 生产者消费者模式 人 : 人走东西走向 --> 绿灯走 车 : 车走南北走向 --> 红灯走 街道 成员 : 红绿灯 boolean : true->绿灯->人走 false->红灯->车走 功能: 东西走向 南北走向 注意: 人车要共享一个街道,才能共享一个红绿灯,才能通过一个红绿灯控制多线程的执行问题
public class Class004_Street { public static void main(String[] args) { Street street = new Street(); new Thread(new Person(street)).start(); new Thread(new Car(street)).start(); } } //街道 class Street{ //红绿灯 boolean flag = false; //ns public synchronized void ns(){ if(!flag){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("车走"); //红绿灯变灯 flag = true; //唤醒对方线程 this.notify(); //自己等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } //we public synchronized void we(){ if(flag){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("人走"); //红绿灯变灯 flag = false; //唤醒对方线程 this.notify(); //自己等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Person implements Runnable{ private Street street = null; public Person(Street street) { this.street = street; } @Override public void run() { while(true){ street.we(); } } } class Car implements Runnable{ private Street street = null; public Car(Street street) { this.street = street; } @Override public void run() { while (true){ street.ns(); } } }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)