本篇用来测试线程常用的方法与关键字.
sleep()与interrupt()
static void sleep(long mills):使线程暂时进入"睡眠"状态,持续时间mills毫秒
boolean interrupt():中断线程.
public class SleepTest extends Thread{
public static void main(String[] args) {
//使用匿名内部类创建两个线程对象
Thread t1 = new Thread("Thread1") {
public void run() {
//调用sleep(),需要处理异常
try {
Thread.sleep(10000);//设置睡眠10秒
System.out.println("Thread1未被打断.");
} catch (InterruptedException e) {
System.out.println("Thread1被打断.");
}
}
};
Thread t2=new Thread("Thread2"){
public void run(){
int sleepTime=500;
int totalSleepTime=0;
for (int i = 1; i < 10; i++) {
totalSleepTime+=sleepTime;
try{
Thread.sleep(sleepTime);
System.out.println("线程已经睡眠"+totalSleepTime+"毫秒.");
}catch (InterruptedException e){
e.printStackTrace();
}
}
//5秒后循环结束
t1.interrupt();//使用t1对象调用interrupt()
}
};
t1.start();
t2.start();
}
}
运行结果
线程已经睡眠500毫秒.
线程已经睡眠1000毫秒.
线程已经睡眠1500毫秒.
线程已经睡眠2000毫秒.
线程已经睡眠2500毫秒.
线程已经睡眠3000毫秒.
线程已经睡眠3500毫秒.
线程已经睡眠4000毫秒.
线程已经睡眠4500毫秒.
Thread1被打断.
join()
void join():将当前线程插入到某一个线程中,使某一个线程进入阻塞状态,当前线程执行完毕,另外一个线程进入就绪状态.
有一个程序求1~9999的和,计算逻辑与返回结果的方法在另一个类编写,继承Thread类,然后多次运行,观察有无join()的输出情况.
1.没有join()
public class JoinTest {
public static void main(String[] args) {
//实例化
JoinTask1 j=new JoinTask1();
Thread t=new Thread("2"){
public void run(){
//调用输出结果的方法
System.out.println(j.getSum());
}
};
j.start();
t.start();
}
}
class JoinTask1 extends Thread{//实现Runnable接口
private int sum;
//计算1~9999的和
public void run() {
for (int i = 1; i < 10000; i++) {
sum+=i;
}
}
//返回值
public int getSum() {
return sum;
}
}
下面是连续运行10次的结果 :
0
49995000
0
4674153
49995000
0
49995000
49995000
9307455
0
可以看到并不是预想中的只输出 49995000的情况,有输出0的情况,甚至还有运行期间被插入的情况.
2.再来看看加入join()的情况,注意需要处理异常.
public class JoinTest {
public static void main(String[] args) {
//实例化
JoinTask1 j=new JoinTask1();
Thread t=new Thread("2"){
public void run(){
try {//需要处理异常
j.join();//调用join()
} catch (InterruptedException e) {
e.printStackTrace();
}
//调用输出结果的方法
System.out.println(j.getSum());
}
};
j.start();
t.start();
}
}
class JoinTask1 extends Thread{//实现Runnable接口
private int sum;
//计算1~9999的和
public void run() {
for (int i = 1; i < 10000; i++) {
sum+=i;
}
}
//返回值
public int getSum() {
return sum;
}
}
连续运行10次的情况
49995000
49995000
49995000
49995000
49995000
49995000
49995000
49995000
49995000
49995000
可以看到并没有出现上面的情况,全部输出的是求和方法执行完毕的结果 .
调用join()时,当前线程暂时进入阻塞状态,调用join()的线程开始运行,调用join()的线程结束后,当前线程继续运行.
yield()
static void yield():暂停当前正在执行的线程对象,并执行其他线程.
但是实际效果并不是很明显,是否调用也没有看出明显的区别,不知道有没有什么可以明显看出该方法作用的条件.
public class YieldTest {
public static void main(String[] args) {
YieldTask y=new YieldTask();
Thread t=new Thread(){
public void run(){
//调用yield()
Thread.yield();
for (int i = 1; i < 11; i++) {
System.out.println("test"+i+"次.");
}
}
};
t.start();
y.start();
}
}
class YieldTask extends Thread{
public void run(){
for (int i = 1; i < 11; i++) {
System.out.println("Task"+i+"次.");
}
}
}
多次运行没看出是否调用的异同,这里粘贴一次其中的结果:
test1次.
test2次.
test3次.
test4次.
test5次.
test6次.
test7次.
test8次.
test9次.
test10次.
Task1次.
Task2次.
Task3次.
Task4次.
Task5次.
Task6次.
Task7次.
Task8次.
Task9次.
Task10次.
synchronized关键字
锁机制:当一个线程进入同步的代码块后,就会获得锁对象的使用权,其他线程如果执行到此处,会发现锁对象被占用,只能处于等待状态.当线程执行完同步的代码块后,或者出现异常,都会自动释放锁,.
合适的锁对象:必须是一个引用类型,而且必须使多个线程都可以使用这个锁,因此this对象比较适合.
范围:
- 可以是方法内的一部分代码,也可以是全部代码(这种情况相当于给方法上锁.)
- 给方法上添加该修饰词,锁对象为this,如果一个类的所有成员方法都使用了该修饰词,当某一个线程操作了其中一个方法,另外的线程即使操作的不是这个方法,也会进入锁池状态.
- 静态方法也可以添加该修饰词,锁对象为类对象,调用方式:类名.class,每一种类都有一个唯一的类对象.
/**
* 方法带synchronized的测试
* */
public class SynchronizedMethodTest {
public static void main(String[] args) {
Cal c=new Cal(500,100);
//同时开启两个不用同步锁的
Thread t1=new NoSyc(c);
Thread t2=new NoSyc(c);
t1.start();
t2.start();
//睡眠500毫秒以观察是否带同步的对比
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//同时开启两个用同步锁的
Thread t3=new Syc(c);
Thread t4=new Syc(c);
t3.start();
t4.start();
}
}
/*使用同步锁方法的类*/
class Syc extends Thread{
private Cal c;
public Syc(Cal c){
this.c=c;
}
public void run() {
c.add();
}
}
/*未使用同步锁方法的类*/
class NoSyc extends Thread{
private Cal c;
public NoSyc(Cal c){
this.c=c;
}
public void run() {
c.mul();
}
}
class Cal {
private int num1;
private int num2;
public Cal(int num1, int num2) {
this.num1 = num1;
this.num2 = num2;
}
/*带同步锁的加法*/
public synchronized void add(){
System.out.println("带同步锁的计算加法的方法.");
System.out.println("计算"+num1+num2+"的结果为:"+(num1+num2)+".");
System.out.println("计算加法结束.");
System.out.println();
}
/*不带同步锁的乘法*/
public void mul(){
System.out.println("不带同步锁的计算乘法的方法.");
System.out.println("计算"+num1+"*"+num2+"的结果为"+(num1*num2)+".");
System.out.println("计算乘法结束.");
System.out.println();
}
}
其中一次的运行结果为
不带同步锁的计算乘法的方法.
不带同步锁的计算乘法的方法.
计算500*100的结果为50000.
计算500*100的结果为50000.
计算乘法结束.
计算乘法结束.
带同步锁的计算加法的方法.
计算500100的结果为:600.
计算加法结束.
带同步锁的计算加法的方法.
计算500100的结果为:600.
计算加法结束.
不带 synchronized关键字的方法就有可能出现上面两次输出语句交替出现的情况,而带synchronized关键字的无论运行多少次都不会出现两次输出语句交替出现的情况.
wait(),notify()与notifyAll()
都是Object提供的方法,用来调控线程状态,使用位置必须是同步块中(synchronized),若不是同步块中会报异常.
wait():当前获取锁对象的线程准备释放锁,给其他线程获取锁的机会.
wait(long timeout):当前获取锁对象的线程如果没有被通知,延迟timeout毫秒后,释放锁对象.
wait(long timeout,int nanos):功能一样,只不过比上一个方法延迟的时间更加精确.
notify()/notifyAll():当前获取锁对象的线程准备释放锁,使用此方法通知处于wait()等待的线程.
notify()与notifyAll()的区别:
notify()只会随机通知等待线程中的其中一个.
notifyAll()会通知所有等待线程来竞争锁.
多线程经典案例:生产者-消费者-仓库模式
逻辑:仓库有设定的上限容量(100),若要生产的货物数量与库存相加大于这个容量,就无法生产(生产者.wait()).生产者生产完成后,提示消费者可以开始购买了(生产者.notifyAll()).消费者购买后就提示生产者可以开始生产了(消费者.notifyAll()),若消费者要购买的数量大于库存就无法购买(消费者.wait()),小于等于就可以正常购买,同时库存减去消费者购买的数量.
1.仓库类
/**
* 仓库类
*/
public class WareHouse {
//仓库库存
private static int inventory=100;
//仓库剩余数量
private int left;
/*构造器*/
public WareHouse(int left){
this.left=left;
}
/**
* 生产方法
* 注意wait()需要添加在同步块中
* @param proNum 一次生产的数量
*/
public synchronized void produce(int proNum){
while (proNum+left>inventory){
//要生产的数量加上库存大于仓库容量的话就不能生产,使用wait().
try{
System.out.println("生产之后"+proNum+"个超过最大库存,无法生产,目前库存:"+left+"个.");
this.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
//没满就可以生产
System.out.println("开始生产,数量为:"+proNum+".");
left+=proNum;
//通知消费者可以购买
this.notifyAll();
System.out.println("生产"+proNum+"个完毕,目前库存为:"+left+"个.");
}
/**
* 购买方法
* 注意wait()需要添加在同步块中
* @param needNum 消费者要购买的数量
*/
public synchronized void buy(int needNum){
while (needNum>left){
//需要购买的数量大于库存就无法购买,需要等待,即调用wait().
try {
System.out.println("要购买的数量:"+needNum+",大于库存:"+left+",无法购买.");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//需要购买的数量不大于库存就可以够买.
System.out.println("可以购买"+needNum+"个.");
left-=needNum;
//通知生产者可以开始生产.
this.notifyAll();
System.out.println("购买"+needNum+"个成功,目前库存"+left+"个.");
}
}
2.生产者类
/**
* 生产者类
* 继承Thread类.
* */
public class In extends Thread{
//一个仓库
private WareHouse wareHouse;
//生产者名字
private String name;
//将要生产的数量
private int proNum;
/*构造器*/
public In(WareHouse wareHouse,String name,int proNum){
this.wareHouse=wareHouse;
this.name=name;
this.proNum=proNum;
}
/*重写run()*/
public void run() {
wareHouse.produce(proNum);
}
}
3.消费者类
/**
* 消费者类
* 继承Thread类.
* */
public class Out extends Thread{
//一个仓库
private WareHouse wareHouse;
//消费者名字
private String name;
//需要购买的数量
private int needNum;
/*构造器*/
public Out(WareHouse wareHouse,String name,int needNum){
this.wareHouse=wareHouse;
this.name=name;
this.needNum=needNum;
}
/*重写run()*/
public void run() {
wareHouse.buy(needNum);
}
}
4.测试类
public class Producer_Customer_Exercise {
public static void main(String[] args) {
//初始化仓库,设置当前库存为30
WareHouse wareHouse=new WareHouse(30);
//5个消费者
Out o1=new Out(wareHouse,"A",50);
Out o2=new Out(wareHouse,"B",30);
Out o3=new Out(wareHouse,"C",25);
Out o4=new Out(wareHouse,"D",60);
Out o5=new Out(wareHouse,"E",40);
//5个生产者
In i1=new In(wareHouse,"M",30);
In i2=new In(wareHouse,"N",20);
In i3=new In(wareHouse,"X",60);
In i4=new In(wareHouse,"Y",80);
In i5=new In(wareHouse,"Z",45);
//开启以上线程
o1.start();o2.start();o3.start();o4.start();o5.start();
i1.start();i2.start();i3.start();i4.start();i5.start();
}
}
多次运行效果都会不一样,这里取其中两次的运行结果:
1.
要购买的数量:50,大于库存:30,无法购买.
要购买的数量:40,大于库存:30,无法购买.
要购买的数量:60,大于库存:30,无法购买.
开始生产,数量为:60.
生产60个完毕,目前库存为:90个.
可以购买60个.
购买60个成功,目前库存30个.
要购买的数量:40,大于库存:30,无法购买.
要购买的数量:50,大于库存:30,无法购买.
生产之后80个超过最大库存,无法生产,目前库存:30个.
可以购买25个.
购买25个成功,目前库存5个.
开始生产,数量为:80.
生产80个完毕,目前库存为:85个.
可以购买50个.
购买50个成功,目前库存35个.
要购买的数量:40,大于库存:35,无法购买.
开始生产,数量为:20.
生产20个完毕,目前库存为:55个.
可以购买40个.
购买40个成功,目前库存15个.
要购买的数量:30,大于库存:15,无法购买.
开始生产,数量为:30.
生产30个完毕,目前库存为:45个.
可以购买30个.
购买30个成功,目前库存15个.
开始生产,数量为:45.
生产45个完毕,目前库存为:60个.
此时控制台提示运行结束.
2.
要购买的数量:50,大于库存:30,无法购买.
要购买的数量:60,大于库存:30,无法购买.
要购买的数量:40,大于库存:30,无法购买.
可以购买25个.
购买25个成功,目前库存5个.
开始生产,数量为:20.
生产20个完毕,目前库存为:25个.
要购买的数量:30,大于库存:25,无法购买.
开始生产,数量为:30.
生产30个完毕,目前库存为:55个.
生产之后60个超过最大库存,无法生产,目前库存:55个.
可以购买40个.
购买40个成功,目前库存15个.
要购买的数量:60,大于库存:15,无法购买.
要购买的数量:50,大于库存:15,无法购买.
开始生产,数量为:60.
生产60个完毕,目前库存为:75个.
可以购买30个.
购买30个成功,目前库存45个.
开始生产,数量为:45.
生产45个完毕,目前库存为:90个.
可以购买50个.
购买50个成功,目前库存40个.
生产之后80个超过最大库存,无法生产,目前库存:40个.
要购买的数量:60,大于库存:40,无法购买.
第二种情况中,控制台提示还没有运行完成,表示这个线程在等待notifyAll(),但是别的线程都已经结束了,所以就只能等着,导致程序无法完全结束.