java线程

 1 多线程

        我们在之前,程序没有跳转语句的前提下,都是由上至下依次进行,那现在想要设计一个程序,边打游戏边听歌,怎么设计?

        要解决上面的问题,需要使用多线程多进程解决。

1.1 并发与并行

        并发:指两个或多个事件在同一个时间段内发生

        并行:指两个或多个时间在同一个时刻发生(同时发生)

1.2 线程与进程

        进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序是一个进程从创建、运行到消亡的过程。

        线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以成为多线程程序。

        简而言之,一个程序运行后至少有一个进程,一个进程中可以包含多个线程

线程调度 :

        分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间

        抢占式调度: 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),java使用的为抢占式调度



 1.3 创建多线程的第一种方法(Thread类)

        主线程:执行主(main)方法的线程

        单线程程序:java程序中只有一个线程,执行从main方法开始,从上到下依次执行

        JVM执行main方法,main方法会进入到栈内存

        JVM会找到操作系统开辟一条main方法通向CPU的执行路径

        CPU就可以通过这个路径来执行main方法

        而这个路径有一个名字,叫main(主)线程

创建多线程程序的第一种方式:创建Thread类的子类

        java.lang.Thread类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类。

实现步骤:

        1.创建一个Thread类的子类

        2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)

        3.创建Thread类的子类对象

        4.调用Thread类中的方法start方法,开启新的线程,执行run方法

                void start():使该线程开始执行;java虚拟机调用该线程的run方法

                结果是两个线程并发的运行,当前线程(main线程)和另一个线程(创建的新线程,执行其run方法)

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

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

public class MyThread extends Thread{
    //重写Thread类中的run方法,设置线程任务
    @Override
    public void run(){
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}
public class Demo_getThreadName {
    public static void main(String[] args) {
        //创建Thread类的子类对象
        MyThread mt=new MyThread();
        //调用start方法,开启新线程,执行run方法
        mt.start();

        for (int i = 0; i < 5; i++) {
            System.out.println("main"+i);
        }
    }
}

输出:
         

2 多线程原理

随机性打印结果说明 

多线程内存图解  

3  Thread类常用方法

3.1 获取线程的名称

        1.使用Thread类中的方法getName()

                String getName():返回该线程的名称

//定义一个Thread类的子类
public class MyThread extends Thread{
    //重写Thread类中的run方法,设置线程任务
    @Override
    public void run(){
        //获取线程名称
        String name = getName();
        System.out.println(name);
    }
}
/**
 * 线程名称:
 *      主线程:main
 *      新线程:Thread-0,Thread-1,Thread-2
 */
public class Demo_getThreadName {
    public static void main(String[] args) {
        //创建Thread类的子类对象
        MyThread mt=new MyThread();
        //调用start方法,开启新线程,执行run方法
        mt.start();
        new MyThread().start();
        new MyThread().start();

    }
}

 输出:

        

        2.可以先获取到当前正在执行的线程,使用线程中的方法getName()获取线程的名称

                static Thread currentThread():返回对当前正在执行的线程对象的引用

public class MyThread extends Thread{
    //重写Thread类中的run方法,设置线程任务
    @Override
    public void run(){
        //获取线程名称
//        String name = getName();
//        System.out.println(name);

        Thread t=Thread.currentThread();
        System.out.println(t);

        String name=t.getName();
        System.out.println(name);
    }
}
/**
 * 线程名称:
 *      主线程:main
 *      新线程:Thread-0,Thread-1,Thread-2
 */
public class Demo_getThreadName {
    public static void main(String[] args) {
        //创建Thread类的子类对象
        MyThread mt=new MyThread();
        //调用start方法,开启新线程,执行run方法
        mt.start();
        new MyThread().start();
        new MyThread().start();

    }
}

输出:

         

//定义一个Thread类的子类
public class MyThread extends Thread{
    //重写Thread类中的run方法,设置线程任务
    @Override
    public void run(){
        //获取线程名称
//        String name = getName();
//        System.out.println(name);

//        Thread t=Thread.currentThread();
//        System.out.println(t);
//
//        String name=t.getName();
//        System.out.println(name);

        //链式编程
        System.out.println(Thread.currentThread().getName());
    }
}
/**
 * 线程名称:
 *      主线程:main
 *      新线程:Thread-0,Thread-1,Thread-2
 */
public class Demo_getThreadName {
    public static void main(String[] args) {
        //创建Thread类的子类对象
        MyThread mt=new MyThread();
        //调用start方法,开启新线程,执行run方法
        mt.start();
        new MyThread().start();
        new MyThread().start();

        //链式编程
        System.out.println(Thread.currentThread().getName());
    }
}

输出:

        

3.2 设置线程名称的方法(了解)

         1.使用Thread类中的方法setName(名字)

                void setName(String name):改变线程名称,使之与参数name相同

        2.创建一个带参数的构造方法,参数传递线程的名称,调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字

                Thread(String name):分配新的Thread对象

//定义一个Thread类的子类
public class MyThread extends Thread{
    public MyThread() {
    }

    public MyThread(String name) {
        super(name);//用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字
    }

    //重写Thread类中的run方法,设置线程任务
    @Override
    public void run(){
        //获取线程名称
        //链式编程
        System.out.println(Thread.currentThread().getName());
    }
}
/**
 * 设置线程名称的方法
 */
public class Demo_getThreadName {
    public static void main(String[] args) {
        //开启多线程
        MyThread mt=new MyThread();

        //设置线程名称的第一种方法:
        mt.setName("cici");
        mt.start();

        //开启多线程
        new MyThread("kaka").start();

    }
}

输出:

        

3.3 sleep 线程暂停

        public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂停执行)

        毫秒数结束之后,线程继续执行

public class DemoSleep {
    public static void main(String[] args) {
        //模拟秒表
        for (int i = 0; i < 9; i++) {
            System.out.println(i);

            //使用Thread类的sleep方法,让程序睡眠一秒钟
            try {
                Thread.sleep(1000);//1000毫秒等于1秒
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

 输出:

        

4 创建多线程程序的第二种方法(Runnable)

实现Runnable接口:

        java.lang.Runnable:该接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个成为run的无参数方法。

         java.lang.Thread类的构造方法:

                Thread(Runnable target):分配新的Thread对象

                Thread(Runnable target,String name):分配新的Thread对象

实现步骤:
        1.创建一个Runnable接口的实现类

        2.在实现类中重写Runnable接口的run方法,设置线程任务

        3.创建一个Runnable接口的实现类对象

        4.创建Thread类对象,创建方法中传递Runnable接口的实现类对象

        5.调用Thread类中的start方法,开启新的线程执行run方法

//1.创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable{

    // 2.在实现类中重写Runnable接口的run方法,设置线程任务
    @Override
    public void run(){
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}
public class Demo_Runnable {
    public static void main(String[] args) {
        // 3.创建一个Runnable接口的实现类对象
        RunnableImpl run=new RunnableImpl();
        //4.创建Thread类对象,创建方法中传递Runnable接口的实现类对象
        Thread t=new Thread(run);
        // 5.调用Thread类中的start方法,开启新的线程执行run方法
        t.start();
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}

输出:

        

5  Thread和Runnable的区别

实现Runnable接口创建多线程程序的好处:

        1.避免了单继承的局限性

                一个类只能继承一个类,类继承了Thread类就不能继承其他的类

                实现了Runnable接口,还可以继承其他的类,实现其他的接口

        2.增强了程序的扩展性,降低了程序的耦合性(解耦)

                实现Runnable接口的方式把设置线程任务和开启线程进行了分离(解耦)

                实现类中,重写了run方法:用来设置线程任务

                创建Thread类对象,调用start方法:用来开启新线程

//1.创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable{

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

    // 2.在实现类中重写Runnable接口的run方法,设置线程任务
    @Override
    public void run(){
        for (int i = 0; i < 20; i++) {
            System.out.println("hello");
        }
    }
}
public class Demo_Runnable {
    public static void main(String[] args) {
        // 3.创建一个Runnable接口的实现类对象
        RunnableImpl run=new RunnableImpl();
        //4.创建Thread类对象,创建方法中传递Runnable接口的实现类对象
        Thread t=new Thread(new RunnableImpl2());
        // 5.调用Thread类中的start方法,开启新的线程执行run方法
        t.start();
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}

输出:

         

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

匿名:没有名字

内部类:写在其他类内部的类

匿名内部类作用:简化代码

        把子类继承父类,重写父类的方法,创建子类对象合成一步完成

        把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成

匿名内部类最终产物:子类/实现类对象,而这个类没有名字

格式:

        new 父类/接口(){

                重写父类/接口中的方法

        }

public class DemoInnerClassThread {
    public static void main(String[] args) {
        //线程的父类是Thread
        //new MyThread().start();
        new Thread(){
            //重写run方法
            @Override
            public void run() {
                for (int i = 0; i < 6; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->hello");
                }
            }
        }.start();

        //线程的接口Runnable
        //RunnableImpl r=new RunnableImpl();//多态
        Runnable r=new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i < 6; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->world");
                }
            }
        };
        new Thread(r).start();

        //简化接口的方式
        new Thread(new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i < 6; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->new");
                }
            }
        }).start();
    }
}

输出:

         

7 线程安全

//实现卖票案例
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) {
                    throw new RuntimeException(e);
                }
                //票存在
                System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票" );
                ticket--;
            }
        }
    }
}
/**
 * 模拟卖票案例
 * 创建三个线程,同时开启,对共享的票进行出售
 */
public class Demo_ticket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run=new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t1=new Thread(run);
        Thread t2=new Thread(run);
        Thread t3=new Thread(run);
        //调用start方法开启多线程
        t1.start();
        t2.start();
        t3.start();
    }
}

 输出:

        

        增加sleep的睡眠功能,增加了安全问题出现的概率。出现了重复的票和不存在的票,说明有线程安全问题。 

7.1 线程安全问题产生的原理

7.2 解决线程安全问题

7.2.1 同步代码块

        synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

格式:

        synchronized(同步锁){

                需要同步操作的代码(可能会出现线程安全问题的代码)(访问了共享数据的代码)

        }

同步锁:

        对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁

        1.锁对象 可以是任意类型

        2.多个线程对象 要使用同一把锁

注意:

        1.通过代码块中的锁对象,可以使用任意的对象

        2.但是必须保证多个线程使用的锁对象是同一个

        3.锁对象的作用:

                把同步代码块锁住,只让一个线程在同步代码块中执行

//实现卖票案例

//解决线程安全问题的第一种方案:同步代码块
public class RunnableImpl implements Runnable{
    //定义一个多线程的共享的票源
    private int ticket=100;

    //创建一个锁对象,
    Object obj=new Object();

    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环,让卖票操作重复执行
        while (true){
            //同步代码块
            synchronized (obj){
                //先判断票存不存在
                if (ticket>0){
                    //增加安全问题出现的概率,让程序睡眠一下
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    //票存在
                    System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票" );
                    ticket--;
                }
            }
        }
    }
}
/**
 * 模拟卖票案例
 * 创建三个线程,同时开启,对共享的票进行出售
 */
public class Demo_ticket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run=new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t1=new Thread(run);
        Thread t2=new Thread(run);
        Thread t3=new Thread(run);
        //调用start方法开启多线程
        t1.start();
        t2.start();
        t3.start();
    }
}

 输出:

        

原理:

7.2.2 同步方法

        使用synchronized修饰的方法,就叫做同步方法。保证A线程执行该方法的时候,其他线程只能在方法外等着。

格式:
        修饰符 synchronized 返回值类型 方法名(参数列表){

                可能会产生线程安全问题的代码

        }

使用步骤:
        1.把访问了共享数据的代码抽取出来,放到一个方法中

        2.在方法上添加了修饰符

//实现卖票案例

//解决线程安全问题的第二种方法:同步方法
public class RunnableImpl implements Runnable{
    //定义一个多线程的共享的票源
    private int ticket=100;

    //定义一个同步方法
    public synchronized void payTicket(){
        //先判断票存不存在
        if (ticket>0){
            //增加安全问题出现的概率,让程序睡眠一下
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            //票存在
            System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票" );
            ticket--;
        }
    }
    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环,让卖票操作重复执行
        while (true){
           payTicket();
        }
    }
}
/**
 * 模拟卖票案例
 * 创建三个线程,同时开启,对共享的票进行出售
 */
public class Demo_ticket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run=new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t1=new Thread(run);
        Thread t2=new Thread(run);
        Thread t3=new Thread(run);
        //调用start方法开启多线程
        t1.start();
        t2.start();
        t3.start();
    }
}

输出:
         

         定义一个同步方法,同步方法也会把方法内部的代码锁住,只让一个线程执行

同步方法的锁对象是谁?

        就是实现类对象 new RunnableImpl,也就是this

静态同步方法

        静态代码块加上static修饰,成员变量也加上static修饰。

        静态的同步方法锁对象是谁?

                不能是this,因为this是创建对象爱你给之后产生的,静态方法优先于对象。

                静态方法的锁对象是本类的class属性-->class文件对象(反射)

//实现卖票案例

//解决线程安全问题的第二种方法:同步方法
public class RunnableImpl implements Runnable{
    //定义一个多线程的共享的票源
    private static int ticket=100;

    //定义一个同步方法
    public static synchronized void payTicket(){
        synchronized (RunnableImpl.class) {
            //先判断票存不存在
            if (ticket > 0) {
                //增加安全问题出现的概率,让程序睡眠一下
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //票存在
                System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
                ticket--;
            }
        }
    }
    //设置线程任务:卖票
    @Override
    public void run() {
        System.out.println("this+"+this);
        //使用死循环,让卖票操作重复执行
        while (true){
           payTicket();
        }
    }
}
/**
 * 模拟卖票案例
 * 创建三个线程,同时开启,对共享的票进行出售
 */
public class Demo_ticket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run=new RunnableImpl();
        System.out.println("run+"+run);
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t1=new Thread(run);
        Thread t2=new Thread(run);
        Thread t3=new Thread(run);
        //调用start方法开启多线程
        t1.start();
        t2.start();
        t3.start();
    }
}

7.2.3 锁机制

        java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更加强大,更体现面向对象

方法:

        public void lock():加同步锁

        public void unlock():释放同步锁

java.util.concurrent.locks.Reentrantlock implements Lock接口

使用步骤:
        1.在成员位置创建一个ReentrantLock对象

        2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁

        3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁

//实现卖票案例

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//解决线程安全问题的第三种方法:使用lock锁
public class RunnableImpl implements Runnable{
    //定义一个多线程的共享的票源
    private int ticket=100;

    //1.在成员位置创建一个ReentrantLock对象
    Lock l=new ReentrantLock();

    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环,让卖票操作重复执行
        while (true){
            //2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
            l.lock();

            //先判断票存不存在
            if (ticket>0){
                //增加安全问题出现的概率,让程序睡眠一下
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //票存在
                System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票" );
                ticket--;
            }

            //  3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
            l.unlock();
        }
    }
}
/**
 * 模拟卖票案例
 * 创建三个线程,同时开启,对共享的票进行出售
 */
public class Demo_ticket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run=new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t1=new Thread(run);
        Thread t2=new Thread(run);
        Thread t3=new Thread(run);
        //调用start方法开启多线程
        t1.start();
        t2.start();
        t3.start();
    }
}

或者

//实现卖票案例

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//解决线程安全问题的第三种方法:使用lock锁
public class RunnableImpl implements Runnable{
    //定义一个多线程的共享的票源
    private int ticket=100;

    //1.在成员位置创建一个ReentrantLock对象
    Lock l=new ReentrantLock();

    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环,让卖票操作重复执行
        while (true){
            //2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
            l.lock();

            //先判断票存不存在
            if (ticket>0){
                //增加安全问题出现的概率,让程序睡眠一下
                try {
                    Thread.sleep(10);
                    //票存在
                    System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票" );
                    ticket--;
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }finally {
                    //  3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
                    l.unlock();//无论程序是否异常,都会把锁释放掉
                }
            }
        }
    }
}
/**
 * 模拟卖票案例
 * 创建三个线程,同时开启,对共享的票进行出售
 */
public class Demo_ticket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run=new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t1=new Thread(run);
        Thread t2=new Thread(run);
        Thread t3=new Thread(run);
        //调用start方法开启多线程
        t1.start();
        t2.start();
        t3.start();
    }
}

8 等待唤醒机制

8.1 线程间的通信

        多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。

比如:

        线程A用来生产包子,线程B用来吃包子,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么现成A与B之间就存在线程通信问题。

为什么要处理线程间通信:

         多个线程并发执行时,在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
如何保证线程间通信有效利用资源:
        多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。就是多个线程在操作同一份数据时,避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即一一 等待唤醒机制。

8.2 等待唤醒机制

定义:
        这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时候你们更多是一起合作以完成某些任务。
        就是在一个线程进行了规定操作后,就进入等待状态 (wait()),等待其他线程执行完他们的指定代码过后再将其唤醒( notify());在有多个线程进行等待时,如果需要,可以使用 notifyAll()来唤醒所有的等待线程,wait/notify 就是线程间的一种协作机制。


等待唤醒中的方法:
        等待唤醒机制就是用于解决线程间通信的问题的,使用到的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方法是属于Obiect类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Obiect类的。
        3.wait方法与notifv方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法

8.3 等待唤醒机制练习

public class BaoZi {
    //皮
    String pi;
    //馅
    String xian;
    //包子的状态,有true,没有false,设置初始值为false没有包子
    boolean flag=false;
}

/**
 * 注意:
 *  包子铺线程和包子线程关系——通信(互斥)
 *  必须使用同步技术保证两个线程只能有一个执行
 *  锁对象必须保证唯一,可以使用包子对象作为锁对象
 *  包子铺和吃货类就需要把包子对象作为参数传递进来
 *      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.flag == true) {
                    //包子铺调用wait方法进入等待状态
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                //被唤醒之后执行,包子铺生产包子
                //增加趣味性,交替生产两种包子
                if (count%2==0){
                    //生产薄皮三鲜包子
                    bz.pi="薄皮";
                    bz.xian="三鲜";
                }else {
                    //生产冰皮猪肉馅
                    bz.pi="冰皮";
                    bz.xian="猪肉";
                }
                count++;
                System.out.println("包子铺正在生产:"+bz.pi+bz.xian+"包子");
                //生产包子需要3秒钟
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //包子铺生产好了包子
                //修改包子状态为true
                bz.flag=true;
                //唤醒吃货线程,让吃货线程吃包子
                bz.notify();
                System.out.println("包子铺正在生产:"+bz.pi+bz.xian+"包子,吃货可以开始吃了");
        }

        }

    }
}
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.flag==false){
                    //吃货调用wait方法进入等待状态
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                // 被唤醒之后执行,吃包子
                System.out.println("吃货正在吃包子"+bz.pi+bz.xian+"包子");
                //吃货吃完包子
                //修改包子的状态为false
                bz.flag=false;
                //吃货唤醒包子铺线程,生产包子
                bz.notify();
                System.out.println("吃货已经把"+bz.pi+bz.xian+"包子吃完了,包子铺生产包子");
                System.out.println("----------");
            }
        }
    }
}
public class Demo {
    public static void main(String[] args) {
        //创建包子对象
        BaoZi bz=new BaoZi();
        //创建包子铺线程,生产包子
        new BaoZiPu(bz).start();
        //创建吃货线程,吃包子
        new ChiHuo(bz).start();
    }
}

输出:

        

9 线程状态

9.1线程状态概述

9.2 Timed Waiting(计时等待)

         Timed Waiting在API中的描述为:一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。单独的去理解这句话,真是玄之又玄,其实我们在之前的操作中已经接触过这个状态了,在哪里呢?
        在我们写卖票的案例中,为了减少线程执行大快,现象不明显等问题,我们在run方法中添加了sleep语句,这样就强制当前正在执行的线程休眠( 暂停执行 ),以”减慢线程”。
        其实当我们调用了sleep方法之后,当前执行的线程就进入到“休眠状态”其实就是所谓的Timed Waiting(计时等待)

9.3 BLOCKED(锁阻塞)

        Blocked状态在API中的介绍为: 一个正在阻塞等待一个监视器锁( 锁对象)的线程处于这一状态.我们已经学完同步机制,那么这个状态是非常好理解的了。比如,线程A与线程B代码中使用同一锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态
这是由Runnable状态进入Blocked状态。除此Waiting以及Time Waiting状态也会在某种情况下进入阻塞状态,而这部分内容作为扩充知识点带领大家了解一下。
        Blocked 线程状态图

9.4 Waiting(无限等待)

        Wating状态在API中介绍为:一个正在无限期等待另一个线程执行一个特别的( 唤醒)动作的线程处于这一状态
        那么我们之前遇到过这种状态吗? 答案是并没有,但并不妨碍我们进行一个简单深入的了解。

9.5 等待唤醒案例代码实现

 等待唤醒案例:线程之间的通信

        创建一个顾客县城(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu执行,进入到WAITING状态(无限等待)

        创建一个老板线程(生产者):花了5秒做包子,做好包子后,调用notify方法,唤醒顾客吃包子

注意:
        顾客和老板线程必须使用同步代码块包惠起来,保证等待和唤醒只能有一个在执行

        同步使用的锁对象必须保证唯一

        只有锁对象才能调用wait和notify方法

obejct类中的方法:
        void wait():在其他线程调用此对象的 notify() 方法或 notifyAlL() 方法前,导致当前线程等待。

        void notify()唤醒在此对象监视器上等待的单个线程。会继续执行ivait方法之后的代码

public class DemoWaitAndNotify {
    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("包子已经做好了,开吃");
                    }
                }
            }
        }.start();


        new Thread(){
            @Override
            public void run() {
                //一直做包子
                while (true){
                    //花了5秒做包子
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //保证等待和唤醒的线程只有一个执行,使用同步技术
                    synchronized (obj){
                        System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了");
                        //做好包子后,调用notify方法,唤醒顾客吃包子
                        obj.notify();
                    }
                }
            }
        }.start();
    }
}

输出:

        

10 Object类中wait带参方法和notifyAll方法 

 进入到Timewaiting(计时等待)有两种方式:

        1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/BLocked状态

        2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/BLocked状态

唤醒的方法:

        void notify() :唤醒在此对象监视器上等待的单个线程。

        void notifyALL() :唤醒在此对象监视器上等待的所有线程。

public class DemoWaitAndNotify2 {
    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开吃");
                    }
                }
            }
        }.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开吃");
                    }
                }
            }
        }.start();


        new Thread(){
            @Override
            public void run() {
                //一直做包子
                while (true){
                    //花了5秒做包子
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //保证等待和唤醒的线程只有一个执行,使用同步技术
                    synchronized (obj){
                        System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了");
                        //做好包子后,调用notify方法,唤醒顾客吃包子
//                        obj.notify();//如果有多个等待线程,随机唤醒一个
                        obj.notifyAll();//唤醒所有等待的线程
                    }
                }
            }
        }.start();
    }
}

输出:

        

11 线程池

我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?

在Java中可以通过线程池来达到这样的效果。今天我们就来详细讲解一下Java的线程池。 

11.1 定义

         线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁建线程对象的操作,无需反复创建线程而消耗过多资源。

        由于线程池中有很多操作都是与优化资源相关的,我们在这里就不多整述。我们通过一张图来了解线程池的工作原理 :

合理利用线程池能够带来三个好处::
        1.降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务.
        2.提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
        3.提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。 

11.2 代码实现

 线程池: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中的方法shutdoln销毁线程池(不建议执行)

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

//线程池的使用
public class DemoThreadPool1 {
    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-2创建了一个新的线程
        //线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用
        es.submit(new RunnableImpl());//pool-1-thread-2创建了一个新的线程

        //4.调用Executorservice中的方法shutdoln销毁线程池(不建议执行)
        es.shutdown();

        es.submit(new RunnableImpl());//抛异常,线程池没有,就不能获取线程了
    }
}
public class RunnableImpl implements Runnable{
    //2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"创建了一个新的线程");
    }
}

输出:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值