Java基础之 多线程

一、创建多线程程序的第一种方式: 继承(extends) Thread类 

  Thread类的子类: MyThread 

    //1.创建一个Thread类的子类
public class MyThread extends Thread{
    //2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
    @Override
    public void run() {
        for (int i = 0; i <20 ; i++) {
            System.out.println("run:"+i);
        }
    }
}

  主线程: MyThread 

public class MainThread {
    public static void main(String[] args) {
        //3.创建Thread类的子类对象
        MyThread mt = new MyThread();
        //4.调用Thread类中的方法start方法,开启新的线程,执行run方法
        mt.start();
for (int i = 0; i <20 ; i++) { System.out.println("main:"+i); } } }

结果:随机性打印

main:0
run:0
main:1
run:1
main:2
run:2
main:3
run:3
main:4
main:5
。。。。

原理:

  1、JVM执行  MainThread类的main 方法时,知道OS开辟了一条main方法通向CPU的路径。

    这个路径叫做main线程,主线程。CPU通过这个路径可以执行main方法。

  2、JVM执行到  mt.start();时,开辟了一条通向CPU的新路径来 执行run方法。

  3、对于CPU而言,就有了两条执行的路径,CPU就有了选择权,我们控制不了CPU,

    两个线程,main线程和,新的 MyThread的新线程,一起抢夺CPU的执行权

    (执行时间),谁抢到谁执行。

      多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。

    java程序属于抢占式调度,优先级高的线程优先执行;同一个优先级,随机执行

线程的内存解析图:

二、创建多线程程序的第二种方式: 实现(implements) Runnable 类

  实现Runable接口的子类:MyRunbale
    //1.创建一个Runnable接口的实现类
public class MyRunnable implements Runnable {
    //2.在实现类中重写Runnable接口的run方法,设置线程任务
    @Override
    public void run() {
        for (int i = 0; i <20 ; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}

  主线程: MyThread 

public class MainThread {
    public static void main(String[] args) {
        //3.创建一个Runnable接口的实现类对象
        MyRunnable run = new MyRunnable();
        //4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        //Thread t = new Thread(run);//打印线程名称
        Thread t = new Thread(run);//打印HelloWorld
        //5.调用Thread类中的start方法,开启新的线程执行run方法
        t.start();

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

结果:随机性打印

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-0-->5
main-->6
Thread-0-->6
main-->7
Thread-0-->7

原理和内存和实现Thread相识。
实现Runnable接口创建多线程程序的好处:
        1.避免了单继承的局限性
            一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类
            实现了Runnable接口,还可以继承其他的类,实现其他的接口
        2.增强了程序的扩展性,降低了程序的耦合性(解耦)
            实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
            实现类中,重写了run方法:用来设置线程任务
            创建Thread类对象,调用start方法:用来开启新线程

 三、匿名内部类实现线程的创建

 匿名内部类作用:简化代码
把子类继承父类,重写父类的方法,创建子类对象合一步完成
把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成
匿名内部类的最终产物:子类/实现类对象,而这个类没有名字

实现代码:

/*
    匿名内部类方式实现线程的创建

    匿名:没有名字
    内部类:写在其他类内部的类

    格式:
        new 父类/接口(){
            重复父类/接口中的方法
        };
 */
public class InnerClassThread {
    public static void main(String[] args) {
        //线程的父类是Thread
        // new MyThread().start();
        new Thread(){
            //重写run方法,设置线程任务
            @Override
            public void run() {
                for (int i = 0; i <20 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->"+"NOT_Copy");
                }
            }
        }.start();

        //线程的接口Runnable
        //Runnable r = new RunnableImpl();//多态
        Runnable r = new Runnable(){
            //重写run方法,设置线程任务
            @Override
            public void run() {
                for (int i = 0; i <20 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->"+"程序员");
                }
            }
        };
        new Thread(r).start();

        //简化接口的方式
        new Thread(new Runnable(){
            //重写run方法,设置线程任务
            @Override
            public void run() {
                for (int i = 0; i <20 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->"+"MWW");
                }
            }
        }).start();
    }
}

四、线程安全

  1、线程安全产生的原因:

   1、单线程不会出现线程安全问题

   2、多个线程,没有访问共享资源,也不会产生线程安全问题

   3、多个线程,且访问了共享资源,就会产生线程安全问题。

  代码示例:

  线程实现类:

/*
    实现卖票案例
 */
public class RunnableImpl implements Runnable{
    //定义一个多个线程共享的票源
    private  int ticket = 100;
//设置线程任务:卖票 @Override public void run() { //使用死循环,让卖票操作重复执行 while(true){ //先判断票是否存在 if(ticket>0){ try { Thread.sleep(10); //提高安全问题出现的概率,让程序睡眠 } catch (InterruptedException e) { e.printStackTrace(); } //票存在,卖票 ticket-- System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票"); ticket--; } } } }

  MainThread类:

/*
    模拟卖票案例
    创建3个线程,同时开启,对共享的票进行出售
 */
public class MainThread {
    public static void main(String[] args) {
            //创建Runnable接口的实现类对象
            RunnableImpl run = new RunnableImpl();
            //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
            Thread t0 = new Thread(run);
            Thread t1 = new Thread(run);
            Thread t2 = new Thread(run);
            //调用start方法开启多线程
            t0.start();
            t1.start();
            t2.start();
    }
}

结果:

Thread-1-->正在卖第100张票
Thread-0-->正在卖第99张票
Thread-2-->正在卖第98张票
Thread-1-->正在卖第97张票
Thread-0-->正在卖第96张票
Thread-2-->正在卖第95张票
Thread-1-->正在卖第94张票
Thread-0-->正在卖第94张票
Thread-2-->正在卖第92张票
Thread-1-->正在卖第91张票
Thread-0-->正在卖第91张票
Thread-2-->正在卖第89张票
Thread-0-->正在卖第88张票
Thread-1-->正在卖第88张票
Thread-2-->正在卖第86张票
Thread-0-->正在卖第85张票
Thread-1-->正在卖第85张票
Thread-2-->正在卖第83张票
Thread-1-->正在卖第82张票
Thread-0-->正在卖第82张票
Thread-2-->正在卖第80张票
Thread-0-->正在卖第79张票
Thread-1-->正在卖第79张票
Thread-2-->正在卖第77张票
Thread-1-->正在卖第76张票
Thread-0-->正在卖第76张票
Thread-2-->正在卖第74张票
Thread-1-->正在卖第73张票
Thread-0-->正在卖第73张票
Thread-2-->正在卖第71张票
Thread-1-->正在卖第70张票
Thread-0-->正在卖第70张票
Thread-2-->正在卖第68张票
Thread-0-->正在卖第67张票
Thread-1-->正在卖第67张票
Thread-2-->正在卖第65张票
Thread-0-->正在卖第64张票
Thread-1-->正在卖第64张票
Thread-2-->正在卖第62张票
Thread-1-->正在卖第61张票
Thread-0-->正在卖第61张票
Thread-2-->正在卖第59张票
Thread-0-->正在卖第58张票
Thread-1-->正在卖第58张票
Thread-2-->正在卖第56张票
Thread-1-->正在卖第55张票
Thread-0-->正在卖第55张票
Thread-2-->正在卖第53张票
Thread-1-->正在卖第52张票
Thread-0-->正在卖第52张票
Thread-2-->正在卖第50张票
Thread-0-->正在卖第49张票
Thread-1-->正在卖第49张票
Thread-2-->正在卖第47张票
Thread-0-->正在卖第46张票
Thread-1-->正在卖第46张票
Thread-2-->正在卖第44张票
Thread-1-->正在卖第43张票
Thread-0-->正在卖第42张票
Thread-2-->正在卖第41张票
Thread-2-->正在卖第40张票
Thread-1-->正在卖第40张票
Thread-0-->正在卖第40张票
Thread-1-->正在卖第37张票
Thread-2-->正在卖第37张票
Thread-0-->正在卖第37张票
Thread-0-->正在卖第34张票
Thread-1-->正在卖第34张票
Thread-2-->正在卖第34张票
Thread-1-->正在卖第31张票
Thread-2-->正在卖第31张票
Thread-0-->正在卖第31张票
Thread-1-->正在卖第28张票
Thread-0-->正在卖第28张票
Thread-2-->正在卖第28张票
Thread-2-->正在卖第25张票
Thread-1-->正在卖第25张票
Thread-0-->正在卖第25张票
Thread-1-->正在卖第22张票
Thread-0-->正在卖第22张票
Thread-2-->正在卖第22张票
Thread-1-->正在卖第19张票
Thread-0-->正在卖第19张票
Thread-2-->正在卖第19张票
Thread-0-->正在卖第16张票
Thread-1-->正在卖第16张票
Thread-2-->正在卖第16张票
Thread-1-->正在卖第13张票
Thread-0-->正在卖第13张票
Thread-2-->正在卖第13张票
Thread-0-->正在卖第10张票
Thread-1-->正在卖第10张票
Thread-2-->正在卖第10张票
Thread-0-->正在卖第7张票
Thread-1-->正在卖第7张票
Thread-2-->正在卖第7张票
Thread-2-->正在卖第4张票
Thread-1-->正在卖第4张票
Thread-0-->正在卖第4张票
Thread-0-->正在卖第1张票
Thread-1-->正在卖第0张票
Thread-2-->正在卖第-1张票

结果出现了卖重复的票,卖不存在的票。

2、线程安全问题的解决方案:

  1)、同步代码块:

/*
    格式:
        synchronized(锁对象){
            可能会出现线程安全问题的代码(访问了共享数据的代码)
        }

    注意:
        1.通过代码块中的锁对象,可以使用任意的对象
        2.但是必须保证多个线程使用的锁对象是同一个
        3.锁对象作用:
            把同步代码块锁住,只让一个线程在同步代码块中执行
 */

  修改线程实现类:

/*
    实现卖票案例
 */
public class RunnableImpl implements Runnable{
    //定义一个多个线程共享的票源
    private  int ticket = 100;
    //创建一个锁对象
    Object object=new Object();
    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环,让卖票操作重复执行
        while(true){
            //同步代码块
            synchronized (object){
                //先判断票是否存在
                if(ticket>0){
                    try {
                        Thread.sleep(10); //提高安全问题出现的概率,让程序睡眠
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //票存在,卖票 ticket--
                    System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                    ticket--;
                }
            }
        }
    }
}

将可能出现线程安全问题的代码放到   synchronized (object){}  代码块中。

结果:

Thread-0-->正在卖第100张票
Thread-0-->正在卖第99张票
Thread-0-->正在卖第98张票
Thread-0-->正在卖第97张票
Thread-2-->正在卖第96张票
Thread-2-->正在卖第95张票
Thread-2-->正在卖第94张票
Thread-2-->正在卖第93张票
Thread-2-->正在卖第92张票
Thread-2-->正在卖第91张票
Thread-2-->正在卖第90张票
Thread-2-->正在卖第89张票
Thread-2-->正在卖第88张票
Thread-2-->正在卖第87张票
Thread-2-->正在卖第86张票
Thread-2-->正在卖第85张票
Thread-2-->正在卖第84张票
Thread-2-->正在卖第83张票
Thread-2-->正在卖第82张票
Thread-2-->正在卖第81张票
Thread-2-->正在卖第80张票
Thread-2-->正在卖第79张票
Thread-2-->正在卖第78张票
Thread-2-->正在卖第77张票
Thread-2-->正在卖第76张票
Thread-2-->正在卖第75张票
Thread-2-->正在卖第74张票
Thread-2-->正在卖第73张票
Thread-2-->正在卖第72张票
Thread-2-->正在卖第71张票
Thread-2-->正在卖第70张票
Thread-2-->正在卖第69张票
Thread-2-->正在卖第68张票
Thread-2-->正在卖第67张票
Thread-2-->正在卖第66张票
Thread-2-->正在卖第65张票
Thread-2-->正在卖第64张票
Thread-2-->正在卖第63张票
Thread-2-->正在卖第62张票
Thread-2-->正在卖第61张票
Thread-2-->正在卖第60张票
Thread-2-->正在卖第59张票
Thread-2-->正在卖第58张票
Thread-2-->正在卖第57张票
Thread-2-->正在卖第56张票
Thread-2-->正在卖第55张票
Thread-2-->正在卖第54张票
Thread-2-->正在卖第53张票
Thread-2-->正在卖第52张票
Thread-2-->正在卖第51张票
Thread-2-->正在卖第50张票
Thread-2-->正在卖第49张票
Thread-2-->正在卖第48张票
Thread-2-->正在卖第47张票
Thread-2-->正在卖第46张票
Thread-2-->正在卖第45张票
Thread-2-->正在卖第44张票
Thread-2-->正在卖第43张票
Thread-2-->正在卖第42张票
Thread-2-->正在卖第41张票
Thread-2-->正在卖第40张票
Thread-2-->正在卖第39张票
Thread-2-->正在卖第38张票
Thread-2-->正在卖第37张票
Thread-2-->正在卖第36张票
Thread-2-->正在卖第35张票
Thread-2-->正在卖第34张票
Thread-2-->正在卖第33张票
Thread-2-->正在卖第32张票
Thread-2-->正在卖第31张票
Thread-2-->正在卖第30张票
Thread-2-->正在卖第29张票
Thread-2-->正在卖第28张票
Thread-2-->正在卖第27张票
Thread-2-->正在卖第26张票
Thread-2-->正在卖第25张票
Thread-2-->正在卖第24张票
Thread-2-->正在卖第23张票
Thread-2-->正在卖第22张票
Thread-2-->正在卖第21张票
Thread-2-->正在卖第20张票
Thread-2-->正在卖第19张票
Thread-2-->正在卖第18张票
Thread-2-->正在卖第17张票
Thread-2-->正在卖第16张票
Thread-2-->正在卖第15张票
Thread-2-->正在卖第14张票
Thread-2-->正在卖第13张票
Thread-2-->正在卖第12张票
Thread-2-->正在卖第11张票
Thread-2-->正在卖第10张票
Thread-2-->正在卖第9张票
Thread-1-->正在卖第8张票
Thread-1-->正在卖第7张票
Thread-1-->正在卖第6张票
Thread-1-->正在卖第5张票
Thread-1-->正在卖第4张票
Thread-1-->正在卖第3张票
Thread-1-->正在卖第2张票
Thread-1-->正在卖第1张票

不再出现重复票,和不存在的票了。

  实现原理:使用一个锁对象,这个对象叫做同步锁,也叫作对象监视器。

  上述代码中三个线程抢占CPU执行权,

  t0 抢到了CPUd 执行权,执行run方法,遇到synchronized代码块,

  这时候 t0 会检查 synchronized 代码块是否有锁对象,

  发现有就会获取到锁对象进入到同步代码块执行。

  t1 抢到了CPU的执行权,执行  run 方法,遇到 synchronized 代码块

  这时 t1会检查synchronized代码块是否有锁对象

  发现没有 t1 就会进入到阻塞状态,会一直等到 t0 线程归还 锁对象

  一直到 t0 线程执行完同步代码,会把锁对象归还给同步代码块。

  t1 才能获取到 锁对象 进入到同步执行。

  总结:同步中的线程没有执行完毕不会释放锁,没有锁也进不进同步代码,

    这样就保证了只有一个线程在同步中执行共享数据,保证了安全,

    程序频繁的判断锁,获取锁,释放锁,效率会降低。

 

  2)、同步方法

  修改线程实现类:

/*
    实现卖票案例
 */
public class RunnableImpl implements Runnable{
    //定义一个多个线程共享的票源
    private  int ticket = 100;
    //创建一个锁对象
    Object object=new Object();
    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环,让卖票操作重复执行
        while(true){
           payTicket();
        }
    }
    /*
        定义一个同步方法
        同步方法也会把方法内部的代码锁住
        只让一个线程执行
        同步方法的对象是谁?
        就是实现类对象 new RunnableImpl()
     */
    public synchronized void payTicket(){
        //先判断票是否存在
        if(ticket>0){
            try {
                Thread.sleep(10); //提高安全问题出现的概率,让程序睡眠
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //票存在,卖票 ticket--
            System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
            ticket--;
        }
    }
}

还可以用静态的同步代码:

package DemoThread;
/*
    实现卖票案例
 */
public class RunnableImpl implements Runnable{
    //定义一个多个线程共享的票源
    private static int ticket = 100;
    //创建一个锁对象
    Object object=new Object();
    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环,让卖票操作重复执行
        while(true){
            payTicketStatic();
        }
    }
    /*
        定义一个静态的同步方法
        对象是谁?
        就是实现类对象 本类的class属相 --> class文件对象(反射)
     */
    public static synchronized void payTicketStatic(){
        //先判断票是否存在
        if(ticket>0){
            try {
                Thread.sleep(10); //提高安全问题出现的概率,让程序睡眠
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //票存在,卖票 ticket--
            System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
            ticket--;
        }
    }
}

将可能出现线程安全问题的代码放到   synchronized 修饰的方法中。

结果:

Thread-0-->正在卖第100张票
Thread-2-->正在卖第99张票
Thread-2-->正在卖第98张票
Thread-2-->正在卖第97张票
Thread-2-->正在卖第96张票
Thread-2-->正在卖第95张票
Thread-2-->正在卖第94张票
Thread-2-->正在卖第93张票
Thread-2-->正在卖第92张票
Thread-2-->正在卖第91张票
Thread-2-->正在卖第90张票
Thread-2-->正在卖第89张票
Thread-2-->正在卖第88张票
Thread-2-->正在卖第87张票
Thread-2-->正在卖第86张票
Thread-2-->正在卖第85张票
Thread-2-->正在卖第84张票
Thread-2-->正在卖第83张票
Thread-2-->正在卖第82张票
Thread-2-->正在卖第81张票
Thread-2-->正在卖第80张票
Thread-2-->正在卖第79张票
Thread-2-->正在卖第78张票
Thread-2-->正在卖第77张票
Thread-2-->正在卖第76张票
Thread-2-->正在卖第75张票
Thread-2-->正在卖第74张票
Thread-2-->正在卖第73张票
Thread-2-->正在卖第72张票
Thread-2-->正在卖第71张票
Thread-2-->正在卖第70张票
Thread-2-->正在卖第69张票
Thread-2-->正在卖第68张票
Thread-2-->正在卖第67张票
Thread-2-->正在卖第66张票
Thread-2-->正在卖第65张票
Thread-2-->正在卖第64张票
Thread-2-->正在卖第63张票
Thread-2-->正在卖第62张票
Thread-2-->正在卖第61张票
Thread-2-->正在卖第60张票
Thread-2-->正在卖第59张票
Thread-2-->正在卖第58张票
Thread-2-->正在卖第57张票
Thread-2-->正在卖第56张票
Thread-2-->正在卖第55张票
Thread-2-->正在卖第54张票
Thread-2-->正在卖第53张票
Thread-2-->正在卖第52张票
Thread-2-->正在卖第51张票
Thread-2-->正在卖第50张票
Thread-2-->正在卖第49张票
Thread-2-->正在卖第48张票
Thread-2-->正在卖第47张票
Thread-2-->正在卖第46张票
Thread-2-->正在卖第45张票
Thread-2-->正在卖第44张票
Thread-2-->正在卖第43张票
Thread-2-->正在卖第42张票
Thread-2-->正在卖第41张票
Thread-2-->正在卖第40张票
Thread-2-->正在卖第39张票
Thread-2-->正在卖第38张票
Thread-2-->正在卖第37张票
Thread-2-->正在卖第36张票
Thread-2-->正在卖第35张票
Thread-2-->正在卖第34张票
Thread-2-->正在卖第33张票
Thread-2-->正在卖第32张票
Thread-2-->正在卖第31张票
Thread-2-->正在卖第30张票
Thread-2-->正在卖第29张票
Thread-2-->正在卖第28张票
Thread-2-->正在卖第27张票
Thread-2-->正在卖第26张票
Thread-2-->正在卖第25张票
Thread-2-->正在卖第24张票
Thread-2-->正在卖第23张票
Thread-2-->正在卖第22张票
Thread-2-->正在卖第21张票
Thread-2-->正在卖第20张票
Thread-2-->正在卖第19张票
Thread-2-->正在卖第18张票
Thread-2-->正在卖第17张票
Thread-2-->正在卖第16张票
Thread-2-->正在卖第15张票
Thread-2-->正在卖第14张票
Thread-2-->正在卖第13张票
Thread-2-->正在卖第12张票
Thread-2-->正在卖第11张票
Thread-2-->正在卖第10张票
Thread-2-->正在卖第9张票
Thread-2-->正在卖第8张票
Thread-2-->正在卖第7张票
Thread-2-->正在卖第6张票
Thread-2-->正在卖第5张票
Thread-2-->正在卖第4张票
Thread-2-->正在卖第3张票
Thread-2-->正在卖第2张票
Thread-2-->正在卖第1张票

同步方法和静态同步的方法的不同点在于,同步锁的对象不同。

同步方法     的锁对象就是 实现类对象  new RunnableImpl()
静态同步的方法  的锁对象就是 本类的 RunnableImpl.class。

  3)、锁机制

代码实现:

/*
    卖票案例出现了线程安全问题
    卖出了不存在的票和重复的票

    解决线程安全问题的三种方案:使用Lock锁
    java.util.concurrent.locks.Lock接口
    Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
    Lock接口中的方法:
        void lock()获取锁。
        void unlock()  释放锁。
    java.util.concurrent.locks.ReentrantLock implements Lock接口


    使用步骤:
        1.在成员位置创建一个ReentrantLock对象
        2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
        3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
 */
public class RunnableImpl implements Runnable{
    //定义一个多个线程共享的票源
    private int ticket = 100;
    //1.在成员位置创建一个ReentrantLock对象
    Lock lock1 = new ReentrantLock();
    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环,让卖票操作重复执行
        while(true){
            lock1.lock(); //2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
            if(ticket>0){
                try {
                    Thread.sleep(10); //提高安全问题出现的概率,让程序睡眠
                    //票存在,卖票 ticket--
                    System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock1.unlock();//3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
                    //放在finally{} 中 无论程序是否异常,都会把锁释放。
                }
            }
        }
    }
}

结果同上面的两种方式相同。

 五、线程的状态

线程状态  导致状态发生条件
NEW(新建)  线程刚被创建,但是并未启动。还没调用start方法。
Runnable(可运行)
线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操
作系统处理器。
Blocked(锁阻塞)
当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状
态;当该线程持有锁时,该线程将变成Runnable状态。
Waiting(无限等待)
一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个
状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
TimedWaiting(计时等待)
同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态
将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Teminated(被终止)
因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

  1、Timed Waiting 线程状态图: 

  sleep方法的使用还是很简单的。我们需要记住下面几点:
    1. 进入 TIMED_WAITING 状态的一种常见情形是调用的 sleep 方法,
      单独的线程也可以调用,不一定非要有协作关系。
    2. 为了让其他线程有机会执行,可以将Thread.sleep()的调用放线程run()之内。
      这样才能保证该线程执行过程中会睡眠
    3. sleep与锁无关,线程睡眠到期自动苏醒,并返回到Runnable(可运行)状态。
 
    注:sleep()中指定的时间是线程不会运行的最短时间。
    因此,sleep()方法不能保证该线程睡眠到期后就开始立刻执行。

  2、Blocked(锁阻塞)

  上面已经讲过了同步机制,那么这个状态也就非常好理解了,比如线程A与线程B代码中使用

  同一把锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B进入到Blocked锁阻塞状态。

  这里是由Runnable状态进入Blocked状态,除此之外Waiting(无限等待)以及

  Time Waiting(计时等待)也会在某种情况下进入到阻塞状态

  3、Waiting(无限等待)

    1、等待与唤醒案例:

/*
    等待唤醒案例:线程之间的通信
        创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
        创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子

    注意:
        顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
        同步使用的锁对象必须保证唯一
        只有锁对象才能调用wait和notify方法

    Obejct类中的方法
    void wait()
          在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
    void notify()
          唤醒在此对象监视器上等待的单个线程。
          会继续执行wait方法之后的代码
 */
public class MainThread {
    public static void main(String[] args) {
        //创建锁对象,保证唯一
        Object obj = new Object();
        // 创建一个顾客线程(消费者)
        new Thread(){
            @Override
            public void run() {
                //一直等着买包子
                while(true){
                    //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                    synchronized (obj){
                        System.out.println("告知老板要的包子的种类和数量");
                        //调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //唤醒之后执行的代码
                        System.out.println("包子已经做好了,开吃!");
                        System.out.println("---------------------------------------");
                    }
                }
            }
        }.start();

        //创建一个老板线程(生产者)
        new Thread(){
            @Override
            public void run() {
                //一直做包子
                while (true){
                    //花了5秒做包子
                    try {
                        Thread.sleep(5000);//花5秒钟做包子
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                    synchronized (obj){
                        System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了");
                        //做好包子之后,调用notify方法,唤醒顾客吃包子
                        obj.notify();
                    }
                }
            }
        }.start();
    }
}

结果:

告知老板要的包子的种类和数量
老板5秒钟之后做好包子,告知顾客,可以吃包子了
包子已经做好了,开吃!
---------------------------------------
告知老板要的包子的种类和数量
老板5秒钟之后做好包子,告知顾客,可以吃包子了
包子已经做好了,开吃!
---------------------------------------
告知老板要的包子的种类和数量

  2、等待与唤醒案例2:wait(long m)和notifyAll() 方法

代码实现:

/*
    进入到TimeWaiting(计时等待)有两种方式
    1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
    2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态

    唤醒的方法:
         void notify() 唤醒在此对象监视器上等待的单个线程。
         void notifyAll() 唤醒在此对象监视器上等待的所有线程。
 */
public class MainThread {
    public static void main(String[] args) {
        //创建锁对象,保证唯一
        Object obj = new Object();
        // 创建一个顾客线程(消费者)
        new Thread(){
            @Override
            public void run() {
                //一直等着买包子
                while(true){
                    //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                    synchronized (obj){
                        System.out.println("顾客1告知老板要的包子的种类和数量");
                        //调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //唤醒之后执行的代码
                        System.out.println("包子已经做好了,顾客1开吃!");
                        System.out.println("---------------------------------------");
                    }
                }
            }
        }.start();

        // 创建一个顾客线程(消费者)
        new Thread(){
            @Override
            public void run() {
                //一直等着买包子
                while(true){
                    //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                    synchronized (obj){
                        System.out.println("顾客2告知老板要的包子的种类和数量");
                        //调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //唤醒之后执行的代码
                        System.out.println("包子已经做好了,顾客2开吃!");
                        System.out.println("---------------------------------------");
                    }
                }
            }
        }.start();

        //创建一个老板线程(生产者)
        new Thread(){
            @Override
            public void run() {
                //一直做包子
                while (true){
                    //花了5秒做包子
                    try {
                        Thread.sleep(5000);//花5秒钟做包子
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                    synchronized (obj){
                        System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了");
                        //做好包子之后,调用notify方法,唤醒顾客吃包子
                        //obj.notify();//如果有多个等待线程,随机唤醒一个
                        obj.notifyAll();//唤醒所有等待的线程
                    }
                }
            }
        }.start();
    }
}

结果:

顾客1告知老板要的包子的种类和数量
顾客2告知老板要的包子的种类和数量
老板5秒钟之后做好包子,告知顾客,可以吃包子了
包子已经做好了,顾客2开吃!
---------------------------------------
顾客2告知老板要的包子的种类和数量
包子已经做好了,顾客1开吃!
---------------------------------------
顾客1告知老板要的包子的种类和数量
老板5秒钟之后做好包子,告知顾客,可以吃包子了
包子已经做好了,顾客1开吃!
---------------------------------------
顾客1告知老板要的包子的种类和数量
包子已经做好了,顾客2开吃!
---------------------------------------

等待与唤醒机制:又名 线程之间的通讯。

  等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:
  1. wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,
    这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象
    上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
  2. notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
  3. notifyAll:则释放所通知对象的 wait set 上的全部线程。
 
  注:哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而
    此刻它已经不持有锁,所以它需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调
    用 wait 方法之后的地方恢复执行
 
  总结如下:
  如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
  否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态

如下所示:

调用wait和notify方法需要注意的细节
  1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对
    象调用的wait方法后的线程。
  2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继
    承了Object类的。
  3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。 

   3、生产者和消费者问题

  等待唤醒机制其实就是经典的“生产者与消费者”的问题。

  重新整理代码:

  BaoZi类:

/*
    资源类:包子类
    设置包子的属性
        皮
        陷
        包子的状态: 有 true,没有 false
 */
public class BaoZi {
    private String pier ;
    private String xianer ;
    private boolean flag = false ;//包子资源 是否存在 包子资源状态

    public String getPier() {
        return pier;
    }

    public void setPier(String pier) {
        this.pier = pier;
    }

    public String getXianer() {
        return xianer;
    }

    public void setXianer(String xianer) {
        this.xianer = xianer;
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

  BaoZiPu类:

/*
    生产者(包子铺)类:是一个线程类,可以继承Thread
    设置线程任务(run):生产包子
    对包子的状态进行判断
    true:有包子
        包子铺调用wait方法进入等待状态
    false:没有包子
        包子铺生产包子
        增加一些趣味性:交替生产两种包子
            有两种状态(i%2==0)
        包子铺生产好了包子
        修改包子的状态为true有
        唤醒吃货线程,让吃货线程吃包子

    注意:
        包子铺线程和包子线程关系-->通信(互斥)
        必须同时同步技术保证两个线程只能有一个在执行
        锁对象必须保证唯一,可以使用包子对象作为锁对象
        包子铺类和吃货的类就需要把包子对象作为参数传递进来
            1.需要在成员位置创建一个包子变量
            2.使用带参数构造方法,为这个包子变量赋值
 */
public class BaoZiPu extends Thread {
    //1.需要在成员位置创建一个包子变量
    private BaoZi bz;

    //2.使用带参数构造方法,为这个包子变量赋值
    public BaoZiPu(BaoZi bz) {
        this.bz = bz;
    }

    //设置线程任务(run):生产包子
    @Override
    public void run() {
        //定义一个变量
        int count = 0;
        //让包子铺一直生产包子
        while(true){
            //必须同时同步技术保证两个线程只能有一个在执行
            synchronized (bz){
                //对包子的状态进行判断
                if(bz.isFlag()==true){
                    //包子铺调用wait方法进入等待状态
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //被唤醒之后执行,包子铺生产包子
                //增加一些趣味性:交替生产两种包子
                if(count%2==0){
                    //生产 薄皮三鲜馅包子
                    bz.setPier("薄皮");
                    bz.setXianer("三鲜馅");
                }else{
                    //生产 冰皮 牛肉大葱陷
                    bz.setPier("冰皮");
                    bz.setXianer("牛肉大葱陷");
                }
                count++;
                System.out.println("包子铺正在生产:"+bz.getPier()+bz.getXianer()+"包子");
                //生产包子需要3秒钟
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //包子铺生产好了包子
                //修改包子的状态为true有
                bz.setFlag(true);
                //唤醒吃货线程,让吃货线程吃包子
                bz.notify();
                System.out.println("包子铺已经生产好了:"+bz.getPier()+bz.getXianer()+"包子,吃货可以开始吃了");
            }
        }
    }
}

  ChiHuo类:

/*
    消费者(吃货)类:是一个线程类,可以继承Thread
    设置线程任务(run):吃包子
    对包子的状态进行判断
    false:没有包子
        吃货调用wait方法进入等待状态
    true:有包子
        吃货吃包子
        吃货吃完包子
        修改包子的状态为false没有
        吃货唤醒包子铺线程,生产包子
 */
public class ChiHuo extends Thread{
    //1.需要在成员位置创建一个包子变量
    private BaoZi bz;

    //2.使用带参数构造方法,为这个包子变量赋值
    public ChiHuo(BaoZi bz) {
        this.bz = bz;
    }
    //设置线程任务(run):吃包子
    @Override
    public void run() {
        //使用死循环,让吃货一直吃包子
        while (true){
            //必须同时同步技术保证两个线程只能有一个在执行
            synchronized (bz){
                //对包子的状态进行判断
                if(bz.isFlag()==false){
                    //吃货调用wait方法进入等待状态
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //被唤醒之后执行的代码,吃包子
                System.out.println("吃货正在吃:"+bz.getPier()+bz.getXianer()+"的包子");
                //吃货吃完包子
                //修改包子的状态为false没有
                bz.setFlag(false);
                //吃货唤醒包子铺线程,生产包子
                bz.notify();
                System.out.println("吃货已经把:"+bz.getPier()+bz.getXianer()+"的包子吃完了,包子铺开始生产包子");
                System.out.println("----------------------------------------------------");
            }
        }
    }
}

  测试类:

/*
    测试类:
    包含main方法,程序执行的入口,启动程序
    创建包子对象;
    创建包子铺线程,开启,生产包子;
    创建吃货线程,开启,吃包子;
 */
public class Demo {
    public static void main(String[] args) {
        //创建包子对象;
        BaoZi bz =new BaoZi();
        //创建包子铺线程,开启,生产包子;
        new BaoZiPu(bz).start();
        //创建吃货线程,开启,吃包子;
        new ChiHuo(bz).start();
    }
}

结果:

包子铺正在生产:薄皮三鲜馅包子
包子铺已经生产好了:薄皮三鲜馅包子,吃货可以开始吃了
吃货正在吃:薄皮三鲜馅的包子
吃货已经把:薄皮三鲜馅的包子吃完了,包子铺开始生产包子
----------------------------------------------------
包子铺正在生产:冰皮牛肉大葱陷包子
包子铺已经生产好了:冰皮牛肉大葱陷包子,吃货可以开始吃了
吃货正在吃:冰皮牛肉大葱陷的包子
吃货已经把:冰皮牛肉大葱陷的包子吃完了,包子铺开始生产包子
----------------------------------------------------
包子铺正在生产:薄皮三鲜馅包子
包子铺已经生产好了:薄皮三鲜馅包子,吃货可以开始吃了
吃货正在吃:薄皮三鲜馅的包子
吃货已经把:薄皮三鲜馅的包子吃完了,包子铺开始生产包子
----------------------------------------------------
包子铺正在生产:冰皮牛肉大葱陷包子

等待与唤醒机制:又名 线程之间的通讯。

原理说明:

  通讯:对包子的状态进行判断

  没有包子-->吃货线程 唤醒包子铺线程-->吃货等待-->包子铺线程做包子-->做好包子-->修改包子的状态为有包子

  有包子-->包子铺线程唤醒 吃货线程-->包子铺线程等待-->吃货线程吃包子-->吃完包子-->修改包子的状态为没有包子

  。。。

 六、线程池

  为什么使用线程:

     当并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,

    这样频繁创建线程就会大大的降低系统的效率,因为频繁创建线程和销毁线程需要时间。

  1、线程池的概念

    线程池:其实就是一个容纳多个线程的容器,其中线程可以反复使用,省去了频繁创建线程对象的操作,

    无需反复创建线程池而消耗了过多的资源。

    图解:

 合理利用线程池能够带来三个好处:

  1、降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复使用,可执行多个任务。

  2、提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行

    3、提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内

     存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)

  2、线程池的使用

/*
    2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
 */
public class RunnableImpl implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"创建了一个新的线程执行");
    }
}

  测试类;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*
    线程池:JDK1.5之后提供的
    java.util.concurrent.Executors:线程池的工厂类,用来生成线程池
    Executors类中的静态方法:
        static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池
        参数:
            int nThreads:创建线程池中包含的线程数量
        返回值:
            ExecutorService接口,返回的是ExecutorService接口的实现类对象,
       我们可以使用ExecutorService接口接收(面向接口编程)   java.util.concurrent.ExecutorService:线程池接口 用来从线程池中获取线程,调用start方法,执行线程任务 submit(Runnable task) 提交一个 Runnable 任务用于执行 关闭/销毁线程池的方法 void shutdown() 线程池的使用步骤: 1.使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池 2.创建一个类,实现Runnable接口,重写run方法,设置线程任务 3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法 4.调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
*/ public class MainThread { public static void main(String[] args) { //1.使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池 ExecutorService es = Executors.newFixedThreadPool(2); //3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法 es.submit(new RunnableImpl());//pool-1-thread-1创建了一个新的线程执行 //线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用 es.submit(new RunnableImpl());//pool-1-thread-1创建了一个新的线程执行 es.submit(new RunnableImpl());//pool-1-thread-2创建了一个新的线程执行 //4.调用ExecutorService中的方法shutdown销毁线程池(不建议执行) es.shutdown(); es.submit(new RunnableImpl());//抛异常,线程池都没有了,就不能获取线程了 } }

结果:

pool-1-thread-1创建了一个新的线程执行
pool-1-thread-2创建了一个新的线程执行
pool-1-thread-1创建了一个新的线程执行
pool-1-thread-2创建了一个新的线程执行
pool-1-thread-2创建了一个新的线程执行

  因为线程使用完了,会自动把线程归还给线程池,线程可以继续使用,所以我只在线程池中设置了两个线程,

  却可以反复使用,执行多次任务。

注:多线程执行时为什么调用的是start()方法而不是run()方法?
    如果调用代码thread.run()就不是异步执行了,而是同步,那么此线程对象就不会交给“线程规划器”来进行处理。而是由main主线程来调用run()方法,也就是说必须要等到run()方法中的代码执行完成后才可以执行后面的代码。
  start()用来启动一个线程,当调用start()方法时,系统才会开启一个线程,通过Thead类中start()方法来启动的线程处于就绪状态(可运行状态),此时并没有运行,一旦得到CPU时间片,就自动开始执行run()方法。 

待续。。。。

原文地址:https://www.cnblogs.com/mww-NOTCOPY/p/11456814.html