2018-04-16

多线程基础

基本的概念:

多线程其实就是进程中一个独立的控制单元或者说是执行路径,线程控制着进程的执行,【重点】一个进程中,至少有一个线程存在。

目录

【0】线程的状态转换图

【1】【线程的创建】怎样继承Thread类,创建多个线程?这个和实现Runnable接口创建的方式有什么区别?

【2】创建线程为什么要覆写run方法和为什么在main方法中执行线程程序,每一次运行的效果都不一样?

【3】怎样获得当前线程的名称?

【4】【重点】线程中存在的安全问题是什么,怎样发现线程安全?

【5】【重点】实例分析:汽车站售票程序【其中涉及了线程同步】

【6】同步的两种表现形式是什么?

【7】懒汉式单例模式分析

【8】多线程的死锁问题

【9】JDK1.5中关于线程的新特性

【10】多线程之间的通信模式分析

【11】停止线程的方法

【12】join和yield方法总结

【13】开发过程中为了提高效率,怎样单独封装?

开始:

【0】线程的状态转换图

线程状态图.PNG

new:新建状态

Runnable:就绪状态。线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

Running:运行状态。就绪状态的线程获取了CPU,执行程序代码。

Blocked:阻塞状态。阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

对于堵塞的情况原因大概有三种:

1、等待堵塞:运行的线程执行wait()方法,JVM把线程放入等待状态。

2、同步阻塞:运行的线程在获取对象的同步锁时,该锁被别的线程占用。

3、其他阻塞:运行的线程执行sleep()或join()方法,或者发出I/O请求时。或者当sleep()状态超时、join()等待线程终止或者超时、或I/O处理完毕时,线程重新转入就绪状态。

Dead:死亡状态。线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

注意:
   1、yield():线程让步,暂停当前正在运行执行的线程对象,并执行其他线程。

   2、join():让一个线程A加入到另一个线程B的尾部。在A执行完之前,B不能操作。另外还有超时限制的功能。如join(5000):让线程等待5秒,如果超时,则停止等待,变为可运行状态。

【1】继承Thread类创建线程和实现Runnable接口来创建线程

Thread类是一个线程类,我们在具体定义定义多线程开发时,有两种方式创建线程,其中一种就是通过继承的方式来完成,覆写Thread类中的run方法:但是这种创建方式有一定的弊端:那就是被创建的子类不能再继承其他的类;为了解决这种弊端,一般我们都直接去实现Runnable接口去实现多线程的创建,这其实也正是JAVA中解决多态性的具体方案,实现一个接口之后,也能去继承其他的父类或者被子类继承!

原理明白了,好了,写个简单的程序演示演示吧:

class Mythread extends Thread//这里就是一个简单的继承创建线程的方式
{
    public void run()//覆写了父类中的run方法,定义了自定义的函数主体for循环
    {
        for (int i=0;i<50 ;i++ )
        {
            System.out.println("thread hao!="+i);//仅仅是演示线程执行的内容
        }
        
    }
}
//这里就是通过main方法来调用
class ThreadDemo1 
{
    public static void main(String[] args) 
    {
        Mythread mt=new Mythread();//把这个线程对象实例化
        mt.start();//调用父类的start方法,启动并执行线程
    }
}

class Cus implements Runnable//这里是通过实现Runnable接口的方式创建多线程的
{
    public void run()//同理也要覆写接口的run方法
    {
        for (int i=0;i<3;i++ )//简单的函数主体
        {
            System.out.println("....."+i);//用于演示而已
        }
    }
}

class SynchronizedDemo4 
{
    public static void main(String[] args) 
    {
        Cus c=new Cus();//创建接口的子类对象
        Thread c1=new Thread(c);//定义了两个线程,把接口的子类对象和线程相关联起来
        Thread c2=new Thread(c);
        c1.start();//启动两个线程并且执行
        c2.start();
    }
}

【2】为什么要复写run方法

复写run方法主要是因为:在Thread类中,run方法是储存线程要运行的重要代码,所以在定义的时候,我们必须根据具体的需求去覆写run方法,从而自定义要实现的功能主体!

start方法是父类开启和执行线程的方法,直接调用就行了,但是我们如果不覆写run方法,直接在子类中调用的话,是毫无意义的,因为这样的话,相当于我们只是创建了线程,而没有运行!

在main方法中,多次运行多线程的结果都不一定一样的原因是:我们知道,多线程在执行的时候,都是在获取cpu的一个执行权,cpu说想把执行权给谁(线程),谁(线程)就去执行相应的操作!但是在某一时刻,只能有一个程序在运行,当然多核除外

这也就反应除了多线程的一个重要特性:那就是随机性!(可以理解为:谁抢到谁就执行)

【3】获取当前线程的名称

很简单了:用线程类的静态方法就可以:

Thread.currentThread().getName():这样就能够获得当前线程的名称:

线程名称的基本格式是:Thread-编号,编号都是从0开始的

【4】发现线程安全

可以模拟出线程在运行中出现的问题:在这里卖票系统中就是最好的分析****

一般通过下列代码就能找到其安不安全的地方!因为现在只要在这里休眠一下,其他线程自然会获得cpu的执行权进来,这样就没法保证在共享的代码中,不出现问题:在不同步的情况下,售票窗口可能会售出0号,甚至是-1号票,这样就要求我们必须要保证共享数据的同步性:

对于发现线程的安全问题:

具体步骤:

分析:哪里出现了线程的安全问题

如何去找原因:

1.明确哪些代码是多线程运行的代码:

2.明确哪里是共享的数据

3.明确多线程的运行代码中哪里涉及到了要执行共享的数据

                    try { //这种简单的方式只是模拟出现问题而已:                         
                        Thread.sleep(10);  
                    }  
                    catch (Exception e)  
                    {  
                    } 

【5】汽车站售票小程序

模拟卖票窗口,4个窗口同时卖100张共同的票

/*
实现Runnable接口的具体步骤和解决安全性的问题
*/

//1、继承Thread类的做法:
/*
class Ticket extends Thread
{
    private static int tickets=100;  
    //为了确保票的唯一性,必须用static修饰,否则每个线程窗口会各自卖100张票

    //覆写父类的run方法,定义线程卖票方式
    public void run()
    {
        while (true)
        {
            if (tickets>0) //当票大于0时,才能出票
            {
                //打印结果,获取各线程(窗口)卖票情况
                System.out.println(Thread.currentThread().getName()+".....sale:"+tickets --);
            }
        }
    }
}

class ThreadDemo3 
{
    public static void main(String[] args) 
    {
        Ticket t1=new Ticket();
        Ticket t2=new Ticket();
        Ticket t3=new Ticket();
        Ticket t4=new Ticket();  //创建4个窗口(4个线程)
        
        t1.start();
        t2.start();
        t3.start();
        t4.start(); //启动并执行窗口的卖票

        /*
        部分结果:
        Thread-2.....sale:76
        Thread-2.....sale:75
        Thread-2.....sale:74
        Thread-3.....sale:80
        Thread-1.....sale:81
        Thread-1.....sale:71
        Thread-1.....sale:70
        Thread-3.....sale:72
        Thread-2.....sale:73
        Thread-0.....sale:78
        Thread-2.....sale:67
        Thread-2.....sale:65
        Thread-3.....sale:68
        Thread-1.....sale:69
        Thread-3.....sale:63
        Thread-2.....sale:64
        
    }
}
*/
//实现Runnable接口
class Ticket implements Runnable
{
    private int tickets=100;
    Object obj=new Object();
    public void run()
    {
        while (true)
        {
            synchronized(obj)
            {
                if (tickets>0)
                {
                    /*这里是模拟cpu让进来的某一个线程没有分配到执行权,必然会售出不正常的票
                    try
                    {
                        Thread.sleep(10);
                    }
                    catch (Exception e)
                    {
                    }
                    */

                    //为了解决这样的安全问题,要用到同步代码块
                    try
                    {
                        Thread.sleep(10);
                    }
                    catch (InterruptedException e)
                    {
                    }
                    System.out.println(Thread.currentThread().getName()+"....sale:"+tickets --);
                }else
                {
                    break;
                }
            }
        }
    }
}
class ThreadDemo3 
{
    public static void main(String[] args) 
    {
        Ticket t=new Ticket();
        Thread t1=new Thread(t);
        Thread t2=new Thread(t);
        Thread t3=new Thread(t);
        Thread t4=new Thread(t);

        /*自定义线程名称的方式:
        Thread t1=new Thread(t,"窗口1");
        Thread t2=new Thread(t,"窗口2");
        Thread t3=new Thread(t,"窗口3");
        Thread t4=new Thread(t,"窗口4");
        
        部分结果:
        窗口1....sale:44
        窗口1....sale:43
        窗口1....sale:42
        窗口1....sale:41
        窗口1....sale:40
        窗口4....sale:80
        窗口2....sale:81
        窗口2....sale:37
        窗口2....sale:36
        窗口2....sale:35
        窗口2....sale:34
        窗口2....sale:33
        窗口2....sale:32
        窗口2....sale:31
        */

        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }
}

【6】同步的两种表现形式:

一种是代码块同步,一种是函数同步:

/*
实现Runnable接口的具体步骤和解决安全性的问题
*/

//1、继承Thread类的做法:
/*
class Ticket extends Thread
{
    private static int tickets=100;  
    //为了确保票的唯一性,必须用static修饰,否则每个线程窗口会各自卖100张票

    //覆写父类的run方法,定义线程卖票方式
    public void run()
    {
        while (true)
        {
            if (tickets>0) //当票大于0时,才能出票
            {
                //打印结果,获取各线程(窗口)卖票情况
                System.out.println(Thread.currentThread().getName()+".....sale:"+tickets --);
            }
        }
    }
}

class ThreadDemo3 
{
    public static void main(String[] args) 
    {
        Ticket t1=new Ticket();
        Ticket t2=new Ticket();
        Ticket t3=new Ticket();
        Ticket t4=new Ticket();  //创建4个窗口(4个线程)
        
        t1.start();
        t2.start();
        t3.start();
        t4.start(); //启动并执行窗口的卖票

        /*
        部分结果:
        Thread-2.....sale:76
        Thread-2.....sale:75
        Thread-2.....sale:74
        Thread-3.....sale:80
        Thread-1.....sale:81
        Thread-1.....sale:71
        Thread-1.....sale:70
        Thread-3.....sale:72
        Thread-2.....sale:73
        Thread-0.....sale:78
        Thread-2.....sale:67
        Thread-2.....sale:65
        Thread-3.....sale:68
        Thread-1.....sale:69
        Thread-3.....sale:63
        Thread-2.....sale:64
        
    }
}
*/
//实现Runnable接口
class Ticket implements Runnable
{
    private int tickets=100;
    Object obj=new Object();
    public void run()
    {
        while (true)
        {
            synchronized(obj)
            {
                if (tickets>0)
                {
                    /*这里是模拟cpu让进来的某一个线程没有分配到执行权,必然会售出不正常的票
                    try
                    {
                        Thread.sleep(10);
                    }
                    catch (Exception e)
                    {
                    }
                    */

                    //为了解决这样的安全问题,要用到同步代码块
                    try
                    {
                        Thread.sleep(10);
                    }
                    catch (InterruptedException e)
                    {
                    }
                    System.out.println(Thread.currentThread().getName()+"....sale:"+tickets --);
                }else
                {
                    break;
                }
            }
        }
    }
}
class ThreadDemo3 
{
    public static void main(String[] args) 
    {
        Ticket t=new Ticket();
        Thread t1=new Thread(t);
        Thread t2=new Thread(t);
        Thread t3=new Thread(t);
        Thread t4=new Thread(t);

        /*自定义线程名称的方式:
        Thread t1=new Thread(t,"窗口1");
        Thread t2=new Thread(t,"窗口2");
        Thread t3=new Thread(t,"窗口3");
        Thread t4=new Thread(t,"窗口4");
        
        部分结果:
        窗口1....sale:44
        窗口1....sale:43
        窗口1....sale:42
        窗口1....sale:41
        窗口1....sale:40
        窗口4....sale:80
        窗口2....sale:81
        窗口2....sale:37
        窗口2....sale:36
        窗口2....sale:35
        窗口2....sale:34
        窗口2....sale:33
        窗口2....sale:32
        窗口2....sale:31
        */

        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }
}

【7】懒汉式单例模式具体分析

/*
写两种方式的单例模式:注意其区别
*/

/*
    饿汉式的单例模式

class Single
{
    private static final Single s=new Single();
    private Single(){}
    public static Single getInstance()
    {
        return s;
    }

}

*/

/*
    懒汉式的单例模式(延迟加载)
*/
class Single 
{
    private static Single s=null;
    private Single(){}
    //1.为了解决这种安全问题,可以同步函数的方法解决,但是每个线程进来都必须判断锁,效率比较低
    //private static synchronized Single getInstance()

    //2.为了提高效率,可以用同步代码块的方法解决
    public static Single getInstance()
    {
        if (s==null) //只要有一个线程执行成功,那么以后进来的线程就不再判断锁了,
        //因为s已经不为null了,直接不能创建单例的对象了,直接用就行了
        {
            synchronized(Single.class)
            {
                if (s==null)
                {
                    //--0线程
                    //--1线程
                    //当0线程醒了,会创建一个对象
                    //当1线程醒了,也会创建一个对象
                    //……这就是出现了安全问题,不是单例模式了
                    s=new Single();
                    System.out.println(Thread.currentThread().getName()+"访问成功!");
                }
            }
        }   
        return s;
    }
}

class SingleDemo6 
{
    public static void main(String[] args) 
    {
        Single.getInstance();//在main方法中通过单例类对外提供的getInstance方法访问
        //结果:main访问成功!
    }
}

【8】多线程的死锁问题

什么是死锁:两个线程彼此之间抢锁的情况,互不相让,就产生死锁

代码分析:

/*
    自己写一个死锁程序
    用锁的嵌套方式来模拟死锁
    模拟了两个线程彼此之间抢锁的情况,互不相让,就产生死锁
*/

class TestDeaDLock implements Runnable
{
    private boolean flag;//设置flag是为了让线程进入不一样的锁中去执行

    TestDeaDLock(boolean flag)//在Runnable子类初始化时就设置flag
    {
        this.flag=flag;
    }
    public void run()
    {
        if (flag)
        {
            synchronized(MyLock.m1)
            {
                System.out.println("true:----lock! m1 ");
                //演示:拿着锁1的线程想进入具备锁2的内容中去,必然要拿到锁2才行
                //此时持有锁2的线程可能会把锁2给持着锁1的线程,但是也可能互不想让,导致死锁的产生
                synchronized(MyLock.m2)
                {
                    System.out.println("true:----lock m2 !");

                }
            }
            
        }else
        {
            synchronized(MyLock.m2)
            {
                System.out.println("false:----lock m2 !");
                //演示:拿着锁2的线程想进入具备锁1的内容中去,必然要拿到锁1才行
                synchronized(MyLock.m1)
                {
                    System.out.println("false:----lock m1 !");

                }
            }
        }
    }
}

class MyLock //定义自己的锁类,其中有两种锁
{
    static MyLock m1=new MyLock(); //设置锁1
    static MyLock m2=new MyLock();//设置锁2
}

class DeadLockDemo7 
{
    public static void main(String[] args) 
    {
        Thread t1=new Thread(new TestDeaDLock(true)); //一个设置为true
        Thread t2=new Thread(new TestDeaDLock(false));//一个设置为false,主要是为了能够进入不一样的锁中执行
        
        t1.start();
        t2.start(); 
            /*
            演示结果可能会出现:
            true:----lock! m1
            false:----lock m2 !
            */

    }
}

【9】JDK1.5中线程的新特

我们都知道,在线程中有一个很重要的机制,那就是等待唤醒机制:例如代码分析:

/*
线程的等待唤醒机制
代码的优化
【优化的重点】
1.资源中:包含基本的属性,存入,取出函数
2.存入类:直接调用资源的存入函数
3.取出类:直接调用资源的取出函数

这样看起来非常符合逻辑,层次清楚,便于代码的维护和修改等
*/
//定义了一个共同的资源
class Res
{
    private String name;
    private String sex;
    boolean flag=false;
    public synchronized void set(String name,String sex)
    {
        if (flag)
            try{wait();}catch(Exception e){}
        this.name=name;
        this.sex=sex;
        this.flag=true;
        this.notify();
    }

    public synchronized void out()
    {
        if (!flag)
            try{wait();}catch(Exception e){}
        System.out.println("name="+name+" ....... sex="+sex);
        this.flag=false;
        this.notify();
    }
}
//定义存入资源类
class Input implements Runnable
{
    Res r;
    //存入类在初始化时,就有资源
    Input(Res r)
    {
        this.r=r;
    }
    //覆写run方法
    public void run()
    {   
        int id=0;
        
        while (true)
        {
            //加锁为了保证线程的同步,只能允许一个线程在其中操作直到完成位置
            if (id==0)
                r.set("crystal","man");
            else
                r.set("杨女士","女");
            id=(id+1)%2;
        }
    }
}
//定义取出资源类
class Output implements Runnable
{
    Res r;
    Output(Res r)
    {
        this.r=r;
    }
    public void run()
    {
        while (true)
        {
            r.out();
        }
    }
}
/*
    上述的存入资源中和取出资源中:共享的代码就是存入操作和取出操作,一定要保证通过进行,
    否则,当一个线程进入时,存完一个之后,再进入存第二时(存完名称时,挂了),这是取出资源的线程
    在运行,这时候就会把出现:r.name="杨女士"和r.sex="man";的情况
*/
class InputOutputDemo3
{
    public static void main(String[] args) 
    {
        /*
        Res r=new Res(); //资源实例化

        Input in=new Input(r); //把资源和Runnable子类相关联
        Output out=new Output(r);

        Thread t1=new Thread(in);//定义了两个不同线程,一个存入,一个取出
        Thread t2=new Thread(out);
        
        t1.start(); //开启并执行线程
        t2.start();
        */

        Res r=new Res();
        new Thread(new Input(r)).start();
        new Thread(new Output(r)).start();

        /*result:
            …………
            …………
            name=crystal ....... sex=man
            name=杨女士 ....... sex=女
            name=crystal ....... sex=man
            name=杨女士 ....... sex=女
            name=crystal ....... sex=man
            name=杨女士 ....... sex=女
            name=crystal ....... sex=man
            name=杨女士 ....... sex=女
            name=crystal ....... sex=man
            name=杨女士 ....... sex=女
            name=crystal ....... sex=man
            name=杨女士 ....... sex=女
            name=crystal ....... sex=man
            name=杨女士 ....... sex=女
            name=crystal ....... sex=man
            name=杨女士 ....... sex=女
            name=crystal ....... sex=man
            name=杨女士 ....... sex=女
            name=crystal ....... sex=man
            …………
            …………
        */
    }
}

那么在这里面用到了同步锁,在JDK1.5中,新特性出现了:

将同步synchronized替换成为了Lock操作

将Object中的wait,notify,notifyAll,替换成了Condition对象

该对象可以Lock锁,进行获取

在该例子中,实现了本方只唤醒对方的操作

用这种新特性:程序会进行得非常完美,完全符合逻辑!不浪费资源

import java.util.concurrent.locks.*;

class Resource
{
    private String name;
    private int id=1;
    boolean flag=false;
    private Lock lock=new ReentrantLock();
    private Condition condition_pro=lock.newCondition();
    private Condition condition_con=lock.newCondition();

    //定义生产者的同步生产函数
    public void set(String name)
    {
        lock.lock(); //进来就获得锁进入
        try
        {
            while (this.flag)
                //condition.await(); //首次进来因为为false,所以直接执行下面的语句
                condition_pro.await();//让自身的线程等待
            this.name=name+"....."+id++;
            System.out.println(Thread.currentThread().getName()+" Producer:----"+this.name);
            this.flag=true;
            //condition.signalAll();//唤醒线程池中所有的线程,为的是让不要出现全部等待的状况
            condition_con.signal();//不用唤醒所有了,只需要唤醒线程池中的对方就行,所以不用All了
        }
        catch (Exception e)
        {
        }
        finally
        {
            lock.unlock(); //最终释放锁
        }   
    }
    
    //定义消费者的同步消费函数
    public void out()
    {
        lock.lock();
        try
        {   
            while(!(this.flag))
                //condition.await();
            //同理:
            condition_con.await(); //本方等待
            System.out.println(Thread.currentThread().getName()+" Consumer:"+name);
            this.flag=false;
            //condition.signalAll();
            condition_pro.signal(); //唤醒对方
        }
        catch (Exception e)
        {
        }
        finally
        {
            lock.unlock();
        }
        
    }
}

//定义生产者具备调用生产功能
class Producer implements Runnable
{   
    private Resource res;
    Producer(Resource res)
    {
        this.res=res;
    }
    public void run()
    {
        while (true)
        {
            res.set("汽车");
        }
        
    }
}
//定义消费者具备消费功能
class Consumer implements Runnable
{   
    private Resource res;
    Consumer(Resource res)
    {
        this.res=res;
    }
    public void run()
    {
        while (true)
        {
            res.out();
        }
    }
}

class ProducerConsumerDemo6
{
    public static void main(String[] args) 
    {
        //把商品资源实例化
        Resource res=new Resource();

        //定义了两个生产者路线(线程)和两个消费者路线(线程)
        //并且同时启动这些线程和执行
        new Thread(new Producer(res)).start();
        new Thread(new Producer(res)).start();
        new Thread(new Consumer(res)).start();
        new Thread(new Consumer(res)).start();

        /*
        结果:
        ………………
        Thread-1 Producer:----汽车.....3522
        Thread-2 Consumer:汽车.....3522
        Thread-0 Producer:----汽车.....3523
        Thread-3 Consumer:汽车.....3523
        Thread-1 Producer:----汽车.....3524
        Thread-2 Consumer:汽车.....3524
        Thread-0 Producer:----汽车.....3525
        Thread-3 Consumer:汽车.....3525
        Thread-1 Producer:----汽车.....3526
        Thread-2 Consumer:汽车.....3526
        Thread-0 Producer:----汽车.....3527
        Thread-3 Consumer:汽车.....3527
        Thread-1 Producer:----汽车.....3528
        Thread-2 Consumer:汽车.....3528
        Thread-0 Producer:----汽车.....3529
        Thread-3 Consumer:汽车.....3529
        Thread-1 Producer:----汽车.....3530
        ………………
        */
    }
}

【10】多线程之间的通信模式分析

多线程之间的通信就是:多个线程对象去访问共享的数据空间,但是每个线程具体的功能是不一样的,例如:有一个资源,一个线程是向其中存,另一个线程是往里面取出……

/*
线程之间的通信:
*/
//定义了一个共同的资源
class Res
{
    String name;
    String sex;
}
//定义存入资源类
class Input implements Runnable
{
    Res r;
    //存入类在初始化时,就有资源
    Input(Res r)
    {
        this.r=r;
    }
    //覆写run方法
    public void run()
    {   
        int id=0;
        while (true)
        {
            //加锁为了保证线程的同步,只能允许一个线程在其中操作直到完成位置
            synchronized(r)
            {
                if (id==0)
                {
                    r.name="crystal";
                    r.sex="man";
                }else
                {
                    r.name="杨女士";
                    r.sex="女";
                }
                id=(id+1)%2;
            }   
        }
    }
}
//定义取出资源类
class Output implements Runnable
{
    Res r;
    Output(Res r)
    {
        this.r=r;
    }
    public void run()
    {
        while (true)
        {
            //加锁为了保证线程的同步
            //取出资源也是一样的,必须保证同步,只能有一个线程操作
            synchronized(r)
            {
                System.out.println(r.name+"....."+r.sex);
            }
        }
    }
}
/*
    上述的存入资源中和取出资源中:共享的代码就是存入操作和取出操作,一定要保证通过进行,
    否则,当一个线程进入时,存完一个之后,再进入存第二时(存完名称时,挂了),这是取出资源的线程
    在运行,这时候就会把出现:r.name="杨女士"和r.sex="man";的情况
*/
class InputOutputDemo1
{
    public static void main(String[] args) 
    {
        Res r=new Res(); //资源实例化

        Input in=new Input(r); //把资源和Runnable子类相关联
        Output out=new Output(r);

        Thread t1=new Thread(in);//定义了两个不同线程,一个存入,一个取出
        Thread t2=new Thread(out);
        
        t1.start(); //开启并执行线程
        t2.start();
        /*result:
        ………………
        ………………
        杨女士.....女
        杨女士.....女
        杨女士.....女
        crystal.....man
        crystal.....man
        ………………
        ………………
        */
    }
}

【11】停止线程的方法总结

停止线程的方法:

由于stop方法已经过时了

那么怎样才能停止线程呢?

思想只有一种,那就是让run方法结束

一般开启多线程的运行,运行代码通常是循环结构

所以只要控制好循环,就能让run方法结束,也就是让线程结束

让线程停止的3中方法:

1.设置标记:主线程结束之前改变标记让run方法结束

2.使用interrupt():当线程处于wait(必须在同步中进行)或者sleep中断时(在冻结的区域中),用此方法清除冻结,使线程回到运行中,这时会抛出异常,就在异常中设置标记,结束run方法,让线程不再进入去继续等待

3.使用守护线程的方法,setDaemon(true):在【线程启动之前】就设定为守护线程,主要是为了当主线程结束时,后台会自动结束被守护的线程

【其实我们看到的都是前台的线程,后台也有线程在运行,可以理解为后台依赖前台的关系,当前台结束了,后台线程也就over了,这就是守护线程的特点】

class StopThread implements Runnable
{   private boolean flag=true;
    
    //在同步中,当线程进入冻结状况时,就不会读到标记了,线程就不会结束
    public synchronized void run()
    {
        while (flag)
        {
            try
            {
                wait(); //两个线程依次进来都是在这里等待了……没法被唤醒
                //所以该线程没有被停止掉,并且主线程中改变标记的方法,在这里,线程也无法读到了

                //在这个时候就要用到线程中的interrupt方法,主要是为了让线程从冻结状态回到运行状态上
                //但是在运行之后,依然会进入等待状态!

                //当回到运行状态的时候,就需要在抛出的异常中去处理标记,改变标记值就OK了,当看到标记为false的
                //时候两个线程就无法进入该方法区了,这样线程就结束了
            }
            catch (InterruptedException e)
            {
                System.out.println(Thread.currentThread().getName()+".....Exception");
                flag=false;
            }
            System.out.println(Thread.currentThread().getName()+" run .....");
        }
    }

    public void setFlag()
    {
        flag=false;
    }
}
class StopDemo7 
{
    public static void main(String[] args) 
    {
        StopThread st=new StopThread();
        Thread t1=new Thread(st);
        Thread t2=new Thread(st);

        //t1.setDaemon(true);//分别把t1,t2线程都设置为守护线程后再启动
        //t2.setDaemon(true);

        t1.start();
        t2.start();
        int i=0;
        while (true)
        {
            if (i++ == 50)
            {
                st.setFlag(); //主线程中改变了标记,让其run方法主体结束,线程必然也结束了

                //t1.interrupt(); 
                //t2.interrupt();
                //在主线程结束之前把处于等待的线程用interrupt()方法清除掉冻结的线程,让其回到运行状态
                
                break;
            }
            System.out.println(Thread.currentThread().getName()+"  ....."+i);
        }
        /*结果,这里是通过改变标记的方法,结束run方法体,让线程无法进入,直到主线程结束
        ,其他两个线程也结束了
        …………
        main  .....41
        main  .....42
        Thread-0 run .....
        main  .....43
        main  .....44
        main  .....45
        main  .....46
        main  .....47
        main  .....48
        main  .....49
        main  .....50
        Thread-1 run .....
        Thread-0 run .....
        */

        /*
        运用了中断线程,清除了冻结的方法之后:两个线程也结束了,这是在同步中的做法
        main  .....38
        main  .....39
        main  .....40
        main  .....41
        main  .....42
        main  .....43
        main  .....44
        main  .....45
        main  .....46
        main  .....47
        main  .....48
        main  .....49
        main  .....50
        Thread-0.....Exception
        Thread-0 run .....
        Thread-1.....Exception
        Thread-1 run .....
        */

        /*
        设置为守护线程后运行的结果:不论是不是同步,主线程结束,后台线程就被Over了
        ………………
        main  .....35
        main  .....36
        main  .....37
        main  .....38
        main  .....39
        main  .....40
        main  .....41
        main  .....42
        main  .....43
        main  .....44
        main  .....45
        main  .....46
        main  .....47
        main  .....48
        main  .....49
        main  .....50

        */
    }
}

【12】join()和yield()方法的总结

《1》首先join方法:

join():方法的作用

1.当线程A执行到了线程B的B.join()方法时,A就会释放执行权给B,自己处于等待的冻结状态;

2.当线程B都执行完之后,线程A才能从冻结状态回到运行状态去执行;

3.所以join可以用来临时加入线程执行内容!

了解:

线程A把执行权释放了,让线程B去执行,自己冻结,这时线程B如果被等待了,线程A也没法回到运行状态

那么,这时就要用到interrupt()方法,去中断清除A的冻结,从而回到运行状态,当然也可以中断线程A

只是会受到伤害(抛出异常),在异常中处理就行了实际开发中:一般都是使用匿名内部类来完成的

class TestJoin implements Runnable
{
    public void run()
    {
        for (int i=0;i<80 ;i++ )
        {
            System.out.println(Thread.currentThread().getName()+".....run....."+i);
        }
        
    }
}

class JoinDemo8 
{
    public static void main(String[] args) 
    {
        TestJoin tj=new TestJoin();
        Thread t1=new Thread(tj);
        Thread t2=new Thread(tj);

        t1.start();

        //try{t1.join();}catch(Exception e){} 
        //执行的结果是:线程0全部把run函数执行完之后,主线程mian和线程1才交替执行
        //所以:join的功能是能够临时获得主线程的执行权,此程序中,主线程main把执行权释放给了t1,
        //自己处于了冻结状态,当t1线程结束之后,主线程才回到运行状态和t2线程继续交替执行
        
        t2.start();
        try{t1.join();}catch(Exception e){} 
        //如果t1.join()处于这里的话:主线程仍然是把自己的执行权释放给了t1,自己处于冻结状态,
        //但是t2也是存活的线程,这时候cpu会自动发放执行权给t1或者t2去交替执行!主线程就悲催了,只有等到
        //t1执行完之后,才能拿到自己的执行权,从冻结状态回到运行状态

        for (int i=0; i<90; i++)
        {
            System.out.println(Thread.currentThread().getName()+".....mian....."+i);
        }
    }
}

《2》yield()方法:

暂停当前正在执行的线程对象,并执行其他线程。

当线程A进入时,暂停当前正在执行的内容,释放执行权,让其他的线程进来执行,当其他的进来执行

之后,又重新获得执行权,继续执行,这样一来,等同于被共享的线程执行内容是“交替执行的‘

class TestSetPriority implements Runnable
{
    int i=100;
    public void run()
    {
        for (int i=0; i<20 ; i++)
        {
            System.out.println(Thread.currentThread().toString()+"....run...."+i);
            Thread.yield();
            /*
            Thread[Thread-0,5,main]....run....0
            Thread[Thread-1,5,main]....run....0
            Thread[Thread-0,5,main]....run....1
            Thread[Thread-1,5,main]....run....1
            Thread[Thread-0,5,main]....run....2
            Thread[Thread-1,5,main]....run....2
            Thread[Thread-0,5,main]....run....3
            Thread[Thread-1,5,main]....run....3
            Thread[Thread-0,5,main]....run....4
            Thread[Thread-1,5,main]....run....4
            Thread[Thread-0,5,main]....run....5
            Thread[Thread-1,5,main]....run....5
            */
        }
    }
}

class SetPriorityDemo9 
{
    public static void main(String[] args) 
    {
        TestSetPriority sd=new TestSetPriority();
        Thread t1=new Thread(sd);
        Thread t2=new Thread(sd);
        t1.start();
        t2.start();

        /*
        for (int i=0; i<20; i++)
        {
            System.out.println(Thread.currentThread().toString()+"....mians....");
            /*
            Thread[main,5,main]....mian....
            Thread[main,5,main]....mian....
            Thread[main,5,main]....mian....
            
        }
        */
        /*结果://这里是toString()方法,该线程类覆写了Object的这个方法
        在其中封装了进了线程名称,线程优先级,线程组
        …………………………
        Thread[Thread-0,5,main]....run.... 
        Thread[Thread-1,5,main]....run....
        Thread[Thread-0,5,main]....run....
        Thread[Thread-1,5,main]....run....
        Thread[Thread-0,5,main]....run....
        Thread[Thread-1,5,main]....run....
        Thread[Thread-0,5,main]....run....
        Thread[Thread-1,5,main]....run....
        Thread[Thread-0,5,main]....run....
        Thread[Thread-1,5,main]....run....
        Thread[Thread-0,5,main]....run....
        Thread[Thread-1,5,main]....run....
        ………………………………
        */

    }
}

【13】开发过程中为了提高效率,怎样单独封装?

实际开发中:一般都是使用匿名内部类来完成的 用法:在独立运算中:相互不相干扰的时候,可以单独封装一下,提高了执行效率 下面就是三个线程同时执行,很高效!

class StandardThreadDemo10 
{
    public static void main(String[] args) 
    {

        //相当于继承的方法
        new Thread(){
            public void run()
            {
                for (int i=0; i<5 ;i++ )
                {
                    System.out.println(Thread.currentThread().getName()+"...."+i);
                }
            }
        }.start();


        for (int i=0;i<6;i++ )
        {
            System.out.println(Thread.currentThread().getName()+"...."+i);
        }

        //相当于实现接口的方法
        Runnable r=new Runnable()
        {
            public void run()
            {
                for (int i=0; i<10 ;i++ )
                {
                    System.out.println(Thread.currentThread().getName()+"...."+i);
                }
            }
        };
        new Thread(r).start();

        /*
        main....0
        Thread-0....0
        main....1
        Thread-0....1
        main....2
        Thread-0....2
        main....3
        Thread-0....3
        main....4
        Thread-0....4
        main....5
        Thread-1....0
        Thread-1....1
        Thread-1....2
        Thread-1....3
        Thread-1....4
        Thread-1....5
        Thread-1....6
        Thread-1....7
        Thread-1....8
        Thread-1....9
        */
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,980评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,422评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,130评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,553评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,408评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,326评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,720评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,373评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,678评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,722评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,486评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,335评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,738评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,283评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,692评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,893评论 2 335

推荐阅读更多精彩内容

  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,943评论 1 18
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,430评论 1 15
  • 单任务 单任务的特点是排队执行,也就是同步,就像再cmd输入一条命令后,必须等待这条命令执行完才可以执行下一条命令...
    Steven1997阅读 1,158评论 0 6
  • 今天在适配iPhone X的时候,发现iPhone X的屏幕上下没有铺满。新建的项目,以及写的Demo都是铺满...
    manger阅读 1,710评论 2 4
  • 今年的桃花,看了三处: 三月的重庆,四月初的杭州,四月末的天津。在天津还看到了重庆不易见的紫藤和紫丁香。最后一张照...
    清音涧阅读 297评论 0 0