JAVA基础-高级编程-多线程

多线程

目录
  • 基本概念:程序、进程、线程
  • 线程的创建和使用
  • 线程的生命周期
  • 线程的同步
  • 线程的通信
  • JDK5.0新增的线程创建方式

一、线程的基本概念

  • 程序(program):一段静态代码,静态对象。
  • 进程(process):正在运行的一个程序,一个动态的过程,有生命周期。
  • 线程(thread):一个程序内部的一个执行单元。

二、线程的创建

方式一

继承Thread类,重写run()方法

class MyThread extends Thread{
	@Override
    public void run() {
        //要执行的操作
    }
}

//调用线程 Thread子类对象调用start()方法
public class ThreadTest{
    public static void main(String[] args) {
        MyThread myThread=new MyThread();
        myThread.start();//1.启动线程 2.调用线程的run()方法
    }
}
//匿名创建线程
new Thread(){
            @Override
            public void run() {
                super.run();
            }
 }.start();
注意

不能直接调用run()启动线程,这相当于直接调用类的方法,并不启动线程。
需要开辟几个线程,就new多少个Thread子类对象,调用其父类Thread的start()方法。

Thread常用方法
  • Thread常用方法
  • 1.start()启动当前线程调用当前线程的run()方法
  • 2.run()通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
  • 3.currentThread()静态方法,返回执行当前代码的线程
  • 4.currentThread().getName()获取当前线程的名字
  • 5.setName()设置当前线程的名字
  • 6.yield()释放当前cpu执行权
  • 7.sleep(long millitime)当前线程进入睡眠,需要try catch
  • 8.join()在线程a中调用线程b的join()方法,线程a进入阻塞状态,知道线程b完全执行完之后,线程a才结束阻塞状态,会报异常
  • 9.stop()方法已经过时,执行此方法,强制结束当前线程
  • 10.isAlive()判断当前线程是否存活
线程优先级设置

MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5
涉及的方法:

  • getPriority():返回线程优先值
  • setPriority(int newPriority):改变线程优先级
创建线程方式二
  • 1.创建一个实现了runnable接口的类
  • 2.实现runnable中的抽象方法
  • 3.创建实现类的对象
  • 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  • 5.通过Thread类的对象调用start方法
public class ThreadTest2 implements Runnable{
    @Override
    public void run() {
        //要执行的操作
    }
}
class MThread {
    public static void main(String[] args) {
        Thread thread=new Thread(new ThreadTest2());
        thread.start();//1.启动线程 2.调用runnable的run方法
    }
}
比较创建线程的两种方式

开发中优先选择实现runnable接口的方式

  • 实现方式没有类的单继承性
  • 实现的方式更适合来处理多个线程有共享数据的情况

联系:Thread本身也实现了runnable接口
相同点:都需要重写run()方法,将线程要执行的逻辑声明在方法中

三、线程的生命周期

#####Thread.State类定义了线程的几种状态

  • 新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
  • 就绪:处于新建状态的线程被start()后,将进入线程队列等待cpu时间片,此时他已具备了运行条件,只是没分配到cpu资源
  • 运行:当就绪的线程被调度并获得cpu资源时,便进入运行状态,run()方法定义了线程的操作和功能
  • 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出cpu并临时终止自己的执行,进入阻塞状态
  • 死亡:线程完成了它的全部工作或被线程提前强制性的中止或出现异常导致结束

四、线程的同步

例子

/**
 * 创建三个窗口卖票,总票数100,使用Thread继承方式
 *问题:买票过程中 出现重票 漏票 负票
 *问题出现原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也来操作车票(共享数据被并行操作)
 *如何解决:当一个线程在操作ticket时候,其他线程不能参与进来,直到线程a操作完ticket时,其他县城才可以开始操作
 *即使线程a阻塞也不能被改变
 * @author from z
 * @create 2021-01-31 21:11


 * 在java中我们通过同步机制来解决线程安全问题
 * 方式一:同步代码块
 * synchronized(同步监视器){
 *     //需要同步的代码
 * }
 * 说明:操作共享数据的代码即为需要被同步的代码   注意(不能包含多了也不能包含少了)
 *  共享数据:多个线程共同操作的变量。比如:此代码中ticket
 *  同步监视器:俗称锁。任何一个类的对象都可以充当锁
 * 方式二:
 */
public class WindowTest {
    public static void main(String[] args) {
        Window window=new Window();
        Window window1=new Window();
        Window window2=new Window();
        window.setName("窗口1");
        window2.setName("窗口3");
        window1.setName("窗口2");
        window.start();
        window2.start();
        window1.start();
    }
}

class Window extends Thread{
    private static int ticket=100;//此时依然存在线程安全问题
    @Override
    public void run() {
        while (true){
            if (ticket>0){
                System.out.println(getName()+":卖票,票号为"+ticket);
                ticket--;
            }else {
                break;
            }
        }
    }
}

方式一同步代码块

class Window extends Thread{
private static int ticket=100;
static Object o=new Object();//此时的o充当锁,因为是继承thread类的线程,每次创建线程都会重新new对象,所以锁的对象必须是唯一,必须用static修饰,否则锁不相同
    @Override
    public void run() {
        /*synchronized (o) {//同步代码块,此时放在while之外,
        则会一个线程执行完所有。锁也不能是this,因为用的是继承thread,
        如果用this,则一个对象一个锁,锁不唯一,也会造成不同步*/
          while (true) {
          //synchronized(Window.class){}类也可以充当监视器,类也是一个对象
          synchronized (o){
                if (ticket > 0) {
                    System.out.println(getName() + ":卖票,票号为" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
  • 注意问题:1锁必须唯一
  • 2类 .class也可以充当监视器,类对象也是一个对象
  • 3包裹代码块不能包多了也不能包少了

方式二:同步方法

//如果操作共享数据的代码完整的声明在一个方法中,我们可以使用同步方法
public class WindowTest1 implements Runnable {
    private int ticket=100;
    @Override
    public void run() {
        while (ticket>0){
            sTicket();
        }
    }
   // public static synchronized void sTicket(){
   //继承thread类的监视器为:this,此时就会出错,因为每个线程对象都有一个新的this,所以锁不唯一,此时只能把方法写成静态static,方法为static之后 锁的对象为class唯一
    public synchronized void sTicket(){//同步方法//同步监视器:this 
        if (ticket>0){
            System.out.println(Thread.currentThread().getName()+":卖票,票号为"+ticket);
            ticket--;
        }
    }
}
  • 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器
    在继承Thread继承类创建线程的方式中,可以考虑当前类充当监视器
    当使用同步方法时,synchronized的锁是this对象,通过继承创建的线程需要将同步方法声明为静态static,否则锁不唯一,此时锁为 class对象,锁是同一个。

  • 关于同步方法的总结:

  • 同步方法仍然涉及到监视器,只是不需要我们显示的声明

  • 静态同步方法的监视器是当前类本身,非静态同步方法的监视器是当前对象本身

单例模式-懒汉式线程安全问题

//同步方法
class  Bank{
    private static Bank instance=null;
    private Bank(){}
    //懒汉式单例模式
    public static synchronized Bank getInstance() {
        if (instance==null){
            instance=new Bank();
        }
        return instance;
    }
}
class  Bank{
    private static Bank instance=null;
    private Bank(){}
    //懒汉式单例模式
    public static Bank getInstance() {
       /** //方式一:效率稍差
        synchronized (Bank.class) {
            if (instance==null){
                instance=new Bank();
            }
        }*/

        //方式二 此时如果第一个线程拿到了单例对象,后面未进入的线程通过第一个if就可以直接取的instance对象,不需要再在锁中等待
        //效率快
        if (instance==null) {
            synchronized (Bank.class) {
                if (instance==null){
                    instance=new Bank();
                }
            }
            
        }
        return instance;
    }
}
死锁的问题
/**
 * 演示死锁的问题
 * @author from z
 * @create 2021-02-01 22:23
 */
public class ThreadTest {
    public static void main(String[] args) {
        StringBuffer s1=new StringBuffer();
        StringBuffer s2=new StringBuffer();
        new Thread(){
            @Override
            public void run() {
                synchronized (s1){
                    s1.append("a");
                    s2.append("1");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s2){
                        s1.append("b");
                        s2.append("2");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();
        new Thread(new Runnable(){
            @Override
            public void run() {
                    synchronized (s2){
                        s1.append("c");
                        s2.append("3");
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        synchronized (s1){
                            s1.append("d");
                            s2.append("4");
                            System.out.println(s1);
                            System.out.println(s2);
                        }
                    }
                }
        }).start();
    }
}

说明:不同线程分别占用对方需要的同步资源不放弃,都在等在对方放弃自己需要的同步资源,就形成了线程死锁
1.出现死锁后,不会出现异常,不会出现提示,只是所有线程都处于阻塞状态,无法继续
2.我们使用同步时,要避免出现死锁

lock锁

/**
 * 解决线程安全的方式三:lock锁,jdk5.0新增
 * @author from z
 * @create 2021-02-01 22:40
 */
public class LockTest {
    public static void main(String[] args) {
        Window window=new Window();
        Thread thread1=new Thread(window);
        Thread thread2=new Thread(window);
        Thread thread3=new Thread(window);
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
class Window implements Runnable{
    private int ticket=100;
    private ReentrantLock lock=new ReentrantLock(true);//无参为flase 有参是代表公平锁
    @Override
    public void run() {
        while (true){
            try {
                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 {
                lock.unlock();//调用解锁方法
            }
        }
    }
}

lock锁是ReentrantLock类 需要同步的代码块之前 调用ReentrantLock类的lock()方法 需要try catch 最后一定要finally 因为需要解锁 否则会死锁,解锁调用 unlock()方法

synchronized和lock的异同:二者都可以解决线程安全
synchronized机制在执行完相应的同步代码之后自动的释放同步监视器
lock需要手动的去启动同步同时需要结束同步

五、线程的通信

/**
 * 线程通信例子:使用两个线程打印1-100,线程1,线程交替打印
 * 涉及到的三个方法
 * wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
 *  notify():唤醒被wait的一个线程,如果多个线程被wait 则唤醒优先级最高的
 *  notifyAll():唤醒所有wait的线程
 *  
 *  说明:1.三个方法必须使用在同步代码块或同步方法
 *  2.三个方法的调用者 必须是同步代码块或同步方法中的同步监视器,否者会出现异常
 *  3.三个方法定义在Object类中
 * @author from z
 * @create 2021-02-02 13:35
 */
public class CommunicationTest {


    public static void main(String[] args) {
        Number number=new Number();
        Thread thread1=new Thread(number);
        Thread thread2=new Thread(number);
        thread1.setName("线程1");
        thread2.setName("线程2");
        thread1.start();
        thread2.start();
    }
}
class Number implements Runnable{
    private int number=1;
    @Override
    public void run() {
        while (true){
            synchronized (this) {
                notify();//唤醒线程,当线程1阻塞,线程2拿到锁进来唤醒线程1
                //notifyAll();唤醒多个线程 按优先级
                if (number < 100) {
                    System.out.println(Thread.currentThread().getName() + ":" + number);
                    number++;

                    try {
                        wait();//使得调用如下方法的内容进入阻塞状态此时会释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                } else {
                    break;
                }
            }
        }
    }
}

sleep()和wait()
一旦执行,都可以使当前线程进入阻塞状态
sleep在Thread类中声明,wait在Object类中声明
sleep可以在任何需要场景下调用
wait必须在同步代码块或同步方法中
wait会释放同步监视器

六、新增创建线程方式JDK5.0

方式一:实现Callable接口

/**创建线程方式三:Callable接口
 *
 *如何理解实现callable接口比实现runnable接口更强大:
 *      call()可以有返回值,可以抛异常,callable支持泛型
 * @author from z
 * @create 2021-02-02 14:24
 */
//1.创建一个实现Callable接口的实现类
class  NumThread implements Callable{
    //2.实现call()方法,将线程需要执行的操作声明在call()方法中 有返回值
    @Override
    public Object call() throws Exception {
        int sum=0;
        for (int i = 0; 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构造器中创建线程对象开启线程
        new Thread(futureTask).start();
        try {
            //get()方法返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
            Object o=futureTask.get();
            System.out.println(o);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

方式二:创建线程池

/**创建线程方式四:使用线程池
 * 
 * 好处:
 * 1.提高响应速度(减少创建新线程的时间)
 * 2.降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
 * 3.便于线程管理
 *      corePoolSize:核心池的大小
 *      maximumPoolSize:最大线程数
 *      keepAliveTime:线程没有任务是最多保持多长时间后会终止
 * @author from z
 * @create 2021-02-02 15:48
 */
class NumberThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}
public class ThreadPool {
    public static void main(String[] args) {
        //提供指定数量线程的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor executor=(ThreadPoolExecutor) executorService;
        //设置线程池属性
        executor.setCorePoolSize(15);
        //executor.setKeepAliveTime();

        //执行指定的线程操作。需要提供实现runnable接口或callable接口实现类的对象
        executorService.execute(new NumberThread());//适合使用runnable
        //executorService.submit();//适合使用callable
        //关闭连接池
        executorService.shutdown();
    }
}

创建多线程四种方式!

你可能感兴趣的:(java)