多进程概述
进程
多线程
线程调度
线程调度概述
线程优先级
设置对象优先级
线程控制:其他方法
线程睡眠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-----默认是5
10---设置后
1
5
东方不败: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线程:main
Thread-1:0
Thread-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...0
Thread-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..生产者..烤鸭136416
Thread-2...消费者........烤鸭136416
Thread-0..生产者..烤鸭136417
Thread-3...消费者........烤鸭136417
Thread-1..生产者..烤鸭136418
Thread-2...消费者........烤鸭136418
Thread-0..生产者..烤鸭136419
Thread-3...消费者........烤鸭136419
Thread-1..生产者..烤鸭136420
Thread-2...消费者........烤鸭136420
Thread-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进入wait
main....2//main输出
main....3
main....4
interrupt前//主函数,顺序
interrupt后//主函数,顺序(interrupt在线程中,并行)
over//主函数
线程//线程2输出,之后2进入wait
Thread-1.....java.lang.InterruptedException//interrupt1,运行run的catch
Thread-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();