JAVA--线程(2)

本篇用来测试线程常用的方法与关键字.

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次的情况

扫描二维码关注公众号,回复: 5037315 查看本文章
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对象比较适合.

范围:

  1. 可以是方法内的一部分代码,也可以是全部代码(这种情况相当于给方法上锁.)
  2. 给方法上添加该修饰词,锁对象为this,如果一个类的所有成员方法都使用了该修饰词,当某一个线程操作了其中一个方法,另外的线程即使操作的不是这个方法,也会进入锁池状态.
  3. 静态方法也可以添加该修饰词,锁对象为类对象,调用方式:类名.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(),但是别的线程都已经结束了,所以就只能等着,导致程序无法完全结束.

猜你喜欢

转载自blog.csdn.net/bujiujie8/article/details/82591560