编写一个线程的实例如下:






















编译运行:
可见打印出了线程的名称,其中Thread-0是系统自动分配的。
我们在main函数中先创建一个MyThread类的对象mt,启动线程,然后打印输出获得主函数线程的语句,然而运行程序先输出主函数线程名称,后输出MyThread类的对象启动的线程名称。
这是因为,当运行程序时,先执行主函数方法所在线程,系统为其分配一个时间片,而当主函数执行到mt.start();时并没有用完时间片,所以先输出了main函数的线程名称,然后mt.start();执行,执行mt的方法run所在线程。
Thread类的setDaemon方法设置线程是否为后台线程,只有一个布尔型参数,true表示时后台线程。
setDaemon方法必须在线程启动之前调用:




























编译运行:
可见,当主函数方法所在线程终止时,后台线程也会终止执行。
当我们在run()中打印完线程名称时,再执行语句yield();则让出执行时间片,运行结果:
可以设置和获取线程的优先级:
setPriority(int newPriority) ;
getPriority() ;
Thread类定义了三个优先级常量:
MAX_PRIORITY 最大优先级10
MIN_PRIORITY 最小优先级1
NORM_PRIORITY 默认优先级5
改变一个线程的优先级:
mt.setPriority(Thread.MAX_PRIORITY);
查看运行情况:
(此时如果yield();仍然被执行)
如果删除yield();语句,则:
只有Thread-0在执行。
也可以去实现Runnable类的接口:






















编译运行:
通常,如果我们并不需要修改线程类当中除了run方法以外其它方法的行为,最好去实现Runnable接口。
实现Runnable接口有两个好处:
一个是:如果我们定义的类(MyThread)已经继承了一个类,因为java中支持单继承,所以我们定义的类(MyThread)无法再去继承其它的类。所以可以采用实现接口的方式。
另一个是:实现Runnable接口,如果多线程同时访问同一种资源,这时会很方便。比如:




























编译运行:
可见,多个线程可以同时都访问同一个共享计数变量index,实现计数。
另外,还可以通过内部类实现上述多个线程同时访问同一个共享计数变量的功能:


































编译运行:
下面通过一个售票系统实例来应用线程的知识:



























编译运行:
上面的程序存在隐患:
如果在售最后一张票时,各个线程进入到if(tickets>0)语句时,时间片都到期了,则下一次执行时间片时分别打印出1,0,-1,-2,-3这样的票,显然是不正确的。
我们运行程序之所以没有出现这种情况是因为时间片很短,但是在实际中经过长期运行就可能发生上面的隐患。
然而,真正找到错误原因又是很困难的。
下面我们使用Thread类的静态方法sleep,让隐患重现:


































编译运行:
要解决这个问题,要涉及到线程的同步问题。
■ 线程同步
同步的两种方式:同步块和同步方法。
上面程序中,if语句块是一个临界区,要实现线程互斥地访问共享变量tickets。
用同步块实现如下:






































编译运行:
用同步块实现,现在结果正常了。
同步块实现机制:
synchronized(){}实现了线程的同步,在{}之间的代码段成为理临界区。
java中每一个对象都有一个监视器,或者叫做锁。
上面程序通过obj作为一个监视器对象,同步块实现了对obj监视器加锁和解锁功能。
同步方法的实现如下:







































编译运行:
用同步方法,这就实现了线程同步的功能。
同步方法实现机制:
类中有一个this对象,this对象作为一个监视器,同步方法实现对this监视器的加锁和解锁操作,实现线程同步。
同步方法利用的是this所代表的对象的锁。
每个class也有一个锁,是这个class所对应的Class对象的锁。
测试在同步方法中是this对象的加锁解锁:




































































编译运行:
我们可以看到同步块和同步方法交错执行,实现同步。验证了this对象作为一个监视器实现加锁解锁。
■ 死锁实例
死锁的例子如下:











































































编译运行:
执行过程:
第一个线程先访问obj对象,加锁,然后进入睡眠状态;
然后第二个线程执行sell方法,对this对象进行加锁;
第二个进程进入sell方法后请求obj对象,所以进入等待状态,唤醒第一个线程;
第一个线程通过执行synchronized(this)请求this对象。
这就陷入了这样的循环等待:
第一个线程占有obj对象,而请求对this对象进行加锁;而第二个线程占有this对象,而请求对obj对象进行加锁。
双方互相等待,程序的执行不能够向前推进。
■ 生产者-消费者问题















































































编译运行:
如果在方法put和get不用synchronized修饰为同步方法,则运行出错:
因为它们必须对同一个this对象进行操作。
使用wait方法时会抛出异常,必须进行异常捕捉处理。
■ 线程的状态
线程的终止可以通过设置一个flag变量,并结合interrupt()方法。
我们用设置一个boolean型变量bStop:









































编译运行:
我们发现程序并没有退出,因为在MyThread类的run方法中,我们启动mt.start()的线程处于等待状态。
我们可以设置interrupt()方法终止线程的运行:
















































编译运行: