赤赤赤赤辰

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 :: 管理 ::

Java多进程

多线程

每日一考

    1. 对程序,进程,线程的理解
    1. 带吗完成继承Thread的方式创建分线程,并遍历100以内的自然数
    1. 代码完成实现Runnable接口的方法创建分线程,并遍历100以内的自然数
    1. 对比两种创建方式
    1. 说说你对IDEA中Project和Module的理解
    • 不同于Eclipse,IDEA顶级的就是Project,一个Project下创建多个Module。可以理解为一个大的项目分为多个模块,每个模块就是一个Module。
    • 如果要打开多个Project,则需要打开两个IDEA窗口。
    • IDEA中的Project~~~~Eclipse中的workspace

复习

IDEA配置

  • 见文档

程序、进程、线程的理解

  • 程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一 段静态的代码,静态对象。

  • 进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生存在消亡的过程。——生命周期

    • 如:运行中的QQ,运行中的MP3播放器
    • 程序是静态的,进程是动态的
    • 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
  • 线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。 

    • 若一个进程同一时间并行执行多个线程,就是支持多线程的 
    • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开 销小
    • 一个进程中的多个线程共享相同的内存单元/内存地址空间:它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资 源可能就会带来安全的隐患
  • 内存结构:

  • 进程可以细化为多个线程

    • 每个线程,拥有自己独立的栈、程序计数器。
    • 多个线程,共享同一个进程中的结构
  • 使用多线程的优点

    • 背景:以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方 法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?
    • 多线程程序的优点
        1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
        2. 提高计算机系统CPU的利用率
        3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修复。
  • 何时需要多线程 ?

    • 程序需要同时执行两个或多个任务。
    • 程序需要实现一些需要等待的任务时,如用户输入、文件读写 操作、网络操作、搜索等。
    • 需要一些后台运行的程序时。

并行与并发

  • 单核CPU多核CPU的理解
    • 单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程 的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费 才能通过,那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以 把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费)。但是因为CPU时 间单元特别短,因此感觉不出来。
    • 如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
    • 一个Java应用程序java.exe,其实至少有个线程:main()主线程gc() 垃圾回收线程异常处理线程。当然如果发生异常,会影响主线程。
  • 并行并发
    • 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
    • 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。

创建多线程的两种方式

方式一:继承Thread类的方式

package com.shc.java1;

/**
 * 多线程的创建:方式一:继承于Thread类
 * 1. 创建一个继承于Thread类的子类
 * 2. 重写Thread类的run()
 * 3. 创建Thread类的子类的对象
 * 4. 通过此对象调用start
 * 说明问题:
 		① 启动一个线程,必须调用start(),不能用run()方法启动线程。
 		② 如果再启动一个线程,必须重新创建一个Thread类的子类,调用此对象的start()方法。
 * 例子:遍历100以内的所有的偶数
 * @author shc
 * @create 2021-05-16 8:20
 */

//1. 创建一个继承于Thread类的子类
class MyThread extends Thread{
    //2.重写Thread类的run()
    public void run(){
        for(int i=0;i<100;i++){
            if(i%2==0){
                System.out.println(Thread.currentThread().getName()+"----"+i);
            }
        }
    }
}

public class ThreadTest{
    public static void main(String[] args) {
        //3.闯进Thread类的子类的对象
        MyThread t1 = new MyThread();

        //4. 通过此对象调用start()(Thread类中的方法)
        //start()作用:①启动当前线程 ②调用当前线程的run()
        t1.start();
//        t1.run(); //只调用t1.run()不可以,这样只是在主线程里调用了一个方法,并没有开启另一个线程。
        t1.start();//已经start()的进程不能再次start()
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+"*");
        }
        System.out.println("hello");
    }
}

方式二 实现Runnable接口的方式

package com.shc.java1;

/**
 * 创建多线程的方式二:实现Runnable接口
 * 1. 创建一个实现了Runnable接口的类
 * 2. 实现类去实现Runnable中的抽象方法run()
 * 3. 创建实现类的对象
 * 4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
 * 5. 通过Thread类的对象调用start()
 *
 *
 * 比较线程创建的两种方式:
 * 开发中,优先选择Runnable接口的方式
 * 原因:
 * ①实现的方式没有类的单继承的局限性
 * ②实现的方式更适合来处理多个线程共享数据的情况
 *
 * 联系:public class Thread implements Runnable{}
 * 相同点:两种方式都要重写run(),将线程要执行的逻辑声明在run()中。
 目前两种方式,想要启动线程,都是调用Thread类中的start()
 * @author shc
 * @create 2021-05-16 11:35
 */

class YourThread implements Runnable{

    private int x = 10 ;
    @Override
    public void run() {
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+"--"+i);
//            getName();这样创建就不能直接写getName()要通过Thread.currentThread().getName()来获取对象
            //因为当前类没有继承于Thread ,只是实现了一个Runnable接口
        }
    }
}


public class ThreadTest1 {
    public static void main(String[] args) {
        //3. 创建实现类的对象
        YourThread t = new YourThread();

        //4. 将此对象作为参数传递到Thread类的构造器中。创建Thread类的对象
        Thread th = new Thread(t); //t赋给Thread类中的target
        th.setName("线程0");
        //5. 通过Thread类的对象调用start():① 启动线程 ②调用当前线程的run()--->调用了Runnable类型的target的run()
        th.start();

        //再启动一个线程
        Thread th2 = new Thread(t);
        th2.setName("线程1");
        th2.start();
    }

}

线程中的常用方法

package com.shc.exer1;

/**
 * 测试Thread中的常用方法
 * 1. start():启动当前线程,调用当前线程的run()
 * 2. run():通常需要重写Thread类中的此类方法,将创建的线程要执行的去操作声明在此方法中。
 * 3. currentThread():静态方法,返回执行当前代码的进程
 * 4. getName():获取当前线程的名字
 * 5. setName():设置当前线程的名字
 * 6. yield(): 释放当前cpu的执行权
 * 7. join(): 在线程A中调用线程B的join方法,此时线程A进入阻塞状态,
 *              直到线程B完全执行完之后,线程A才结束阻塞状态。
 * 8. stop(): 强制结束当前线程的生命进程 Deprecated
 * 9. sleep()(long millitime): 让当前线程睡眠指定的毫秒,在指定睡眠的时间内处于阻塞状态
 *
 *
 *
 * 线程的优先级:
 * 1.
 * MAX_PRIORITY:10
 * MIN_PRIORITY:1
 * NORM_PRIORITY:5
 * 2.如何获取和设置当前线程的优先级
 * getPriority()
 * setPriority(int p)
 * 线程对象.setPriority(Thread.Max_Priority)
 * 说明:高优先级的线程要抢占低优先级的cpu的执行权,但是只是从概率上讲,高优先级有高概率被执行,并不意味着只有当高优先级执行完以后,低优先级才执行。
 *
 
 * 线程通信:wait()/notify()/notifyAll().此三个方法定义在Object类中而非Thread类中。
 * @author shc
 * @create 2021-05-16 9:11
 */

class MyThread3 extends Thread{
    @Override
    public void run(){
        for(int i=0;i<100;i++){
            if(i%2==0) {
//                Thread.sleep(5); sleep是Thread的方法,Thread是当前MyThread的父类 又因为非静态方法中可以调用静态方法且子类也没有重写seelp方法。
//                所以直接写sleep();即可调用父类的sleep方法
                //只能try-catch 不能throw! 因为我们的run是重写的,而在父类的run()中并没有抛出遗产和,
                // 所以根据重写规则:我们重写的子类也不可以抛出异常
                try {
                    sleep(100); //阻塞1s钟 阻塞结束之后,再等带dpu分配资源
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(getName() + "--" + i);
                //在线程内部 Thread.currentThread().getName()可以直接写getName()
                //因为getName()是继承于父类的。子类的非静态方法可以直接调用父类的静态和非静态方法
                //注:Thread.currentThread()即为this
            }
            if(i%20==0) this.yield(); //释放当先cpu的
            //this等同于Thread.currentName(); 返回当前线程对象
        }
    }

    public MyThread3(String str){
        super(str);
    }

    public MyThread3(){
        super();
    }

}


public class ThreadMethodTest {
    public static void main(String[] args) throws InterruptedException {
        MyThread3 myThread3 = new MyThread3("线程0");
                myThread3.start();

        Thread.currentThread().setName("主线程");

        for(int i=0;i<100;i++){
            if(i%2==0) System.out.println(Thread.currentThread().getName()+"--"+i);
            if(i==20) myThread3.join();
        }
        System.out.println(myThread3.isAlive());
    }
}

补充:线程的分类

  • 用户线程守护线程
  • Java中的线程分为两类:一种是守护线程,一种是用户线程。 
  • 它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
  • 守护线程是用来服务用户线程的,通过在start()方法前调用 thread.setDeamon(true)可以把一个用户线程变成一个守护线程。
  • Java垃圾回收就是一个典型的守护线程。
  • 若JVM中都是守护线程,当前JVM将退出。
  • 形象理解:兔死狗烹,鸟尽弓藏

Thread的生命周期

说明:

  • 生命周期关注两个概念:状态、相应的办法

  • 关注:

    • 状态切换时哪些方法会被自动调用,将我们希望执行的操作写在那些方法里。即状态a-->状态b,那些方法执行了(回调方法)

    • 某个方法主动调用 状态a-->状态b

  • 阻塞:临时状态,不可以作为最终状态

  • 死亡:最终状态

线程的同步机制

    1. 背景

/**
 *  * 例子:创建三个窗口卖票,总票数为100张 使用Runnable接口的方式
 *  1. 问题:卖票过程中,出现了重票,错票。
 *  2. 问题出现原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票
 *  3. 如何解决:当一个线程在操作ticket时,其他线程不能参与进来,直到线程A操作完ticket,其他线程才可以开始操作ticket。
 *  这种情况即使线程A出现了阻塞,也不能被改变。
 *  4. Java中,我们通过同步机制,来解决线程的安全问题
 */
    1. Java解决方法:同步机制(有3种)
/*  方法一:同步代码块
 *  synchronized(同步代码块){
 *      //需要被同步的代码
 *  }
 *  说明:1.操作共享数据的代码,即为需要被同步的代码  -- 不能包多也不能包少!!
 *        2.共享数据:多个数据共同操作的变量
 *        3.同步监视器:俗称锁。任何一个类的对象,都可以充当锁。
 *        要求:多个线程,只能使用一把锁
 *        补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器
 *        4.对于实现Runnable接口的,不用new Object()。用当前对象充当即可。 extends Thread的创建线程方式不可以。
 *
 * 
 * 方法二:同步方法
 * 如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步的。
 * * 关于同步方法的总结:
 * 1. 同步方法仍然涉及到同步监视器,知识不需要我们显示的声明
 * 2. 非静态的同步方法,同步监视器是:this
 *  静态的同步方法,同步监视器是:当前类本身
 *
 *
 * 方式三:Lock锁 ---JDK5.0新增
 * 1. 面试题:synchronized与lock的异同?
 *  相同:二者都可以解决线程安全问题
 *  不同:synchronized机制在执行完相应代码以后,自动的释放同步监视器,Lock需要手动的启动lock(),同时结束时也许压迫手动的实现(unlock())
 *
 *  2. 优先使用顺序
 * Lock --> 同步代码块(已经进入了方法体,分配了相应资源) --> 同步方法
 * (在方法体之外)
 */
  • 利弊
/*  5. 同步的方式:
  *  解决了线程的安全问题 --好处
 *   操作同步代码时,只能有一个县城参与,其他线程等待。相当于是一个单线程的过程,效率低--局限性。
    */

线程安全的单例模式(懒汉式)

package com.shc.exer1;

/**
 * 使用同步设计模式,将单例设计模式中的懒汉式改写为线程安全。
 * @author shc
 * @create 2021-05-18 21:33
 */
public class BankTest {

}

class Bank{
    private static Bank instance = null;
    public Bank(){}
    public static  Bank getInstance(){ //同步静态方法的同步监视器是类本身 public static synchronized Bank getInstance(){}
        // ctrl+alt+t :自动包裹synchronized
        //方式一:效率稍差 制造了synchronized之后还会再次到这里变成单线程,实际上没必要
//         synchronized (Bank.class) { //同步代码块需要显示填入同步锁
//            if(instance==null) instance = new Bank();
//            return instance;
//        }

        //方式二:效率更高
        if(instance==null){
            synchronized (Bank.class) { //同步代码块需要显示填入同步锁
                if(instance==null) instance = new Bank();
            }
        }
        return instance;
        //return语句虽然用了instance,但是没有进行修改也没有进行判断,仅仅就是返回,所以可以不看做共享数据,
        // 不用把这句放入synchronized()
    }
}

  • 面试题:写一个线程安全的单例模式
    • 饿汉式
    • 线程安全的懒汉式

死锁

package com.shc.java1;
//注:很显然sleep会加大线程安全问题发生的可能
//因为sleep时 当前线程阻塞,cpu就极有可能去执行另一条线程
class A {
	public synchronized void foo(B b) { //同步监视器:A类对象:a
		System.out.println("当前线程名: " + Thread.currentThread().getName()
				+ " 进入了A实例的foo方法"); // ①
		try {
			Thread.sleep(200);
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}
		System.out.println("当前线程名: " + Thread.currentThread().getName()
				+ " 企图调用B实例的last方法"); // ③
		b.last();
	}

	public synchronized void last() {//同步监视器:A类对象:a
		System.out.println("进入了A类的last方法内部");
	}
}

class B {
	public synchronized void bar(A a) {//同步监视器:B类对象:b
		System.out.println("当前线程名: " + Thread.currentThread().getName()
				+ " 进入了B实例的bar方法"); // ②
		try {
			Thread.sleep(200);
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}
		System.out.println("当前线程名: " + Thread.currentThread().getName()
				+ " 企图调用A实例的last方法"); // ④
		a.last();
	}
	//同步监视器:B类对象:b
	public synchronized void last() {
		System.out.println("进入了B类的last方法内部");
	}
}

public class DeadLock implements Runnable {
	A a = new A();
	B b = new B();

	public void init() {
		Thread.currentThread().setName("主线程");
		// 调用a对象的foo方法
		a.foo(b);
		System.out.println("进入了主线程之后");
	}

	public void run() {
		Thread.currentThread().setName("副线程");
		// 调用b对象的bar方法
		b.bar(a);
		System.out.println("进入了副线程之后");
	}

	public static void main(String[] args) {
		DeadLock dl = new DeadLock();
		new Thread(dl).start();
		dl.init();
	}
}

线程安全范例

  • 例子:使用同步代码块解决继承自Thread类的线程问题。
package com.shc.java1;
/**
 * 例子:使用同步代码块解决继承自Thread类的线程问题。
 *
 * 同步代码块解决 注意监视器是用同一个对象!!
 * 说明:在使用Thread创建多线程的方式中,慎用this充当同步监视器,可以考虑使用当前类充当同步监视器
 * @author shc
 * @create 2021-05-16 11:21
 */

class Window extends Thread{
    private static int ticket = 500; //注意这里是static
    private static Object obj = new Object();
    @Override
    public void run(){
        while(true){
//            synchronized(obj){
//            synchronized (this){错误的方式 this1 this2 this3 是三个对象
//            Class c = Window.class
              synchronized(Window.class){ //Window.class 类也可以作为对象出现 Window.class 只会加载一次,是唯一的
                if(ticket>0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket--);
                }else{
                    System.out.println(this.getName()+"票没了");
                    break;
                }
            }
        }
    }
}

public class WindowTest {
    public static void main(String[] args) {
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();
        w2.start();
        w1.start();
        w3.start();
        /*
         * 为什么控制台上显示的不是递减的?
         * 实际上抢到票的顺序是递减的,只是有的没有及时显示在控制台上。
         */
    }
}
  • 例子:使用同步代码块解决Runnable接口的线程问题
/*  例子:使用同步代码块解决Runnable接口的线程问题
 * @author shc
 * @create 2021-05-17 18:17
 */
public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w1 = new Window1();
        //由于加入的是同一个对象w1 所以三个线程做同一件事
        Thread t1 = new Thread(w1); t1.setName("线程1"); t1.start();
        Thread t2 = new Thread(w1); t2.setName("线程2"); t2.start();
        Thread t3 = new Thread(w1); t3.setName("线程3"); t3.start();
    }
}


class Window1 implements Runnable{

    public static int[] cnt = new int[510];
    Object obj = new Object();
    private int num = 500; //不加static 多个窗口用的也是同一个num 因为只造了一个对象w1
    @Override
    public void run(){
        while(true){
//            if(cnt[num]==1) System.out.println(Thread.currentThread().getName()+" WRONG!: "+num);
//            cnt[num]++;
//            System.out.println(Thread.currentThread().getName()+"卖出第"+num--+"张票");
//            if(num==0){
//                System.out.println(Thread.currentThread().getName()+"票卖完了");
//                break;
//            }
            //如果while(true)放入synchronized(){}中 则会出现一个线程买完全部票的情况
            synchronized(this){ // synchronized(obj)  this就是当前Window类的对象
                if(cnt[num]==1) System.out.println(Thread.currentThread().getName()+" WRONG!: "+num);
                cnt[num]++;
                if(num>0){
                    //加上sleep之后,num出现0和-1的概率大大提升。
                    // 因为到当前线程走到这里会阻塞,cpu执行这里切换到另一个线程概率大大变大
                    //num=1时,线程1走到这里阻塞,cpu去搞线程2,线程2到这里阻塞,纯朴去搞线程3,线程三走到这里阻塞
                    //而后继续执行,线程1sout(num=1) 线程2sout(num=0) 线程3sout(num=-1)
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"卖出第"+num--+"张票");
                }else{
                    break;
                }
            }
        }
    }
}
  • 例子:使用同步方法解决Runnable接口的线程问题
package com.shc.java1;

/**
 * 例子:使用同步方法解决Runnable接口的线程问题
 *
 * 关于同步方法的总结:
 * 1. 同步方法仍然涉及到同步监视器,知识不需要我们显示的声明
 * 2. 非静态的同步方法,同步监视器是:this
 *  静态的同步方法,同步监视器是:当前类本身
 * @author shc
 * @create 2021-05-17 18:17
 */
public class WindowTest2 {
    public static void main(String[] args) {
        Window2 w1 = new Window2();
        //由于加入的是同一个对象w1 所以三个线程做同一件事
        Thread t1 = new Thread(w1); t1.setName("线程1"); t1.start();
        Thread t2 = new Thread(w1); t2.setName("线程2"); t2.start();
        Thread t3 = new Thread(w1); t3.setName("线程3"); t3.start();
    }
}

class Window2 implements Runnable{
    private int num = 1000; //不加static 多个窗口用的也是同一个num 因为只造了一个对象w1
//    @Override
////    public synchronized void run(){ 不对 synchronized包多了
//    public void run(){
//        while(true){
//            if(num>0){
//                System.out.println(Thread.currentThread().getName()+"卖出第"+num--+"张票");
//            }else{
//                break;
//            }
//        }
//    }
    @Override
//    public synchronized void run(){ 不对 synchronized包多了
    public void run(){
        while(true){
            show();
            if(num==0) break;
        }
    }
    private synchronized void show() { //同步监视器即为this
//        synchronized (this){
        if(num>0){
                System.out.println(Thread.currentThread().getName()+"卖出第"+num--+"张票");
        }
    }
}
  • 例子:使用同步方法解决继承自Thread类的线程问题
package com.shc.java1;

/**
 * 例子:使用同步方法解决继承自Thread类的线程问题
 * @author shc
 * @create 2021-05-18 20:33
 */
public class WindowTest3 {
    public static void main(String[] args) {
        Window3 t1 = new Window3();
        Window3 t2 = new Window3();
        Window3 t3 = new Window3();
        t1.start();
        t2.start();
        t3.start();
    }
}

class Window3 extends Thread{
    private static int ticket = 500; //注意这里是static
    @Override
    public void run(){
        while(true){
            show();
            if(ticket<=0) break;
        }
    }
    private  static synchronized void show(){ //同步监视器 Window.class
//    private synchronized void show() { 同步监视器 t1 t2 t3 此种解决方式是错误的。
        if(ticket>0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket--);
        }else{
            System.out.println(Thread.currentThread().getName()+"票没了"); //静态方法中不可以直接调用本类的非静态方法
        }
    }
}

线程安全练习

package com.shc.exer1;

/**
 * 银行有一个账户。
 * 有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。
 *
 * 分析:
 * 1. 是否是多线程问题? 是,两个储户线程
 * 2. 是否有共享数据? 有,账户
 * 3. 是否有线程安全问题? 有
 * 4. 需要考虑如何解决线程安全问题? 同步机制:有三种方式
 * @author shc
 * @create 2021-05-18 23:20
 */
public class AccountTest {
    public static void main(String[] args) {
        //如何让两个顾客共用一个账户? 单独建一个账户类
        Account a = new Account(0);
        Customer c1 = new Customer(a);
        Customer c2 = new Customer(a);
        c1.start();
        c2.start();
    }
}

class Account{
    private int balance;
    public Account(int balance){
        this.balance = balance;
    }
    //对于代码块或者非静态方法采用同步锁 同步监视器是该类对象即this 但对于继承Thread的方式来说,我们说过要慎用this,因为继承Thread的方式可能会有多个this,为什么这样说?
    //因为我们要同步的方法很有可能是在我们造的Thread里的,而同步这个方法用的则是this,很显然这么多线程,this不是一个
    public synchronized  void deposit(int x){ //同步监视器是this。虽然本例是通过Thread继承实现多线程,但是this是唯一的。因为this是账户。
        if(x>0){
            balance+=x;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"存钱成功 余额为"+balance);
        }
    }
}

class Customer extends Thread{
    private Account acct;

    public Customer(Account acct) { this.acct = acct; }
    @Override
    public void run(){
        for(int i=0;i<3;i++){
            acct.deposit(1000);
        }
    }
}

线程通信

    1. 线程通信涉及到的三个方法
/* wait();一旦执行此方法,当前线程就进入阻塞状态,同时释放监视器
 * notify();一旦执行此方法,就会唤醒被wait的线程,如果有多个线程被wait,则会唤醒优先级高的线程
 * notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
    1. 说明
 /* 说明:
 * 1. wait(),notify(),notifyAll() 三个方法必须使用在同步代码块或者同步方法中
 * 2. 1是为什么? 因为wait,notify,notifyAll这三个方法,必须是同步代码块或者同步方法的同步监视器来调用,否则会java.lang.IllegalMonitorStateException
 * 3. wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中的。(为了让任意对象都可以充当同步锁)
 * 4. wait的线程必须要人为显示唤醒
    1. 面试题
* 面试题:
 * sleep()和wait()方法的异同?
 * 1. 相同点:一旦执行方法,都可以使得当前线程进入阻塞状态
 * 2. 不同点:
 *      1)两个方法定义的位置不同,Thread类中定义sleep(),Object类中定义wait()
 *      2)调用的要求:sleep()可以在任意需要的场景下调用,wait()必须使用在同步代码块或者同步方法中
 *      3)关于是否释放同步监视器:如果两个方法都是必须用在同步代码块或同步方法中,sleep()不一定使用在其中。
    1. 小结释放锁的操作

 当前线程的同步方法、同步代码块执行结束。

 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、 该方法的继续执行。

 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导 致异常结束。

 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线 程暂停,并释放锁。

    1. 小结不会释放锁的操作

线程执行同步代码块或同步方法时,程序调用Thread.sleep()、 Thread.yield()方法暂停当前线程的执行

线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程 挂起,该线程不会释放锁(同步监视器)。 应尽量避免使用suspend()和resume()来控制线程

JDK5.0 新增的创建线程的方式

  • 新增方式一:实现Callable接口

  • 说明:

* 如和理解实现Callable接口的方式创建多线程比实现Runnable接口创建的多线程强大。
 * 1. call()方法可以有返回值
 * 2. call()方法可以抛出异常,被外面的操作捕获,获取异常信息
 * 3. Callable()支持泛型
package com.shc.java2;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 创建线程的方式三:实现Callable接口。---JDK5.0新增
 *
 * 如和理解实现Callable接口的方式创建多线程比实现Runnable接口创建的多线程强大。
 * 1. call()方法可以有返回值
 * 2. call()方法可以抛出异常,被外面的操作捕获,获取异常信息
 * 3. Callable()支持泛型
 * @author shc
 * @create 2021-05-19 19:35
 */
//1. 创建一个实现了Callable接口的实现类
public class ThreadNew {
    public static void main(String[] args) {
        //3. 创建Callable接口实现类的对象
        NumThread numThread = new NumThread();
        //4. 将此Callable接口实现类的对象作为传递到FutureTask的构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(numThread);
        //5. 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start();

        new Thread(futureTask).start();//启动线程
        //如果我们对call方法的返回值不感兴趣,则不用进行下面的get方法
        try {
            //6.get():获取Callable中的call方法的返回值
            //get方法的返回值,即为futureTask构造器的参数:Callable接口的实现类对象重写的call()方法的返回值
            Object sum = futureTask.get();
            System.out.println(sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class NumThread implements Callable{
//  2. 实现Call方法,将此线程需要执行的操作声明在call方法中
    @Override
    public Object call() throws Exception{ //因为call方法在接口中可以抛异常,所以咱们在这里也可以抛出异常
        int res=0;
        for(int i=0;i<100;i++){
            if(i%2==0) res+=i;
        }
        return res;//int类型不是Object的子类 但是有自动装箱操作
    }
}
  • 新增方式二:使用线程池
  • 好处
 * 好处:
 * 1. 提高响应速度(减少了创建新线程的时间)
 * 2. 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
 * 3. 便于线程管理
 *  corePoolSize:核心池的大小
 *  maximumPoolSize:最大线程数
 *  keepAliveTime:线程没有任务时最多保持多长时间后会终止
package com.shc.java2;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
/**
 * 创建线程的方式四:使用连接池
 *
 * 好处:
 * 1. 提高响应速度(减少了创建新线程的时间)
 * 2. 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
 * 3. 便于线程管理
 *  corePoolSize:核心池的大小
 *  maximumPoolSize:最大线程数
 *  keepAliveTime:线程没有任务时最多保持多长时间后会终止
 *
 *  面试题:创建多线程有几种方式? 4种!
 * @author shc
 * @create 2021-05-19 20:26
 */

class NumThreadx implements Runnable{
    @Override
    public void run(){
        for(int i=0;i<100;i++){
            if(i%2==0) System.out.println(i);
        }
    }
}

public class ThreadPool {
    public static void main(String[] args) {
        //1. 提供指定数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10); //ExecutorService是一个接口
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service; //强转 Executor是一个比较"窄"的接口 能操作的属性较少,操作不到我们要进行修改的属性

        //设置线程池的属性
        System.out.println(service.getClass());//对象.getClass() 获取当前对象是哪个类的
        service1.setCorePoolSize(12);
//        service1.setKeepAliveTime()

        //2. 执行指定线程的操作,需要提供实现Runnable接口或Callable接口的实现类对象
        service.execute(new NumThreadx()); //适用于Runnable
//        System.out.println(((ThreadPoolExecutor) service).getPoolSize()); 1
//        service.submit(Callable callable); 适用于Callable
        service.shutdown(); //关闭连接池
    }
}

每天一考

    1. 画图说明线程的生命周期,以及各个状态切换使用到的方法
    • 状态切换--->导致方法执行:将自己在状态切换想要执行的动作写在状态变换时自动调用的方法里
    • 方法--->导致切换:start,join,wait,sleep.....
    1. 同步代码块中涉及到同步监视器共享数据,阐述对同步监视器和共享数据的理解,以及注意点。
synchronized(同步监视器){
    //操作共享数据的代码 (不能包多了,也不能包少了)
}
    1. sleep()wait()的区别
    • 定义位置
    • 使用位置
    • sleep自动结束,wait需要唤醒
    • sleep不释放锁,wait释放锁
    1. 写一个线程安全的懒汉式
    1. 创建多线程有哪几种方式?
    • 继承Thread类
    • 实现Runnable接口
    • 实现Callable接口
    • 线程池(响应速度提高了,提高了资源的重用率,便于管理)
posted on 2021-03-08 19:57  咕噜辰  阅读(923)  评论(2)    收藏  举报