一:线程与进程
进程是程序的一次动态的执行过程。多进程操作系统能同时运行多个进程(程序),由于cpu具备分时机制,所以每个进程都能循环获得自己的cpu时间片段。由于cpu执行速度非常快,是的所有程序好像在同时运行一样。
线程是比进程更小的执行单位,线程是在进程的基础上经一部划分的。这些线程可以同时存在、同时运行。一个进程可能包括多个同时执行的线程。所谓线程(Thread)是指程序的运行流程,多线程机制则是指可以同时运行多个程序块,使程序运行的效率变得更高。
二:java实现多线程操作有两种方式
继承Thread类,实现Runnable接口。多线程之间是交错运行的。
编译并运行程序后,发现以上程序时先执行完m1对象在执行m2对象,并没有交错运行,也就是说线程实际上并没有被启动,还是属于顺序执行的方式。那么如何启动线程呢?
调用从Thread类继承而来的start()方法。将run()修改为start()就行。
解释:为什启动线程不能直接调用run()方法呢?
因为线程的运行需要本机的操作系统的支持,run()方法属于java虚拟机栈(执行java服务,也就是字节码服务),而start方法(实际上调用的是star0方法)属于本地方法栈(执行native方法服务),native服务适用于本地操作系统。
使用Runnable接口启动多线程
三:线程的状态
1创建状态:
调用Thread thread = new Thread();线程对象便处于新建状态,此时,它已经有了相应的内存空间和其他资源
2就绪状态:
调用start()方法后,线程进入就绪状态,等待cpu服务。
3运行状态:
当调用run()方法时,程序进入运行状态,获得了处理器资源
4堵塞状态:
如果调用sleep、suspend、wait等方法,线程进入堵塞状态,只有当堵塞的原因消除之后,线程才可以转入就绪状态
5死亡状态:
线程调用stop()方法或run()方法执行结束后,就处于死亡状态。处于死亡状态的线程不具有运行能力。
四:线程操作的相关方法
0最近在看java多线程,偶然看到一个java线程通信的知识,这里作为补充吧
一、采用synchronized保证线程同步时如何实现线程通信
synchronized(锁))调用,如果synchronized锁的对象和实现了wait(),notify(),notifyAll()这三种方法的对象不是一个的话,将会抛出 java.lang.IllegalMonitorStateException异常。接下来我们了解下这三个方法是作用。
- wait():导致当前线程等待,直到其他线程调用该同步监视器(即锁)的notify()或notifyAll()方法时来唤醒该线程。同时调用wait()方法的当前线程会释放对该同步监视器的锁定(翻译成人话就是:当调用wait方法时,会释放这个同步锁,也就是synchronized锁定的那个对象,也就是调用wait()方法的对象)。
- notify():调用notify()方法时,会唤醒由于阻塞在同一个同步监视器调用了wait()方法的线程,翻译成书面语就是,唤醒在此同步监视器上等待的当个线程,如果在此同步监视器上阻塞着很多线程,则会选择一个唤醒,至于唤醒那个线程,这基于JVM对线程的调度。
- notifyAll():唤醒在此同步监视器上阻塞的所有线程。也就是唤醒所有线程由于调用了该同步监视器的await方法而导致阻塞。
编程注意事项:
wait(),notify(),notifyAll()这三种方法的调用必须在synchronized方法或者块中
虽然多线程是交替运行的,但是要想输出结果严格按照上面来,还是必须要一定手段的。
在这里,首先在同步代码块中加入了一个notify方法,用于唤醒下一个线程。然后,在代码块的执行结束地方加入一个wait方法,用于使当前线程等待。或许有人想问,为什么要用notify方法?如果所有线程都wait了,那么谁来执行呢?所以要用notify。
这里补充一下线程操作的常用方法:
1. java如何实现一个多线程
(1) 继承Thread
start()方法开启一个线程
(2) 实现Runnable
run()方法执行线程内容
Thread(Runnable)
2. 线程常用方法
a) currentThread()
在线程Thread1中调用该方法返回当前线程名字是thread1,在main方法中运行 thread1.start() 再调用该方法返回当前线程名字是main。所以该方法主要用来获取当前运行的线程。
b) isAlive() 检测当前线程是否是存活状态,存活返回true,否则返回false。
c) getId() 获取调用方线程的线程id。
d) stop()
暴力停止停止一个线程的运行。jdk api 过期作废的方法不建议使用,次方法使用时抛出 java.lang.ThreadDeath,此异常不需要显示捕获。同时存在一些潜在的问题,强制停止线程可能使一些清理的工作得不到完成,另外一个情况就是对锁定的对象“解锁”会导致监视器数据不能同步处理完成,出现数据不一致。推荐使用interrupt异常法。
e) sleep(long)
锁的情况下,线程呈同步阻塞状态,不会释放锁直到程序执行完成。
f) suspend()/resume()
suspend可以暂停线程,resume恢复线程。与stop一样,过期作废的方法。不推荐使用的原因----独占。使用不当时,极易对公共对象独占,其它线程获取不到公共同步对象,进而导致死锁。
g) yield()
放弃当前的CPU资源,放弃的时间不确定,可能刚刚放弃立马又获得到CPU时间片。在操作系统中线程优先级越高,CPU越优先执行。设置线程优先级可以使用setPriority(),在java中优先级被分为1~10,小于1或者大于10 抛出异常 throw new IllegalArgumentException()。
线程具有继承性,线程A启动线程B 则线程B拥有A同级的优先权。优先级高的线程总是大部分优先执行,但不代表全部会执行,优先级与代码执行的顺序无关,CPU会尽量将执行资源让给优先级较高的线程。
h) interrupt()/isInterrupted()/interrupted() 借鉴另外文档
i) wait()/wait(long)/notify()/notifyAll()
Object的方法,等待/通知,线程在执行该几个方法前,必须获得该对象的对象级别的锁,可以是同步方法或者同步代码块,如果没有锁则抛出IllegalMonitorStateException。调用wait()后线程进入等待状态,直到有线程调用了该对象监视器notify()/notifyAll() 对等待状态唤醒。wait(long) 方法在long毫秒后自动唤醒。wait与sleep不同的是sleep 不会释放锁,而wait会释放当前的线程所持有的对象锁。
在多线程中wait/notify 常被用于线程之间的通信。生产者与消费者之间的关系。
sleep与wait对比:
都可以使线程进入暂时停止的状态,sleep时间结束后自动唤醒,await时间结束后或者直到被notify后唤醒。
sleep睡眠期间不释放锁,wait释放
与interrupt结合使用都可以使用异常法停止线程,释放对象锁,但是wait的线程对象会进入线程等待池,等待被唤醒。
j) join()/join(long)
在很多情况下,主线程创建启动子线程后,如果子线程运行时间比较早,主线程先于子线程结束,如果主线程需要等待子线程处理完某些程序或者数据,主线程需要这样的逻辑或者数据时,就可以使用到join了。在主线程内调用thread.start()方法后调用thread.join()。则可以等到子线程结束后主线程执行结束。所以说join是等待线程结束。
join与interrupt相遇后,如果主线程被打断则抛出异常线程结束,此时子线程依然在运行。
join(long) 主线程只等待long毫秒,join基于wait实现,所以会释放锁。
1取得和设置线程的名称
MyThread my = new MyThread();
new Thread(my).start(); //系统自动设置线程名称
new Thread(my,"线程-A").start(); //手动设置线程名称
new Thread(my).start(); //系统自动设置线程名称
然后在main方法里面调用Thread.currentThread().getName()取得当前线程的名称。
得到的结果如下:Thread-0、线程-A、Thread-1
说明:
java至少启动两个线程,一个是main方法(main),一个是垃圾回收线程
2判断线程是否启动
isAlive方法判断
3线程的强制运行
使用join方法使线程强制运行,线程强制运行期间,其他线程无法运行。
4线程的休眠
Thread.sleep(500)
5中断线程
当一个线程运行时,另外一个线程可以直接通过interrupt()方法中断其运行状态
程序运行结果:
1、进入run方法
3、休眠被终止
从以上程序可以看出,一个线程启动之后进入了休眠状态,原本是要休眠10秒之后再继续执行,但是主方法在线程启动之后的两秒就将其中断,休眠一旦中断之后将执行catch中的代码,并利用里面的return语句返回程序调用处。
6后台线程
即使java进程结束了,后台线程依然会执行。要想设置后台线程,只需要在start方法之前加上thread.setDaemon(true)就行。
7线程的优先级
thread.setPriority(Thread.MIN_PRIORITY)//设置最低优先级
thread.setPriority(Thread.NORM_PRIORITY)//设置中等优先级
thread.setPriority(Thread.MAX_PRIORITY)//设置最高优先级
8线程的礼让
,可以使用yield()方法将一个线程的操作暂时让给其他线程执行。
五:同步和死锁
一个多线程的程序如果是通过Runnable接口实现的,则意味着类中的属性将被多个属性共享,那么这样一来就会造成一种问题,如果这多个线程要操作同一资源时就很有可能出现资源同步的问题。例如以下的买票程序,如果多个线程同时操作时就容易出现票数为负数的情况。
结果:
卖票:ticket = 1
卖票:ticket = 0
卖票:ticket = -1
如果要解决这个问题,则必须使用同步。所谓同步就是指多个操作在同一个时间段内只有有个线程进行,其他线程要等待此线程完成之后才可以继续执行。
如图:
将以上代码做出如下修改,就可以使票数不会出现负数了
使用同步代码块实现格式:
synchronized(同步对象){
需要同步的代码;
}
六:死锁
同步可以保证资源共享操作的正确性,但是过多的同步也会产生死锁。所谓的死锁,就是指两个线程都在等待对方先完成,造成程序的停滞,一般的死锁都是在程序运行时出现的
七:线程的生命周期
大部分线程生命周期前面已经讲过,下面说说这三个方法
1 suspend()方法:暂时挂起线程
2resume()方法:恢复挂起的线程
3stop()方法:停止线程
对于这三种方法并不推荐使用,因为这三种方法在操作时会产生死锁的问题。
那么如何停止线程运行呢?
一旦调用stop方法,就会将MyThread类中的flag变量设置为false,这样run()方法就会停止运行
终止线程的方法有三种:
(1)使用退出标志,使线程正常退出,也就是当run()方法完成之后线程终止
(2)使用Thread的interrupt()终止线程
(3)使用Thread的stop()方法强行终止线程(这个方法不推荐使用,因为stop()和suspend()、resume()方法一样,也可能发生不可预料的结果)。