多进程概述
进程
多线程
线程调度
线程调度概述
线程优先级
设置对象优先级
线程控制:其他方法
线程睡眠sleep
线程加入 join():
线程礼让,暂停当前线程,执行其他线程
后台线程
中断线程
线程的生命周期
实现多线程
1.继承Thread类
线程名称
获取线程名称
设置线程名称:2种
2.实现Runnable接口(常用)
概述
实现
线程安全问题
实现卖电影票案例(不安全)
方式1:继承Thread类
方式2:实现Runnable接口
问题分析:同票和负数票
同步(synchronized)
同步概述
同步方法
同步代码块
买票同步代码块
同步方法
买票同步方法
方法1
方法2
方法3:静态方法锁
银行存钱案例
Lock锁(JDK5之后)
Lock锁卖票案例
线程死锁
死锁问题及其代码
死锁案例
方法1
方法2:
线程间通信
线程间通信概述
等待/唤醒机制
生产消费:加入等待唤醒机制,加入判断**
方法1(更好):
方法2:
优化生产消费问题
多生产者,多消费者的问题。烤鸭生产一只消费一只
Condition等待/唤醒机制
优化生产消费问题
线程组ThreadGroup
概述
获取线程组,名字
修改线程组
线程池
概述
Callable接口:创建线程3
1.求和案例
匿名内部类方式使用多线程
定时器
循环一次
循环调用
案例:在指定的时间删除的指定目录
多线程的单例
停止线程
习题
多线程Thread
多进程概述
进程
- 1.定义:正在进行的程序(任务管理器中可以看到)(一个CPU同一时间点只运行一个进程,只运行其中的一个线程)。是
系统进行资源分配和调用的独立单位
。每一个进程都有他自己的内存空间和系统资源。
- 2.多进程有什么意义呢?
- 可以提高CPU的使用率。
单进程的计算机只能做一件事情,而我们现在的计算机都可以做多件事情。举例:一边玩游戏(游戏进程),一边听音乐(音乐进程)。
也就是说现在的计算机都是支持多进程的,可以在一个时间段内执行多个任务。
- 3.问题:一边玩游戏,一边听音乐是同时进行的吗?
- 单cpu:
不是。因为单CPU在某一个时间点上只能做一件事情。
而我们在玩游戏,或者听音乐的时候,是CPU在做着程序间的高效切换让我们觉得是同时进行的。
- 多CPU:可能是。
多线程
线程是依赖于进程而存在。
- 线程:在同一个进程内又可以执行多个任务,而这每一个任务我就可以看出是一个线程。
(1)线程定义:是程序的执行单元,执行路径。是程序使用CPU的最基本单位。
(2) 单线程:如果程序只有一条执行路径。
(3)多线程:如果程序有多条执行路径。
(360是一个进程,同时运行360的不同功能是多线程);
一个进程中至少有一个线程。
(4)目的:开启多个线程是为了同时运行多部分代码。
- 程序:每一个线程都有自己运行的内容,这个内容可以成为线程要执行的程序。
- 多线程意义:
为了提高应用程序的使用率
。
程序的执行其实都是在抢CPU的资源,CPU的执行权。
多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多(线程多),就会有更高的几率抢到CPU的执行权。
我们是不敢保证哪一个线程能够在哪个时刻抢到,所以线程的执行有随机性。
注意并行和并发
:
并行:是逻辑上同时发生,指在某一个时间段内同时运行多个程序。
并发:是物理上同时发生,指在某一个时间点同时运行多个程序。
- Java程序运行原理
由java命令启动JVM,JVM启动就相当于启动了一个进程。接着由该进程创建了一个主线程去调用main方法。
- jvm虚拟机的启动是单线程的还是多线程的?
多线程的。
原因是垃圾回收线程(finalize())
也要先启动,否则很容易会出现内存溢出。
垃圾回收线程+主线程,最低启动了两个线程,所以,jvm的启动其实是多线程的。
- 多线程的好处:解决了多部分同时运行的问题
如果多线程中的某一条线程发生错误,会显示异常,并停止这条线程,但其他线程不受影响.
- 创建线程的目的:
是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行。
- 多线程实现:
由C/C++
去调用系统功能创建进程,然后由Java
去调用进程实现多线程
系统进行资源分配和调用的独立单位
。每一个进程都有他自己的内存空间和系统资源。- 可以提高CPU的使用率。
单进程的计算机只能做一件事情,而我们现在的计算机都可以做多件事情。举例:一边玩游戏(游戏进程),一边听音乐(音乐进程)。
也就是说现在的计算机都是支持多进程的,可以在一个时间段内执行多个任务。
- 单cpu:
不是。因为单CPU在某一个时间点上只能做一件事情。
而我们在玩游戏,或者听音乐的时候,是CPU在做着程序间的高效切换让我们觉得是同时进行的。 - 多CPU:可能是。
线程是依赖于进程而存在。
(1)线程定义:是程序的执行单元,执行路径。是程序使用CPU的最基本单位。
(2) 单线程:如果程序只有一条执行路径。
(3)多线程:如果程序有多条执行路径。
(360是一个进程,同时运行360的不同功能是多线程);
一个进程中至少有一个线程。
(4)目的:开启多个线程是为了同时运行多部分代码。
为了提高应用程序的使用率
。程序的执行其实都是在抢CPU的资源,CPU的执行权。
多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多(线程多),就会有更高的几率抢到CPU的执行权。
我们是不敢保证哪一个线程能够在哪个时刻抢到,所以线程的执行有随机性。
注意并行和并发
: 并行:是逻辑上同时发生,指在某一个时间段内同时运行多个程序。
并发:是物理上同时发生,指在某一个时间点同时运行多个程序。
由java命令启动JVM,JVM启动就相当于启动了一个进程。接着由该进程创建了一个主线程去调用main方法。
多线程的。
原因是
垃圾回收线程(finalize())
也要先启动,否则很容易会出现内存溢出。 垃圾回收线程+主线程,最低启动了两个线程,所以,jvm的启动其实是多线程的。
如果多线程中的某一条线程发生错误,会显示异常,并停止这条线程,但其他线程不受影响.
是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行。
由
C/C++
去调用系统功能创建进程,然后由Java
去调用进程实现多线程1.多线程优点:
- 资源利用率更好
- 程序设计在某些情况下更简单
- 程序响应更快
2.多线程缺点:
- 设计更复杂
- 上下文切换的开销:上下文切换并不廉价。如果没有必要,应该减少上下文切换的发生。
- 增加资源消耗。
3.多线程有几种实现方案,分别是哪几种?
两种。
1.继承Thread类
2.实现Runnable接口
扩展一种:实现Callable接口。这个得和线程池结合。(一般可以不答)
线程调度
线程调度概述
假如我们的计算机只有一个CPU,那么CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。
线程有两种调度模型:
- 分时调度模型:所有线程轮流使用CPU 的使用权,平均分配每个线程占用CPU 的时间片
- 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU 时间片相对多一些。
Java使用的是抢占式调度模型。
线程优先级
- 线程默认优先级是:5
- 线程优先级的范围是:1-10
- 线程优先级:最大为10,最小为1,默认为5
方法 | 定义 |
---|---|
public final int getPriority() |
获取线程对象的优先级 |
public final void setPriority(int newPriority) |
设置线程的优先级 |
设置对象优先级
public class ThreadPriorityDemo { public static void main(String[] args) { ThreadPriority tp1 = new ThreadPriority(); ThreadPriority tp2 = new ThreadPriority(); //设置线程名 tp1.setName("东方不败"); tp2.setName("岳不群"); // 获取默认优先级 System.out.println(tp1.getPriority()); //设置线程优先级 tp1.setPriority(10); tp2.setPriority(1); System.out.println(tp1.getPriority()); System.out.println(tp2.getPriority()); tp1.start(); tp2.start(); }}///////////////5-----默认是510---设置后15东方不败:0东方不败:1
注意:
线程优先级高仅仅表示线程获取的 CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到比较好的效果。
线程控制:其他方法
sleep(long millis) |
线程休眠,单位是毫秒(ms)。(时间到了继续运行) |
---|---|
join() |
线程加入,只有这个线程完毕,其他线程才可以继续 |
yield() |
线程礼让,暂停当前线程,执行其他线程 |
setDaemon(boolean on) |
后台线程 :将该线程标记为守护线程或用户线程必须在启动线程前调用(一旦只剩下守护线程,后台线程立即结束) |
stop() |
中断线程 |
interrupt() |
中断线程,把线程的状态终止,并抛出一个异常,不会影响后续代码运行 |
toString() |
Thread.*currentThread*().toString(); Thread[Thread-0,5,main] |
线程睡眠sleep
public class SleepThread extends Thread{ public void run(){ for(int i = 0 ; i < 5 ;i++){ try{ Thread.sleep(2000);//此线程sleep时,会运行其他线程//只能用try catch,不能throws,因为父类没抛这个异常,子类也不能抛//Thread.sleep(1000);//可以直接写sleep(1000);而省略Thread }catch(Exception ex){} System.out.println(getName()+" "+i); } }}SleepThread s1 = new SleepThread();SleepThread s2 = new SleepThread();s1.setName("意");s2.setName("而");s1.start();s2.start();
线程加入 join():
等待该线程终止,只有这个线程完毕,其他线程才可以继续
public class SleepThread extends Thread{ public void run(){ for(int i = 0 ; i < 50 ;i++){ System.out.println(getName()+" "+i); } }}//__________________SleepThread s1 = new SleepThread();SleepThread s2 = new SleepThread();SleepThread s3 = new SleepThread();s1.setName("111");s2.setName("222");s3.setName("333");s1.start();s1.join();//将1线程加入s2.start(); s3.start();
线程礼让,暂停当前线程,执行其他线程
注意:
理论上是一个线程执行一次后,等待下一个线程执行,然后第一个线程再执行第二次,……实际上可能有"误差"
public void run() { for (int x = 0; x < 100; x++) { System.out.println(getName() + ":" + x); Thread.yield(); }}//____________________________________________________ThreadYield ty1 = new ThreadYield();ThreadYield ty2 = new ThreadYield();ty1.setName("林青霞");ty2.setName("刘意");ty1.start();ty2.start();
后台线程
方法 | 定义 |
---|---|
setDaemon(boolean on) |
将该线程标记为守护线程或用户线程。 当正在运行的线程只剩下守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。 |
关羽张飞守护刘备,刘备完成走了,关羽张飞就也走了
ThreadDaemon td1 = new ThreadDaemon();ThreadDaemon td2 = new ThreadDaemon();td1.setName("关羽");td2.setName("张飞");// 设置守护线程(注意:必须在start()方法之前设置,否则会有异常!!)td1.setDaemon(true);td2.setDaemon(true);td1.start();td2.start();Thread.currentThread().setName("刘备");//改一改main线程的名字for (intx = 0; x < 5; x++) { System.out.println(Thread.currentThread().getName() + ":" + x);}但是不会立马结束,还会运行几次
中断线程
方法 | 定义 |
---|---|
stop() : |
让线程停止,这一个线程停止 |
interrupt() |
停止程序,运行catch部分代码 |
public class ThreadStopDemo { public static void main(String[] args) { ThreadStop ts = new ThreadStop(); ts.start(); try { Thread.sleep(3000); ts.stop();//3s后会停止程序 } catch (InterruptedException e) { e.printStackTrace();}}}
--3s后程序停止
public static void main(String[] args) { ThreadStop ts = new ThreadStop(); ts.start(); try { Thread.sleep(3000); ts.interrupt();//结束线程,运行catch内容 } catch (InterruptedException e) { e.printStackTrace(); }}
好处:不影响后续代码执行(继续运行catch之后的)
线程的生命周期
- 新建new:创建线程对象
- 就绪:有执行资格,没有执行权
- 运行runable:有执行资格,有执行权
- 阻塞:由于一些操作(sleep(),wait())让线程处于了该状态。没有执行资格,没有执行权;而另一种操作(sleep()时间到,notify())却可以把它激活,激活后处于就绪状态
- 死亡:线程对象变成垃圾,等待回收。
CPU同一时刻只能处理一个线程,多线程是多个线程轮流运行,允许运行但在等待的线程处于阻塞状态。
方法 | 定义 |
---|---|
sleep | 需要指定睡眠时间,单位是毫秒(ms)。(时间到了继续运行); |
wait() | 等待,自己无法醒来,用notify(),可以唤醒; |
- CPU的执行资格:可以被CPU处理,在处理队列中排队;
- CPU的执行权:正在被CPU处理;
实现多线程
1.继承Thread类
- 实现步骤
1.继承Thread类2.重写run方法3.直接创建Thread的子类对象创建线程。4.调用start方法开启线程并调用线程的任务run方法执行。
- Run方法中定义的是线程中要运行的任务代码
不是类中的所有代码都需要被线程执行的,为了区分哪些代码能够被线程执行,java提供了Thread类中的
run()
用来包含那些被线程执行的代码。(代码若想被多线程执行,必须写(封装)在run里面)一般来说,被线程执行的代码肯定是比较耗时的。
- 方法:
方法 | 定义 |
---|---|
run() |
定义类时需要覆写的方法,定义的是线程中要运行的代码块 |
start() |
开启线程,调用run方法 |
getName() |
获取线程的名称(Thread-编号(从0开始))run和start都是Thread-0 |
super(name) |
带参构造时,设置线程名称 |
Thread.currentThread() |
返回正在运行的线程对象;Thread thread = Thread.currentThread(); |
- run()和start()区别
run()
:仅仅是封装被线程执行的代码,直接调用是普通方法,是按顺序运行start()
:首先启动了线程,然后再由jvm去调用该线程的run()方法,是随机运行
注意:
同一个线程只能调用一次!!(如my.start();只能用一次,第二次会显示异常IllegalThreadStateException)
- 简单示例:
//创建一个类,继承Thread,实现run方法public class MyThread extends Thread { public void run() { for (int x = 0; x < 10000; x++) { System.out.println(x); } }}//测试,调用线程直接new一个,start即可public class MyThreadDemo { public static void main(String[] args) { // 创建两个线程对象 MyThread my1 = new MyThread();//开启一个新的线程 MyThread my2 = new MyThread(); my1.start();//执行线程 my2.start(); }}//-----这是三个线程,my1,my2,main
线程名称
名字 | 方法 | 获取的线程 |
---|---|---|
获取main线程名称 获取当前线程名称 |
Thread.currentThread().getName() | main |
当前的线程(main无法使用) | 方法1:Thread.currentThread().getName() 方法2:getName() |
Thread-0 |
- 无参构造
public class MyThread extends Thread { public void run() { for (int x = 0; x < 100; x++) { System.out.println(getName() + ":" + x); } }}
- 带参构造
public class MyThread extends Thread { public MyThread() {} ////在这里可以设置线程名比如super("林"); public MyThread(String name){ super(name); } @Override public void run() { for (int x = 0; x < 100; x++) { System.out.println(getName() + ":" + x); } }}
获取线程名称
//在main方法中调用MyThread my1 = new MyThread();MyThread my2 = new MyThread();System.out.println("main线程:"+Thread.currentThread().getName());//获取主线程名称my1.start();my2.start();//*************************************************************************main线程:mainThread-1:0Thread-1:1
设置线程名称:2种
方法 | 定义 |
---|---|
setName(String name) | 设置线程的名称(main线程无法改名) |
无参构造设置线程名
MyThread my1 = new MyThread();MyThread my2 = new MyThread();my1.setName("林青霞");my2.setName("刘意");my1.start();my2.start();////////林青霞:0刘意:0...
带参构造
MyThread my1 = new MyThread("林青霞");MyThread my2 = new MyThread("刘意");my1.start();my2.start();//****************林青霞:13刘意:0
2.实现Runnable接口(常用)
概述
1.为什么要给Thread类的构造函数传递Runnable的子类对象?
因为线程的任务都封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时就必须明确要运行的任务。
2.为什么可以避免由于Java单继承带来的局限性
比如说,某个类已经有父类了,而这个类想实现多线程,但是这个时候它已经不能直接继承Thread类了(接口可以多实现implements,但是继承extends只能单继承),它的父类也不想继承Thread因为不需要实现多线程。
3.实现Runnable接口的好处:
- 可以避免由王Java单继承带来的 限性。
- 适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面问对象的设计思想。
实现
- 实现步骤
1,实现Runnable接口。2,重写run方法,将线程的任务代码封装到run方法中。3,创建本类的对象4,创建Thread类的对象,并把3步骤的对象作为构造参数传递。
- 编写实现类
class Demo implements Runnable{ //通过接口的形式完成。public void run(){ //覆盖接口中的run方法 for(int x=0; x<100; x++) System.out.println(Thread.currentThread().getName()+"....."+x);//获取名称,只能间接用 }}
- 不初始化线程名:
class ThreadDemo{ public static void main(String[] args) { Demo d = new Demo();//注意:MyRunnable对象只需要创建一个即可,多个Thread对象可以接收同一个MyRunnable对象 Thread t1 = new Thread(d);//通过Thread类创建线程对象,并传递Runnable。 Thread t2 = new Thread(d); t1.start(); t2.start(); }}//-----结果---------Thread-0...0Thread-0...1
- 初始化线程名
Demo my = new Demo();Thread t1 = new Thread(my, "林青霞"); //创建,初始化线程名Thread t2 = new Thread(my, "刘意");t1.start();t2.start();//****************************林青霞...84刘意...64
线程安全问题
- 导致安全问题的出现的原因:
- 多个线程在操作共享的数据。
- 操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算。就会导致线程安全问题的产生。(例如,卖票,多线程,可能把数据卖到-1,因为各线程互相独立)
注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。
- 解决思路--同步代码块
就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程时不可以参与运算的。必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。(同一时间,只能有一个线程参与运算---同步机制)
- 回顾以前线程安全的类
StringBuffer sb = new StringBuffer();Vector<String> v = new Vector<String>();Hashtable<String, String> h = new Hashtable<String, String>();(在定义时就锁了,因此效率低)
注意
:Vector是线程安全的时候才去考虑使用的,但是即使要安全,也不用
- 把一个线程不安全的集合类变成一个线程安全的集合类:
以前的:List
list1 = new ArrayList ();// 线程不安全
public static <T> List<T> synchronizedList(List<T> list)List<String> list2 = Collections.synchronizedList(new ArrayList<String>()); // 线程安全
实现卖电影票案例(不安全)
某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
方式1:继承Thread类
public class SellTicket extends Thread { // 定义100张票 // 为了让多个线程对象共享这100张票,我们其实应该用静态修饰 private static int tickets = 100; public void run() { // 定义100张票 // 每个线程进来都会走这里,这样的话,每个线程对象相当于买的是自己的那100张票,这不合理,所以应该定义到外面 // 是为了模拟一直有票 while (true) { if (tickets > 0) { System.out.println(getName() + "正在出售第" + (tickets--) + "张票"); }}}}
调用
public class SellTicketDemo { public static void main(String[] args) { // 创建三个线程对象 SellTicket st1 = new SellTicket(); SellTicket st2 = new SellTicket(); SellTicket st3 = new SellTicket(); // 给线程对象起名字 st1.setName("窗口1"); st2.setName("窗口2"); st3.setName("窗口3"); // 启动线程 st1.start(); st2.start(); st3.start(); }}
----
说明的是,通过继承Thread类来实现题中的需求并不是很好(tickets要用static修饰,并不太好),其实用Runnable接口更好地进行数据分离
方式2:实现Runnable接口
public class SellTicket implements Runnable { // 定义100张票 private int tickets = 100; public void run() { while (true) { if (tickets > 0) { System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票"); } } }}
调用
public class SellTicketDemo { public static void main(String[] args) { // 创建资源对象 SellTicket st = new SellTicket(); // 创建三个线程对象 Thread t1 = new Thread(st, "窗口1"); Thread t2 = new Thread(st, "窗口2"); Thread t3 = new Thread(st, "窗口3"); // 启动线程 t1.start();t2.start(); t3.start(); }}
问题分析:同票和负数票
这种代码会有安全隐患,比如一个窗口获取票后,过来一段时间才订,在这段时间其他窗口就运行了,此处以sleep模拟,让每个线程休息一段时间
if (tickets > 0) { // 为了模拟更真实的场景,我们稍作休息 try { Thread.sleep(100); // t1就稍作休息,t2就稍作休息 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(......);}}}}
但是出问题了!!!
- 问题1:出现了重复的票:
CPU的一次操作必须是原子性的(在读取tickets--的原来的数值和减1之后的中间挤进了两个线程而出现重复)
- 问题2:出现了0和负票:
随机性和延迟导致的(三个线程同时挤进一个循环里,tickets--的减法操作有可能在同一个循环中被执行了多次而出现越界的情况,比如说tickets要大于0却越界到了-1)
也就是说,线程1执行的同时线程2也可能在执行,而不是线程1执行的时候线程2不能执行。
这就是线程的安全问题
- 解决方法:(下方有实现)
- 同步代码块
- 同步方法(简洁)
- lock锁:
同步(synchronized)
同步概述
同一时间,只能有一个线程访问该代码;
同步可以解决安全问题的根本原因就在那个对象synchronized上。该对象如同锁的功能。(对象锁,同步锁)
格式:
synchronized(对象){ 需要被同步的代码 ;}
- 同步的特点
- 同步的前提:同步中必须有多个线程并使用同一个锁。未满足这两个条件,不能称其为同步。
- 同步的好处: 解决了线程的安全问题.
- 同步的弊端:当线程相当多时,因为每个线程都会去判断同步锁,这是很耗费资源的,无形中会降低程序的运行效率。
同步方法
- 方法1:同步代码块
Object obj = new Object();//同步,必须作为成员变量, 不能放在run内,public void run(){ synchronized(obj){…}}
- 方法2:
public void run(){ synchronized(this或Ticket.class){…}}
- 方法3:同步方法
public synchronized void add(int num){...}
同步代码块
- 同步代码块的锁对象是:
任意对象
买票同步代码块
票是按顺序减少的且没有了同票和负票
//卖票,共有100张票,多个线程同时卖票,一张票只能卖一次public class SellTicket implements Runnable { // 定义100张票 private int tickets = 100; //创建锁对象 private Object obj = new Object(); public void run() { while (true) { synchronized (obj) {//必须用在run外面定义的obj,而不能用new Object,那样不是同一把锁,还会出错 if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace();} System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票"); } } } }}
调用
public class SellTicketDemo { public static void main(String[] args) { // 创建资源对象 SellTicket st = new SellTicket(); // 创建三个线程对象 Thread t1 = new Thread(st, "窗口1"); Thread t2 = new Thread(st, "窗口2"); Thread t3 = new Thread(st, "窗口3"); // 启动线程 t1.start(); t2.start(); t3.start(); }}
票是按顺序减少的且没有了同票和负票
同步方法
public synchronized void add(int num){ … }
同步方法用的锁
:this
静态方法的锁对象是
:当前类名.class
,可以用对象.getClass()
方法获取也可以用类名.class
表示;- 同步方法的格式:把同步关键字加在方法上。
- 同步函数和同步代码块的区别:
- 同步函数的锁是固定的this。
- 同步代码块的锁是任意的对象。
建议使用同步代码块。
public static synchronized void show(){}(其中num要该为static)
买票同步方法
方法1
//定义出售的票源public class Tickets implements Runnable{ private int ticket = 100; public void run(){ while(true){ payTicket(); } } public synchronized void payTicket(){ if( ticket > 0){ try{ Thread.sleep(10); }catch(Exception ex){} System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--); } }}
方法2
会出错,因为第一个synchronized的锁是d,而第二个同步方法的锁是this,会出错
- 方法:
- 方法1.把第一个的锁换成:
this
- 方法2.在第二个方法锁的第一行加上同样的锁
synchronized(d)
public class Tickets implements Runnable{ private int ticket = 100; private int x = 0; //run方法定义 public void run() { while (true) { if(x%2==0){ synchronized (d) {//this if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票 "); } } }else { sellTicket(); } x++; } } //-------------测试----------- private static synchronized void sellTicket() { if (tickets > 0) { try { // Thread.sleep(100);//延时 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票 "); } } }
方法3:静态方法锁
在总票数和同步方法上添加
static
出错,静态方法锁不能用this,需要用的是当前类的class对象:SellTicket.class
public class Tickets implements Runnable{ private static int tickets = 100;// 用静态方法锁,ticket必须加静态 private int x = 0; public void run() { while (true) { if (x % 2 == 0) { synchronized (this) {// SellTicket.class if (tickets > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票 "); } } } else { sellTicket(); } x++; } } private static synchronized void sellTicket() { if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票 "); } }}
银行存钱案例
两个人到银行存钱,每人存3次,每次100‘
class Bank {// 建立银行类 private int sum;// 金库 public synchronized void add(int num) {// 同步函数(第二种写法),存钱 sum = sum + num; try { Thread.sleep(10); } catch (InterruptedException e) {} // 延时等待,使顺序 System.out.println("sum=" + sum);// 显示金库余额 }}class Cus implements Runnable {// 定义线程类 private Bank b = new Bank(); public void run() {// 覆盖run方法 for (int x = 0; x < 3; x++) {// 存钱,存三次,每次100 b.add(100); } }}// 循环有次数限制,不会出现死循环class BankDemo { public static void main(String[] args) { Cus c = new Cus();// 实例化线程类 Thread t1 = new Thread(c);// Thread Thread t2 = new Thread(c); t1.start();// 开启线程 t2.start(); }}
Lock锁(JDK5之后)
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
方法 | 定义 |
---|---|
void lock() | 获取锁 |
void unlock() | 释放锁 |
ReentrantLock
是Lock的实现类(序列化类)
Lock锁卖票案例
public class SellTicket implements Runnable {//实现接口 private int tickets = 100; // 定义锁对象 private Lock lock = new ReentrantLock(); public void run() { while (true) { try { // 加锁 lock.lock(); if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票"); } } finally { // 释放锁 lock.unlock();// 放在finally中,使释放锁必须进行 } } }}
线程死锁
死锁问题及其代码
- 同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。
- 死锁前提:多线程,同步嵌套
- 线程进入同步取锁,进去了不出去
- 死锁概述:是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
死锁案例
方法1
1.定义锁对象类,两个锁A和B
public class MyLock { public static final Object lockA=new Object(); public static final Object lockB=new Object();}
2.线程任务类
public class ThreadTask implements Runnable { int x = new Random().nextInt(2);// 0,1 获取[0,2)之间随机值 // 指定线程要执行的任务代码 public void run() { while (true) { if (x % 2 == 0) { // 情况一 synchronized (MyLock.lockA) { System.out.println("if-LockA"); synchronized (MyLock.lockB) { System.out.println("if-LockB"); System.out.println("if大口吃肉"); } } } else { // 情况二 synchronized (MyLock.lockB) { System.out.println("else-LockB"); synchronized (MyLock.lockA) { System.out.println("else-LockA"); System.out.println("else大口吃肉"); } } } x++;} }}
3.测试类
public class ThreadDemo { public staticvoid main(String[] args) { // 创建线程任务类对象 ThreadTask task = new ThreadTask(); // 创建两个线程 Thread t1 = new Thread(task); Thread t2 = new Thread(task); // 启动线程 t1.start(); t2.start(); }}
方法2:
定义两个锁:
public class LockA { // 做一个私有构造器,保证锁是私有的 private LockA() {} // 为了让其他类可以调用,这样调用,用final使其他类不能修改 // 只能通过类名调用静态成员获得 public static final LockA locka = new LockA();}///////////////////public class LockB { private LockB() {} public static final LockB lockb = new LockB();}
线程任务类
public class DeadLock implements Runnable {// 写死循环 private int i = 0; public void run() { while (true) { if (i % 2 == 0) {// 偶数 // 先进入A同步,再进入B同步 synchronized (LockA.locka) { System.out.println("if...locka");// 先进入A同步 synchronized (LockB.lockb) { System.out.println("if...lockb"); } } } else { // 先进入B同步,再进入A同步 synchronized (LockB.lockb) { System.out.println("else...lockb");// 先进入B同步 synchronized (LockA.locka) { System.out.println("else...locka"); } } } i++;// 一次奇数一次偶数 } }}
测试类
public class DeadLockDemo { public static void main(String[] args) { DeadLock dead = new DeadLock(); Thread t0 = new Thread(dead); Thread t1 = new Thread(dead); t0.start(); t1.start(); }}//------结果---if...locka if...lockb else...lockb if...locka 形成死锁
线程间通信
线程间通信概述
线程间通讯:多个线程在处理同一资源,但是任务却不同。(不同线程处理同一个资源)
例程:
2.必须加同步,否则会出现(mike...女女)姓名和性别不对应的情况
输入和输出都要加锁,
必须是同一个锁(此处用resource作为锁可以,用this作为锁不可以(输入输出不是同一个锁))
//资源,一对一,输入一个,输出一个class Resource { String name; String sex;}
生产者
// 输入,生产者(输入),对资源对象Resource中成员变量赋值class Input implements Runnable {//实现接口 Resource r;// 反参数传递,因为:输入输出是同一个资源,不能用new了,可以加private Input(Resource r) { this.r = r; }// 反参数传递,构造函数,可以加public public void run() { int x = 0; while (true) { synchronized (r) {// 解决线程问题,与输出锁相同, 同步必须在while里面 if (x == 0) {// x两次切换赋值,使得好像输入很多 r.name = "mike"; r.sex = "nan"; } else { r.name = "丽丽"; r.sex = "女女"; } } x = (x + 1) % 2;// x的0、1变化}}}
消费者
//输出,消费者(输出)class Output implements Runnable { Resource r; Output(Resource r) { this.r = r;} public void run() { while (true) { synchronized (r) {// 和输入要用相同的锁,同步必须在while里面 System.out.println(r.name + "....." + r.sex); } } }}
测试
class aaa { public static void main(String[] args) { Resource r = new Resource();// 创建资源。 Input in = new Input(r);// 创建任务 Output out = new Output(r); Thread t1 = new Thread(in);// 创建线程,执行路径。 Thread t2 = new Thread(out); t1.start();// 开启线程 t2.start(); }}
- 优化:使输入和输出一对一:(下方的等待/唤醒机制案例)
等待/唤醒机制
虽然数据安全了,但是呢,一次一大片不好看(获取同一个数据一次而输出多次,也就是set一次却get了多次),我就想依次的一次一个输出(也就是set一次get一次)。
如何实现呢?
通过Java提供的等待唤醒机制解决。
在Object类中提供了方法:
- 涉及方法:
方法 | 定义 |
---|---|
wait() |
让线程处于冻结状态,被wait的线程会被存储到线程池中。(释放CPU执行资格和执行权)this.wait(); |
notify() |
唤醒线程池中一个线程(任意)。notify(); |
notifyAll() |
唤醒线程池中的所有线程。notifyAll(); |
这些方法都必须定义在同步中。因为这些方法是用于操作线程状态的方法。必须要明确到底操作的是哪个锁上的线程。
- 为什么wait、notify、notifyAll定义在了Object类中?
因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁。任意的对象调用的方式一定定义在Object类中。
- wait 和 sleep 区别?
wait | sleep |
---|---|
指定时间 | 都可以 |
暂停时 | 释放执行权,释放锁。(别的线程可以进去) |
方法
方法 | 定义 |
---|---|
if |
if(flag),只有一次,会导致不该运行的线程运行了。出现了数据错误的情况。(等待后,获得执行权不会再次进行判断flag)(一对一使用) |
while |
while(flag),解决了线程获取执行权后,是否要运行!(等待后,获得执行权可以再次进行判断flag)(多对多使用) |
notify |
只能唤醒一个线程,如果本方唤醒了本方法,没有意义。而且while判断标记+notify会导致死锁。(一对一使用) |
notifyAll |
解决了本方线程一定会唤醒对方线程的问题。(多对多使用) |
生产消费:加入等待唤醒机制,加入判断**
方法1(更好):
资源类
class Resource { private String name;// 安全优化,私有化 private String sex;// 安全优化,私有化 private boolean flag = false;// 一对一输入输出 public synchronized void set(String name, String sex) { if (flag) // 若资源有内容,暂时不输入,等输出后再赋值 try { this.wait(); } catch (InterruptedException e) { } // wait需要异常处理 this.name = name; this.sex = sex; flag = true; this.notify();// 赋值完成,唤醒输出 } public synchronized void out() { if (!flag)// 若资源无内容,暂时不输出 try { this.wait(); } catch (InterruptedException e) { } System.out.println(name + "..." + sex); flag = false;// 输出完成,定义flag无内容 this.notify();// 唤醒输入 }}
输入
class Input implements Runnable { Resource r;// 反参数传递,因为:输入输出是同一个资源,不能用new了 Input(Resource r) { this.r = r; }// 反参数传递,构造函数 public void run() { int x = 0; while (true) { if (x == 0)// x两次切换赋值,使得好像输入很多 r.set("mike", "nan"); else r.set("丽丽", "女女女女女女"); x = (x + 1) % 2;// x的0、1变化} }}
输出
class Output implements Runnable { Resource r; Output(Resource r) {this.r = r;} public void run() { while (true) { r.out();} }}
方法2:
资源
public class Student { String name; int age; boolean flag; // 判断默认情况是没有数据,如果是true,说明有数据}
生产者
public class SetThread implements Runnable { // 生产者 private Student s; private int x = 0; public SetThread(Student s) { this.s = s; } public void run() { while (true) { synchronized (s) { if (s.flag) { // 判断有没有 try {// 若有 s.wait(); // 等待(必须是锁的wait),并释放锁,释放CPU执行权; } catch (InterruptedException e) { e.printStackTrace();} } if (x % 2 == 0) {// 若没有,或没有了之后释放锁 s.name = "林青霞"; s.age = 27; } else { s.name = "刘意"; s.age = 30; } x++; // x=1 s.flag = true;// 生产完,修改标记,使进入消费 s.notify(); // 唤醒t2,抢CPU的执行权。 } // t1有,或者t2有 } }}
消费
public class GetThread implements Runnable {// 消费 private Student s; public GetThread(Student s) { this.s = s; } public void run() { while (true) { synchronized (s) { if (!s.flag) {// 若此时没有 try { s.wait(); // 等待。并立即释放锁。将来醒过来的时候,是从这里醒过来 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(s.name + "---" + s.age);// 当有内容的时候, // 林青霞---27 // 刘意---30 s.flag = false; // 修改标记防止再次进入消费 s.notify();// 唤醒线程唤醒t1 } } }}
测试
public class StudentDemo { public static void main(String[] args) { // 创建资源 Student s = new Student(); // 设置和获取的类 SetThread st = new SetThread(s); GetThread gt = new GetThread(s); // 线程类 Thread t1 = new Thread(st); Thread t2 = new Thread(gt); // 启动线程 t1.start(); t2.start(); }}//-----结果----林青霞---27刘意---30林青霞---27刘意---30林青霞---27
优化生产消费问题
多生产者,多消费者的问题。烤鸭生产一只消费一只
资源
class Resource { private String name;// 商品名称 private int count = 1;// 烤鸭编号 private boolean flag = false;// 标记 public synchronized void set(String name) {// 生产设置烤鸭名字 while (flag)// while进行判断,如果正在烤 try {this.wait();} catch (InterruptedException e) {} // 等待t1 t0 this.name = name + count;// 烤鸭+编号----若已经消费了 count++;// 下一个编号2 3 4 System.out.println(Thread.currentThread().getName() + "..生产者.." + this.name);// 生产烤鸭1 flag = true; notifyAll();// 多线程,只唤醒一个可能出现死锁,需要唤醒所有线程 } public synchronized void out() {// 消费t3 while (!flag)// 如果正在卖 try {this.wait();} catch (InterruptedException e) {} // 等待t2 t3 System.out.println(Thread.currentThread().getName() + "...消费者........" + this.name);// 消费烤鸭1 flag = false; notifyAll(); }}
生产者
class Producer implements Runnable {// 生产者 private Resource r; Producer(Resource r) {this.r = r;}// 构造 public void run() { while (true) {r.set("烤鸭");} // 生产烤鸭 }}
消费者
class Consumer implements Runnable {// 消费者 private Resource r; Consumer(Resource r) {this.r = r;} public void run() { while (true) { r.out();} // 消费烤鸭 }}
测试
class ProducerCustomerDemo { public static void main(String[] args) { Resource r = new Resource();// 实例化资源 Producer pro = new Producer(r);// 生产 Consumer con = new Consumer(r);// 消费 Thread t0 = new Thread(pro);// 线程t0负责生产 Thread t1 = new Thread(pro); Thread t2 = new Thread(con); Thread t3 = new Thread(con); t0.start(); t1.start(); t2.start(); t3.start(); }}//----结果----Thread-1..生产者..烤鸭136416Thread-2...消费者........烤鸭136416Thread-0..生产者..烤鸭136417Thread-3...消费者........烤鸭136417Thread-1..生产者..烤鸭136418Thread-2...消费者........烤鸭136418Thread-0..生产者..烤鸭136419Thread-3...消费者........烤鸭136419Thread-1..生产者..烤鸭136420Thread-2...消费者........烤鸭136420Thread-0..生产者..烤鸭136421
Condition等待/唤醒机制
jdk1.5以后将同步和锁封装成了对象。并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。
- Synchronized()同步的锁是隐式的
- Lock接口: 出现替代了同步代码块Synchronized或者同步函数。将同步的隐式锁操作变成显示锁操作。同时更为灵活。可以一个锁上加上多组监视器。
Lock lock = new ReentrantLock();
Condition接口
:出现替代了Object中的wait、notify、notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象。可以任意锁进行组合。
可以有多个监视器
接口 | 方法 | 定义 |
---|---|---|
Lock接口 | Lock lock = new ReentrantLock(); | |
lock() | 获取锁。lock.lock(); | |
unlock() | 释放锁,通常需要定义finally代码块中。lock.unlock(); | |
Condition接口 | Condition con = lock.newCondition(); | |
await() | 代替wait()功能,con.await(); | |
signal() | 代替notify()功能,con.signal(); | |
signalAll() | 代替notifyAll()功能,con.signalAll(); |
优化生产消费问题
资源
//jdk1.5以后将同步和锁封装成了对象。import java.util.concurrent.locks.*;class Resource { private String name; private int count = 1; private boolean flag = false; Lock lock = new ReentrantLock();// 创建一个锁对象。 // 通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者。 Condition producer_con = lock.newCondition();// 监视生产锁 Condition customer_con = lock.newCondition();// 监视消费锁 public void set(String name) {// 去掉synchronized同步 lock.lock();// 获取锁---同步 try {// 异常处理 while (flag)// 用while,循环检测 try { producer_con.await(); } catch (InterruptedException e) { } // 生产等待 this.name = name + count; count++; System.out.println(Thread.currentThread().getName() + "...生产者5.0..." + this.name);// 生产烤鸭 flag = true; customer_con.signal();// 生产完,释放另一组锁的一个,运行消费 } finally { lock.unlock(); } // 释放锁 } public void out() { lock.lock();// 获取锁---同步 try { while (!flag)// 用while,循环检测 try { customer_con.await(); } catch (InterruptedException e) { } // 消费等待 System.out.println(Thread.currentThread().getName() + "...消费者.5.0......." + this.name);// 消费烤鸭 flag = false; producer_con.signal();// 消费后,释放另一组锁的一个,运行生产 } finally { lock.unlock(); } // 释放锁 }}
线程组ThreadGroup
概述
- 线程组: 把多个线程组合到一起。
它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
- 默认情况下,所有的线程都属于主线程组。
我们也可以给线程设置分组
方法 | 定义 |
---|---|
public final ThreadGroup getThreadGroup() | 获取所属的线程组 |
public final String getName() | 获取名字 |
Thread(线程组名, 类名, 自定义线程) | 创建线程组 |
Void destroy() | 销毁此线程组及其所有子组 |
Void interrupt() | 中断此线程组中的所有线程 |
Void setDaemon() | 变为后台线程:守护线程:只有守护线程会结束程序 |
Void setMaxPriority() | 设置最高优先级 |
public class MyRunnable implements Runnable { public void run() { for (int x = 0; x < 100; x++) { System.out.println(Thread.currentThread().getName() + ":" + x); } }}
测试
获取线程组,名字
MyRunnable my = new MyRunnable();Thread t1 = new Thread(my, "林青霞");Thread t2 = new Thread(my, "刘意");// 获取所在线程组的名字// 线程类里面的方法:public final ThreadGroup getThreadGroup()ThreadGroup tg1 = t1.getThreadGroup(); //获取线程组ThreadGroup tg2 = t2.getThreadGroup();// 线程组名的方法:public final String getName()String name1 = tg1.getName(); //获取线程组名字String name2 = tg2.getName();System.out.println(name1);System.out.println(name2);//获取主线程线程组名字System.out.println(Thread.currentThread().getThreadGroup().getName());
默认情况下,所有的线程都属于主线程组main
获取线程组名字,自己建立的线程属于main线程组
修改线程组
创建一个线程组
创建其他线程的时候,把其他线程的组指定为我们自己新建线程组
//创建一个新的线程组ThreadGroup tg = new ThreadGroup("这是一个新的组");MyRunnable my = new MyRunnable();//Thread(ThreadGroup group, Runnable target, String name)Thread t1 = new Thread(tg, my, "林青霞");Thread t2 = new Thread(tg, my, "刘意");System.out.println(t1.getThreadGroup().getName());System.out.println(t2.getThreadGroup().getName());//通过组名称设置后台线程,表示该组的线程都是后台线程---守护线程:若只剩下守护线程,程序结束//设置守护线程tg.setDaemon(true);
----创建成功
线程池
概述
(1****)原因:创建和销毁线程成本是比较高的,而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
(2)线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
线程池主要用来**解决线程生命周期开销问题和资源不足问题**。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使**应用程序响应更快**。另外,通过适当的调整线程中的线程数目可以**防止****出现资源不足**的情况。
(3)JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
方法 | 定义 |
---|---|
public static ExecutorService newCachedThreadPool() | 创建一个具有缓存功能的线程池 |
public static ExecutorService newFixedThreadPool(int nThreads) | 创建一个可重用的,具有固定线程数nThreads的线程池; ExecutorService pool = Executors.newFixedThreadPool(2); |
public static ExecutorService newSingleThreadExecutor() | 创建一个只有单线程的线程池,相当于上个方法的参数是1 |
Future submit(Runnable task) | pool.submit(new MyRunnable()); |
Future submit(Callable task) | 调用 |
Void shutdown() | 结束线程池 |
这些方法的返回值是ExecutorService
对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。
它提供了如下方法
import java.util.concurrent.ExecutorService;public class MyRunnable implements Runnable { public void run() { for (int x = 0; x < 100; x++) { System.out.println(Thread.currentThread().getName() + ":" + x); } }}//_____________________________________________________________________public class ExecutorsDemo { public static void main(String[] args) { // 创建一个线程池对象,控制要创建几个线程对象。 ExecutorService pool = Executors.newFixedThreadPool(2); // 可以执行Runnable对象或者Callable对象代表的线程 pool.submit(new MyRunnable());//调用线程池中线程,提交一次调用一个线程,用完返回 pool.submit(new MyRunnable()); pool.shutdown();//结束线程池,没有他,程序一直不关闭 }}
Callable接口:创建线程3
run方法没有返回值,不能抛出异常,而这个接口的方法call()可以。
- (1)callable---是接口,有返回值,而且依赖于线程池才能使用,一般很少用
- (2)Callable:是带泛型的接口。
这里指定的泛型其实是
call()
方法的返回值类型。 - (3)好处:
- 可以有返回值
- 可以抛出异常(Runable不能抛出异常)
- (4)弊端:代码比较复杂,所以一般不用
- (5)获取返回值:
Future<String> f = es.submit(new ThreadPoolCallable());String s = f.get();//get是泛型
案例
public class MyCallable implements Callable {//可以直接设置call的类型 Callable<String> public String call() throws Exception { for (int x = 0; x < 100; x++) { System.out.println(Thread.currentThread().getName() + ":" + x); } return null; }}
测试
public class CallableDemo { public static void main(String[] args) { //创建线程池对象 ExecutorService pool = Executors.newFixedThreadPool(2); //可以执行Runnable对象或者Callable对象代表的线程 pool.submit(new MyCallable()); pool.submit(new MyCallable()); //结束线程池 pool.shutdown(); }}
1.求和案例
使用多线程技术,求和 , 两个线程,1个线程计算1+100,另一个线程计算1+200的和
public class MyCallable implements Callable<Integer> { private int number; public MyCallable(int number) { this.number = number; } public Integer call() throws Exception {//求1-number的和 int sum = 0; for (int x = 1; x <= number; x++) { sum += x; } return sum; }}/////////////////////////public class CallableDemo { public static void main(String[] args) throws InterruptedException, ExecutionException { // 创建线程池对象 ExecutorService pool = Executors.newFixedThreadPool(2); // 可以执行Runnable对象或者Callable对象代表的线程 Future<Integer> f1 = pool.submit(new MyCallable(100)); Future<Integer> f2 = pool.submit(new MyCallable(200)); // V get() Integer i1 = f1.get();//获得结果---需要抛出异常 Integer i2 = f2.get(); System.out.println(i1);//显示结果 System.out.println(i2); // 结束 pool.shutdown(); }}
匿名内部类方式使用多线程
(1)匿名内部类的格式:
new 类名或者接口名() { 重写方法;};
(2)本质:是该类或者接口的子类对象。
public class ThreadDemo { public static void main(String[] args) { // 1.继承Thread类来实现多线程-----只用一次 new Thread() { public void run() { for (int x = 0; x < 100; x++) { System.out.println(Thread.currentThread().getName() + ":"+ x); } }.start(); } }}
// 2.实现Runnable接口来实现多线程new Thread(new Runnable() { public void run() { for (int x = 0; x < 100; x++) { System.out.println(Thread.currentThread().getName() + ":" + x); } } }) {}.start();}
加上这个,就是两个线程互相抢了
// 更有难度的new Thread(new Runnable() { public void run() { for (int x = 0; x < 100; x++) { System.out.println("hello" + ":" + x); } }}) {}.start();
hello也加入了抢,共三个
new Thread(new Runnable() { public void run() { for (int x = 0; x < 100; x++) { System.out.println("world" + ":" + x); } }}.start();
全部运行的话,不会运行hello,只会运行world,还是3个
方法3:
Runnable r = new Runnable(){ public void run(){ System.out.println("###"); }};new Thread(r).start();
定时器
- 概述
定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能
- 定时器:可以让我们在指定的时间做某件事情,还可以重复的做某件事情。
在开发中一般不会用Timer,因为太弱,开发中一般用框架:
Quartz是一个完全由java编写的开源调度框架。
Timer---在util包中---定时器类
方法 | 定义 |
---|---|
public Timer() | 创建一个新的计时器 |
public void schedule(TimerTask(任务),Date (时间)) | 安排在指定的时间执行的任务(1次) |
public void schedule(TimerTask(任务),long(延迟)) | 安排在指定延迟后执行的任务(1次) |
public void schedule(TimerTask (任务),long (时间),long period) | 安排指定的任务从指定的延迟后开始进行重复的固定延迟执行 |
public void schedule(TimerTask (任务),Date (时间),long (延迟)) | 安排指定的任务在指定的时间开始进行重复的固定延迟执行 |
TimerTask--在Object包中:由Timer安排为一次执行或重复执行的任务
方法 | 定义 |
---|---|
public abstract void run() | 此计时器要执行的操作 |
public boolean cancel() | 终止此计时器 |
Int purge() | 从此计时器的任务队列中移除所有已取消的任务 |
Long sheduleExecutionTime() | 返回此任务最近实际执行是已安排执行时间 |
循环一次
// 做一个任务class MyTask extends TimerTask { private Timer t;//创建一个新的计时器 public MyTask(){}//构造 public MyTask(Timer t){ this.t = t;}//构造方法 public void run() { System.out.println("beng,爆炸了"); t.cancel();//终止要在任务执行完结束 }}public class TimerDemo { public static void main(String[] args) { // 创建定时器对象 Timer t = new Timer(); //3s后运行,并结束任务 t.schedule(new MyTask(t), 3000); }}
循环调用
循环爆炸:3秒后执行爆炸第一次,每隔2秒再继续炸---不用加结束了,不然会只运行一次
// 做一个任务class MyTask2 extends TimerTask { public void run() { System.out.println("beng,爆炸了"); }}public class TimerDemo2 { public static void main(String[] args) { // 创建定时器对象 Timer t = new Timer(); // 3秒后执行爆炸任务第一次,每隔2秒再继续炸 t.schedule(new MyTask2(), 3000, 2000); }}
案例:在指定的时间删除的指定目录
//需求:在指定的时间删除我们的指定目录(我使用项目路径下的demo)(只删除1次)class DeleteFolder extends TimerTask {// 定义删除类 public void run() { File srcFolder = new File("demo"); deleteFolder(srcFolder);// 调用删除方法 t.cancel();// 终止要在任务执行完结束 } // 递归删除目录 public void deleteFolder(File srcFolder) { File[] fileArray = srcFolder.listFiles();// 文件数组 if (fileArray != null) {// 若有内容 for (File file : fileArray) {// 遍历 if (file.isDirectory()) {// 若是文件夹 deleteFolder(file);// 遍历 } else {// 若是文件—删除,并输出名字 System.out.println(file.getName() + ":" + file.delete()); } } // 若没有内容,删除文件夹 System.out.println(srcFolder.getName() + ":" + srcFolder.delete()); } }}public class TimerTest { public static void main(String[] args) throws ParseException { Timer t = new Timer(); String s = "2014-11-27 15:45:00"; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date d = sdf.parse(s);// 定义删除时间 t.schedule(new DeleteFolder(), d);// 在指定时间删除指定内容 }}
多线程的单例
//多线程下的单例//饿汉式--单例模式,简单,使用class Single {// 公式 private static final Single s = new Single(); private Single() {} public static Single getInstance() { return s;}}// 懒汉式--延迟加载单例模式,麻烦,但是考试多(技术含量高)// 加入同步为了解决多线程安全问题。// 加入双重判断是为了解决效率问题。class Single { private static Single s = null; private Single() {} public static Single getInstance() {// 同步使同一时间只进入一个线程 if (s == null) {// 加入双重判断是为了解决效率问题。 synchronized (Single.class) {// 加入同步为了解决多线程安全问题。 // 不可以用this.getClass(),因为是非静态的 if (s == null) s = new Single(); }} return s;}}class SingleDemo { public static void main(String[] args) { Single s1 = Single.getInstance();// 单例调用 Single s2 = Single.getInstance();// 用s1,s2调用单例其他方法}}
停止线程
- 停止线程:
- stop方法。已经过时不再使用。
- run方法结束。
- 怎么控制线程的任务结束呢?
- 方法1:定义循环结束标记
任务中都会有循环结构,只要控制住循环就可以结束任务。控制循环通常就用定义标记(条件:while之类)来完成。(若有wait(处于冻结状态),sleep(时间有时很长)无法结束)
- 如果线程处于了冻结状态,无法读取标记。如何结束呢?
- 使用
interrupt
(中断)方法。
可以使用
interrupt()
方法将线程从冻结状态强制恢复到运行状态中来,让线程具备cpu的执行资格。 但是强制动作会发生了InterruptedException,记得要处理。 - 使用
class StopThread implements Runnable { private boolean flag = true; public synchronized void run() { while (flag) { try { System.out.println("线程"); wait(); } // 有wait,处于冻结状态,无法正常结束 catch (InterruptedException e) {// 中断异常处理 System.out.println(Thread.currentThread().getName() + ".." + e); flag = false;// 中断后,结束 } System.out.println(Thread.currentThread().getName() + "......++++"); } } public void setFlag() { flag = false; }}class StopThreadDemo { public static void main(String[] args) { StopThread st = new StopThread(); Thread t1 = new Thread(st); Thread t2 = new Thread(st); t1.start();// run中,直接进入wait状态, t2.setDaemon(true); // t2设置为守护线程(后台线程)在启动线程前调用 t2.start(); int num = 1; for (;;)// 无限循环 { if (++num == 5) {// 若num==20,线程结束 System.out.println("interrupt前"); t1.interrupt();// t1中断,运行run中的中断异常处理 System.out.println("interrupt后"); break; } System.out.println("main...." + num); } System.out.println("over");// main结束 }}//-----结果---线程//线程1输出,之后1进入waitmain....2//main输出main....3main....4interrupt前//主函数,顺序interrupt后//主函数,顺序(interrupt在线程中,并行)over//主函数线程//线程2输出,之后2进入waitThread-1.....java.lang.InterruptedException//interrupt1,运行run的catchThread-1......++++//线程1,顺序输出
习题
1.如果错误 错误发生在哪一行?
class Test implements Runnable{ public void run(Thread t){}}----------------------------------------------------错误在第一行,应该被abstract修饰:class abstract Test implements Runnable{原因:这个类实现了接口,但是抽象方法run没有进行覆盖,那么类要定义为抽象类
2.输出的是什么?
class ThreadTest { public static void main(String[] args) { new Thread(new Runnable() { public void run() { System.out.println("runnable run"); } }) { public void run() { System.out.println("subThread run"); }// run进行了覆写 }.start();}}-------------------------------------------------输出:subThread run若是后面{}里没有内容,输出:runnable run正常写法:new Thread(){ public void run(){ System.out.println("run覆写"); }}.start();
来源:https://www.cnblogs.com/ziyue7575/p/12193933.html