java进阶——day05-2 异常、线程
多线程
学习程序在没有跳转语句的前提下,都是由上至下依次执行,那么现在想要设计一个程序,边听歌边打游戏,如何设计?
并发与并行
并发:指两个或多个事件在同一时段内发生。
并行:指两个或多个事件在同一时刻发生(同时发生)

线程与进程
1、进程:
指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;
进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序既是一个进程创建--运行--消亡的过程

2、线程:
线程是进程的一个执行单元,负责当前进程中程序的执行,一个进程至少有一个线程。
一个进程可以有多个线程的,这个应用也可以称之为多线程程序。
总而言之:一个程序运行后,至少一个进程,一个进程可以包含多个线程

线程调度
1、分时调度
所有线程轮流使用CPU使用权,平均分配每个线程占用CPU的时间
2、抢占式调度
优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,Java使用的为抢占式线程
设置线程优先级

3、注意
多线程程序并不能提高程序运行的速度,但能提高程序运行效率,让CPU使用率更高。
创建线程类
主线程(main单线程)
单线程,从上到下依次执行
package day06; public class Person { private String name; //自定义run方法 public void run(){ for (int i = 0; i < 5; i++) { System.out.println(this.name+"---》"+i); } } public Person() { } public Person(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
package day06; public class demo01 { //主线程演示 public static void main(String[] args) { //创建Person对象 Person p1 = new Person("大黄"); p1.run(); Person p2 = new Person("旺财"); p2.run(); //执行和输出都是从上往下 } }

主线程的缺陷:
当发生异常时,会中断程序,不再执行后续代码
创建线程类---第一种方法
1、简述:
java.lang.Thread类:是描述线程的类,我们想要实现多线程,就必须继承Thread类
2、实现步骤
1.创建一个Thread类的子类,继承Thread类
2.在Thread类的子类中,重写Thread类中的run方法,设置线程任务(开启线程做什么?)
3.创建Thread类的子类对象
4.调用Thread类中的方法start方法,开启新的线程,执行run方法
void start() 使该线程开始执行;Java虚拟机调用该线程run方法。
3、注意事项
1.start()运行结果是两个线程并发地运行;
当前线程(main主线程)
另一个线程(创建的新线程,执行run方法)
2.多次启用一个线程是非法的。特别是当线程已经结束执行,不能重新启动。
package day06.day06_1Thread; public class ThreadSub extends Thread { //重写run方法 @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("Thread.run--->"+i); } } }
package day06.day06_1Thread; import day06.Person; public class demo01 { public static void main(String[] args) { //创建多线程子类对象 ThreadSub p1 = new ThreadSub(); //启用主线程 p1.start(); //创建当前线程 for (int i = 0; i < 5; i++) { System.out.println("main--->"+i); } } }
主线程和Thread线程同时执行
多线程原理
例如:
package day06.day06_1Thread; public class ThreadSub extends Thread { //重写run方法 @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("Thread.run--->"+i); } } }
package day06.day06_1Thread; import day06.Person; public class demo01 { public static void main(String[] args) { //创建多线程子类对象 ThreadSub p1 = new ThreadSub(); //启用主线程 p1.start(); //创建当前线程 for (int i = 0; i < 5; i++) { System.out.println("main--->"+i); } } }
流程图

1、程序启动main时,Java虚拟机启动一个进程,主线程main在main()调用时被创建。
2、随机调用start()方法,新线程启动,这样整个应用就在多线程下运行。
多线程为什么能并发执行呢?
多程序执行时,在栈内存中,每一个执行线程都有一片属于自己的栈内存空间,进行方法的压栈和弹栈

当线程执行完毕,线程自动在栈内存中释放。当所有线程执行结束,进程也就结束了
Thread类
常用方法
获取当前线程名称:
1、使用Thread类中的getName();
String getName()返回该线程名称
package day06.day06_1Thread; public class MyThread extends Thread { @Override public void run() { //获取当前线程名称 String name = getName(); System.out.println(name); } }
package day06.day06_1Thread; public class demo02 { public static void main(String[] args) { //创建Thread子类对象 MyThread mt = new MyThread(); //调用start方法 开启新线程 执行run方法 mt.start();//Thread-0 MyThread mt1 = new MyThread(); mt1.start();//Thread-1 MyThread mt3 = new MyThread(); System.out.println(mt3.getName());//Thread-2 } }
2、可以先获取当前正在执行的线程,使用线程中的getName(),获取线程名称。
static Thread currentThread() 返回当前正在执行线程对象的引用
package day06.day06_1Thread; public class MyThread extends Thread { @Override public void run() { //获取当前线程名称 String name = getName(); System.out.println(name); } }
package day06.day06_1Thread; public class demo02 { public static void main(String[] args) { //创建Thread子类对象 MyThread mt = new MyThread(); //调用start方法 开启新线程 执行run方法 mt.start();//Thread-0 //获取当前正在执行的线程 Thread t = Thread.currentThread(); System.out.println(t);//Thread[main,5,main] } }
3、链式编程Thread.currentThread.getName();
package day06.day06_1Thread; public class MyThread extends Thread { @Override public void run() { //获取当前线程名称 // String name = getName(); // System.out.println(name); //链式编程 System.out.println(Thread.currentThread().getName());//main } }
package day06.day06_1Thread; public class demo02 { public static void main(String[] args) { //创建Thread子类对象 MyThread mt = new MyThread(); mt.start();//Thread-0 System.out.println(Thread.currentThread().getName());//main } }
public void start():导致此线程开始执行; Java虚拟机调用此线程的run方法。
public void run() :此线程要执行的任务在此处定义代码。
public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
package day06.day06_1Thread; public class demo03 { /** * 使当前正在执行的程序以指定的毫秒数进行"睡眠"---暂停执行 * 毫秒结束后 继续执行 */ public static void main(String[] args) { //模拟钟表 一秒打印一次 for (int i = 1; i <= 60; i++) { System.out.println(i); try { //睡眠 1 秒 Thread.sleep(1000); } catch (InterruptedException e) { System.out.println(e.getMessage()); } } } }
创建线程----方式二
采用Java.lang.Runnable也是其一,我们只需要重写run方法即可
步骤:
1、定义Runnable的实现类,并重写Runnable中的run方法
2、创建Runnable实现类对象
3、将对象作为Thread的target来创建Thread对象
4、调用线程对象start()方法
package day06.day06_1Thread; //创建Runnable实现类 public class RunnableImpl implements Runnable{ //重写Runnable中的run方法 @Override public void run() { for (int i = 0; i < 4; i++) { System.out.println(Thread.currentThread().getName()+"--->"+i); } } }
package day06.day06_1Thread; public class demo04 { public static void main(String[] args) { //创建Runnable实现类对象 RunnableImpl runImp = new RunnableImpl(); //创建Thread类 传递Runnable对象 Thread t = new Thread(runImp); //调用t.start() t.start(); //创建main方法中的线程 for (int i = 0; i < 4; i++) { System.out.println("main"+"--->"+i); } } }
Thread和Runnable的区别
主要区别:与继承类 实现类的区别类似
实现Runnabled接口,创建多线程的好处:
1、避免了单继承的局限性
一个类只能继承一个类,类继承了Thread类就不能继承其他的类
实现Runnable接口,还可以继承其他的类,实现其他的接口
2、增强了程序的扩展性,降低了程序的耦合性(解耦)
实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
实现类重写了run方法:用来设置新线程任务
创建Thread类对象,调用start()方法:用来开启新线程。
匿名内部类实现线程的创建
使用线程匿名内部类的方式,以便于实现每个线程执行不同的任务。
使用匿名内部类实现Runnable接口,重写Runnable接口中的run()方法
package day06.day06_1Thread; //创建Runnable实现类 public class RunnableImpl implements Runnable{ //重写Runnable中的run方法 @Override public void run() { for (int i = 0; i < 4; i++) { System.out.println(Thread.currentThread().getName()+"--->"+i); } } }
package day06.day06_1Thread; public class demo05 { public static void main(String[] args) { //创建Runnable实现类对象 RunnableImpl runImp = new RunnableImpl(); //创建Thred类对象 将实现类对象 作为target传递 Thread thread = new Thread(runImp); //启动新线程 thread.start(); //创建主方法中的线程 for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName()+"-->"+i); } //创建匿名内部类实现Runnable多线程 Runnable newRun = new Runnable(){ //重写Runnable中run() @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println("Hello"+i); } } }; //启动内部类线程 new Thread(newRun).start(); } }
线程安全
如果有多个线程同时运行,而这些线程会同时运行这段代码。程序每次运行结果和单线程一样的。而且其他的变量的值也和预期的是一样的,就是线程安全。
例如:
模拟电影院的售票窗口,实现多个窗口售票(多个窗口卖100张票)
package day06.day06_1Thread; public class Ticket implements Runnable { //定义票的数量 int ticket = 100; @Override public void run() { //每个窗口的买票操作 //窗口 永远开启 while (true){ if(ticket>0){//有票 //出票操作 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } String name = Thread.currentThread().getName(); System.out.println(name + "正在卖"+ticket--); } } } }
package day06.day06_1Thread; public class Caname { public static void main(String[] args) { Ticket ticket = new Ticket(); //模拟三个窗口 Thread t1 = new Thread(ticket); Thread t2 = new Thread(ticket); Thread t3 = new Thread(ticket); //同时售票 t1.start(); t2.start(); t3.start(); } }

发现:
1、相同的票数,比如100被售出3次
2、不存在的票,比如0与-1
这几种问题,几个窗口(线程)票数不同步了,这种问题称为线程不安全
注意:
线程安全是不允许产生的
线程同步
注意事项:
当我们使用多线程访问同一资源时,且多线程中对资源有写的操作,就容易出现线程安全问题。
解决办法:
要解决上述多线程并发访问同一资源的安全性问题,也就是解决重复票与不存在票的问题,java中提供了同步机制(synchonized)来解决。
根据案例简述:
窗口1进入操作时,窗口2和窗口3只能在外面等,窗口1操作结束,窗口1、2、3才有机会进入代码去执行。
也就是说在某个线程修改共享资源时,其他线程不得修改,等等线程修改完毕,才能去抢夺CPU资源,完成操作,保证数据的同步性,解决线程不安全的现象。
解决详情:
1、同步代码块
2、同步方法
3、锁机制
同步代码块
1、格式:
synchronized(同步锁){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
2、注意
1.通过代码块中的锁对象,可以使用任意的对象,例如Object....
2.必须保证多个线程使用同一个同步锁
3.锁对象作用:
把同步代码块锁住,只能让一个线程在同步代码块中执行
3、例如
package day06.day06_2; public class RunnableImpl implements Runnable { //定义票数量 private int tickets = 100; //创建同步锁对象 Object obj = new Object(); @Override public void run() { //使用死循环 重复买票操作 while (true){ //同步代码块 synchronized (obj){ if(tickets>0){ //提高安全性 使用睡眠 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //票存在 卖票 System.out.println(Thread.currentThread().getName()+"--->正在出售:"+tickets+"号票"); tickets--; } } } } }
package day06.day06_2; public class demo { public static void main(String[] args) { //创建Runnable实现类对象 RunnableImpl run = new RunnableImpl(); //创建三个窗口 Thread T1 = new Thread(run); Thread T2 = new Thread(run); Thread T3 = new Thread(run); //三个窗口同时售票 T2.start(); T1.start(); T3.start(); } }
4、同步技术的原理
使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器
例如上述例子中,3个线程,同时抢夺CPU资源,谁抢到谁执行run方法进行卖票。
T1抢到cpu执行权--->执行run()--->遇到synchronized代码块--->T1检查代码块是否有锁对象--->发现有--->获取锁对象进入同步代码块--->执行
T2抢到cpu执行权--->执行run()--->遇到synchronized代码块--->T1检查代码块是否有锁对象--->发现没有--->等待上一线程(T1)归还锁对象--->上一线程(T1)归还锁对象--->获取锁对象进入同步代码块--->执行
5、注意
1.同步中的线程,没有执行完毕不会释放对象锁,同步外的线程没有锁不会进入同步代码块。
2.因为同步代码块需要反复的--->检查对象锁--->获取同步锁--->归还同步锁 效率会比较低 但是安全
同步方法
1、同步方法:
使用synchronized修饰的方法,就叫做同步方法
2、格式:
修饰符 synchronized 返回值类型 方法名(){
可能会产生线程安全问题的代码}
3、例如:
package day06.day06_3; public class RunnableImpl implements Runnable { //定义票数量 private int tickets = 100; @Override public void run() { //使用死循环 重复买票操作 while (true){ //调用同步方法 method(); } } //创建同步方法 public synchronized void method(){ if(tickets>0){ //提高安全性 使用睡眠 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //票存在 卖票 System.out.println(Thread.currentThread().getName()+"--->正在出售:"+tickets+"号票"); tickets--; } } }
package day06.day06_3; public class demo { public static void main(String[] args) { //创建Runnable实现类对象 RunnableImpl run = new RunnableImpl(); //创建三个窗口 Thread T1 = new Thread(run); Thread T2 = new Thread(run); Thread T3 = new Thread(run); //三个窗口同时售票 T2.start(); T1.start(); T3.start(); } }
4、原理
同步方法的对象锁是谁?
就是实现类对象new RunnableImpl();-----也就是this
验证:
package day06.day06_3; public class RunnableImpl implements Runnable { //定义票数量 private int tickets = 100; @Override public void run() { //使用死循环 重复买票操作 while (true){ //调用同步方法 method(); } } //创建同步方法 public /*synchronized*/ void method(){ //验证同步方法的锁对象 synchronized (this){ if(tickets>0){ //提高安全性 使用睡眠 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //票存在 卖票 System.out.println(Thread.currentThread().getName()+"--->正在出售:"+tickets+"号票"); tickets--; }} } }
package day06.day06_3; public class demo { public static void main(String[] args) { //创建Runnable实现类对象 RunnableImpl run = new RunnableImpl(); //创建三个窗口 Thread T1 = new Thread(run); Thread T2 = new Thread(run); Thread T3 = new Thread(run); //三个窗口同时售票 T2.start(); T1.start(); T3.start(); } }
Lock锁
java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
public void lock();:加同步锁
public void unlock();:释放同步锁
java.util.concurrent.locks.Locks.RenntrantLock implements Losck接口
1、使用步骤:
1.在成员变量位置创建RenntrantLock实现类对象
2.在可能出现线程安全问题的代码前,调用Lock接口中的方法lock()获取锁
3.在可能出现线程安全问题的代码后,调用Lock接口中的方法unlock()释放锁
2、例如
package day06.day06_4; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class RunnableImpl implements Runnable { //定义票数量 private int tickets = 100; //定义Lock ReentranLock对象 Lock Suo = new ReentrantLock(); @Override public void run() { //使用死循环 重复买票操作 while (true){ //在会出现线程安全的地方调用LOCK 的lock() Suo.lock(); if(tickets>0){ //提高安全性 使用睡眠 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //票存在 卖票 System.out.println(Thread.currentThread().getName()+"--->正在出售:"+tickets+"号票"); tickets--; } //在会出现线程安全的代码后调用unlock释放锁 Suo.unlock(); } } }
package day06.day06_4; public class demo { public static void main(String[] args) { //创建Runnable实现类对象 RunnableImpl run = new RunnableImpl(); //创建三个窗口 Thread T1 = new Thread(run); Thread T2 = new Thread(run); Thread T3 = new Thread(run); //三个窗口同时售票 T2.start(); T1.start(); T3.start(); } }
3、将unlock设置到finally中
无论线程是否发生异常都会释放lock锁
package day06.day06_5; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class RunnableImpl implements Runnable { //定义票数量 private int tickets = 100; //定义Lock ReentranLock对象 Lock Suo = new ReentrantLock(); @Override public void run() { //使用死循环 重复买票操作 while (true){ //在会出现线程安全的地方调用LOCK 的lock() Suo.lock(); if(tickets>0){ //提高安全性 使用睡眠 try { Thread.sleep(1000); //票存在 卖票 System.out.println(Thread.currentThread().getName()+"--->正在出售:"+tickets+"号票"); tickets--; } catch (InterruptedException e) { e.printStackTrace(); }finally { //在会出现线程安全的代码后调用unlock释放锁----无论是否产生异常 都会释放锁 Suo.unlock(); } } } } }
package day06.day06_5; public class demo { public static void main(String[] args) { //创建Runnable实现类对象 RunnableImpl run = new RunnableImpl(); //创建三个窗口 Thread T1 = new Thread(run); Thread T2 = new Thread(run); Thread T3 = new Thread(run); //三个窗口同时售票 T2.start(); T1.start(); T3.start(); } }
线程状态
概述:
当线程被创建并启动后,它既不是一启动就进入执行状态,也不是一直处于执行状态。线程也是有生命周期的
线程的六种状态

注意:
我们无需研究这几种状态的实现原理,我们只需知道线程操作中存在这一的状态。
流程图

注意:
waitting表示的是无限等待状态,需要调用Object中的notify()方法去唤醒

浙公网安备 33010602011771号