现在很多程序员,搞 java 的就是Spring,做PHP的就是Laravel、TP、YII,然后再掌握些Mysql的基础知识,写几笔sql语言,懂几个业务模型,就称为“大拿”了。其实,干这些工作很少考虑 多线程 ,思维都单向线性的,程序员干个3-5年,水平跟干5-10年其实相差是不大的,无非是唯手熟尔。
小编觉得作为一名以源代码为事业的码农,应该持续不断地学习,不断提高自己的技术水平。今天特开启“java多线程模式”系列,彻底把多线程拦路虎干趴下。
进程、线程、协程的概念,我就不讲了。简单说进程 > 线程 > 协程;进程之间不能共享内存,但线程之间是可共享内存的。废话不多说,先来写一个多线程程序:
⑴ Thread
class MyThread extends Thread {
private int ticket=10;
public void run(){
for ( int i=0;i<20;i++){
if ( this .ticket > 0){
System. out .println(“卖票:ticket”+ this .ticket–);
}
}
}
public static void main(String[] args) {
MyThread mt1 = new MyThread();
mt1. start ();
MyThread mt2 = new MyThread();
mt2.start();
MyThread mt3 = new MyThread();
mt3.start();
}
}
⑵ Runnable
public class MyThread implements Runnable{
private int ticket=10;
public void run(){
for ( int i=0;i<20;i++){
if ( this .ticket > 0){
System. out .println(“卖票:ticket”+ this .ticket–);
}
}
}
public static void main(String[] args) {
MyThread mt = new MyThread();
new Thread(mt).start();// 同一个mt,但是在Thread中就不可以,如果用同一
new Thread(mt).start();// 个实例化对象mt,就会出现异常
new Thread(mt).start();
}
}
编译执行后,你看到了什么不同吗?
同样是启动3个线程去卖票,总共10张票,但extends Thread时售出30张,implements Runnable时售出10张。下面总结下Thread和Runnable的几个知识点:
① 其实Thread也是Runnable的子类。
② Runnable可实现资源共享。
③ 写多线程时,多用implements Runnable,很少用extends Thread。
2、wait(), notify(),notifyAll(),sleep()都有什么不同
⑴ 举个wait,notify的例子:
public class requestQueue{
private final LinkedList queue = new LinkedList();
public synchronized String getRequest(){
while (0 >= queue.size()){
try{
wait();
}catch(InterruptedException e){}
return (String)queue.removeFirst();
}
}
Public synchronized putRequest(String request){
queue.addLast(request);
notifyAll();
}
}
首先有3点必须明确:
①wait(), notify(), notifyAll()是属于Object的方法。Object是java最底层的类,也就是说每个对象都有wait, notify功能。
②我们只能在被synchronized的同步方法或者同步块里面调用wait()和notify()。
③当线程调用wait()方法后会进入等待队列(进入这个状态会释放所占有的所有资源,与阻塞状态不同),进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒(由于notify()只是唤醒一个线程,但我们由不能确定具体唤醒的是哪一个线程,也许我们需要唤醒的线程不能够被唤醒,因此在实际使用时,一般都用notifyAll()方法,唤醒有所线程),线程被唤醒后会进入锁池,等待获取锁标记。
⑵ 举2个sleep的例子
①
import java.text.SimpleDateFormat;
import java.util.Date;
public class SleepTest {
public static void main(String[] args) throws InterruptedException{
int count = 5;
while(count >= 0){
Thread.sleep(1000);
System.out.println(“current time is:”+new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”).format(new Date()));
count –;
}
}
}
看下,输出什么?你猜是不是该每隔1秒打印出6条时间信息。
②
public class SleepTest extends Thread {
public void run() {
int count = 5;
while(count >= 0){
try {
Thread.sleep(1000);
System.out.println(“current time is:”+new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”).format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
count –;
}
}
public static void main(String[] args) {
new SleepTest().start();
}
}
这下,又该输出什么?
sleep()方法是属于Thread类的,sleep()方法让程序暂停执行指定的时间,让出cpu,但线程不会释放对象锁。
3、多线程的状态转换
① 初始状态(new):new了对象后,线程就进入“初始状态”。
② 可运行状态(runnable):当对象调用了start()方法后,该线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
③ 运行状态(running):线程获得了cpu时间片(timeslice),执行程序代码。
④ 阻塞状态(block):指线程因为某种原因放弃了cpu 使用权,即让出cpu timeslice,暂时停止运行。阻塞的情况分三种:
⑴等待阻塞:运行(running)的线程执行wait()方法, JVM 会把该线程放入等待队列(waitting queue)中。
⑵同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
⑶其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
⑤ 死亡(dead):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
4、Join的说明
举2个例子:
1)
public class JoinTest {
public static void main(String args[]){
Thread t1 = new Thread(){
public void run(){
System. out .println(“aaa”);
}
};
t1.start();
System. out .println(“AAA”);
}
}
输出什么?
2)
public class JoinTest {
public static void main(String args[]) throws InterruptedException{
Thread t1 = new Thread(){
public void run(){
System. out .println(“aaa”);
}
};
t1.start();
t1.join();
System. out .println(“AAA”);
}
}
又输出什么?
thread.join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的join()方法,直到线程A执行完毕后,才会继续执行线程B。join其实就是使异步执行线程转为同步执行。
5、yeild()的说明
放弃对当前CPU的占用,进入可运行状态。不过这类方法,没事最好不要去用。其实多线程就简单几个方法用用就好。
6、stop(), suspend(),resume()都已经被弃用。