Java打怪之路----Java高级之多线程

(一)多线程概念

这句话很重要:要想实现多线程,必须在主线程中创建新的线程对象。

1.1并行与并发的区别

并行是多个任务在同一时刻内发生,并发是多个任务在同一时间间隔内发生。

(二)多线程实现

2.1多线程的创建

2.1.1继承于Thread类

多线程的创建,方式一:继承于Thread类

  1. 创建一个继承于Thread类的子类
  2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中
  3. 创建Thread类的子类的对象
  4. 通过此对象调用start()
class MyThread extends Thread{
    
    
    @Override
    public void run() {
    
    
        for(int i=0;i<100;i++){
    
    
            if(i%2==0){
    
    
                System.out.println(Thread.currentThread().getName()+i);
            }
        }
    }
}
public class ThreadFirst {
    
    
    // * 多线程的创建,方式一:继承于Thread类
    // * 1. 创建一个继承于Thread类的子类
    // * 2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中
    // * 3. 创建Thread类的子类的对象
    // * 4. 通过此对象调用start()
    public static void main(String args[]) {
    
    
        MyThread T1 = new MyThread();
        T1.start();

        for (int i = 0; i < 100; i++) {
    
    
            if (i % 2 == 0) {
    
    
                System.out.println(Thread.currentThread().getName() + i);
            }

        }
    }
}

2.1.2实现Runnable接口

方法二:实现Runnable接口
步骤与上面相似,但是不能直接创建线程对象调用start方法。

package com.sgyj.java;
class thread01 implements Runnable{
    
    
    @Override
    public void run() {
    
    
        System.out.println(Thread.currentThread().getName()+"你好");
    }
}

public class ThreadTest1 {
    
    
    public static void main(String args[]){
    
    
        thread01 t1=new thread01();
        Thread t2=new Thread(t1);
        //调用的是thread的run方法,调用的是runnable类型的target的run 即t1的run
        t2.start();
        System.out.println(Thread.currentThread().getName()+"mooo");

    }
}

两种方法的对比
比较创建线程的两种方式。

  • 开发中:优先选择:实现Runnable接口的方式

  • 原因:1. 实现的方式没有类的单继承性的局限性
    2. 实现的方式更适合来处理多个线程有共享数据的情况。

  • 联系:public class Thread implements Runnable

  • 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。

2.1.3实现Callable

步骤:
1、创建一个实现Callable的实现类
2、实现call方法,将此线程需要执行的操作声明在call()中
3、创建Callable接口实现类的对象
4、将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
5、将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
6、获取Callable中call方法的返回值。get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
 * 创建线程的方式三:实现Callable接口。 --- JDK 5.0新增
 *
 *
 * 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
 * 1. call()可以有返回值的。
 * 2. call()可以抛出异常,被外面的操作捕获,获取异常的信息
 * 3. Callable是支持泛型的
 *
 * @author shkstart
 * @create 2019-02-15 下午 6:01
 */
//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;
    }
}


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();

        try {
    
    
            //6.获取Callable中call方法的返回值
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
            Object sum = futureTask.get();
            System.out.println("总和为:" + sum);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } catch (ExecutionException e) {
    
    
            e.printStackTrace();
        }
    }

}

实现Callable有什么好处:
1、call()可以有返回值的。
2、call()可以抛出异常,被外面的操作捕获,获取异常的信息
3、Callable是支持泛型的

2.1.4使用线程池

好处:
1.提高响应速度(减少了创建新线程的时间)
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3.便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止

package com.atguigu.java2;

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

/**
 * 创建线程的方式四:使用线程池
 *
 * 好处:
 * 1.提高响应速度(减少了创建新线程的时间)
 * 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
 * 3.便于线程管理
 *      corePoolSize:核心池的大小
 *      maximumPoolSize:最大线程数
 *      keepAliveTime:线程没有任务时最多保持多长时间后会终止
 *
 *
 * 面试题:创建多线程有几种方式?四种!
 * @author shkstart
 * @create 2019-02-15 下午 6:30
 */

class NumberThread implements Runnable{
    
    

    @Override
    public void run() {
    
    
        for(int i = 0;i <= 100;i++){
    
    
            if(i % 2 == 0){
    
    
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

class NumberThread1 implements Runnable{
    
    

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

public class ThreadPool {
    
    

    public static void main(String[] args) {
    
    
        //1. 提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        //设置线程池的属性
//        System.out.println(service.getClass());
//        service1.setCorePoolSize(15);
//        service1.setKeepAliveTime();


        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread());//适合适用于Runnable
        service.execute(new NumberThread1());//适合适用于Runnable

//        service.submit(Callable callable);//适合使用于Callable
        //3.关闭连接池
        service.shutdown();
    }

}

2.2多线程的常用方法

测试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():已过时。当执行此方法时,强制结束当前线程。
  9. sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。
  10. isAlive():判断当前线程是否存活

方法面试题
sleep()与wait()的异同
相同点:都能让线程进入阻塞状态
不同点:
声明位置不同:Thread类中声明sleep(),object类中声明wait()
调用的要求不用:sleep()可以在任何场景下调用,wait()只能在同步代码快中调用
同步监视器的释放:两个方法如果都定义在同步代码快或者同步方法中,sleep不释放锁,wait会释放锁。

2.3线程的优先级

MAX_PRIORITY :10
MIN _PRIORITY :1
NORM_PRIORITY :5

涉及的方法
getPriority() :返回线程优先值
setPriority(int newPriority) :改变线程的优先级\

(三)线程的生命周期

操作系统得学好!
在这里插入图片描述

(四)线程的安全问题(同步与互斥)

4.1使用(synchronized)代码块

语法:
synchronized(同步监视器){
需要被同步的代码
}

说明:
1.操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。
补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
4.同步的方式,解决了线程的安全问题。—好处
操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。 —局限性

注意:多个线程必须要共用同一把锁。
1、这点比较重要,我在下面有两个程序一个继承Thread,一个实现Runnable ,分别打印了obj锁的内容。

2、解决办法:
实现Runnable 中可以把锁写成this对象继承
继承Thread可以把锁变加static或者写为(当前类.class)

//这个共用同一把锁
class windows implements Runnable{
    
    
    private int ticket=100;
    Object obj=new Object();
    @Override
    public void run() {
    
    
        System.out.println(obj);
        //Object obj=new Object();
        while(true){
    
    
            synchronized (obj){
    
    
            if(ticket>0){
    
    
                //给其他线程一个机会
                try {
    
    
                    Thread.sleep(100);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+":"+"卖票,票号为:"+ticket);
                ticket--;
            }else {
    
    
                break;
            }
            }
        }
    }
}
public class ThreadSynchronized {
    
    
    public static void main(String[] args) {
    
    
        windows w1=new windows();
        Thread t1=new Thread(w1);
        Thread t2=new Thread(w1);
        Thread t3=new Thread(w1);
        t1.setName("售票处1");
        t2.setName("售票处2");
        t3.setName("售票处3");
        t1.start();
        t2.start();
        t3.start();
    }
}
//输出
java.lang.Object@1f38108d
java.lang.Object@1f38108d
java.lang.Object@1f38108d
//这分别创建三个锁
class windowsT extends Thread{
    
    
    private static int ticket=100;
    Object obj=new Object();

    @Override
    public void run() {
    
    
        System.out.println(obj);
        //Object obj=new Object();
        while(true){
    
    
            synchronized (obj){
    
    
                if(ticket>0){
    
    
                    //给其他线程一个机会
                    try {
    
    
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":"+"卖票,票号为:"+ticket);
                    ticket--;
                }else {
    
    
                    break;
                }
            }
        }
    }
}

public class ThreadSynchronizedT {
    
    
    public static void main(String[] args) {
    
    
        //三个对象 使用三把锁,每个对象都创建了一个obj
        windowsT w1=new windowsT();
        windowsT w2=new windowsT();
        windowsT w3=new windowsT();

//        Thread t1=new Thread(w1);
//        Thread t2=new Thread(w1);
//        Thread t3=new Thread(w1);
        w1.setName("售票处1");
        w2.setName("售票处2");
        w3.setName("售票处3");
        w1.start();
        w2.start();
        w3.start();
        System.out.println();
    }
}

//输出,三个锁不一样
java.lang.Object@740759f
java.lang.Object@c7618ab
java.lang.Object@6857aa8a

//可以把锁设置为static 解决继承锁不统一
private static Object obj=new Object();

4.2使用同步方法

在方法前加入Synchronized关键字
1、实现Runnable实现同步方法解决安全问题

class Window3 implements Runnable {
    
    

    private int ticket = 100;

    @Override
    public void run() {
    
    
        while (true) {
    
    

            show();
        }
    }
    private synchronized void show(){
    
    //同步监视器:this
        //synchronized (this){
    
    

            if (ticket > 0) {
    
    

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

                System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);

                ticket--;
            }
        //}
    }
}
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();
    }

}

2、继承Thread实现同步方法解决安全问题

class Window4 extends Thread {
    
    


    private static int ticket = 100;

    @Override
    public void run() {
    
    

        while (true) {
    
    

            show();
        }

    }
    private static synchronized void show(){
    
    //同步监视器:Window4.class
        //private synchronized void show(){ //同步监视器:t1,t2,t3。此种解决方式是错误的
        if (ticket > 0) {
    
    

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

            System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
            ticket--;
        }
    }
}
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();
    }
}

总结:

  1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
  2. 非静态的同步方法,同步监视器是:this
    静态的同步方法,同步监视器是:当前类本身

4.3 使用lock解决线程安全问题

1、从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同
步锁对象来实现同步。同步锁使用Lock对象充当。
2、java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的
工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象
加锁,线程开始访问共享资源之前应先获得Lock对象。
3、ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和
内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以
显式加锁、释放锁

class Window implements Runnable{
    
    

    private int ticket = 100;
    //1.实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();

    @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.调用解锁方法:unlock()
                lock.unlock();
            }

        }
    }
}
public class LockTest {
    
    
    public static void main(String[] args) {
    
    
        Window w = new Window();

        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();
    }
}

注意:如果同步代码块有异常 ,需要用try finally包裹。 finally中是释放锁的代码,一定要记得释放

面试题:synchronized 与 Lock的异同?
相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())

(五)死锁

5.1什么时候会释放锁

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

不会释放锁的操作

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

5.2死锁事例

package com.sgyj.java;

public class DeadLock {
    
    
    //死锁问题
    public static void main(String[] args) {
    
    
        final StringBuffer s1 = new StringBuffer();
        final StringBuffer s2 = new StringBuffer();
        new Thread() {
    
    
            public void run() {
    
    
                synchronized (s1) {
    
    
                    s2.append("A");
                    synchronized (s2) {
    
    
                        s2.append("B");
                        System.out.print(s1);
                        System.out.print(s2);
                    }
                }
            }
        }.start();
        new Thread() {
    
    
            public void run() {
    
    
                synchronized (s2) {
    
    
                    s2.append("C");
                    synchronized (s1) {
    
    
                        s1.append("D");
                        System.out.print(s2);
                        System.out.print(s1);
                    }
                }
            }
        }.start();
    }

}

说明:当以下情况发生时,发生死锁:
线程1拿到s1锁,线程2拿到s2锁,由于同步代码块中的程序还未执行完,两个锁未释放
线程1要拿到s2锁执行剩下代码,线程2要拿到s1锁执行剩下代码
此时两个线程都在等待对方释放锁来执行剩下代码,程序陷入死锁。

(六)线程的通信

6.1线程通信的三个方法

  1. wait():一旦执行此方法,当前线程就会进入阻塞状态
  2. notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
  3. notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。

在线程通信中:
1、wait(),notify(),notifyAll()三个方法都需要在同步代码块或者同步方法中,不能使用lock
2、wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或者同步方法中的同步监视器
3、wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。

6.1线程通信例子

使用两个线程打印 1-100。线程1, 线程2 交替打印

class Number implements Runnable{
    
    
    private int number = 1;
    private Object obj = new Object();
    @Override
    public void run() {
    
    

        while(true){
    
    

            synchronized (obj) {
    
    

                obj.notify();

                if(number <= 100){
    
    

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

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

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

                }else{
    
    
                    break;
                }
            }

        }

    }
}


public class CommunicationTest {
    
    
    public static void main(String[] args) {
    
    
        Number number = new Number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);

        t1.setName("线程1");
        t2.setName("线程2");

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

6.2生产者消费者问题

package com.sgyj.java;

class Clerk{
    
    
    private int product=0;

    public Clerk(int product) {
    
    
        this.product = product;
    }
    public synchronized void get(){
    
    
        if(this.product>0){
    
    
            System.out.println(Thread.currentThread().getName()+":拿走了第"+product+"个产品");
            product--;
            notify();
        }else{
    
    
            System.out.println("请您等一下,正在做");
            try {
    
    
                wait();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }

        }
    }
    public synchronized void put(){
    
    
        if(this.product<20){
    
    
            product++;
            System.out.println(Thread.currentThread().getName()+":开始生产了第"+product+"个产品");
            notify();
        }else{
    
    
            System.out.println("商品已经放不下了,请稍等在做");
            try {
    
    
                wait();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }

        }

    }
}
class Producer extends Thread {
    
    
    private Clerk clerk;

    public Producer(Clerk clerk) {
    
    
        this.clerk = clerk;
    }

    @Override
    public void run() {
    
    
        while (true) {
    
    
            try {
    
    
                sleep(200);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            clerk.put();
        }
    }
}

class Customerr extends Thread {
    
    
    private Clerk clerk;

    public Customerr(Clerk clerk) {
    
    
            this.clerk = clerk;
        }

        @Override
        public void run() {
    
    
            while (true) {
    
    
                try {
    
    
                    sleep(100);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }

                clerk.get();
            }
        }
    }


    public class Product {
    
    
        public static void main(String[] args) {
    
    
            Clerk clerk = new Clerk(10);
            Producer p = new Producer(clerk);
            Customerr c = new Customerr(clerk);
            p.setName("生产者");
            c.setName("消费者");
            p.start();
            c.start();
        }
    }

猜你喜欢

转载自blog.csdn.net/weixin_44020747/article/details/118605369