JAVA-15-多线程的调度和控制、线程安全、死锁、等待和唤醒

本节我们开始讲解多线程:
1: 多线程的概念
  (1)线程是依赖于进程而存在的。
    A:进程 正在运行的应用程序
    B:线程 进程的执行路径,执行单元
    
  注意:
  ①CPU在某一时间点上只能执行一个操作,至于执行哪一个,就得该时间点上看谁抢到了CPU执行权,谁抢到CPU执行权,执行谁。
  ②CPU的抢占具有随机性。
  ③CPU的执行权在多个线程之间的切换时非常迅捷的,肉眼分辨不出来,所以看上去是多个线程同时执行,但本质上还是CPU在某一时间点上只能执行一个操作。

  (2)多线程的两种方案:
    ①继承Thread类:
    ②实现Runnable接口:
  (3)多线程的几个问题:
    A:启动线程用的是哪个方法
      start()
    B:start()和run()的区别
      start():1.开启线程 2.执行run()方法里面的代码
      run():执行的是线程里面执行的代码,并不会开启线程
    C:为什么要重写run()
      因为每个线程需要执行的代码都是都是不一样的,
      我们需要将每个线程自己独立执行的代码写到run()方法中执行
    D:线程可以多次启动吗
      如果是不同的线程对象,是可以同时开启的;同一个线程对象是不能多次启动的
①继承Thread类

package com.edu_02;
public class MyThread extends Thread{
    //1.继承Thread类
    //2.重写run方法,重写run方法中的代码之后,当我们启动了这个线程之后,我们的这个线程就会执行run方法中的代码
    @Override
    public void run() {
        //需求:开启该线程之后,执行一个for循环
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }
    }   
}
package com.edu_02;
public class Test {
    public static void main(String[] args) {
        //只要我们创建了一个线程对象,并且启动该线程的实例,我们就相当于开启了一个线程
        MyThread mt = new MyThread();
        mt.start();//1.开启了一个线程   2.让开启的这个线程执行他对应的类中的run方法

        //在次创建一个子线程,并开启这个子线程执行他的run方法
        MyThread mt2 = new MyThread();
        mt2.start();
    }
}

②实现Runnable接口

package com.edu_03;
public class MyThread implements Runnable{
    @Override
    public void run() {
        //启动该线程对象之后,需要执行的代码
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }   
    }
}
package com.edu_03;
public class Test {
    public static void main(String[] args) {
        //创建Mythread对象
        MyThread mt = new MyThread();
        //开启这个线程
        //mt.start();//这里的这个类仅仅是实现了Runnalble接口的一个类,但是start方法在Thread类中
        //但是我们想要开启一个线程,就必须调用start方法,请问怎么办?

        //public Thread(Runnable target)
        Thread t1 = new Thread(mt);
        t1.start(); 
    }
}

  (4)线程的调度和控制
    线程休眠(Thread.sleep(毫秒值))
    线程名称(setName(),getName();)
    线程的调度及优先级setPriority(10)(注意默认值是5,区间在1-10之间)
    什么叫线程优先级:就是设置抢占cpu执行权抢占到的概率

package com.edu_05;
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //线程休眠(Thread.sleep(毫秒值))
            try {
                Thread.sleep(1000);//在此处出现的异常我们只能抓取,不能抛出
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            //获取执行线程的姓名
            System.out.println(getName()+i);
        }
    }
}
package com.edu_05;
public class Test {
    public static void main(String[] args) {
        //线程名称(setName(),getName();)
        //创建3个线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        //给三个线程设置姓名
        t1.setName("刘备");
        t2.setName("张飞");
        t3.setName("关羽");

        //设置线程的优先级
        //线程的调度及优先级setPriority(10)(注意默认值是5,区间在1-10之间)
        //t1.setPriority(100);//设置的区间必须在1-10之间
        t1.setPriority(10);

        //开启线程
        t1.start();
        t2.start();
        t3.start();
    }
}

  (5)多线程案例(两种方式实现,睡一会出现线程安全问题):
    5.1继承Thread卖票
    5.2实现Runnable卖票(睡一会出现线程安全问题)
①继承Thread

package com.edu_07;
public class MyThread extends Thread{
    //共有100张票,将ticket改为静态之后,被类的所有对象所共享
    static int ticket = 100;

    @Override
    public void run() {
        //用一个while true循环模拟三个窗口一直处于打开的状态
        while (true) {
            //只有当ticket>0的时候,才可以出售票
            if (ticket>0) {
                System.out.println(getName()+"正在出售第:"+ticket--+"张票");
            }
        }
    }
}


//测试
package com.edu_07;
//5.1继承Thread卖票
public class Test {
    public static void main(String[] args) {
        //创建三个线程模拟三个售票窗口
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        MyThread mt3 = new MyThread();

        //给线程设置名称
        mt1.setName("窗口一");
        mt2.setName("窗口二");
        mt3.setName("窗口三");

        //启动线程,开启售票
        mt1.start();
        mt2.start();
        mt3.start();
    }
}

②实现Runnable

package com.edu_08;
public class MyThread implements Runnable{
    int ticket = 100;

    @Override
    public void run() {
        while (true) {
            if (ticket>0) {
                System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"张票");
            }
        }
    }
}


//测试
package com.edu_08;
public class Test {
    public static void main(String[] args) {
        //创建MyThread这个对象
        MyThread mt = new MyThread();

        //创建3个窗口
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);
        Thread t3 = new Thread(mt);

        //给线程对象设置名称
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        //启动窗口开始售票
        t1.start();
        t2.start();
        t3.start();
    }
}

实际上,卖票操作中间是有时间间隔的,因此我们加上睡眠在进行测试,
测试时,却发现出现了这样的两个问题:
  A:相同的票卖了多次(重票)
    CPU的一次操作必须是原子性的(操作是CPU执行一次就可以直接完成的)
  B:出现了负数的票
    随机性和延迟导致的
    出现上面的问题称为线程安全问题。

package com.edu_09;
public class MyThread implements Runnable{
    //定义100张票
    int ticket = 100;
    Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            //同步代码块
            //synchronized (new Object()) {//t1,t2,t3三个线程不共享同一把锁每个线程都有自己的议案锁
            synchronized (obj) {//这样3个线程才可以共享同一把锁   
            if (ticket>0) {
                    //考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果
                    try {
                        Thread.sleep(100);
                        /**
                         * 分析:为什么会出现两张100张票
                         * t1抢占到cpu的执行权,此时ticket=100,但是此刻休眠了
                         * 此时被t2抢占到了cpu的执行权,此时ticket=100,
                         * t1,t2分别睡了100毫秒之后,分别醒来了。。
                         * t1此时出售第100张票
                         * t2此时出售第100张票
                         */

                        /**
                         * 分析:为什么会出现0张票和-1张票
                         * 假设此时票池中仅剩1张票了,
                         * t1进来休眠了
                         * t2进来休眠了
                         * t3进来休眠了
                         */
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"张票");
                    /**
                     * t1醒来,出售的是第1张票,此时tickt=0
                     * t2醒来,出售第0张票,此时ticket=-1
                     * t3醒来,出售第-1张票,此时ticket=-2
                     */

                    /**
                     * ticket--这个动作一共包含几步:
                     * 1.打印出ticket此刻本身的值
                     * 2.ticket自减1
                     * 3.将自减之后的ticket的最新的值赋值给变量ticket
                     */
                }
            }
            //当被同步的代码执行完毕之后,t1手里拿着的obj这个锁才会被释放,
            //t1,t2,t3重新抢占cpu的执行权,谁抢到了继续拿着obj这个锁,执行同步代码块中的内容
        }

    }
}


//测试
package com.edu_09;

public class Test {
    public static void main(String[] args) {
        //创建MyThread对象
        MyThread mt = new MyThread();

        //创建三个窗口
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);
        Thread t3 = new Thread(mt);

        //给每一个窗口设置姓名
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        //开启窗口进行售票
        t1.start();
        t2.start();
        t3.start();
    }
}

  (6)多线程安全问题
    A:是否是多线程环境
    B:是否有共享数据
    C:是否有多条语句操作共享数据

  (7)如何解决多线程安全问题 线程安全执行效率就低
    A:同步代码块(必须共用一把锁)
      synchronized(对象) {
        需要被同步的代码。
      }
  注意:
    1.对象是什么?
      任意对象 ,相当于是一把锁,只要线程进去就把锁锁上
    2.需要同步的代码是什么 ?
      就是被线程执行的代码

    C:锁对象问题
      a:同步代码块(定义一个抽象类,里面专门定义一个锁)(前面已经演示)
        任意对象
      b:同步方法(仅适用于实现runable接口)
        public synchronized void sellTicket(){同步代码}
        this
      c:静态同步方法
        类的字节码对象
        public static synchronized void sellTicket() {
          需要同步的代码
        }

package com.edu_10;
public class MyThread implements Runnable{
    //定义100张票
    static int ticket = 100;
    Object obj = new Object();
    int x = 0;

    @Override
    public void run() {
        while (true) {
            if (x%2==0) {
                synchronized (MyThread.class) {//这样3个线程才可以共享同一把锁    
                    /**
                     * synchronized (obj)
                     * synchronized (this)
                     * synchronized (MyThread.class)
                     */
                    if (ticket>0) {
                            //考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }

                            System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"张票");
                        }
                    }
            }else {
                sellTicket();   
            }
            x++;
        }

    }

    /*private void sellTicket() {
        synchronized (obj) {//这样3个线程才可以共享同一把锁   
            if (ticket>0) {
                    //考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"张票");
                }
            }
    }*/


    //同步方法:同步方法是将synchronized关键字加到方法上,同步方法的锁是this
/*  private synchronized void sellTicket() {    
        if (ticket>0) {
                //考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"张票");

        }
    }*/

    //静态同步方法,他的锁是本类的字节码文件对象:类名.class。
    private static synchronized void sellTicket() { 
        if (ticket>0) {
                //考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"张票");

        }
    }
}


//测试
package com.edu_10;
public class Test {
    public static void main(String[] args) {
        //创建MyThread对象
        MyThread mt = new MyThread();

        //创建三个窗口
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);
        Thread t3 = new Thread(mt);

        //给每一个窗口设置姓名
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        //开启窗口进行售票
        t1.start();
        t2.start();
        t3.start();
    }
}

  (8)匿名内部类的方式使用多线程(掌握)
    ①继承Thread类:
    new Thread() {
      public void run() {
       …
      }
    }.start();
    
    ②实现Runnable接口:
    new Thread(new Runnable(){
      public void run() {
       …
      }
    }).start();

案例:利用匿名内部类,启动多个线程,验证单例设计模式之懒汉式所存在的缺陷,当使用多线程的时候就不单例了。。

package com.edu_12;
public class SingleIntanceDemo {
    //私有化构造
    private SingleIntanceDemo(){}

    private static SingleIntanceDemo instance = null;

    public synchronized static SingleIntanceDemo getInstance(){
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        if (instance==null) {
            instance = new SingleIntanceDemo();
        }
        return instance;
    }
}


//测试
package com.edu_12;
public class Test {
    public static void main(String[] args) {
        //启动第一个线程
        new Thread(){
            @Override
            public void run() {
                System.out.println(SingleIntanceDemo.getInstance());
                //不加同步方法时com.edu_12.SingleIntanceDemo@2d7fc1e7
            }
        }.start();

        //启动第二个线程
        new Thread(){
            public void run() {
                System.out.println(SingleIntanceDemo.getInstance());
                //不加同步方法时com.edu_12.SingleIntanceDemo@2a8b83e3
            };
        }.start();  
    }
}
//加上同步方法之后
//com.edu_12.SingleIntanceDemo@6c89db9a
//com.edu_12.SingleIntanceDemo@6c89db9a

  (9) JDK5的Lock锁,我们之前造的所有的锁都没有手动释放锁
      static Lock lock = new ReentrantLock();
    枷锁:lock.lock();
    释放锁:lock.unlock();
这样可以让我们明确的知道在哪里加锁和释放锁。为了保证我们创建的锁一定会被释放,用以下代码进行改进
      try{….}finally{…..}

package com.edu_13;

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

public class MyThread implements Runnable{
    //定义100张票
    int ticket = 100;
    Object obj = new Object();
    //创建一个锁
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try{
                //加上锁,获取锁
                lock.lock();
                if (ticket>0) {
                        //考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }

                        System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"张票");
                    }
            }finally{
                //这里面的代码一定会被执行
                //释放锁
                lock.unlock();
            }
            }
    }
}



//测试
package com.edu_13;

public class Test {
    public static void main(String[] args) {
        //创建MyThread对象
        MyThread mt = new MyThread();

        //创建三个窗口
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);
        Thread t3 = new Thread(mt);

        //给每一个窗口设置姓名
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        //开启窗口进行售票
        t1.start();
        t2.start();
        t3.start();
    }
}

  (10)死锁问题
    同步嵌套,锁里面套了一个锁,出现同步嵌套

package com.edu_14;
public abstract class MyLock {
    //定义两个锁
    public static final Object objA = new Object();
    public static final Object objB = new Object();
}



package com.edu_14;
public class DieThread extends Thread{
    boolean flag;

    //提供一个有参构造
    public DieThread(boolean flag){
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag) {
            synchronized (MyLock.objA) {
                System.out.println("if"+"objA");
                synchronized (MyLock.objB) {
                    System.out.println("if"+"objB");
                }
            }

        }else {
            synchronized (MyLock.objB) {
                System.out.println("else"+"objB");
                synchronized (MyLock.objA) {
                    System.out.println("else"+"objA");
                }
            }   
        }
    }
}



package com.edu_14;
public class Test {
    public static void main(String[] args) {
        //创建两个线程,分别设置不同的布尔值
        DieThread dt = new DieThread(true);
        DieThread dt2 = new DieThread(false);

        //开启两个线程
        dt.start();
        dt2.start();
    }
}

  (11)线程等待和唤醒机制(案例演示:waitThread,NotifyThread,MyLock,Test)
    锁对象调用wait()锁对象调用notify()
  注意:wait和sleep的区别
wait线程等待,在等待的同时释放锁,而sleep()方法在执行的过程中是不会释放锁的。

package com.edu_15;
public abstract class MyLock {
    public static final Object obj = new Object();
}



package com.edu_15;

public class WaitThread extends Thread{

    @Override
    public void run() {
        synchronized (MyLock.obj) {
            //让等待线程处于等待状态
            try {
                MyLock.obj.wait();//当线程处于等待状态的时候,线程就不会继续往下执行了
                                  //线程在处于等待的时候,会释放掉自己手中的锁
                                 //sleep()这个方法,在线程休息的时候会释放锁码?
                                //答:不会
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.println("我被唤醒了");
    }
}



package com.edu_15;

public class NotifyThread extends Thread{
    @Override
    public void run() {
        synchronized (MyLock.obj) {
            //幻想等待线程
            MyLock.obj.notify();//唤醒正在等待的线程,唤醒的等待线程的锁对象,必须和等待线程的锁对象一致
        }
    }
}



package com.edu_15;
public class Test {
    public static void main(String[] args) {
        //创建等待线程,让等待线程处于一个等待状态
        WaitThread wt = new WaitThread();
        wt.start();

        //睡上5秒钟之后唤醒等待线程
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }


        //创建唤醒线程对象
        NotifyThread nt = new NotifyThread();
        nt.start(); 
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值