进程
中同时运行了多个线程,且每个线程间相互独立
,用来完成不同的工作,则称之为多线程
栈区
中都有自己的执行空间,自己的方法区、自己的变量
。相互争夺cpu资源竞相执行相关对象的代码段。
同时执行
可能会造成线程安全
问题(线程之间同时拥有一个变量,且发生了修改),为了避免这个问题,需要同步锁
来使一个线程执行时,其他线程会等待这个线程执行完毕才执行区别 | 进程 | 线程 |
---|---|---|
根本区别 | 进程是作为资源分配 的单位,一个程序就是一个进程 |
线程是cpu调度和执行 的单位 |
开销 | 每个进程都有独立的代码和数据空间(进程上下文),进 程间的切换会有较大的开销。 | 线程可以看成时轻量级的进程 ,同一类线程共享代码 和数据空间 ,每个线程有独立的运行栈 和程序计数器(PC),CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。 |
所处环境 | 在操作系统 中能同时运行多个任务(程序) |
在同一程序(进程) 中有多个顺序流同时执行 |
内存分配 | 系统在运行的时候会为每个进程分配独立的内存区域,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段 | 除了CPU之外,不会为线程分配内存 ,线程是共享进程中的数据的,使用相同的地址空间(线程所使用的资源是它所属的进程的资源) |
包含关系 | 没有线程的进程是可以被看作单线程的,如果一个进 程内拥有多个线程,则执行过程不是一条线的,而是 多条线(线程)共同完成的。 | 线程是进程的一部分,所以线程有的时候被称 为是轻权进程或者轻量级进程。 |
通信方式 | 同一进程下的线程共享全局变量、静态变量等数据,线程之间的通信方便 ,如何处理好同步与互斥 是编写多线程程序的难点 |
进程之间的通信需要以通信的方式(IPC)进行 |
健壮性 | 一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间 | 多线程程序只要有一个线程死掉,整个进程也死掉 |
进程互斥与同步的区别
浏览器
、Web服务
(现在web是中间件帮你完成了线程的控制),web处理请求
,各种专用服务器
(如游戏服务器)Java桌面应用程序开发
)如果创建了太多的线程,CPU花费在上下文的切换的时间将多于执行程序的时间!
这会降低程序执行效率的。所以有效利用多线程的关键是理解程序是并发执行
而不是串行执行
的。进程仅仅是一个容器
,包含了线程运行中所需要的数据结构等信息。
一个进程创建时,操作系统会创建一个线程,这就是主线程
,而其他的子线程
,却要主线程的代码来创建,也就是由程序员来创建。
当一个程序启动
时,就有一个进程被操作系统(OS)创建,与此同时一个线程也立刻运行
,该线程通常叫做程序的 主线程(Main Thread),因为它是程序开始时就执行的,如果你需要再创建线程,那么创建的线程就是这个主线程的子线程。每个进程至少都有一个主线程, 主线程的重要性体现在两方面:1.是产生其他子线程的线程;2.通常它必须最后完成执行比如执行各种关闭动作
线程交替执行且速度很快,相当于同时执行
。对于线程的分类,我们可以简单划分为:
守护线程: 主要是指在进程中,为主线程提供一种通用服务的线程。
比如 gc线程,因为主线程一旦结束或者销毁,守护线程没有了守护对象,也将同步进行结束或销毁。
非守护线程/用户线程: 通常异步处理一些业务或逻辑
Java 线程分为守护线程(DaemonThread) 和 用户线程(UserThread)两类
默认情况下都属于用户线程
, 当在启动线程之前, 执行thread.setDaemon(true)
时, 线程会变成守护线程
。唯一的区别就是会影响虚拟机的退出(程序的终止)。
只剩下守护线程时,虚拟机会退出
,及程序终止;而当jvm中至少拥有一个用户线程时,jvm都不会退出。
守护线程是依赖于用户线程
,用户线程退出了,守护线程也就会退出,典型的守护线程如垃圾回收线程。子线程
也是守护线程
写操作
, 因为守护进程随时可能结束
。守护线程适用场景
针对于守护线程的特点,笔者认为,java 守护线程通常可用于开发一些为其它用户线程服务的功能
。比如说心跳检测
,事件监听
等。Java 中最有名的守护进程当属GC(垃圾回收)
测试程序退出
public class TestThread {
public static void main(String[] args) {
AnsyTask ansyTask = new AnsyTask();
Thread thread = new Thread(ansyTask);
// 设置线程为守护线程
// thread.setDaemon(true);
// 启动线程
thread.start();
System.out.println("main thread done");
}
}
class AnsyTask implements Runnable{
@Override
public void run() {
while (true){
System.out.println(LocalDateTime.now() + "-hello,thread");
}
}
}
2. 设置thread.setDaemon(true)
后, 线程变成守护线程
(守护线程), 程序直接终止, 仅输出一行信息"main thread done"。 因为程序执行完system 语句之后, main 程序作为唯一的一个用户线程执行结束了, jvm 中只剩下一个守护进程,所以jvm 便退出了。
测试守护线程中创建新的线程
测试会发现, 默认情况下, 守护线程创建的子线程依然是守护线程
,用户创建的守护线程依然是用户线程。也可以在创建子线程时通过setDaemon()方法修改.
public class TestThread {
public static void main(String[] args) throws InterruptedException {
AnsyTask ansyTask = new AnsyTask();
Thread thread = new Thread(ansyTask);
// 设置线程为守护线程
thread.setDaemon(true);
// 启动线程
thread.start();
// 给守护线程点儿执行时间
Thread.sleep(1000l);
}
}
class AnsyTask implements Runnable{
@Override
public void run() {
Thread thread = new Thread("subThread");
System.out.println(thread.getName() + " is daemon:" + thread.isDaemon());
}
}
1.继承java.lang.Thread类
,并且重写它的run方法
(线程体),将线程的执行主体放在其中;
2. 实现java.lang.Runnable接口
,重写它的run方法
(线程体),并将线程的执行主体放在其中;
3. 实现java.util.concurrent.Callable
,重写它的call方法
(线程体),并通过Futrue获取 call 方法的返回值
Java.lang.Thread
这个类run() 称为”线程体”
, run() 可以调用其他方法,使用其他类,并声明变量,就像主线程一样。start()
方法来启动一个线程继承Thread类
或实现Runnable 接口
。其实还有一种方式是实现 Callable与Future接口
, 类似于Runnable接口,但是就功能上来说更为强大一些,也就是被执行之后,可以拿到返回值。注:线程启动的方法是start()而不是run()。因为使用start方法整个线程处于就绪状态,等待虚拟机来进行调度
。而使用run,也就是当作了一个普通的方法进行启动,这样虚拟机不会进行线程调度,虚拟机会执行这个方法直到结束后自动退出
。
推荐单线程的时候使用继承 Thread 类方式创建,多线线程的时候使用Runnable、Callable 接口的方式来创建创建线程
继承 Thread
类创建的线程,编写最为简单,可以直接使用Thread类中的方法
,比如休眠直接就可以使用sleep
方法,而不必在前面加个Thread;获取当前线程Id,只需调用getId
就行,而不必使用Thread.currentThread().getId() 这么一长串的代码。但是使用Thread 类创建的线程,也有其局限性。比如资源不能共享,无法放入线程池
中等等。
使用Runnable、Callable
接口的方式创建的线程,可以增强代码的复用性,并且可以避免
单继承的局限性,可以
和线程池完美结合。但是也有不好的,就是
写起来不太方便,使用其中的方法不够简介```。
/**
* 代理角色
*/
public class ProxyRole implements MyInterface {
private RealRole realRole;
public ProxyRole(RealRole realRole){
this.realRole = realRole;
}
public void before(){
System.out.println("向对方问好");
}
public void after(){
System.out.println("再见");
}
@Override
public void talk() {
before();
realRole.talk();
after();
}
}
/**
* 共同接口
*/
interface MyInterface{
void talk();//谈话
}
/**
* 真实角色
*/
class RealRole implements MyInterface {
@Override
public void talk() {
System.out.println("正在谈话中。。。。");
}
}
class TestMain {
public static void main(String[] args) {
//创建真实角色
RealRole realRole = new RealRole();
//创建代理角色 + 传入真实角色的引用
ProxyRole proxyRole = new ProxyRole(realRole);
//由代理角色代理真实角色调用真实方法
proxyRole.talk();
}
}
描述:
创建一个线程的第一种方法是创建一个新的类,该类继承 Thread 类
,然后创建一个该类的实例。
继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。
该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例
。
public class RunMain {
public static void main(String[] args) {
//调用run 方法相当于普通方法的调用
Rabbit rabbit =new Rabbit();
Tortoise tortoise = new Tortoise();
//调用start()方法会将线程加入
rabbit.start();
tortoise.start();
//默认启动的线程
//1.主线程 2.GC 3.异常
for (int i = 1;i<1000 ;i++ ) {
System.out.println("主线程走了"+i+"步");
}
}
}
/**
* 兔子
* 1.继承Thread类 + 重写 run()(线程体)
* 2.创建子类对象 + 调用start()
*/
class Rabbit extends Thread {
/**
* run方法称为线程体
*/
@Override
public void run() {
for (int i = 1; i<= 1000 ; i++) {
System.out.println("兔子跑了"+i+"步");
}
}
}
class Tortoise extends Thread{
@Override
public void run() {
//线程体
for (int i = 1;i<1000 ;i++) {
System.out.println("乌龟跑了"+i+"步 ");
}
}
}
推荐使用Runnable接口创建线程
实现方式
/**
* 继承Runnable接口实现Run()方法
* 并发:(当一份资源多个使用的时候,可能会产生线程安全问题)
*/
class Web12306 implements Runnable{
private int num = 50;
@Override
public void run() {
while (true){
if(num<=0){
break;//跳出循环
}
System.out.println(Thread.currentThread().getName()+"抢到了票"+(--num));
}
}
}
class TestMain{
public static void main(String[] args) {
// 真实角色(一份资源)
Web12306 web = new Web12306 ();
// 代理角色(多个代理使用)
Thread thread1 = new Thread(web,"张三");
Thread thread2 = new Thread(web,"李四");
Thread thread3 = new Thread(web,"王五");
//启动线程
thread1.start();
thread2.start();
thread3.start();
}
}
实例2
class RunnableDemo implements Runnable {
private Thread t;
private String threadName;
RunnableDemo( String name) {
threadName = name;
System.out.println("Creating " + threadName );
}
public void run() {
System.out.println("Running " + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// Let the thread sleep for a while.
Thread.sleep(50);9
}
}catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
}
public class TestThread {
public static void main(String args[]) {
RunnableDemo R1 = new RunnableDemo( "Thread-1");
R1.start();
RunnableDemo R2 = new RunnableDemo( "Thread-2");
R2.start();
}
}
//Thread也可以进行类似的拓展重写,一模一样
Thread也是实现Runable接口,队Runnable接口进行了一些封装
)
(接口是可以多实现的)
, 不管是继承Thread类还是实现Runnable接口还是其他的,只要是通过同一个实例创建的线程都是可以实现资源共享的,也同样会发生线程安全的问题。例子
与实行Runnable相比, Callable功能更强大些
Future接口
Runnable、Callable
任务的执行结果
进行取消
、查询是否完成
、获取结果
等。FutrueTask
是Futrue接口的唯一的实现类
FutureTask
同时实现
了Runnable, Future
接口。它既可以作为Runnable被线程执
行,又可以作为 Future得到Callable的返回值
通过 Callable 和 Future 创建线程
创建Callable 接口的实现类
,并实现 call(
) 方法,该 call() 方法将作为线程执行体,并且有返回值。
创建 Callable 实现类的实例
,使用 FutureTask
类来包装 Callable 对象
,该FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
使用 FutureTask
对象作为 Thread 对象的 target
创建并启动新线程。
调用 FutureTask
对象的 get()````方法来
获得子线程执行结束后的返回值```。
/**
* 使用Callable创建线程
*/
public class TestCallable {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 从线程池中获取线程
ExecutorService executorService = Executors.newFixedThreadPool(2);
Race tortoise = new Race("乌龟", 1000L);
Race rabbit = new Race("兔子", 5000L);
// 执行并获取值保存到Future中
Future<Integer> tortoiseResult = executorService.submit(tortoise);
Future<Integer> rabbitResult = executorService.submit(rabbit);
//休眠10秒
//Thread.sleep(10000);//2秒
//设置flag为false停止线程
tortoise.setFlag(false);
rabbit.setFlag(false);
//获取线程返回结果
int tortoiseNum = tortoiseResult.get();
int rabbitNum = rabbitResult.get();
System.out.println("乌龟跑了-->"+tortoiseNum+"步");
System.out.println("兔子跑了-->"+rabbitNum+"步");
//停止服务
executorService.shutdownNow();
}
}
/**
* 使用lombok来在编译期间自动生成get/set方法
*/
@Data
class Race implements Callable<Integer> {
private String name;//名称
private Long time;//延时时间
private Boolean flag = true;
private Integer step = 0;//步
public Race(String name) {
this.name = name;
}
public Race(String name, Long time) {
this.name = name;
this.time = time;
}
@Override
public Integer call() throws Exception {
while (flag) {
Thread.sleep(time);
step++;
System.out.println(this.getName()+(step));
}
return step;
}
}
/**
* Lambda 简化(使用一次)线程方式
*
* Lambda 避免内部类创建过多,属于函数式编程概念
*/
public class LambdaThread {
// 1.静态内部类
static class Test implements Runnable{
@Override
public void run() {
System.out.println("听歌");
}
}
public static void main(String[] args) {
new Thread(new Test()).start();
// 2.局部内部类
class Test2 implements Runnable{
@Override
public void run() {
System.out.println("看电视");
}
}
new Thread(new Test2()).start();
// 3.匿名内部类(连名字都没有,必须结束接口或者父类)
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("打篮球");
}
}).start();
// 4.JDK8 Lambda表示简化线程
new Thread(()-> {
System.out.println("吃饭");
}).start();
}
}
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定应调度哪个线程来执行。
注意:优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高后调用优先级低的线程
Thread.NORM_PRIORITY 默认为普通优先级 5
)。Thread类的常量 | 描述 |
---|---|
static int MAX_PRIORITY | 线程可以具有的最高优先级,取值为10 |
static int MIN_PRIORITY | 线程可以具有的最低优先级,取值为1 |
static int NORM_PRIORITY | 分配给线程的默认优先级,取值为5 |
注意: 设置优先级并不能保证线程一定先执行。我们可以通过一下代码来验证。
public class PriorityTest {
public static void main(String[] args) {
Priority t1 = new Priority("张三");
Priority t2 = new Priority("李四");
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
}
}
class Priority extends Thread {
public Priority(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.getName() + " 线程运行开始!");
for (int i = 1; i <= 5; i++) {
System.out.println("子线程" + this.getName() + "运行 : " + i);
try {
sleep(new Random().nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.getName() + " 线程运行结束!");
}
}
自然停止:线程体自然执行完毕
外部干涉:通过线程体标识
具体见代码:
/**
* 线程的状态:
* 如何干涉线程的状态以及停止方法:
*/
public class ThreadStop {
public static void main(String[] args) {
Study study = new Study();
new Thread(study, "张三").start();
// 外部干涉
for (int i = 0; i < 100; i++) {
if (50 == i) {//外部干涉
study.stop();
}
System.out.println("main-->" + i);
}
}
}
class Study implements Runnable {
// 1.线程体中定义线程体使用的标识
private boolean flag = true;
@Override
public void run() {
//2.线程体中使用标识
while (flag) {
System.out.println(Thread.currentThread().getName() + "-->study thread......");
}
}
// 3.对外提供方法改变标识
public void stop() {
this.flag = false;
}
}
可见,当i=50时,stop方法并不是立刻被执行的,这与处理机的调度有关
但还是可以看出通过外部干涉的方法使线程终止
Thread类也提供了stop方法,但是不建议使用,API中也明确提出该方法不安全
yield()方法:暂停当前正在执行的线程对象,并执行其他线程
yield(): 应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
public class YieldTest {
public static void main(String[] args) {
Test1 t1 = new Test1("张三");
Test1 t2 = new Test1("李四");
new Thread(t1).start();
new Thread(t2).start();
}
}
class Test1 implements Runnable {
private String name;
public Test1(String name) {
this.name=name;
}
@Override
public void run() {
System.out.println(this.name + " 线程运行开始!");
for (int i = 1; i <= 5; i++) {
System.out.println(""+this.name + "-----" + i);
// 当为3的时候,让出资源
if (i == 3) {
Thread.yield();
}
}
System.out.println(this.name + " 线程运行结束!");
}
}
上述中的例子我们可以看到,启动两个线程之后
执行yield()的线程有可能在进入到可执行状态后马上又被执行
在这里顺便说下,yield和sleep的区别。
使当前线程重新回到可执行状态
,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行
当前线程进入停滞状态
,所以执行sleep()的线程在指定的时间内肯定不会被执行
这里我们创建两个线程,并使用main方法执行。顺便提一下,其实main方法也是个线程。如果直接执行的话,可能main方法执行完毕了,子线程还没执行完毕,这里我们就让子线程使用join方法使main方法最后执
代码2
public class JoinTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+ "主线程开始运行!");
Join t1=new Join("A");
Join t2=new Join("B");
t1.start();
t2.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");
}
}
class Join extends Thread{
public Join(String name) {
super(name);
}
public void run() {
System.out.println(this.getName() + " 线程运行开始!");
for (int i = 0; i < 5; i++) {
System.out.println("子线程"+this.getName() + "运行 : " + i);
try {
sleep(new Random().nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.getName() + " 线程运行结束!");
}
注释掉所有join()<左>
效果 与 使用join()<右>
如下
方法 | 描述 |
---|---|
sleep() | 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行);不会释放对象锁 |
join() | 指等待t线程终止 |
yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
wait() | 强迫一个线程等待。它是Object的方法,也常常和sleep作为比较。需要注意的是wait会释放对象锁,让其它的线程可以访问;使用wait必须要进行异常捕获,并且要对当前锁实例所调用,即必须采用synchronized中的对象。 |
isAlive() | 判断线程是否还“活”着,即线程是未终止 |
activeCount() | 程序中活跃的线程数 |
enumerate() | 枚举程序中的线程 |
static Thread currentThread() | 得到当前线程 |
setDaemon() | 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束) |
getName() | 得到线程的名字 |
setName(String name) | 设置线程的名字 |
int getPriority() | 获取线程的优先级 |
int setPriority() | 设置一个线程的优先级 |
void() start() | 调用run( )方法启动线程,开启一个线程,但不保证立即运行,交给cpu调度运行 |
void run() | 调用线程的线程体 |
interrupt() | 一个线程是否为守护线程 |
notify() | 通知一个线程继续运行。它也是Object的一个方法,经常和wait方法一起使用 |
status() | 获取线程的状态 NEW RUNNABLE |
就绪状态(Runnable)
运行状态(running)
阻塞状态(blocked)
正在运行的线程还没有结束,暂时让出cpu,这时其他就绪线程就有机会获得cpu时间;
处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己的运行,进入阻塞状态。
在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行。
以下原因会导致线程进入阻塞状态
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
死亡状态(Dead)
在多线程编程时,你需要了解以下几个概念:
描述
Lock锁
Lock和synchronized的区别
优先使用顺序
Lock----同步代码块(已经进入了方法体,分配了相应资源)----同步方法(在方法体之外)
线程同步的好处
线程同步的缺点
/**
* 线程不安全,以及synchronized使用不当实例
*/
public class ThreadSynchronizedTest {
public static void main(String[] args) {
Web12306 web = new Web12306();
//新建状态
Thread thread1 = new Thread(web, "路人甲");
Thread thread2 = new Thread(web, "黄牛乙");
Thread thread3 = new Thread(web, "攻城狮");
//就绪状态(不保证立即运行,由cpu调度)
//运行实际:运行代码前两条路,之后可能还是一条路
thread1.start();
thread2.start();
thread3.start();
}
}
class Web12306 implements Runnable {
private boolean flag = true;
private int num = 10;
@Override
public void run() {
while (flag) {
testSync1();
}
}
/**
*线程不安全
*/
public void testSync6() {
if (num <= 0) {
flag = false;//跳出循环
return;
}
//a b c
synchronized (this) {
try {
Thread.sleep(500);//模拟 延时
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了" + (num--));
}
}
/**
* 线程不安全 锁定资源不正确
*/
public void testSync5() {
//a b c
synchronized ((Integer) num) {
if (num <= 0) {
flag = false;
return;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了" + (num--));
}
}
/**
* 锁定范围不正确 线程不安全
*/
public void testSync4() {
// c 1
synchronized (this) {
//b
if (num <= 0) {
flag = false;
return;
}
}
// b
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了" + (num--));
}//a -->1
/**
* 线程安全 锁定正确
*/
public void testSync3() {
synchronized (this) {
if (num <= 0) {
flag = false;
return;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了" + (num--));
}
}
public synchronized void testSync2() {
if(num <= 0){
flag = false;
return;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了"+ (num--));
}
/**
* 线程不安全
*/
public void testSync1() {
if(num <= 0){
flag = false;
return;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了" + (num--));
}
}
下图分别对应 testSync6——testSync1 方法调用结果
同步监视器的执行过程
public class ThreadDeadLock {
public static void main(String[] args) {
Object goods = new Object();
Object money = new Object();
// 生产者
Producers producers = new Producers(goods, money);
// 消费者
Consumers consumers = new Consumers(goods, money);
// 生产者线程
Thread producersThread = new Thread(producers, "生产者");
// 消费者线程
Thread consumersThread = new Thread(consumers, "消费者");
producersThread.start();
consumersThread .start();
}
}
//生产者
class Producers implements Runnable {
Object goods;
Object money;
public Producers(Object goods, Object money) {
this.goods = goods;
this.money = money;
}
@Override
public void run() {
while (true) {
toGoods();
}
}
public void toGoods() {
synchronized (goods) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (money) {
}
}
System.out.println(Thread.currentThread().getName() + "一手给货");
}
}
//消费者
class Consumers implements Runnable {
Object goods;
Object money;
public Consumers(Object goods, Object money) {
this.goods = goods;
this.money = money;
}
@Override
public void run() {
while (true) {
toMoney();
}
}
public void toMoney() {
synchronized (money) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (goods) {
}
}
System.out.println(Thread.currentThread().getName() + "一手给钱");
}
}
//结果导致程序阻塞
当多个线程共享一份资源
的时候,会发生死锁
的现象,我们一般是通过生产者与消费者模式
进行解决。也有称为信号灯法
。
public class ThreadDeadProducersaAndConsumersMode {
public static void main(String[] args) {
Moive moive = new Moive();
Player player = new Player(moive);
Watcher watcher = new Watcher(moive);
Thread playerThread = new Thread(player, "生产者");
Thread watcherThread = new Thread(watcher, "消费者");
playerThread.start();
watcherThread.start();
}
}
/**
* 共享电影资源类
*/
class Moive {
private String pic;
// 信号灯
// flag->T 生产者生产,消费者等待, 生产完成后通知消费
// flag->F 消费者消费 生产者等待, 消费者完成后通知生产
private boolean flag = true;
/**
* 播放
*/
public synchronized void play(String pic) {
if (!flag) {// 生产者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 开始生产
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("生产了:" + pic);
// 生产完毕
this.pic = pic;
// 通知消费
this.notify();
// 生产者停下
this.flag = false;
}
public synchronized void watch() {
if (flag) {// 消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 开始消费
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费了:" + pic);
// 消费完毕
// 通知生产
this.notifyAll();
// 消费者停止
this.flag = true;
}
}
/**
* 生产者
*/
class Player implements Runnable {
private Moive moive;
public Player(Moive moive) {
this.moive = moive;
}
@Override
public void run() {
for (int i = 0; i <= 20; i++) {
if (0 == i % 2) {
moive.play("左青龙");
} else {
moive.play("右白虎");
}
}
}
}
/**
* 消费者
*/
class Watcher implements Runnable {
private Moive moive;
public Watcher(Moive moive) {
this.moive = moive;
}
@Override
public void run() {
for (int i = 0; i <= 20; i++) {
moive.watch();
}
}
}
/**
* Time定时器类
* TimeTask定时任务类
*
* 了解
* 1.Timer()
* 2.schedule(TimerTask task, Date time)
* 3.schedule(TimerTask task, Date firstTime, long period)
* 4.自学 quartz
*/public class ThreadScheduling {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("1秒打印一次");
}
},new Date(System.currentTimeMillis()+10000), 1000);
//从当前系统时间的第10秒开始启动,每秒执行一次
}
}
这三个方法已经是jdk是过期的方法,为什么仍然要单独拿出来说呢?主要目的是理解jdk多线程API设计的初衷,理解并且更好使用线程API。那么就来说说这三个方法吧:stop方法用于终止一个线程的执行,resume方法用于恢复线程的执行,suspend方法用于暂停线程的执行。
要注意的resume方法需要和suspend方法配对使用,因为被暂停的线程需要执行恢复方法才能继续执行。
方法 | 说明 |
---|---|
public void suspend() | 该方法使线程处于挂起状态,可以使用resume()方法恢复 |
public void stop() | 该方法使线程完全停止 |
public void resume() | 该方法恢复使用suspend()方法挂起的线程 |
public void wait() | 导致当前线程等到另一个线程调用notify() |
public void notify() | 唤醒在此对象监视器上等待的单个线程 |
请注意,最新版本的Java已经不再使用suspend(),resume()和stop()
方法,因此您需要使用可用的替代方法。
class RunnableDemo implements Runnable {
public Thread thread;
private String threadName;
boolean suspended = false;
RunnableDemo(String name) {
threadName = name;
System.out.println("创建 " + threadName);
}
public void run() {
System.out.println("运行 " + threadName);
try {
for (int i = 10; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// Let the thread sleep for a while.
Thread.sleep(300);
synchronized (this) {
while (suspended) {
wait();
}
}
}
} catch (InterruptedException e) {
System.out.println(threadName + " interrupted.");
}
System.out.println(threadName + " exiting.");
}
public void start() {
System.out.println(threadName + "处于就绪状态");
if (thread == null) {
thread = new Thread(this, threadName);
thread.start();
}
}
void suspend() {
suspended = true;
}
synchronized void resume() {
suspended = false;
notify();
}
}
public class TestThread {
public static void main(String args[]) {
RunnableDemo r1 = new RunnableDemo("线程1");
r1.start();
RunnableDemo r2 = new RunnableDemo("线程2");
r2.start();
try {
Thread.sleep(1000);
r1.suspend();
System.out.println("线程1:suspend ");
Thread.sleep(1000);
r1.resume();
System.out.println("线程1:resume");
r2.suspend();
System.out.println("线程2:suspend");
Thread.sleep(1000);
r2.resume();
System.out.println("线程2:resume");
} catch (InterruptedException e) {
System.out.println("主线程 Interrupted");
}
try {
System.out.println("Waiting for threads to finish.");
r1.thread.join();
r2.thread.join();
} catch (InterruptedException e) {
System.out.println("主线程 Interrupted");
}
System.out.println("主线程 exiting.");
}
}
当调用stop()方法时会发生两件事:
public class Main {
public static void main(String[] args) throws Exception {
TestObject testObject = new TestObject();
Thread thread = new Thread() {
public void run() {
try {
testObject.print("1", "2");
} catch (Exception e) {
e.printStackTrace();
}
}
};
thread.start();
Thread.sleep(1000);
thread.stop();
System.out.println("first : " + testObject.getFirst() + " " + "second : " + testObject.getSecond());
}
}
class TestObject {
private String first = "ja";
private String second = "va";
public synchronized void print(String first, String second) throws Exception {
this.first = first;
Thread.sleep(10000);
this.second = second;
}
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getSecond() {
return second;
}
public void setSecond(String second) {
this.second = second;
}
}
//first : 1 second : va
从上面的程序验证结果来看,stop()确实是不安全的。它的不安全主要是针对于第二点:释放该线程所持有的所有的锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放,那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误
suspend()和resume()必须要成对出现,否则非常容易发生死锁,这两个操作就好比播放器的暂停和恢复。
不推荐使用suspend()
去挂起线程的原因,是因为suspend()
在导致线程暂停的同时,并不会去释放任何锁资源
。其他线程都无法访问被它占用的锁。直到对应的线程执行resume()方法后,被挂起的线程才能继续,从而其它被阻塞在这个锁的线程才可以继续执行。
如果一个线程在resume目标线程之前尝试持有这个重要的系统资源锁再去resume目标线程,这两条线程就相互死锁了,也就冻结线程。
public class Test {
public static void main(String[] args) throws Exception {
TestObject testObject = new TestObject();
Thread t1 = new Thread(() -> testObject.print());
t1.setName("A");
t1.start();
Thread.sleep(1000);
Thread t2 = new Thread(() -> {
System.out.println("B已启动,但进入不到print方法中");
testObject.print();
});
t2.setName("B");
t2.start();
}
}
class TestObject {
public synchronized void print() {
if (Thread.currentThread().getName().equals("A")) {
System.out.println("A 线程 独占该资源了");
Thread.currentThread().suspend();
}
}
}
//A 线程 独占该资源了
//已启动,但进入不到print方法中
但是,如果resume()操作出现在suspend()之前执行,那么线程将一直处于挂起状态,同时一直占用锁,这就产生了死锁。此时,通过 jps 和 jstack 命令,来观察线程状态,对于被挂起的线程,它的线程状态居然还是Runnable。
如果你知道进程间通信,那么就很容易理解线程间通信。 当您开发两个或多个线程交换一些信息的应用程序时,线程间通信很重要。
有三个简单的方法和一个小技巧,使线程通信成为可能。 所有三种方法都列在下面 -
方法 | 描述 |
---|---|
public void wait() | 使当前线程等到另一个线程调用notify()方法。 |
public void notify() | 唤醒在此对象监视器上等待的单个线程。 |
public void notifyAll() | 唤醒所有在同一个对象上调用wait()的线程。 |
这些方法已被实现为Object
中的最终(final)
方法,因此它们在所有类中都可用。 所有这三种方法只能从同步
上下文中调用。
这个例子显示了两个线程如何使用wait()和notify()
方法进行通信。 您可以使用相同的概念来创建一个复杂的系统。
class Chat {
boolean flag = false;
public synchronized void Question(String msg) {
if (flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(msg);
flag = true;
notify();
}
public synchronized void Answer(String msg) {
if (!flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(msg);
flag = false;
notify();
}
}
class Thread1 implements Runnable {
Chat chat;
String[] str = {"Hi", "How are you ?", "I am also doing fine!"};
public Thread1(Chat chat) {
this.chat = chat;
new Thread(this, "Question").start();
}
public void run() {
for (int i = 0; i < str.length; i++) {
chat.Question(str[i]);
}
}
}
class Thread2 implements Runnable {
Chat chat;
String[] str = {"Hi", "I am good, what about you?", "Great!"};
public Thread2(Chat chat) {
this.chat = chat;
new Thread(this, "Answer").start();
}
public void run() {
for (int i = 0; i < str.length; i++) {
chat.Answer(str[i]);
}
}
}
public class ThreadCommunication{
public static void main(String[] args) {
Chat m = new Chat();
new Thread1(m);
new Thread2(m);
}
}