其他分享
首页 > 其他分享> > day26_多线程入门丶线程安全问题

day26_多线程入门丶线程安全问题

作者:互联网

相关概念

什么是多线程?

多线程是指从软件或者硬件上实现多个线程并发执行的技术。 具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。

并发和并行

进程和线程

进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,进程也是程序的一次执行过程,是系统运行程序的基本单位。

线程:是进程中的单个顺序控制流,是一条执行路径

进程是操作系统调度和分配资源的最小单位,线程是CPU调度的最小单位。不同的进程之间是不共享内存的。进程之间的数据交换和通信的成本是很高。不同的线程是共享同一个进程的内存的。当然不同的线程也有自己独立的内存空间。对于方法区,堆中中的同一个对象的内存,线程之间是可以共享的,但是栈的局部变量永远是独立的。

多线程的实现方式

方式一:继承Thread类

Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建启动多线程的步骤如下:

  1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
  2. 创建Thread子类的实例,即创建了线程对象
  3. 调用线程对象的start()方法来启动该线程

注意:

run()方法和start()方法的区别

 代码示例

package ThreadDemo;
// 1: 定义一个类MyThread继承Thread类
public class MyThread extends Thread {

    /**
     *2 : 重写run方法,完成该线程执行的逻辑
     */
    @Override
    public void run() {
        for (int i = 0; i < 30; i++) {
            System.out.println("正在执行!" + i);
        }

    }
}

 定义测试类

package ThreadDemo;

public class Test {
    public static void main(String[] args) {
        //3:创建自定义线程对象
        MyThread mt = new MyThread();
        //4:开启新线程
        mt.start();
        //在主方法中执行for循环,主线程代码
        for (int i = 0; i < 30; i++) {
            System.out.println("main线程!"+i);
        }
    }
}

注意:一个自定义线程对象就是一条线程,也就是一条执行路径

方式二:实现Runnable接口

Java有单继承的限制,当我们无法继承Thread类时,那么该如何做呢?在核心类库中提供了Runnable接口,我们可以实现Runnable接口,重写run()方法,然后再通过Thread类的对象代理启动和执行我们的线程体run()方法

步骤如下:

  1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正 的线程对象。
  3. 调用线程对象的start()方法来启动线程。

代码如下:

  1. package demo;
    
    //1:定义一个类MyRunnable实现Runnable接口
    public class MyRunnable implements Runnable {
        @Override
        public void run() {//2:在MyRunnable类中重写run()方法
            for(int i=0; i<100; i++) {
                System.out.println(i);
            }
        }
    }
     class MyRunnableDemo {
        public static void main(String[] args) {
            //3:创建MyRunnable类的对象
            MyRunnable my = new MyRunnable();
    
            //4:创建Thread类的对象,把MyRunnable对象作为构造方法的参数
            Thread t1 = new Thread(my);
    
            //5:启动线程
            t1.start();
    
        }
    }

总结:

实现多线程方式三: 实现Callable接口

实现步骤

  1. 定义一个类MyCallable实现Callable接口
  2. 在MyCallable类中重写call()方法
  3. 创建MyCallable类的对象
  4. 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
  5. 创建Thread类的对象,把FutureTask对象作为构造方法的参数
  6. 启动线程
  7. 再调用get方法,就可以获取线程结束之后的结果。

代码示例

package demo01;

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

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println("锻炼:" + i);
        }
        //返回值就表示线程运行完毕之后的结果
        return "八块腹肌";
    }
}

class Test{
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //线程开启之后需要执行里面的call方法
        MyCallable myCallable = new MyCallable();
        //task 可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象
        FutureTask<String> task = new FutureTask<>(myCallable);
        //创建线程对象
        Thread thread = new Thread(task);
        //开启线程
        thread.start();
        // 获取线程执行完毕之后的对象
        String s = task.get();
        System.out.println(s);
    }
}

三种方式对比:

Thread类

构造方法:

常用方法

守护线程

有一种线程,它是在后台运行的,它的任务是为其他线程提供服务的,这种线程被称为“守护线程”。JVM的垃圾回收线程就是典型的守护线程。守护线程有个特点,就是如果所有非守护线程都死亡,那么守护线程自动死亡。

线程安全

当我们使用多个线程访问同一资源(可以是同一个变量、同一个文件、同一条记录等)的时候,若多个线程只有读操作,那么不会发生线程安全问题,但是如果多个线程中对资源有读和写的操作,就容易出现线程安全问题。

同一个资源问题

查看下面代码

package demo06;

public class SellTicket implements Runnable {
    private int ticket = 100;
    //在SellTicket类中重写run()方法实现卖票,代码步骤如下
    @Override
    public void run() {
        while (true) {
            if(ticket <= 0){
                //卖完了
                break;
            }else{
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket--;
                System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
            }
        }
    }
}

定义测试类

package demo06;

public class Test {
    public static void main(String[] args) {
        //创建SellTicket类的对象
        SellTicket st = new SellTicket();

        //创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
        Thread t1 = new Thread(st,"窗口1");
        Thread t2 = new Thread(st,"窗口2");
        Thread t3 = new Thread(st,"窗口3");

        //启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

执行后的结果

 通过执行结果我们发现了

问题产生原因

安全问题出现的条件

如何解决多线程安全问题呢?基本思想:让程序没有安全问题的环境。Java中提供了同步机制 (synchronized)来解决。

为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。  

同步的好处和弊端

同步代码块

synchronized 关键字可以用于某个区块前面,表示只对这个区块的资源实行互斥访问。

格式

代码示例

public class SellTicket implements Runnable {
    private int tickets = 100;
    private Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (obj) { // 对可能有安全问题的代码加锁,多个线程必须使用同一把锁
                //t1进来后,就会把这段代码给锁起来
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                        //t1休息100毫秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //窗口1正在出售第100张票
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--; //tickets = 99;
                }
            }
            //t1出来了,这段代码的锁就被释放了
        }
    }
}

public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st = new SellTicket();

        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

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

同步锁对象是什么?

同步方法

synchronized 关键字直接修饰方法,表示同一时刻只有一个线程能进入这个方法,其他线程在外面等着。

同步方法:就是把synchronized关键字加到方法上,格式如下:

同步静态方法:就是把synchronized关键字加到静态方法上

 代码示例

package demo02;

public class SaleTicketSafeDemo1 {
    public static void main(String[] args) {
        // 2、创建资源对象
        Ticket ticket = new Ticket();

        // 3、启动多个线程操作资源类的对象
        Thread t1 = new Thread("窗口一") {
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(10);// 加入这个,使得问题暴露的更明显
                        ticket.sale();
                    } catch (Exception e) {
                        e.printStackTrace();
                        break;
                    }
                }
            }
        };
        Thread t2 = new Thread("窗口二") {
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(10);// 加入这个,使得问题暴露的更明显
                        ticket.sale();
                    } catch (Exception e) {
                        e.printStackTrace();
                        break;
                    }
                }
            }
        };
        Thread t3 = new Thread(new Runnable() {
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(10);// 加入这个,使得问题暴露的更明显
                        ticket.sale();
                    } catch (Exception e) {
                        e.printStackTrace();
                        break;
                    }
                }
            }
        }, "窗口三");

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

// 1、编写资源类
class Ticket {
    private int total = 10;

    //非静态方法隐含的锁对象就是this
    public synchronized void sale() {
        if (total > 0) {
            System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余:" + --total);
        } else {
           return;
        }
    }


}

同步方法的锁对象问题

Lock锁

常用方法:

package demo02;

import java.util.concurrent.locks.ReentrantLock;

public class Ticket implements Runnable {
    //票的数量
    private int ticket = 100;
    private ReentrantLock lock = new ReentrantLock();

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

            try {
                lock.lock();
                if (ticket <= 0) {
                    //卖完了
                    break;
                } else {
                    Thread.sleep(100);
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }
    }
}

 class Demo {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();

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

        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

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

死锁

线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行

什么情况下会产生死锁

代码演示

public class Demo {
    public static void main(String[] args) {
        Object objA = new Object();
        Object objB = new Object();

        new Thread(()->{
            while(true){
                synchronized (objA){
                    //线程一
                    synchronized (objB){
                        System.out.println("小康同学正在走路");
                    }
                }
            }
        }).start();

        new Thread(()->{
            while(true){
                synchronized (objB){
                    //线程二
                    synchronized (objA){
                        System.out.println("小薇同学正在走路");
                    }
                }
            }
        }).start();
    }
}

标签:run,Thread,void,day26,线程,多线程,public
来源: https://blog.csdn.net/weixin_44462792/article/details/120249989