测试创建一个多线程

package com.bupt.threadUse;

public class Thread01 {
    public static void main(String[] args) {
        //创建Cat对象,可以当做线程使用了
        //启动线程
        Cat cat = new Cat();
        cat.start();
    }
}

//继承Thread类,重写Run方法,写自己的业务代码
//Thread本身是实现了Runnable接口的
//可以在idea里面选中Thread,右键->Diagram-看他们的关系
class Cat extends Thread {
    int times = 1;

    @Override
    public void run() {

        //ctrl+alt+T 可以选中try-catch块,不用自己写
        while (true) {
            System.out.println("哈哈哈这是多线程第" + (times++) + "次");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(times==10){
                break;
            }
        }
    }
}

我们建立了一个Cat类,通过cat.start()来启动线程
而Cat类本身继承Thread,重写了run方法
当然也可以用Cat类实现Runnable方法,因为Thread本身就是实现了Runnable方法

主线程和子线程交替执行

package com.bupt.threadUse;

public class Thread01 {
    public static void main(String[] args) throws InterruptedException {
        //创建Cat对象,可以当做线程使用了
        //启动线程
        Cat cat = new Cat();
        //启动子线程后,主线程不会被阻塞的(也就是说会继续执行子线程下面的语句)
        cat.start();

        System.out.println("主线程继续执行" + Thread.currentThread().getName());
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程i=" + i);
            Thread.sleep(100);
        }
    }
}

//继承Thread类,重写Run方法,写自己的业务代码
//Thread本身是实现了Runnable接口的
//可以在idea里面选中Thread,右键->Diagram-看他们的关系
class Cat extends Thread {
    int times = 1;

    @Override
    public void run() {

        //ctrl+alt+T 可以选中try-catch块,不用自己写
        while (true) {
            System.out.println("哈哈哈这是多线程第" + (times++) + "次" + "线程名称为:" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (times == 10) {
                break;
            }
        }
    }
}

我们可以看到,我们设置子线程休眠1秒
主线程休眠0.1秒
主线程的执行语句在子线程执行语句之后,但依然会执行不会阻塞

输出

主线程继续执行main
主线程i=0
哈哈哈这是多线程第1次线程名称为:Thread-0
主线程i=1
主线程i=2
主线程i=3
主线程i=4
主线程i=5
主线程i=6
主线程i=7
主线程i=8
主线程i=9
哈哈哈这是多线程第2次线程名称为:Thread-0
哈哈哈这是多线程第3次线程名称为:Thread-0
哈哈哈这是多线程第4次线程名称为:Thread-0
哈哈哈这是多线程第5次线程名称为:Thread-0
哈哈哈这是多线程第6次线程名称为:Thread-0
哈哈哈这是多线程第7次线程名称为:Thread-0
哈哈哈这是多线程第8次线程名称为:Thread-0
哈哈哈这是多线程第9次线程名称为:Thread-0

Process finished with exit code 0

java 线程详细讲解 java 线程教程_主线程


通过jconsole看

java 线程详细讲解 java 线程教程_主线程_02

为什么不能直接调用run而要先调用start

我们测试一下就知道了

Cat cat = new Cat();
        cat.run();
        //启动子线程后,主线程会被阻塞的(也就是说会先执行完cat.run,再执行子线程下面的语句)

        System.out.println("主线程继续执行" + Thread.currentThread().getName());
        for (int i = 0; i < 30; i++) {
            System.out.println("主线程i=" + i);
            Thread.sleep(1000);
        }
哈哈哈这是多线程第59次线程名称为:main
哈哈哈这是多线程第60次线程名称为:main
哈哈哈这是多线程第61次线程名称为:main
哈哈哈这是多线程第62次线程名称为:main
哈哈哈这是多线程第63次线程名称为:main
哈哈哈这是多线程第64次线程名称为:main
主线程继续执行main
主线程i=0
主线程i=1
主线程i=2
主线程i=3
主线程i=4

这样的话执行就有先后顺序,而不是交替进行
靠的是start底层的native方法start0来实现多线程.

调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了

通过Runnable接口来实现多线程
为什么呢?
因为Java是单继承,如果我有一个A继承了B
A又想继承Thread,做不到啊
那么就可以通过实现Runnable接口来实现多线程

看个例子

package com.bupt.threadUse;

public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Thread thread = new Thread(dog);
        thread.start();
    }
}

class Dog implements Runnable {
    int count = 0;
    @Override
    public void run() {
        while (true) {
            System.out.println("hi" + (++count) + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

此时没有start方法了,只能这样启动

Thread thread = new Thread(dog);
        thread.start();

dog实现了Runnable接口

hi1Thread-0
hi2Thread-0
hi3Thread-0
hi4Thread-0
hi5Thread-0
hi6Thread-0
hi7Thread-0
hi8Thread-0

底层使用了静态代理模式
线程代理类,模拟简单的Thread类

class Proxy implements Runnable {
    private Runnable target = null;

    @Override
    public void run() {
    //动态绑定
        if (target != null) {
            target.run();
        }
    }

    public Proxy(Runnable target) {
        this.target = target;
    }

    public void start() {
        start0();
    }

    public void start0() {
        run();
    }
}

两个线程交替打印

package com.bupt.threadUse;

public class Thread03 {
    public static void main(String[] args) {
        Tiger tiger1 = new Tiger();
        Tiger tiger2 = new Tiger();
        Thread thread1 = new Thread(tiger1);
        Thread thread2 = new Thread(tiger2);
        thread1.start();
        thread2.start();
    }
}

class Tiger implements Runnable {

    @Override
    public void run() {
        int count = 0;
        while (true) {
            System.out.println("老虎叫.." + (count++) + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10) {
                break;
            }
        }
    }
}

输出

老虎叫..0Thread-1
老虎叫..0Thread-0
老虎叫..1Thread-1
老虎叫..1Thread-0
老虎叫..2Thread-1
老虎叫..2Thread-0
老虎叫..3Thread-1
老虎叫..3Thread-0
老虎叫..4Thread-0
老虎叫..4Thread-1
老虎叫..5Thread-1
老虎叫..5Thread-0
老虎叫..6Thread-0
老虎叫..6Thread-1
老虎叫..7Thread-1
老虎叫..7Thread-0
老虎叫..8Thread-1
老虎叫..8Thread-0
老虎叫..9Thread-1
老虎叫..9Thread-0

Process finished with exit code 0

当然可以实现两个类

Tiger1 tiger1 = new Tiger1();
        Tiger2 tiger2 = new Tiger2();
        Thread thread1 = new Thread(tiger1);
        Thread thread2 = new Thread(tiger2);
        thread1.start();
        thread2.start();
老虎叫..0Thread-0
假老虎叫..0Thread-1
老虎叫..1Thread-0
假老虎叫..1Thread-1
假老虎叫..2Thread-1
老虎叫..2Thread-0
老虎叫..3Thread-0
假老虎叫..3Thread-1
老虎叫..4Thread-0
假老虎叫..4Thread-1
老虎叫..5Thread-0
假老虎叫..5Thread-1
假老虎叫..6Thread-1
老虎叫..6Thread-0
老虎叫..7Thread-0
假老虎叫..7Thread-1
老虎叫..8Thread-0
假老虎叫..8Thread-1
假老虎叫..9Thread-1
老虎叫..9Thread-0

实现Runnable更适合多个线程共享一个资源的情况
并且避免了单继承的限制

超卖情况

package com.bupt.ticket;

public class SellTicket {
    public static void main(String[] args) {
        SellTicket01 sellTicket01 = new SellTicket01();
        SellTicket01 sellTicket02 = new SellTicket01();
        SellTicket01 sellTicket03 = new SellTicket01();
        Thread thread1 = new Thread(sellTicket01);
        Thread thread2 = new Thread(sellTicket02);
        Thread thread3 = new Thread(sellTicket03);
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class SellTicket01 implements Runnable {
    private static int ticketNum = 100;//多个线程共享num

    @Override
    public void run() {
        while (true) {
            if (ticketNum < 0) {
                System.out.println("售票结束");
                break;
            }
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口" + Thread.currentThread().getName() + "卖出一张票" + "剩余票数" + (ticketNum--));
        }
    }
}

java 线程详细讲解 java 线程教程_java 线程详细讲解_03


出线程安全问题了

通知线程退出

package com.bupt.exit;

public class ThreadExit {
    public static void main(String[] args) {
        T t1 = new T();
        t1.start();
        //修改loop,停止线程
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.setLoop(false);
    }
}

在主线程中调用set方法,设置boolean值,用于控制子线程退出

class T extends Thread {
    private int count = 0;
    private boolean loop = true;

    @Override
    public void run() {
        while (loop) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("T Thread运行了" + (count++) + "次");
        }
    }

    public void setLoop(boolean loop) {
        this.loop = loop;
    }
}

线程中断

注意这个中断并没有真正结束线程,主要针对正在休眠的线程,让他提前起来干活(唤醒)

package com.bupt.method;

public class ThreadMethod1 {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.setName("锦到黑");
        t.setPriority(Thread.MAX_PRIORITY);
        t.start();
        for (int i = 0; i < 5; i++) {
            Thread.sleep(300);
            System.out.println("hi" + i);
        }
        //执行到这里,就会中断t线程的休眠
        t.interrupt();
    }
}

//自定义线程类
class T extends Thread {
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 20; i++) {
                System.out.println(Thread.currentThread().getName() + "吃包子" + i);
            }
            try {
                System.out.println(Thread.currentThread().getName() + "休眠中");
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "被interrupt了");
            }
        }
    }
}

看一看输出

锦到黑吃包子0
锦到黑吃包子1
锦到黑吃包子2
锦到黑吃包子3
锦到黑吃包子4
锦到黑吃包子5
锦到黑吃包子6
锦到黑吃包子7
锦到黑吃包子8
锦到黑吃包子9
锦到黑吃包子10
锦到黑吃包子11
锦到黑吃包子12
锦到黑吃包子13
锦到黑吃包子14
锦到黑吃包子15
锦到黑吃包子16
锦到黑吃包子17
锦到黑吃包子18
锦到黑吃包子19
锦到黑休眠中
hi0
hi1
hi2
hi3
hi4
锦到黑被interrupt了
锦到黑吃包子0
锦到黑吃包子1
锦到黑吃包子2
锦到黑吃包子3
锦到黑吃包子4
锦到黑吃包子5
锦到黑吃包子6
锦到黑吃包子7
锦到黑吃包子8
锦到黑吃包子9
锦到黑吃包子10
锦到黑吃包子11
锦到黑吃包子12
锦到黑吃包子13
锦到黑吃包子14
锦到黑吃包子15
锦到黑吃包子16
锦到黑吃包子17
锦到黑吃包子18
锦到黑吃包子19
锦到黑休眠中
锦到黑吃包子0
锦到黑吃包子1
锦到黑吃包子2
锦到黑吃包子3
锦到黑吃包子4
锦到黑吃包子5
锦到黑吃包子6
锦到黑吃包子7
锦到黑吃包子8
锦到黑吃包子9
锦到黑吃包子10
锦到黑吃包子11
锦到黑吃包子12
锦到黑吃包子13
锦到黑吃包子14
锦到黑吃包子15
锦到黑吃包子16
锦到黑吃包子17
锦到黑吃包子18
锦到黑吃包子19
锦到黑休眠中
锦到黑吃包子0
锦到黑吃包子1
锦到黑吃包子2
锦到黑吃包子3
锦到黑吃包子4
锦到黑吃包子5
锦到黑吃包子6
锦到黑吃包子7
锦到黑吃包子8
锦到黑吃包子9
锦到黑吃包子10
锦到黑吃包子11
锦到黑吃包子12
锦到黑吃包子13
锦到黑吃包子14
锦到黑吃包子15
锦到黑吃包子16
锦到黑吃包子17
锦到黑吃包子18
锦到黑吃包子19
锦到黑休眠中

如果没有interrupt,那么休眠就完完整整执行5秒

yield和join

  • yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功(t1礼让t2,但是不一定能给到t2)
  • join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务(t1线程里面执行t2.join,让t2成功插队)

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

看看join

package com.bupt.method;

public class ThreadMethod2 {
    public static void main(String[] args) throws InterruptedException {
        T2 t2 = new T2();
        t2.start();
        for (int i = 1; i <= 20; i++) {
            Thread.sleep(500);
            System.out.println("主线程吃了" + i + "个包子");
            if (i == 5) {
                System.out.println("大哥先吃");
                t2.join();
                System.out.println("大哥吃完了");
            }
        }
    }
}

class T2 extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 20; i++) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程吃了" + i + "个包子");
        }
    }
}
子线程吃了1个包子
主线程吃了1个包子
子线程吃了2个包子
主线程吃了2个包子
主线程吃了3个包子
子线程吃了3个包子
主线程吃了4个包子
子线程吃了4个包子
主线程吃了5个包子
子线程吃了5个包子
大哥先吃
子线程吃了6个包子
子线程吃了7个包子
子线程吃了8个包子
子线程吃了9个包子
子线程吃了10个包子
子线程吃了11个包子
子线程吃了12个包子
子线程吃了13个包子
子线程吃了14个包子
子线程吃了15个包子
子线程吃了16个包子
子线程吃了17个包子
子线程吃了18个包子
子线程吃了19个包子
子线程吃了20个包子
大哥吃完了
主线程吃了6个包子
主线程吃了7个包子
主线程吃了8个包子
主线程吃了9个包子
主线程吃了10个包子
主线程吃了11个包子
主线程吃了12个包子
主线程吃了13个包子
主线程吃了14个包子
主线程吃了15个包子
主线程吃了16个包子
主线程吃了17个包子
主线程吃了18个包子
主线程吃了19个包子
主线程吃了20个包子

Process finished with exit code 0

我们把join换成yield

for (int i = 1; i <= 10; i++) {
            Thread.sleep(500);
            System.out.println("主线程吃了" + i + "个包子");
            if (i == 5) {
                System.out.println("大哥先吃");
                Thread.yield();
                System.out.println("大哥吃完了");
            }
        }

结果没让出去

主线程吃了1个包子
子线程吃了1个包子
主线程吃了2个包子
子线程吃了2个包子
主线程吃了3个包子
子线程吃了3个包子
主线程吃了4个包子
子线程吃了4个包子
主线程吃了5个包子
子线程吃了5个包子
大哥先吃
大哥吃完了
子线程吃了6个包子
主线程吃了6个包子
子线程吃了7个包子
主线程吃了7个包子
主线程吃了8个包子
子线程吃了8个包子
子线程吃了9个包子
主线程吃了9个包子
子线程吃了10个包子
主线程吃了10个包子
Process finished with exit code 0

用户线程和守护线程

用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
测试一下守护线程

package com.bupt.method;

public class ThreadMethod3 {
    public static void main(String[] args) throws InterruptedException {
        MyDaemonThread myDaemonThread = new MyDaemonThread();
        myDaemonThread.start();
        for (int i = 1; i <= 10; i++) {
            //main线程
            System.out.println("工作工作打工");
            Thread.sleep(1000);
        }
    }
}

class MyDaemonThread extends Thread {
    @Override
    public void run() {
        for (; ; ) {//无限循环
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("你好");
        }
    }
}

目前没有弄守护线程,在主线程结束后,子线程还在运行

工作工作打工
你好
工作工作打工
你好
你好
工作工作打工
你好
你好
工作工作打工
你好
你好
工作工作打工
你好
你好
工作工作打工
你好
你好
工作工作打工
你好
你好
工作工作打工
你好
你好
工作工作打工
你好
你好
工作工作打工
你好
你好
你好
你好
你好
你好
你好
你好
你好
你好
你好
你好
你好
你好
你好
你好
你好

希望主线程结束,子线程也结束,则需要设置子线程为守护线程

public static void main(String[] args) throws InterruptedException {
        MyDaemonThread myDaemonThread = new MyDaemonThread();
        //设置守护线程
        myDaemonThread.setDaemon(true);
        myDaemonThread.start();
        for (int i = 1; i <= 10; i++) {
            //main线程
            System.out.println("工作工作打工");
            Thread.sleep(1000);
        }
    }

看看输出

工作工作打工
你好
工作工作打工
你好
你好
工作工作打工
你好
你好
工作工作打工
你好
你好
工作工作打工
你好
你好
工作工作打工
你好
你好
工作工作打工
你好
你好
工作工作打工
你好
你好
工作工作打工
你好
你好
工作工作打工
你好
你好

Process finished with exit code 0

线程状态的切换

java 线程详细讲解 java 线程教程_学习_04

  • 从Runnable->TimeWaiting
    Thread.sleep(time)
    o.wait(time)
    t.join(time)
    LockSupport.parkUntil()
    LockSupport.parkNanoc()
  • 从TimeWaiting->Runnable
    时间结束
  • 从Runnable->Waiting
    o.wait()
    t.join()
    LockSupport.park()
  • 从Waiting->Runnable
    o.notify
    o.notifyAll()
    LockSupport.unpark()

测试一下状态

package com.bupt.state;

public class ThreadState {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        System.out.println(t.getName() + "状态为" + t.getState());
        t.start();
        while (Thread.State.TERMINATED != t.getState()) {
            System.out.println(t.getName() + "状态" + t.getState());
            Thread.sleep(1000);
        }
        System.out.println(t.getName() + "状态" + t.getState());
    }
}

class T extends Thread {
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 10; i++) {
                System.out.println("hi" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            break;
        }
    }
}

输出结果

Thread-0状态为NEW
Thread-0状态RUNNABLE
hi0
hi1
Thread-0状态RUNNABLE
hi2
Thread-0状态RUNNABLE
Thread-0状态RUNNABLE
hi3
Thread-0状态RUNNABLE
hi4
Thread-0状态RUNNABLE
hi5
Thread-0状态RUNNABLE
hi6
Thread-0状态RUNNABLE
hi7
Thread-0状态RUNNABLE
hi8
Thread-0状态RUNNABLE
hi9
Thread-0状态RUNNABLE
Thread-0状态TERMINATED

Process finished with exit code 0

线程同步机制

  • 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
  • 也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作.
    使用synchronize解决超卖问题
package com.bupt.syn;

public class SellTicket {
    public static void main(String[] args) {
        SellTicket01 sellTicket01 = new SellTicket01();
        SellTicket01 sellTicket02 = new SellTicket01();
        SellTicket01 sellTicket03 = new SellTicket01();
        Thread thread1 = new Thread(sellTicket01);
        Thread thread2 = new Thread(sellTicket02);
        Thread thread3 = new Thread(sellTicket03);
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class SellTicket01 implements Runnable {
    private int ticketNum = 10;//多个线程共享num
    private boolean loop = true;

    public synchronized void sell() {
        if (ticketNum < 0) {
            System.out.println("售票结束");
            loop = false;
            return;
        }
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("窗口" + Thread.currentThread().getName() + "卖出一张票" + "剩余票数" + (ticketNum--));
    }

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

将sell方法用synchronized修饰

窗口Thread-1卖出一张票剩余票数10
窗口Thread-0卖出一张票剩余票数10
窗口Thread-2卖出一张票剩余票数10
窗口Thread-0卖出一张票剩余票数9
窗口Thread-2卖出一张票剩余票数9
窗口Thread-1卖出一张票剩余票数9
窗口Thread-0卖出一张票剩余票数8
窗口Thread-1卖出一张票剩余票数8
窗口Thread-2卖出一张票剩余票数8
窗口Thread-0卖出一张票剩余票数7
窗口Thread-2卖出一张票剩余票数7
窗口Thread-1卖出一张票剩余票数7
窗口Thread-2卖出一张票剩余票数6
窗口Thread-1卖出一张票剩余票数6
窗口Thread-0卖出一张票剩余票数6
窗口Thread-0卖出一张票剩余票数5
窗口Thread-1卖出一张票剩余票数5
窗口Thread-2卖出一张票剩余票数5
窗口Thread-1卖出一张票剩余票数4
窗口Thread-0卖出一张票剩余票数4
窗口Thread-2卖出一张票剩余票数4
窗口Thread-1卖出一张票剩余票数3
窗口Thread-0卖出一张票剩余票数3
窗口Thread-2卖出一张票剩余票数3
窗口Thread-1卖出一张票剩余票数2
窗口Thread-0卖出一张票剩余票数2
窗口Thread-2卖出一张票剩余票数2
窗口Thread-2卖出一张票剩余票数1
窗口Thread-0卖出一张票剩余票数1
窗口Thread-1卖出一张票剩余票数1
窗口Thread-0卖出一张票剩余票数0
窗口Thread-2卖出一张票剩余票数0
售票结束
窗口Thread-1卖出一张票剩余票数0
售票结束
售票结束

Process finished with exit code 0

互斥锁

基本介绍
1.Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
2.每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
3.关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
4.同步的局限性:导致程序的执行效率要降低
5.同步方法((非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)6.同步方法(静态的)的锁为当前类本身。

刚才上面是给方法加的synchronized
还可以给同步代码块加锁

public void sell() {
        synchronized (this) {
            if (ticketNum < 0) {
                System.out.println("售票结束");
                loop = false;
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口" + Thread.currentThread().getName() + "卖出一张票" + "剩余票数" + (ticketNum--));
        }
    }

这里可以不用this
只需要是同一个object就行(工具人object)

class SellTicket01 implements Runnable {
    private int ticketNum = 10;//多个线程共享num
    private boolean loop = true;
    Object object=new Object();
    public void sell() {
        synchronized (object) {
            if (ticketNum < 0) {
                System.out.println("售票结束");
                loop = false;
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口" + Thread.currentThread().getName() + "卖出一张票" + "剩余票数" + (ticketNum--));
        }
    }

我们再来看看静态方法的情况
//1. public synchronized static void m1()锁是加在SellTicket03.class(此时直接加this就不行了)
//2.如果在静态方法中,实现一个同步代码块。

public synchronized static void m1() {

    }

    public static void m2() {
        synchronized (SellTicket.class) {
            System.out.println("m2");
        }
    }

对比一下就更好理解双重校验锁了

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

死锁

线程的死锁

多个线程都占用了对方的锁资源,但不肯相让,导致了死锁在编程是一定要避免死锁的发生。
·应用案例
妈妈:你先完成作业,才让你玩手机小明:你先让我玩手机,我才完成作业.
表演一个死锁

package com.bupt.syn;

public class DeadLock {
    public static void main(String[] args) {
        DeadLockDemo A = new DeadLockDemo(true);
        A.setName("A线程");
        DeadLockDemo B = new DeadLockDemo(false);
        B.setName("B线程");
        A.start();
        B.start();

    }
}

class DeadLockDemo extends Thread {
    static Object o1 = new Object();
    static Object o2 = new Object();
    boolean flag;

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

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

然后一直卡住

A线程进入1
B线程进入3

释放锁

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

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

作业1:A线程交替打印1-100随机数,B线程输入Q使得A线程退出

package com.bupt.homework;
import java.util.Scanner;

public class HomeWork01 {
    public static void main(String[] args) {
        MyThread1 myThread1 = new MyThread1();
        MyThread2 myThread2 = new MyThread2(myThread1);
        myThread1.start();
        myThread2.start();
    }
}

class MyThread1 extends Thread {
    private boolean flag = true;

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

    @Override
    public void run() {
        while (flag) {
            System.out.println((int) (Math.random() * 100 + 1));
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class MyThread2 extends Thread {
    private MyThread1 myThread1;
    Scanner s = new Scanner(System.in);

    public MyThread2(MyThread1 myThread1) {
        this.myThread1 = myThread1;
    }

    @Override
    public void run() {
        while (true) {
            System.out.println("请输入指令(Q)表示退出");
            char key = s.next().toUpperCase().charAt(0);
            if (key == 'Q') {
                myThread1.setFlag(false);
                break;
            }
        }
    }
}

作业2:取钱问题

package com.bupt.homework;

public class HomeWork02 {
    public static void main(String[] args) {
        WithDraw withDraw1 = new WithDraw();
        Thread thread1 = new Thread(withDraw1);
        Thread thread2 = new Thread(withDraw1);
        Thread thread3 = new Thread(withDraw1);
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class WithDraw implements Runnable {
    private int balance = 13000;
    private int withDrawMoney = 1000;

    @Override
    public void run() {
        while (true) {
            //synchronized (this) {
            if (balance < withDrawMoney) {
                System.out.println("余额不足");
                break;
            }
            balance -= withDrawMoney;
            System.out.println("余额减少1000" + Thread.currentThread().getName() + "余额为" + balance);
            //}
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

如果我们不加同步代码块
那么会出现这种情况

余额减少1000Thread-0余额为12000
余额减少1000Thread-2余额为10000
余额减少1000Thread-1余额为11000
余额减少1000Thread-2余额为9000
余额减少1000Thread-1余额为9000
余额减少1000Thread-0余额为9000
余额减少1000Thread-2余额为8000
余额减少1000Thread-0余额为8000
余额减少1000Thread-1余额为8000
余额减少1000Thread-0余额为7000
余额减少1000Thread-2余额为7000
余额减少1000Thread-1余额为7000
余额减少1000Thread-0余额为6000
余额减少1000Thread-2余额为6000
余额减少1000Thread-1余额为6000
余额减少1000Thread-0余额为3000
余额减少1000Thread-1余额为3000
余额减少1000Thread-2余额为3000
余额减少1000Thread-1余额为1000
余额减少1000Thread-2余额为1000
余额减少1000Thread-0余额为1000
余额不足
余额减少1000Thread-2余额为0
余额不足
余额不足

Process finished with exit code 0

很明显他取了钱,但是钱减少的值不是很匹配
把synchronized打开

余额减少1000Thread-0余额为12000
余额减少1000Thread-1余额为11000
余额减少1000Thread-2余额为10000
余额减少1000Thread-2余额为9000
余额减少1000Thread-0余额为8000
余额减少1000Thread-1余额为7000
余额减少1000Thread-0余额为6000
余额减少1000Thread-1余额为5000
余额减少1000Thread-2余额为4000
余额减少1000Thread-0余额为3000
余额减少1000Thread-2余额为2000
余额减少1000Thread-1余额为1000
余额减少1000Thread-1余额为0
余额不足
余额不足
余额不足

Process finished with exit code 0

结束时间:2022-09-08