1 – 线程

1.1 – 进程

进程就是正在运行中的程序进程是驻留在内存中的)

1.2 – 线程

线程就是进程中的单个顺序控制流,也可以理解成是一条执行路径

1.3 –java中多线程的生命周期

 就绪状态就绪状态的线程又叫做可运行状态表示当前线程具有抢夺CPU时间片的权力(CPU时间片就是执行权)。当一个线程抢夺到CPU时间片之后,就开始执行run方法run方法的开始执行标志着线程进入运行状态

运行状态run方法的开始执行标志着这个线程进入运行状态,当之前占有的CPU时间片用完之后,会重新回到就绪状态继续抢夺CPU时间片,当再次抢到CPU时间之后,会重新进入run方法接着上一次代码继续往下执行。

阻塞状态:当一个线程遇到阻塞事件例如接收用户键盘输入,或者sleep方法等,此时线程会进入阻塞状态,阻塞状态的线程会放弃之前占有的CPU时间片。之前的时间片没了需要再次回到就绪状态抢夺CPU时间片。

锁池:在这里共享对象对象锁线程进入锁池找共享对象对象锁的时候,会释放之前占有CPU时间片,有可能找到了,有可能找到,没找到则在锁池中等待,如果找到了会进入就绪状态继续抢夺CPU时间片。(这个进入锁池,可以理解为一种阻塞状态)

1.4 – 多线程的实现方式(一)

/**
 * @author Mr.乐
 * @Description
 */
public class Demo01 {
    public static void main(String[] args) {
        //创建线程
        MyThread t01 = new MyThread();
        MyThread t02 = new MyThread();
        MyThread t03 = new MyThread("线程03");

        //开启线程
//        t01.run();
//        t02.run();
//        t03.run();
        // 不会启动线程,不会分配新的分支栈。(这种方式就是单线程。)
        // start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
        // 这段代码任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。
        // 启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。
        // run方法在分支栈的栈底部main方法在主栈的栈底部。runmain是平级的。
        t01.start();
        t02.start();
        t03.start();
        //设置线程名(补救的设置线程名的方式)
        t01.setName("线程01");
        t02.setName("线程02");
        //设置主线名称
        Thread.currentThread().setName("主线程");
        for (int i = 0; i < 50; i++) {
            //Thread.currentThread() 获取当前正在执行线程的对象
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}
class MyThread extends Thread{
    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }

    //run方法是每个线程运行过程中都必须执行的方法
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(this.getName() + ":" + i);
        }
    }
}

此处最重要的为start()方法。单纯调用run()方法不会启动线程,不会分配新的分支栈。

start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。线程就启动成功了。

启动成功的线程会自动调用run方法(由JVM线程调度机制来运作的),并且run方法在分支栈的栈底部(压栈)。

run方法在分支栈的栈底部main方法在主栈的栈底部。run和main是平级的。

单纯使用run()方法是不能多线程并发的。

1.5 – 设置和获取线程名

1.6 – 多线程的实现方式(二)

/**
 * @author Mr.乐
 * @Description
 */
public class Demo02 {
    public static void main(String[] args) {
        MyRunnable myRun = new MyRunnable();//将一个任务取出来,让多个线程共同去执行
        //封装线程对象
        Thread t01 = new Thread(myRun, "线程01");
        Thread t02 = new Thread(myRun, "线程02");
        Thread t03 = new Thread(myRun, "线程03");
        //开启线程
        t01.start();
        t02.start();
        t03.start();
        //通过匿名内部类的方式创建线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + " - " + i);
                }
            }
        },"线程04").start();
    }
}
//自定义线程类,实现Runnable接口
//这并不是一个线程类,是一个可运行的类,它还不是一个线程。
class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + " - " + i);
        }
    }
}

1.7 – 多线程的实现方式(三)

实现Callable接口( java.util.concurrent.FutureTask; /JUC包下的,属于java并发包,老JDK中没有这个包。新特性。)

1、自定义一个MyCallable类来实现Callable接口

2、在MyCallable类中重写call()方法

3、创建FutureTask,Thread对象,并把MyCallable对象作为FutureTask构造方法参数传递进去,把FutureTask对象传递给Thread对象。

4、启动线程

        这种方式的优点:可以获取到线程的执行结果

        这种方式的缺点:效率比较低,在获取t线程执行结果时候当前线程受阻塞,效率较低。

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
 * @author Mr.乐
 * @Description  线程实现的第三种方式
 */
public class Demo04 {
    public static void main(String[] args) throws Exception {

        // 第一步创建一个“未来任务类”对象。
        // 参数非常重要,需要给一个Callable接口实现类对象。
        FutureTask task = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception { // call()方法就相当于run方法。只不过这个返回值
                // 线程执行一个任务,执行之后可能会有一个执行结果
                // 模拟执行
                System.out.println("call method begin");
                Thread.sleep(1000 * 10);
                System.out.println("call method end!");
                int a = 100;
                int b = 200;
                return a + b; //自动装箱(300结果变成Integer)
            }
        });

        // 创建线程对象
        Thread t = new Thread(task);

        // 启动线程
        t.start();

        // 这里是main方法,这是在主线程中。
        // 在主线程中,怎么获取t线程的返回结果?
        // get()方法的执行会导致“当前线程阻塞”
        Object obj = task.get();
        System.out.println("线程执行结果:" + obj);
        // main方法这里的程序要想执行必须等待get()方法的结束
        // 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
        // 另一个线程执行是需要时间的。
        System.out.println("hello world!");
    }
}

1.8 -线程控制

方法名 说明
void yield() 使当前线程让步,重新回到争夺CPU执行权的队列
static void sleep(long ms) 使当前正在执行的线程停留指定毫秒
void join() 等死(等待当前线程销毁后,再继续执行其它的线程)
void interrupt() 终止线程睡眠

1.8.1 –sleep()方法 (谁执行谁就是当前线程)

/**
 * @author Mr.乐
 * @Description 线程睡眠
 */
public class DemoSleep {
    public static void main(String[] args) {
        //        创建线程
        MyThread1 t01 = new MyThread1("黄固");
        MyThread1 t02 = new MyThread1("欧阳锋");
        MyThread1 t03 = new MyThread1("段智兴");
        MyThread1 t04 = new MyThread1("洪七公");

        //开启线程
        t01.start();
        t02.start();
        t03.start();
        t04.start();
    }
}
class MyThread1 extends Thread{
    public MyThread1() {
    }

    public MyThread1(String name) {
        super(name);
    }

    @Override
    // 重点:run()当中异常不能throws,只能try catch
    // 因为run()方法在父类没有抛出任何异常子类不能比父类抛出更多的异常。
    public void run() {
        for (int i = 1; i < 50; i++) {
            System.out.println(this.getName() + "正在打出第 - " + i + "招");

            try {
                Thread.sleep(500);//让当前正在执行的线程睡眠指定毫秒数
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

        注意:run()方法中的异常只能try catch,因为父类没有抛出异常,子类不能抛出父类更多的异常。 

1.8.2 -interrupt()方法和stop()方法

/**
 * @author Mr.乐
 * @Description  终止线程
 */
public class DemoInterrupt {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable2());
        t.setName("t");
        t.start();
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 终断t线程的睡眠(这种终断睡眠的方式依靠了java异常处理机制。)
        t.interrupt();
//        t.stop(); //强行终止线程
        //缺点:容易损坏数据  线程没有保存数据容易丢失
    }
}
class MyRunnable2 implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "---> begin");
        try {
            // 睡眠1年
            Thread.sleep(1000 * 60 * 60 * 24 * 365);
        } catch (InterruptedException e) {
//            e.printStackTrace();
        }
        //1年之后才会执行这里
        System.out.println(Thread.currentThread().getName() + "---> end");

    }
}

 1.8.3 –合理终止线程

        做一个boolean类型标记

/**
 * @author Mr.乐
 * @Description
 */
public class DemoSleep02 {
    public static void main(String[] args) {
        MyRunable4 r = new MyRunable4();
        Thread t = new Thread(r);
        t.setName("t");
        t.start();

        // 模拟5秒
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 终止线程
        // 你想要什么时候终止t的执行,那么你把标记修改false,就结束了。
        r.run = false;
    }
}
class MyRunable4 implements Runnable {

    // 打一个布尔标记
    boolean run = true;

    @Override
    public void run() {
        for (int i = 0; i < 10; i++){
            if(run){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else{
                // return结束了,你在结束之前还有什么保存的。
                // 在这里可以保存呀。
                //save....
                //终止当前线程
                return;
            }
        }
    }
}

1.8.4 – yield()

暂停当前正在执行的线程对象,并执行其他线程
yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用
yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
注意:在回到就绪之后,有可能还会再次抢到。

/**
 * @author Mr.乐
 * @Description 线程让位
 */
public class DemoYield {
    public static void main(String[] args) {
        //创建线程
        MyThread5 t01 = new MyThread5("线程01");
        MyThread5 t02 = new MyThread5("线程02");
        MyThread5 t03 = new MyThread5("线程03");

        //开启线程
        t01.start();
        t02.start();
        t03.start();
    }
}
class MyThread5 extends Thread{
    public MyThread5() {
    }

    public MyThread5(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            if(30 == i){
                Thread.yield();//当循i环到30时,让线程让步
                //1、回到抢占队列中,又争夺到了执行权
                //2、回到抢占队列中,没有争夺到执行权
            }
            System.out.println(this.getName() + ":" + i);
        }
    }
}

 1.8.5 –join()

1.9 – 线程的调度

/**
 * @author Mr.乐
 * @Description  线程的调度
 */
public class Demo07 {
    public static void main(String[] args) {
        //创建线程
        MyThread t01 = new MyThread("线程01");
        MyThread t02 = new MyThread("线程02");
        MyThread t03 = new MyThread("线程03");
        //获取线程优先级,默认是5
//        System.out.println(t01.getPriority());
//        System.out.println(t02.getPriority());
//        System.out.println(t03.getPriority());
        //设置线程优先级
        t01.setPriority(Thread.MIN_PRIORITY); //低  - 理论上来讲,最后完成
        t02.setPriority(Thread.NORM_PRIORITY); //中
        t03.setPriority(Thread.MAX_PRIORITY); //高  - 理论上来讲,最先完成
        //开启线程
        t01.start();
        t02.start();
        t03.start();
    }
}

2 – 线程的安全

2.1 – 数据安全问题

2.1.1 –变量对线程安全影响

 实例变量:在堆中。

静态变量:在方法区。

局部变量:在栈中

    以上三大变量中:
        局部变量永远都不会存在线程安全问题
        因为局部变量不共享。(一个线程一个栈。)
        局部变量栈中。所以局部变量永远都不会共享。

     实例变量在堆中,堆只有1个。
    静态变量在方法区中,方法区只有1个。
    堆和方法区都是多线程共享的,所以可能在线程安全问题

    局部变量+常量:不会有线程安全问题
    成员变量:可能会有线程安全问题

 2.1.2 -模拟线程安全问题

public class Test {
    public static void main(String[] args) {
        // 创建账户对象(只创建1个)
       Account act = new Account("act-001", 10000);
        // 创建两个线程
        Thread t1 = new AccountThread(act);
        Thread t2 = new AccountThread(act);
        // 设置name
        t1.setName("t1");
        t2.setName("t2");
        // 启动线程取款
        t1.start();
        t2.start();
        //t1对act-001取款5000.0成功,余额5000.0
        //t2对act-001取款5000.0成功,余额5000.0
    }
}
----------------------------------------------------
public class AccountThread extends Thread {

    // 两个线程必须共享同一个账户对象。
    private Account act;

    // 通过构造方法传递过来账户对象
    public AccountThread(Account act) {
        this.act = act;
    }

    public void run(){
        // run方法的执行表示取款操作。
        // 假设取款5000
        double money = 5000;
        // 取款
        // 多线程并发执行这个方法。
        act.withdraw(money);

        System.out.println(Thread.currentThread().getName() + "对"+act.getActno()+"取款"+money+"成功,余额" + act.getBalance());
    }
}
------------------------------------------------
/**
 * @author Mr.乐
 * @Description
 */
public class Account {
        // 账号
        private String actno;
        // 余额
        private double balance;

    public Account() {
        }

    public Account(String actno, double balance) {
            this.actno = actno;
            this.balance = balance;
        }

        public String getActno() {
            return actno;
        }

        public void setActno(String actno) {
            this.actno = actno;
        }

        public double getBalance() {
            return balance;
        }

        public void setBalance(double balance) {
            this.balance = balance;
        }
    //取款的方法
    public void withdraw(double money){
        // t1和t2并发这个方法。。。。(t1和t2是两个栈。两个栈操作堆中同一个对象。)
        // 取款之前的余额
        double before = this.getBalance(); // 10000
        // 取款之后的余额
        double after = before - money;
        // 在这里模拟一下网络延迟,100%会出现问题
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 更新余额
        // 思考:t1执行到这里了,但还没有来得及执行这行代码,t2线程进来withdraw方法了。此时一定出问题。
        this.setBalance(after);
    }
}

2.2 – 线程同步的利弊

2.3 –编程模型 

异步编程模型
            线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,
            谁也不需要等谁,这种编程模型叫做:异步编程模型
            其实就是:多线程并发(效率较高。)

同步编程模型
            线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行
            结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,
            两个线程之间发生了等待关系,这就是同步编程模型
            效率较低。线程排队执行。

2.4 -线程同步

2.4.1 -线程同步方式

        同步语句块:synchronized(this){方法体}  (synchronized括号后的数据必须是多线程共享的数据,才能达到多线程排队)

//        以下代码的执行原理?
//        1、假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。
//        2、假设t1先执行了,遇到synchronized,这个时候自动找“后面共享对象”的对象锁,
//        找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是
//        占有这把锁的。直到同步代码块代码结束,这把锁才会释放。
//        3、假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面
//        共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,
//        直到t1把同步代码块执行结束了,t1会归还这把锁,此时t2终于等到这把锁,然后
//        t2占有这把锁之后,进入同步代码块执行程序。
//
//        这样就达到了线程排队执行。
//        这里需要注意的是:这个共享对象一定要选好了。这个共享对象一定是你需要排队
//        执行的这些线程对象所共享的。
        synchronized (this){
            double before = this.getBalance();
            double after = before - money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBalance(after);
        }

        普通同步方法:修饰符 synchronized 返回值类型 方法名(形参列表){方法体}

        synchronized出现在实例方法上,一定锁的是this(此方法)。不能是其他的对象了。 所以这种方式不灵活。

        另外还有一个缺点:synchronized出现在实例方法上, 表示整个方法体都需要同步,可能会无故扩大同步的 范围,导致程序的执行效率降低。所以这种方式不常用

    public synchronized void withdraw(double money){
        double before = this.getBalance(); // 10000
        // 取款之后的余额
        double after = before - money;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 更新余额
        this.setBalance(after);

        静态同步方法:修饰符 synchronized static 返回值类型 方法名(形参列表){方法体}

 (静态方法中不能使用this)表示找类锁。类锁永远只有1把。

 2.5 –如何解决线程安全问题

    是一上来就选择线程同步吗?synchronized
        不是,synchronized会让程序的执行效率降低,用户体验不好。
        系统用户吞吐量降低。用户体验差。在不得已的情况下再选择
        线程同步机制。

    第一种方案:尽量使用局部变量代替“实例变量和静态变量”。  

    第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样
    实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,
    对象不共享,就没有数据安全问题了。)

    第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候
    就只能选择synchronized了。线程同步机制。

2.6 -Lock

        应用场景不同,不一定要在同一个方法中进行解锁,如果在当前的方法体内部没有满足解锁需求时,可以将lock引用传递到下一个方法中,当满足解锁需求时进行解锁操作,方法比较灵活。

   private Lock lock = new ReentrantLock();//定义Lock类型的锁
   public  void withdraw(double money){
        // t1和t2并发这个方法。。。。(t1和t2是两个栈。两个栈操作堆中同一个对象。)
        // 取款之前的余额
        lock.lock();//上锁
        double before = this.getBalance(); // 10000
        // 取款之后的余额
        double after = before - money;
        // 在这里模拟一下网络延迟,100%会出现问题
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 更新余额
        // 思考:t1执行到这里了,但还没有来得及执行这行代码,t2线程进来withdraw方法了。此时一定出问题。
        this.setBalance(after);
        lock.unlock();//解锁
    }

 2.7 –死锁

形成原因

当两个线程或者多个线程互相锁定的情况就叫死锁

避免死锁的原则

顺序上锁,反向解锁,不要回头

/**
 * @author Mr.乐
 * @Description 死锁
 */
public class DeadLock {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();

        // t1和t2两个线程共享o1,o2
        Thread t1 = new MyThread1(o1,o2);
        Thread t2 = new MyThread2(o1,o2);

        t1.start();
        t2.start();
    }
}

class MyThread1 extends Thread{
    Object o1;
    Object o2;
    public MyThread1(Object o1,Object o2){
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run(){
        synchronized (o1){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o2){

            }
        }
    }
}

class MyThread2 extends Thread {
    Object o1;
    Object o2;
    public MyThread2(Object o1,Object o2){
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run(){
        synchronized (o2){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1){

            }
        }
    }
}

 2.8 –守护线程

java语言中线程分为两大类:
            一类是:用户线程
            一类是:守护线程(后台线程)
            其中具有代表性的就是:垃圾回收线程(守护线程)。

        守护线程的特点:
            一般守护线程是一个死循环,所有的用户线程只要结束,
            守护线程自动结束。
        
        注意:主线程main方法是一个用户线程。

        守护线程用在什么地方呢?
            每天00:00的时候系统数据自动备份
            这个需要使用到定时器,并且我们可以将定时器设置为守护线程。
            一直在那里看着,每到00:00的时候就备份一次。所有的用户线程
            如果结束了,守护线程自动退出,没有必要进行数据备份了。

public class Demo09 {
    public static void main(String[] args) {
        Thread t = new BakDataThread();
        t.setName("备份数据的线程");

        // 启动线程之前,将线程设置为守护线程
        t.setDaemon(true);
        t.start();
        // 主线程:主线程是用户线程
        for(int i = 0; i < 10; i++){
            System.out.println(Thread.currentThread().getName() + "--->" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class BakDataThread extends Thread {
    public void run(){
        int i = 0;
        // 即使是死循环,但由于该线程是守护者,当用户线程结束,守护线程自动终止。
        while(true){
            System.out.println(Thread.currentThread().getName() + "--->" + (++i));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

3 –定时

定时器的作用
            间隔特定的时间,执行特定的程序。

java类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。
            不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持
             定时任务的。 

import java.util.Timer;
import java.util.TimerTask;

/**
 * @author Mr.乐
 * @Description 定时类
 */
public class DemoTimer {
    public static void main(String[] args) {
        Timer timer = new Timer();//创建Timer定时器类的对象

        //匿名内部timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("我被执行了!~");
                System.gc();//告诉JVM运行完毕,可以把我回收
            }
        },5000);
    }
}

3.1 -线程与定时器执行轨迹不同

线程与定时器之间互不抢占CPU时间片

import java.util.Timer;
import java.util.TimerTask;

/**
 * @author Mr.乐
 * @Description 线程与定时器的执行轨迹不同
 */
public class DemoTimer {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + "<--->" + i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        //定时器实现
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + "---" + i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.gc();//将编程垃圾的定时器进行回收
            }
        },5000);
    }
}

4 –生产者消费者

4.1 –关于Object类中的wait和notify方法。

        第一:wait和notify方法不是线程对象的方法,是java中任何一个java对象
        都有的方法,因为这两个方式是Object类中自带的。
            wait方法和notify方法不是通过线程对象调用
            不是这样的:t.wait(),也不是这样的:t.notify()..不对。

 第二:wait()方法作用:
        Object o = new Object();
        o.wait();          

          表示:
            让正在o对象上活动的线程进入等待状态,无期限等待
            直到被唤醒为止。
            o.wait();方法的调用,会让“当前线程(正在o对象上
            活动的线程)”进入等待状态。

 第三:notify()方法作用:
        Object o = new Object();
        o.notify();

          表示:
              唤醒正在o对象上等待的线程。
          还有一个notifyAll()方法:
              这个方法是唤醒o对象上处于等待的所有线程。

  注意:wait方法和notify方法需要建立在synchronized线程同步的基础之上。

  重点:o.wait()方法会让正在o对象上活动的当前线程进入等待状态,并且释放之前占有的o对象的锁;

             o.notify()方法只会通知,不会释放之前占有的o对象的锁。

4.2 –生产者消费者模式

 生产者消费者模式是并发、多线程编程中经典设计模式,通过wait和notifyAll方法实现。

例如生产满了,就不能继续生产了,必须让消费线程进行消费

           消费完了,就不能继续消费了,必须让生产线程进行生产

消费生产者共享的仓库,就为多线程共享的了,所以需要考虑仓库的线程安全问题。

wait方法和notify方法建立在线程同步的基础之上。因为多线程要同时操作一个仓库有线程安全问题。

wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁。

notify方法作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。

例1:

/**
 * @author Mr.乐
 * @Description 生产者和消费者模式
 */
public class wait_notify {
    public static void main(String[] args) {
        Box box = new Box();//实例化奶箱类

        Producer producer = new Producer(box);//生产者对象
        Customer customer = new Customer(box);//消费者对象

        Thread tp = new Thread(producer);//创建生产者线程
        Thread tc = new Thread(customer);//创建消费者线程

        //启动线程
        tp.start();
        tc.start();
    }
}
//奶箱类
class Box{
    private int milk;  //放入奶箱中的第几瓶牛奶
    private boolean state = false; //默认奶箱为空

    /**
     * 生产生产(放)牛奶
     * @param milk  第几瓶
     */
    public synchronized void put(int milk){
        if(state){  //true表示奶箱中有牛奶
            try {
                wait();  //等待,需要有人唤醒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //没有牛奶,需要生产牛奶
        this.milk = milk;
        System.out.println("王五将第" + this.milk + "瓶你牛奶放进了奶箱中");
        this.state = true;//将奶箱状态调整成有牛奶
        notifyAll();//唤醒全部正在等待的线程
    }
    /**
     * 消费者取牛奶
     */
    public synchronized void get(){
        if(!state){  //true表示奶箱中有牛奶
            try {
                wait();  //等待,需要有人唤醒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //有牛奶,需要取牛奶
        System.out.println("张三将第" + this.milk + "瓶牛奶拿走补了身体!");
        this.state = false;//将奶箱状态改变成空
        notifyAll();//唤醒全部正在等待的线程
    }

}
//生产者类
class Producer implements Runnable{
    private Box b;

    public Producer(Box b){
        this.b = b;
    }

    @Override
    public void run() {
        for (int i = 1; i < 8; i++) {
            b.put(i);//放牛奶,放几瓶
        }
    }
}
//消费者类
class Customer implements Runnable{
    private Box b;

    public Customer(Box b){
        this.b = b;
    }

    @Override
    public void run() {
        while (true){
            b.get();//消费者取牛奶
        }
    }
}

例2: 

import java.util.ArrayList;
import java.util.List;

/**
 * @author Mr.乐
 * @Description 生产者和消费者模式02
 */
public class ThreadTest16 {
    public static void main(String[] args) {
        // 创建1个仓库对象,共享的。
        List list = new ArrayList();
        // 创建两个线程对象
        // 生产者线程
        Thread t1 = new Thread(new Producer(list));
        // 消费者线程
        Thread t2 = new Thread(new Consumer(list));

        t1.setName("生产者线程");
        t2.setName("消费者线程");

        t1.start();
        t2.start();
    }
}

// 生产线程
class Producer implements Runnable {
    // 仓库
    private List list;

    public Producer(List list) {
        this.list = list;
    }
    @Override
    public void run() {
        // 一直生产(使用死循环来模拟一直生产)
        while(true){
            // 给仓库对象list加锁。
            synchronized (list){
                if(list.size() > 0){ // 大于0,说明仓库中已经有1个元素了。
                    try {
                        // 当前线程进入等待状态,并且释放Producer之前占有的list集合的锁。
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 程序能够执行到这里说明仓库是空的,可以生产
                Object obj = new Object();
                list.add(obj);
                System.out.println(Thread.currentThread().getName() + "--->" + obj);
                // 唤醒消费者进行消费
                list.notifyAll();
            }
        }
    }
}

// 消费线程
class Consumer implements Runnable {
    // 仓库
    private List list;

    public Consumer(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        // 一直消费
        while(true){
            synchronized (list) {
                if(list.size() == 0){
                    try {
                        // 仓库已经空了。
                        // 消费者线程等待,释放掉list集合的锁
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 程序能够执行到此处说明库中有数据,进行消费。
                Object obj = list.remove(0);
                System.out.println(Thread.currentThread().getName() + "--->" + obj);
                // 唤醒生产者生产。
                list.notifyAll();
            }
        }
    }
}

5 -线程池

5.1 – 概念

线程池就是首先创建一些线程,他们集合称之为线程池。线程池在系统启动时会创建大量空闲线程,程序将一个任务传递给线程池,线程池就会启动一条线程来执行这个任务,执行结束后线程不会销毁(死亡),而是再次返回到线程池中成为空闲状态,等待执行下一个任务。

5.2 – 线程池的工作机制

在线程池的编程模式下,任务是分配给整个线程池的,而不是直接提交某个线程,线程池拿到任务后,就会在内部寻找是否有空闲的线程,如果有,则将任务交个某个空闲线程。

5.3 – 使用线程池的原因

多线程运行时,系统不断创建和销毁新的线程,成本非常高,会过度的消耗系统资源,从而可能导致系统资源崩溃,使用线程池就是最好的选择。

5.4 – 可重用线程

方法名 说明
Executors.newCacheThreadPoll(); 创建一个可缓存的线程池
execute(Runnable run) 启动线程池中的线程
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author Mr.乐
 * @Description  可重用线程池
 */
public class ExecutorsTest {
    public static void main(String[] args) {
        //创建线程池
        ExecutorService threadPoll = Executors.newCachedThreadPool();

        for (int i = 0; i < 10; i++) {
            //如果不睡眠,那么第一个执行完的线程无法及时成为空闲线程,那么线程池就会让一个新的线程执行
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //每次循环都会开启一个线程
            threadPoll.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "正在被执行!~");
                }
            });
        }
        threadPoll.shutdown();//关闭线程池
        //线程池是无限大,当执行当前任务时,上一个任务已经完成,会重复执行上一个任务的线程,而不是每次使用新的线程
    }
}

6 -多线程并发的线程安全问题

        了解了线程池,接下来底层讲一下多线程并发的安全问题。 

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

/**
 * @author Mr.乐
 * @Description  并发安全
 */
public class MyTest {
    //定义静态变量
    static int a=0;
    static int count=2000;
    public static void main(String[] args) {
       //创建线程池
        ExecutorService service = Executors.newCachedThreadPool();
        for(int i=0;i<count;i++){
            service.execute(new Runnable() {
                @Override
                public void run() {
                    a++;
                }
            });
        }
        关闭线程池
        service.shutdown();
        System.out.println(a);
        //1987
    }
}

         以上程序运行并没有达到预期的2000,此处多线程并发,a共享,所以没达到2000

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

/**
 * @author Mr.乐
 * @Description  并发安全
 */
public class MyTest {
    static int a=0;
    static int count=2000;
    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newCachedThreadPool();
        //闭锁 在一些条件下可放开  参数:加多少把锁
        CountDownLatch countDownLatch=new CountDownLatch(count);
        for(int i=0;i<count;i++){
            service.execute(new Runnable() {
                @Override
                public void run() {
                    a++;
                    //解一把锁
                    countDownLatch.countDown();
                }
            });
        }
        service.shutdown();
        //会进入阻塞状态  什么时候把锁全解了   阻塞状态才会解除
        countDownLatch.await();
        System.out.println(a);
        //1987
    }
}

         此处所用的加锁方法也没有实现预期效果

6.1 -CPU多级缓存

        打开任务管理器,在性能中可查看CPU的多级缓存

        程序进程中的数据,都在内存中存着。 而CPU缓存,是为了解决内存没有CPU快的问题。当一个数据需要CPU修改,而内存无法及时给CPU返回数据,就会拖慢CPU的运行速度。所以有了CPU缓存

        当CPU需要在内存中读数据时,在时间局部性上(不久的将来)还得读此数据。,将此数据放在CPU缓存中。

        当用到内存中数据(例如 a)时,而数据旁边的数据(例:static int a=0;   int b=0;   用a时b为旁边的数据)在空间局部性上,会用到相邻的数据(例如 b),CPU也会读到b,将b数据放在CPU缓存中。

        当CPU读取数据时,会让CPU缓存同步内存中的数据。然后CPU缓存中的数据再交给CPU去修改。当CPU修改完后,会把修改的数据传给CPU缓存(此时CPU不需要等待),再由CPU缓存传给内存 。 

当CPU 01将数据修改完后,CPU缓存01还没有将数据传给内存,CPU缓存02读到了a,此时a的值为0。

        以下为线程安全的两种方式。

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

/**
 * @author Mr.乐
 * @Description  并发安全 synchronized
 */
public class MyTest {
    static int a=0;
    static int count=2000;
    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newCachedThreadPool();
        //闭锁 在一些条件下可放开  参数:加多少把锁
        CountDownLatch countDownLatch=new CountDownLatch(count);
        for(int i=0;i<count;i++){
            service.execute(new Runnable() {
                @Override
                public void run() {
                    synchronized (MyTest.class) {
                        a++;
                        //解一把锁
                        countDownLatch.countDown();
                    }
                }
            });
        }
        service.shutdown();
        //会进入阻塞状态  什么时候把锁全解了   阻塞状态才会解除
        countDownLatch.await();
        System.out.println(a);
        //2000
    }
}
-------------------------------------------------------------------
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * @author Mr.乐
 * @Description  并发安全 synchronized
 */
public class MyTest {
    static int a=0;
    static int count=2000;
    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newCachedThreadPool();
        //闭锁 在一些条件下可放开  参数:加多少把锁
        CountDownLatch countDownLatch=new CountDownLatch(count);
        //信号量
        Semaphore semaphore=new Semaphore(1);
        for(int i=0;i<count;i++){
            service.execute(new Runnable() {
                @Override
                public void run() {
                    try {  //拿走一个信号
                        semaphore.acquire();
                        a++;
                        //解一把锁
                        countDownLatch.countDown();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        //释放信号
                        semaphore.release();
                    }
                }
            });
        }
        service.shutdown();
        //会进入阻塞状态  什么时候把锁全解了   阻塞状态才会解除
        countDownLatch.await();
        System.out.println(a);
        //2000
    }
}

7 –总结

        以上就是我对多线程初级的所有总结,希望对大家有所帮助。

原文地址:https://blog.csdn.net/zdl66/article/details/126297036

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_42558.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注