JAVA高级学习笔记-多线程

多线程:

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

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

--程序是静态的,进程是动态的

--进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

线程(thread):进程可以进一步细化为线程,是一个程序内部的一条执行路径。

--若一个进程同一时间并行执行多个线程,就是支持多线程的

--线程作为调度和执行的单位,每个线程拥有独立的运行栈程序计数器(pc),线程切换的开销小

--一个进程中的多个线程共享相同的内存单元/内存地址空间-->他们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源就可能会带来安全隐患。


单核CPU和多核CPU的理解:

>单核CPU,是一种假的多线程,因为一个时间单元内,只能执行一个线程的任务。

>如果是多核的话,才能发挥多线程的效率

>一个JAVA应用程序java.exe,其实至少有三个线程:main()主线程、gc()垃圾回收线程、异常处理线程。如果发生异常,会影响主线程。

并行与并发:

>并行:多个CPU同时执行多个任务。

>并发:一个CPU(采用时间片)同时执行多个任务。

使用多线程的优点:

1、提高应用程序的响应。对图形化界面更有意义,可增强用户体验。

2、提高计算机系统CPU利用率。

3、改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。

何时需要多线程:

程序需要同时执行两个或多个任务

程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等

需要一些后台运行的程序时

线程的调度:

调度策略:时间片-抢占式

JAVA的调度方法:

>同优先级线程组成先进先出队列(先到先服务),使用时间片策略

>对高优先级,使用优先调度的抢占式策略

线程的优先级等级:

高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。

 

多线程创建的两种方式:

多线程的创建,方式一:继承于Thread类
* 1、创建一个继承于Thread类中的子类
* 2、重写Thread类的run方法-->将此线程执行的操作声明在run方法中
* 3、创建Thread类的子类的对象
* 4、通过此对象调用start()
创建多线程的方式二:实现Runnable接口
* 1、创建一个实现了Runnable接口的类
* 2、实现类去实现Runnable中的抽象方法:run()
* 3、创建实现类的对象
* 4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5、通过Thread类的对象调用start()
*
比较创建线程的两种方式:

*开发中优先选择实现Runnable接口的方式
* 原因:
* 1、实现的方式没有类的单继承的局限性
* 2、实现的方式更适合来处理多个线程有共享数据的情况
* 联系: public class Thread implements Runnable
* 相同点:两种方式都需要重写run方法,将线程要执行的逻辑声明在run()方法中

线程的生命周期:

JAVA高级学习笔记-多线程_第1张图片

 

 

线程的同步:**

解决线程安全问题的三种方法:

方式一:同步代码块
 * synchronized(同步监视器){
 *     //需要被同步的代码
 * }
 * 说明:操作共享数据的代码,即为需要被同步的代码-->不能包多了,也不能包少了,
 * 共享数据:多个线程共同操作的变量。比如:tickets
 * 同步监视器:俗称锁。任何一个类的对象,都可以充当锁。obj
 *                    要求:多个线程必须公用一把锁
 *                    简单方法:用当前对象对充当
 *        补充:在实现runnable接口创建多线程的方式中,可以考虑使用this充当同步监视器。
 *
方式二:同步方法
 *如果操作共享数据的代码完整的声明在一个方法中,可以考虑使用this充当同步监视器
 *
 * 5、同步的方式,解决了线程的安全问题。--好处
 *    操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。--局限性

方式三:LOCK锁----JDK5.0新增
 *
 *1、面试题:synchronized 和  LOCK锁的异同?
 * 相同点:
 * 不同:synchronized机制在执行完相同的同步代码之后,自动释放同步监视器
 *       lock需要手动启动同步(lock()),同时结束同步也需要手动的实现(unlock())
 * 2、面试题:如何解决线程安全问题?有几种方式:两种/三种
 * synchronized方法、synchronized代码块、lock代码块锁

利弊:

同步的方式,解决了线程安全的问题。--好处

操作同步代码时,只能一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。--坏处

 

package com.atguigu.java;

/**
 * 例子:创建三个窗口买票,总票数为100张,使用Runnable接口的方式
 * 存在线程安全问题,待解决
 * 1、问题一:买票过程中出现了重票、错票
 * 2、原因:当某个线程来操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票
 * 3、解决:当一个线程在操作共享数据的时候,其他线程不能参与进来,直到这个线程操作完此数据时,其他线程才可以开始操作此数据,即使这个线程出现了阻塞,其他线程也不能因此参与进来
 * 4、在JAVA中,通过同步机制来解决线程安全问题
 * 方式一:同步代码块
 * synchronized(同步监视器){
 *     //需要被同步的代码
 * }
 * 说明:操作共享数据的代码,即为需要被同步的代码-->不能包多了,也不能包少了,
 * 共享数据:多个线程共同操作的变量。比如:tickets
 * 同步监视器:俗称锁。任何一个类的对象,都可以充当锁。obj
 *                    要求:多个线程必须公用一把锁
 *                    简单方法:用当前对象对充当
 *        补充:在实现runnable接口创建多线程的方式中,可以考虑使用this充当同步监视器。
 *
 * 方式二:同步方法
 *如果操作共享数据的代码完整的声明在一个方法中,可以考虑使用this充当同步监视器
 *
 * 5、同步的方式,解决了线程的安全问题。--好处
 *    操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。--局限性
 *
 * @author gaixinyu
 * @create 2021-03-24-21:21
 */
public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w=new Window1();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

class Window1 implements Runnable{

    private int tickes=100;//不需static
    Object obj=new Object();

    @Override
    public void run() {
        while (true){
            synchronized (this){//同步代码块,此时的this是唯一的windows1的对象,这是最方便的,不需要new一个object
                if(tickes>0){

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName()+":买票,票号为:"+tickes);
                    tickes--;
                }else {
                    break;
                }
            }
        }
    }
}
package com.atguigu.java;

/**
 * 例子:创建三个窗口买票,总票数为100张,使用继承的方式
 * 存在线程安全问题,待解决
 * 使用同步代码块来解决继承Thread类的线程安全问题:
 *说明:在继承Thread类创建多线程的方式中,慎用this充当同步监视起,考虑使用当前类充当同步监视器。
 * @author gaixinyu
 * @create 2021-03-24-20:53
 */
public class WindowTest2 {
    public static void main(String[] args) {
        Window2 t1=new Window2();
        Window2 t2=new Window2();
        Window2 t3=new Window2();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

class Window2 extends Thread{

    private static int tickets=100;//static避免重复
//    Object obj=new Object();//这时候有三个锁
        private static Object obj=new Object();
        //因为造了多个对象
    @Override
    public void run() {
        while(true){
            //正确的
            synchronized (obj){
                //类也可以充当。类也是对象
                //synchronized (Window2.class){//Window2只会加载一次,所以唯一
                //错误的,因为此时的this代表着t1,t2,t3三个对象,不唯一
//                synchronized (this){
                if(tickets>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName()+":买票,票号为:"+tickets);
                    tickets--;
                }else {
                    break;
                }
            }
        }
    }
}

 

package com.atguigu.java;

/**
 * 使用同步方法解决实现Runnable接口的线程安全问题
 *
 * 关于同步方法的总结:
 * 1、同步方法仍然涉及到同步监视器,只是不需要我们显式的声明
 * 2、非静态的同步方法,同步监视器是:this
 * 3、静态的同步方法,同步监视器是:当前类本身
 *
 * @author gaixinyu
 * @create 2021-03-25-15:53
 */
public class WindowTest3 {
    public static void main(String[] args) {
        Window3 w=new Window3();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

class Window3 implements Runnable{

    private int tickes=100;//不需static
    Object obj=new Object();

    @Override
    public void run() {
        while (true){
            show();
        }
    }
    //同步监视器:this
    private synchronized void show(){//完整的操作共享数据的方法
        if(tickes>0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName()+":买票,票号为:"+tickes);
            tickes--;
        }
    }
}
package com.atguigu.java;

/**
 * 使用同步方法来处理继承Thread类的线程安全问题:
 * @author gaixinyu
 * @create 2021-03-25-15:59
 */
public class WindowTest4 {
    public static void main(String[] args) {
        Window4 t1=new Window4();
        Window4 t2=new Window4();
        Window4 t3=new Window4();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

class Window4 extends Thread{

    private static int tickets=100;//static避免重复
    @Override
    public void run() {
        while(true){
            show();
        }
    }
    private static synchronized void show(){//同步监视器:Window4.class
        //private synchronized void show(){//此种解决方式是错误的
            if(tickets>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+":买票,票号为:"+tickets);
                tickets--;
            }
    }
}

 

package com.atguigu.java1;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 解决线程安全问题的方式三:LOCK锁----JDK5.0新增
 *
 *1、面试题:synchronized 和  LOCK锁的异同?
 * 相同点:
 * 不同:synchronized机制在执行完相同的同步代码之后,自动释放同步监视器
 *       lock需要手动启动同步(lock()),同时结束同步也需要手动的实现(unlock())
 * 2、面试题:如何解决线程安全问题?有几种方式:两种/三种
 * synchronized方法、synchronized代码块、lock代码块锁
 *
 * @author gaixinyu
 * @create 2021-03-25-18:58
 */
class Windows implements Runnable{
    private int ticket=100;

    //1、实例化ReentrantLock
    private ReentrantLock lock=new ReentrantLock(true);

    @Override
    public void run() {
        while (true){
            try {
                //2、调用lock方法
                lock.lock();

                if(ticket>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName()+":售票,票号为:"+ticket);
                    ticket--;
                }else {
                    break;
                }
            }finally {
                //3、调用解锁方法:
                lock.unlock();
            }
        }
    }
}
public class LockTest {
    public static void main(String[] args) {
        Windows w=new Windows();
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();



    }
}

 

使用同步机制将单例模式中的懒汉式改写成线程安全的:

package com.atguigu.java1;

/**
 * 使用同步机制将单例模式中的懒汉式改写成线程安全的
 * @author gaixinyu
 * @create 2021-03-25-16:09
 */
public class BankTest {
}
class Bank{

    public Bank() {

    }

    private static Bank instance=null;

    public static synchronized Bank getInstance(){
        //方式一:效率稍差
//        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;
    }
}

 

线程的死锁问题:

JAVA高级学习笔记-多线程_第2张图片

 

线程的通信:

三个方法:

 

package com.atguigu.java2;

/**
 * 线程通信的例子
 * 涉及到的方法:
 * wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
 * notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的线程
 * notifyAll():一旦执行此方法,就会唤醒被wait的所有线程。
 *
 * 说明:
 * 1、wait()、notify()、notifyAll()三个方法都必须使用在同步方法或同步代码块中
 * 2、三个方法的调用者必须是同步代码块或者同步方法中的同步监视器,否则,会出现异常
 * 3、三个方法是定义在java.lang.object类中的
 * @author gaixinyu
 * @create 2021-03-25-19:51
 */
public class Communication {
    public static void main(String[] args) {
        Number number=new Number();//注意不同

        Thread n1 = new Thread(number);//
        Thread n2 = new Thread(number);//

        n1.setName("线程1");
        n2.setName("线程2");

        n1.start();
        n2.start();
    }

}

class Number implements Runnable{
    private int number=1;
    @Override
    public void run() {
        while (true){
            synchronized (this) {//同步代码块
                //唤醒
                notify();//互相唤醒

                if(number<=100) {

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName()+"-"+number);
                    number++;

                    try {//使得调用如下wait方法的线程进入阻塞状态
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }


                }else {
                    break;
                }
            }
        }

    }
}

面试题:

1、面试题:synchronized 和  LOCK锁的异同?

相同点:
不同:synchronized机制在执行完相同的同步代码之后,自动释放同步监视器
           lock需要手动启动同步(lock()),同时结束同步也需要手动的实现(unlock())

2、面试题:如何解决线程安全问题?有几种方式:两种/三种
synchronized方法、synchronized代码块、lock代码块锁

3、面试题:sleep()方法和wait()方法的异同:高频

>相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态

>不同点:调用的范围/要求不同:

1、两个方法声明的位置不同:Thread类中声明sleep()方法,Object类中声明wait()方法

2、sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块或同步方法中

3、关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁

4、面试题:三种解决线程安全问题的使用顺序:

Lock(位置比较灵活) --->同步代码块(已经进入方法题,分配了相应的资源)-->同步方法(在方法体之外)

小结释放锁:

JAVA高级学习笔记-多线程_第3张图片

小结不会释放锁:

JAVA高级学习笔记-多线程_第4张图片

创建多线程的另外两种方法:

一:

package com.atguigu.java2;

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

/**
 * 创建线程的方式三:实现Callable接口。---JDK 5.0新增
 *
 * 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程的方式更强大
 * 1、call()可以有返回值的
 * 2、call()可以抛出异常,被外边的操作捕获,获取异常的信息
 * 3、Callable是支持泛型的
 * @author gaixinyu
 * @create 2021-03-25-21:04
 */
public class ThreadNew1 {
    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();

        try {
            //6、获取Callable中call方法的返回值
            //get()方法的返回值,即为FutureTask构造器参数Callable实现类重写call()的返回值
            Integer sum=futureTask.get();
            System.out.println("总和为"+sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
//1、创建一个实现Callable的实现类
class NumThread implements Callable{
    //2、实现Call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Object call() throws Exception {
        int sum=0;
        for (int i = 1; i <=100 ; i++) {
            if(i%2==0){
                System.out.println(i);
                sum+=i;
            }
        }
        return sum;
    }
}

二:

JAVA高级学习笔记-多线程_第5张图片

JAVA高级学习笔记-多线程_第6张图片

package com.atguigu.java2;

import java.lang.reflect.Executable;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 使用线程池的方式创建线程
 * @author gaixinyu
 * @create 2021-03-25-21:30
 */
public class ThreadNew2 {
    public static void main(String[] args) {
        //1、提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
//        service.submit();//适合于Callable
        //2、执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread());//适合于Runnable
        service.execute(new NumberThread1());//适合于Runnable
        //3、关闭连接池
        service.shutdown();
    }
}
class NumberThread implements Runnable{

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

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

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(java,多线程)