第十六章:多线程编程

一、线程

@SuppressWarnings({"all"}) //取消文件警告
public class Hello {
    public static void main(String[] args) {
        Runtime runtime = Runtime.getRuntime();
        //获取当前cpu的数量
        int i = runtime.availableProcessors();
        System.out.println("当前cpu的数量" + i);
    }
}

二、继承Threa创建线程

@SuppressWarnings({"all"}) //取消文件警告
public class Hello {
    public static void main(String[] args) {
        //创建Cat对象,可以当作线程使用
        Cat cat = new Cat();
        cat.start();//启动线程
    }
}

//1.当一个类继承Thread类,该类就可以当作线程使用
//2.我们会重写run方法,写上自己的业务代码
//3.run Thread 类 实现了Runnable 接口的 run方法
class Cat extends Thread {

    @Override
    public void run() { //重写run方法,写上自己的业务逻辑
        while (true) {
            //该线程每隔1秒,在控制台输出“喵喵,我是小猫咪”
            System.out.println("喵喵,我是小猫咪" + "当前线程名"+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

三、通过实现Runnable接口创建线程

@SuppressWarnings({"all"}) //取消文件警告
public class Hello {
    public static void main(String[] args) {
        Cat cat = new Cat();
        
        Thread thread = new Thread(cat);
        thread.start();
    }
}

class Cat implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("喵喵,我是小猫咪" + "当前线程名"+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

案例:实现多个线程;命令行输入:jconsole查看线程  (建议使用Runnable)

@SuppressWarnings({"all"}) //取消文件警告
public class Hello {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new Cat());
        Thread thread2 = new Thread(new Dog());
        thread1.start();
        thread2.start();
    }
}

class Cat implements Runnable{

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(1000);
                System.out.println("猫");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Dog implements Runnable{

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(1000);
                System.out.println("狗");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

四、线程常用方法

  • setName    设置线程的名字
  • getName    返回该线程的名字
  • start            是该线程运行
  • run              调用线程的run方法,这是普通调用
  • setPriority   设置线程优先级
  • getPriority  获取线程的优先级
  • sleep          休眠
  • interrupt      中断线程   唤醒睡眠
  • yield            线程礼让,让出cpu但是不一定成功
  • join              线程插队,一旦插队成功,这一定会执行完在释放

案例:创建一个子线程,每隔1秒输出hello,输出20次,主线程每隔1秒,输出hi,输出20次,要求两个线程同时执行,当主线程输出5次后,就让子线程再继续。

@SuppressWarnings({"all"}) //取消文件警告
public class Hello {
    public static void main(String[] args) {
        Say say = new Say();
        Thread thread = new Thread(say);
        thread.start();

        for (int i = 0; i < 20; i++) {
            if (i == 5){
                try {
//                    Thread.yield(); //主线程让步
                    thread.join(); //子线程插队
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }{
                try {
                    Thread.sleep(1000);
                    System.out.println("hi");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

class Say implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(1000);
                System.out.println("hello");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

五、守护线程

1.用户线程:也叫工作线程,当线程的任务的执行完成或通知方法结束

2.守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束

3.常见的守护线程:垃圾回收机制

@SuppressWarnings({"all"}) //取消文件警告
public class Hello {
    public static void main(String[] args) throws InterruptedException {
        MyDaemonThread myDaemonThread = new MyDaemonThread();
        //如果我们希望当main线程结束后子线程自动结束
        //只需将子线程设置为守护线程即可 注意顺序先设置在执行
        myDaemonThread.setDaemon(true);
        myDaemonThread.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("hi");
            Thread.sleep(1000);
        }
    }
}

class MyDaemonThread extends Thread{

    @Override
    public void run() {
        for (;;) {
            try {
                Thread.sleep(1000);
                System.out.println("hello");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

六、线程的生命周期

七、线程同步机制

1.在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时候,最多有一个线程访问,以保证数据的完整性。2.也可以理解:当一个线程操作内存的时候其他线程不能对该内存进行操作(卖票案例)

@SuppressWarnings({"all"}) //取消文件警告
public class Hello {
    public static void main(String[] args) throws InterruptedException {
        SellTickets sellTickets1 = new SellTickets();
        SellTickets sellTickets2 = new SellTickets();
        SellTickets sellTickets3 = new SellTickets();
        Thread thread1 = new Thread(sellTickets1);
        Thread thread2 = new Thread(sellTickets2);
        Thread thread3 = new Thread(sellTickets3);
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class SellTickets implements Runnable {
    public static int tickets = 100;
    public static boolean loop = true;

    public synchronized void sell() {
        System.out.println(Thread.currentThread().getName() + "窗口卖出了一张票剩余" + --tickets);
        if (tickets <= 0) {
            loop = false;
            System.out.println("售空");
        }
    }

    @Override
    public void run() {
        while (loop) {
            sell();
        }
    }
}

八、互斥锁

  • java语言中引入了对象互斥锁的概念,来保证共享数据操作的完整性
  • 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任意时刻,只能有一个线程访问该对象。
  • 关键字synchronized来与对象的互斥锁联系,当某个对象用synchronized修饰时,表明该对象在任意时刻只能由一个线程访问
  • 同步的局限性:导致程序的执行效率要降低
  • 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
  • 同步方法(静态的)的锁为当前·类本身
@SuppressWarnings({"all"}) //取消文件警告
public class Hello {
    public static void main(String[] args) throws InterruptedException {
        SellTickets sellTickets1 = new SellTickets();
        SellTickets sellTickets2 = new SellTickets();
        SellTickets sellTickets3 = new SellTickets();
        Thread thread1 = new Thread(sellTickets1);
        Thread thread2 = new Thread(sellTickets2);
        Thread thread3 = new Thread(sellTickets3);
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class SellTickets implements Runnable {
    public static int tickets = 100;
    public static boolean loop = true;
    Object object = new Object();

    //同步方法(静态的)的锁为当前·类本身
    //    public synchronized static void m1(){} 锁是加载在SellTickets.class上
    public synchronized static void m1(){

    }
    //静态方法加载在类上
    public static void m2(){
        synchronized (SellTickets.class){

        }
    }


    //说明
//    public synchronized void sell() {} //就是一个同步方法,这时锁在 this对象
    //也可以在代码块上写synchronized,同步代码块,互斥锁还是加载this对象
    //同步代码块的对象变为Object,只要是同一个对象就可以
    public /*synchronized*/ void sell() {
        synchronized (/*this*/ object) {
            System.out.println(Thread.currentThread().getName() + "窗口卖出了一张票剩余" + --tickets);
            if (tickets <= 0) {
                loop = false;
                System.out.println("售空");
            }
        }
    }

    @Override
    public void run() {
        while (loop) {
            sell();
        }
    }
}

注意事项和细节:同步方法如果没有使用staic修饰:默认锁对象为this。如果方法使用static修饰,默认对象为当前类.class

实现的落地步骤:需要先分析上锁的代码,选择同步代码块或同步方法,要求多个线程对象为同一个即可

九、线程的死锁

多个线程都占用了对方的锁资源,但不肯相让,导致死锁,在编程是一定要避免死锁的发生

@SuppressWarnings({"all"}) //取消文件警告
public class Hello {
    public static void main(String[] args) throws InterruptedException {
        new DeadLockDemo(true).start();
        new DeadLockDemo(false).start();
    }
}
class DeadLockDemo extends Thread{
    static Object object1 = new Object();
    static Object object2 = new Object();
    boolean flag;

    public DeadLockDemo(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag){
            synchronized (object1){
                System.out.println(Thread.currentThread().getName()+"进入1");
                synchronized (object2){
                    System.out.println(Thread.currentThread().getName()+"进入2");
                }
            }
        }else{
            synchronized (object2){
                System.out.println(Thread.currentThread().getName()+"进入3");
                synchronized (object1){
                    System.out.println(Thread.currentThread().getName()+"进入4");
                }
            }
        }
    }
}

十、释放锁

(1)下面操作会释放锁:

  1. 当前线程的同步方法,同步代码块执行结束
  2. 当前线程在同代码块、同步方法遇到break,return。
  3. 当前线程在同步代码块,同步方法中出现了未处理的Error或Exception,导致异常结束
  4. 当前线程在同步代码块,同步方法中执行力线程对象的wait()方法,当前线程暂停,并释放锁

(2)下面操作不会释放锁:

  1. 线程执行同步代码块或同步方法时,程序调用Thread.sleep(),Thread.yield()方法暂停当前线程的执行,不会释放锁。
  2. 线程执行同步代码块,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。suspend()和resume()方法已过时

十一、课后作业

(1)在main方法中启动两个线程,第1个线程循环随机打印100以内的整数,直到第2个线程从键盘读取了“Q”命令。

@SuppressWarnings({"all"}) //取消文件警告
public class Hello {
    public static void main(String[] args) throws InterruptedException {
        new PrintNum().start();
        new ScannerQ().start();
    }
}

class PrintNum extends Thread {
    static boolean flg = true;

    @Override
    public void run() {
        while (flg) {
            try {
                System.out.println("你好");
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class ScannerQ extends Thread {
    Scanner sc = new Scanner(System.in);

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(5000);
                System.out.println("请输入Q退出");
                if (sc.next().toUpperCase().charAt(0) == 'Q') {
                    PrintNum.flg = false;
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

(2)存款10000,两个用户取款同一个卡,一次1000

@SuppressWarnings({"all"}) //取消文件警告
public class Hello {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        Thread thread1 = new Thread(t);
        thread1.setName("爸爸");
        Thread thread2 = new Thread(t);
        thread2.setName("儿子");
        thread1.start();
        thread2.start();
    }
}

//编程取款的线程
//1.因为这里涉及多个线程共享资源,所以我们使用实现Runnable方式
class T implements Runnable{
    private int money = 10000;
    @Override
    public void run() {
        while (true){
            //1.这里使用synchronized 实现线程的同步
            //2.当多个线程执行到这里时,就会去争取 this对象锁
            //3.哪个线程夺取(this对象)就会执行synchronized代码块
            //4.争取不到的,就blocked,准备夺取到
            //5.this是一个非公平锁
            synchronized (this){
                //判断余额是否足够
                if (money<=0){
                    System.out.println("余额不足");
                    break;
                }
                money -= 1000;
                System.out.println(Thread.currentThread().getName() + "取出了1000剩余" + money);
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值