多线程

多线程概述

日常生活中很多事都是可以同时进行的,例如:人可以同时进行呼吸、血液循环、思考问题等活动。

多线程是指一个应用程序中有多条并发执行的线索,每条线索都被称作为一个线程,它们会交替执行,彼此之间可以进行通信。

多线程之主线程概念:JVM启动后,会有一个执行路径(线程)从main方法开始的,一直执行到main方法结束,这个线程在Java中称之为主线程。

线程对象调用 run方法和调用start方法区别?

 调用run方法不开启线程。仅是对象调用方法。

 调用start开启线程,并让jvm调用run方法在开启的线程中执行。

Thread

创建线程

    方式一继承Thread

创建线程的步骤

  1. 定义一个类继承Thread类。
  2. 重写run方法。
  3. 创建子类对象

调用start方法,开启线程并让线程执行,同时还会告诉JVM去调用run方法。

 

public class Demo {
    public static void main(String[] args) {
        //创建自定义线程对象
        MyThread myThread = new MyThread();
        //开启新线程    
        myThread.start();
        while (true) {// 死循环
            System.out.println("Main 方法在运行");
        }
    }
}

方式二 实现Runnable接口

  1. 定义类实现Runnable接口
  2. 覆盖接口中的run方法
  3. 创建Thread类的对象
  4. Runnable接口的子类对象作为参数传递给Thread类的构造函数
  5. 调用Thread类的start方法开启线程

方式三实现callable接口:此方法不常用就不做探究了,还有别的开启线程的方法,我最常用的是上面俩种。

线程安全问题

个人理解:线程安全就是说多线程访问同一代码,不会产生不确定的结果,而不安全就是多个线程对同一个对象进行操作,对象本身会产生不确定的结果。

举个例子:你去食堂打饭(你是一个线程),你来晚了,就剩最后一份糖醋排骨了(不纠结喜不喜欢吃的问题)你正在向阿姨要最后一份排骨,这时候另外一个人(另外一个线程)过来插你队也要糖醋排骨。这时候阿姨按理来说应该把排骨给你,可是这个插你队的是阿姨侄子,阿姨可以给你也可以给他侄子(就看公私分不分明)。这样阿姨就处在不确定的情况下。而线程安全就是直接强行规定死了,只能你这个线程打完饭,后面的人才能去打饭,只有等你释放了食堂阿姨这个资源之后下面的人才能去进行(打饭)操作。你不打完饭释放资源,下面的不可能去操作食堂阿姨这个角色。(个人理解有问题欢迎━(*`∀´*)ノ亻!指正)

不喜欢吃糖醋排骨我再来一个:买票

多线程模拟火车站的售票窗口,每一个线程表示一个售票窗口,共出售100张票

ublic class Demo2 {
    public static void main(String[] args) {
        TicketWindow window = new TicketWindow();
        new Thread(window,"窗口1").start();
        new Thread(window,"窗口2").start();
        new Thread(window,"窗口3").start();
        new Thread(window,"窗口4").start();
    }
}
class TicketWindow implements Runnable{
    private int tickets = 100;
    @Override
    public void run() {
        while(true){
            if(tickets > 0){
                try {
                    //睡眠让问题更容易“暴露”
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String threadName = Thread.currentThread().getName();
                System.out.println(threadName + "正在发售第"+tickets--+"张票");
            }else{
                break;
            }
        }
    }
}
View Code

上面的例子,由于多个线程共用了同一个资源(票),会造成线程安全问题,票售出了-1张

错误截图

线程安全问题都是由全局变量静态变量以及共享的变量引起的。若每个线程中对全局变量、静态变量以及共享的变量只有读操作,那么这个全局变量是线程安全的;

若有多个线程同时执行写操作(修改),都需要考虑线程同步,否则就可能发生线程安全问题。

Java中提供了线程同步机制,它能够解决上述的线程安全问题。

方式一:同步代码块 

方式二:同步方法

同步代码块: 在代码块声明上,加上synchronized

synchronized (锁对象(lock、被锁对象)) {
    可能会产生线程安全问题的代码
}

同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。

 lock 是一个锁对象,是同步代码块的关键,锁对象可以是任意类型的对象,是多个线程共享的锁对必须是唯一的,“任意”说的是共享锁对象的类型。所以,锁对象的创建代码不能放到run()方法中,否则每个线程都会创建一个新的对象,每个锁都有自己的标志,那就没有意义了

被锁对象:表示如果当前线程访问"被锁对象"synchronized的代码块时,其它线程不能访问此代码块,另外,也不能访问"被锁对象"中的其它synchronized的代码块;

同步的好处:解决了多线程并发访问的问题,使多线程并发访问的共享数据能够保持一致性

同步的弊端:使当前的对象的工作效率降低;因为要处理线程同步的问题;

同步方法:在方法声明上加上synchronized

public synchronized void method(){
       可能会产生线程安全问题的代码
}


同步方法的也是一样锁住同步的代码,但是锁对象的是Runable实现类对象,也就是this,谁调用方法,就是谁,在这里就是创建的run对象

第三种方法:Lock接口

java.util.concurrent.locks.Lock
Lock接口中的方法:
void lock():获取锁
void unlock():释放锁

使用方法:
1、在Runable实现类的成员变量创建一个ReentrantLock对象
2、在可能产生线程安全问题的代码前该对象调用lock方法获取锁
3、在可能产生线程安全问题的代码后该对象调用unlock方法获取锁

public class xiancheng implements Runnable {
    int ticket = 100;//火车票100张
    Object o = new Object();//创建一个锁对象
    ReentrantLock r = new ReentrantLock();
    // java除了使用关键字synchronized外,还可以使用ReentrantLock实现独占锁的功能。
    // 而且ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景
    // 在Runable实现类的成员变量创建一个ReentrantLock对象

    @Override
    public void run() {

        while (true) {
            if (ticket > 0) {
                try {
                    r.lock();//在可能出现线程安全的地方上锁
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // unlock方法放在finally里面,无论程序是否有出现异常,该方法都会执行,也就是都会释放锁
                    r.unlock();// 3、在可能产生线程安全问题的代码后该对象调用unlock方法获取锁

                }
            }

        }
    }
}
原文地址:https://www.cnblogs.com/cmm123/p/13207727.html