多线程、线程安全、加锁、死锁

一、线程

线程依赖于进程

1、进程

进程就是正在运行的程序,是系统资源进行资源分配和调用的基本单位。在某一时刻,可以运行好几个进程;但在某个具体时间点上CPU(单核)只能执行一个进程。因为CPU可以在多个进程之间进行一个高速的转换,所以人们是感觉不出来的。多进程的意义,在于提高CPU的利用率。

2.线程

在一个进程内部又可以执行多个任务,而这每一个任务我们都可以看成是一个线程。是程序使用CPU的基本单位。所以,程序是拥有资源的基本单位,线程是CPU调度的基本单位。

  1. 线程依赖于进程。进程开启后,他会执行很多的任务,那么每个任务,我们就称之为线程。
  2. 线程具有随机性,它会抢占CPU的执行权。谁抢到,CPU就会在某一时刻执行谁。
  3. 并发:多个任务交替执行,只是交替的间隔很短,让你感觉它在同时进行
  4. 并行:多个任务同时进行
  5. JVM多线程:是至少有两个线程。一个线程是主线程,还有一个垃圾回收线程

二、多线程程序的实现方式

1.多线程程序的实现方式1

我们创建线程的方式1
1.我们定义一个类,继承Thread这个类
2.重写Thread这个类中的run方法
3.创建我们写的这个类的对象
4.开启线程

public class MyThread extends Thread{
    @Override
    public void run() {
        //这个run()方法就是需要线程来执行的代码,一般耗时的操作,我们会放到run()方法里面,由线程去执行
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}
public class MyTest {
    public static void main(String[] args) {

        MyThread th = new MyThread();
        th.start();
        //调用start()开启线程,由线程去调用run()去执行run()里面的方法

        MyThread th2 = new MyThread();
        th2.start();
        //同一个线程不要多次开启,会抛异常
    }
}
2.多线程程序的实现方式2

开启线程的方式2:
1.创建一个类,实现Runable接口,重写该接口中的run方法
2.创建Thread类对象,将Runable接口的子类对象传进来
3.调用start()方法开启线程

public class MyRunable implements Runnable {
    @Override
    public void run() {
        //需要线程执行
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}
public class MyTest {
    public static void main(String[] args) {
        //Runnable接口应该由那些打算通过某一个线程执行其实例的类实现
        MyRunable myRunable = new MyRunable();
        Thread th = new Thread(myRunable);
        th.start();

    }
}
3.多线程程序的实现方式3

开启线程的方式3:
1.创建一个类实现Callable接口
2.创建一个FutureTask类将Callable接口的子类对象作为参数传递进去
3.创建Thread类,将FutureTask对象作为参数传进去
4.开启线程

public class MyCallable implements Callable<Integer> {
    //call()方法就是线程要执行的方法
    @Override
    public Integer call() throws Exception {
        int sum=0;
        for (int i = 0; i <= 100; i++) {
            sum+=i;
        }
        return sum;
    }
}
public class MyTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建进程的方式3
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> task = new FutureTask<Integer>(myCallable);
        Thread th= new Thread(task);
        th.start();
        //线程执行完之后,可以获取结果
        Integer integer = task.get();
        System.out.println(integer);
    }
}

三、多线程的属性和基本方法

1.获取和设置线程对象的名称
public static void main(String[] args) {
        //获取主线程的名称
        Thread thread = Thread.currentThread();//获取当前线程对象
        String name = thread.getName();
        System.out.println(name);

        MyThread th = new MyThread();
        th.start();

        //设置线程名称
        th.setName("小祖宗");

    }
public class MyThread extends Thread{
    @Override
    public void run() {
       //获取线程名称
        //String name = this.getName();
        String name = Thread.currentThread().getName();

        for (int i = 0; i < 100; i++) {
            System.out.println(name+i);
        }
    }
}
2.获取和设置线程的优先级

线程有两种调度模型:

  1. 分时调度模型:所有线程轮流使用CPU的使用权,平均分配给每个线程占用CPU的时间片
  2. 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个
//java使用线程的调度模型,是抢占式调度
//如果说多个线程的优先级一样,线程的执行,就是随机抢占
        MyThread th1 = new MyThread();
        MyThread th2 = new MyThread();
        //设置线程的优先级
        th1.setPriority(Thread.MAX_PRIORITY);
        th2.setPriority(Thread.MIN_PRIORITY);
        //获取线程的优先级
        th1.getPriority();
        th2.getPriority();//线程的优先级1-10,默认优先级是5
        th1.start();
        th2.start();
3.线程控制之休眠线程
public static void main(String[] args) throws InterruptedException {
        //通过构造,给线程设置名字
        MyThread th1 = new MyThread("张飞");
        th1.start();
        th1.sleep(3000);//让当前线程休眠
    }
public class MyThread extends Thread{
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        //让线程睡一会,就是让线程处于一种堵塞状态
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}
4.线程控制之加入线程

意思就是:等待该线程执行完毕了以后,其他线程才能再次执行

public static void main(String[] args) throws InterruptedException {
        MyThread th1 = new MyThread("张飞");
        MyThread th2 = new MyThread("刘备");
        MyThread th3 = new MyThread("关羽");
        //join()在线程开启之后,去调用
        th1.start();
        th1.join();
        th2.start();
        th1.join();
        th3.start();
        th1.join();//串行:多个线程按顺序执行
    }
5.线程控制之礼让线程
public static void main(String[] args) {
        MyThread th1 = new MyThread("张飞");
        MyThread th2 = new MyThread("刘备");
        th1.start();
        th2.start();
    }
public class MyThread extends Thread{
    public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            Thread.yield();//暂停当前正在执行的线程对象,并执行其他线程
            System.out.println(i);
        }
    }
}
6.线程控制之守护线程

用户线程和守护线程都是线程,区别就是Java虚拟机在所有用户线程dead后,程序就会结束。而不管是否还有守护线程还在运行,若守护线程还在运行,则会马上停止。

public static void main(String[] args) {
        //主线程也称为用户线程
        Thread.currentThread().setName("刘备");

        MyThread th1 = new MyThread("张飞");
        MyThread th2 = new MyThread("关羽");
        th1.setDaemon(true);
        th2.setDaemon(true);
        //在线程开启之前,可以设为守护线程
        th1.start();
        th2.start();
 }
7.线程控制之中断线程
public static void main(String[] args) throws InterruptedException {
        MyThread th = new MyThread("张飞");
        th.start();
        th.stop();//强行停止线程的运行
        Thread.sleep(1000);
        th.interrupt();//打断线程的一个堵塞状态
    }
8.多线程复制文件
public class MyThread1 extends Thread{
    //复制第一个文件
    @Override
    public void run() {
        try {
            Files.copy(Paths.get("曾经的你.mp3"), Paths.get("曾经的你2.mp3"), StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class MyThread2 extends Thread{
    //复制第二个文件
    @Override
    public void run() {
        try {
            Files.copy(Paths.get("许巍 - 曾经的你.mp3"), Paths.get("许巍 - 曾经的你2.mp3"), StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public static void main(String[] args) {
        //一个线程复制一个文件
        MyThread1 th1 = new MyThread1();
        MyThread2 th2 = new MyThread2();
        th1.start();
        th2.start();
    }

四、小案例

需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。

  1. 继承Thread类的方式
public class Test1 {
    public static void main(String[] args) {
        MyThread th1 = new MyThread();
        MyThread th2 = new MyThread();
        MyThread th3 = new MyThread();
        th1.setName("窗口1");
        th2.setName("窗口2");
        th3.setName("窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}
public class MyThread extends Thread {

    static int num=100;
    @Override
    public void run() {
        while(num>=1){
            String name = Thread.currentThread().getName();
            System.out.println(name+"正在卖第"+(num--)+"张票");
        }
    }
}
  1. 实现Runable接口的方式
public class Test2 {
    public static void main(String[] args) {
        MyRunable myRunable = new MyRunable();
        Thread th1 = new Thread(myRunable);
        Thread th2 = new Thread(myRunable);
        Thread th3 = new Thread(myRunable);
        th1.setName("窗口1");
        th2.setName("窗口2");
        th3.setName("窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}
public class MyRunable implements Runnable {
    int num=100;
    @Override
    public void run() {
        while(num>=1){
            String name = Thread.currentThread().getName();
            System.out.println(name+"正在售卖第"+(num--)+"张票");
        }
    }
}

五、线程安全、锁

1.线程安全问题

在多线程环境下;存在共享数据;有多条语句操作共享数据;这种情况下,有可能出现线程安全问题

2.同步代码块解决线程安全
  1. 同步代码块的格式:
    synchronized(对象){
    需要同步的代码;
    }
public class MyRunable implements Runnable {
    int num=100;
    Object obj=new Object();
    @Override
    public void run() {
        synchronized (obj){//锁,其实就是一个任意对象,多个线程要共享一把锁
            while(num>=1){
                try {
                    Thread.sleep(3000);//使用休眠来模拟网路延迟
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String name = Thread.currentThread().getName();
                System.out.println(name+"正在售卖第"+(num--)+"张票");
            }
        }
        //释放锁
    }
}
  1. 同步代码块的好处和弊端
    好处:解决了多线程的安全问题
    弊端:当线程较多时,因为每个线程都会去判断同步上的锁,这很耗费资源也降低了程序的运行效率
  2. 同步方法
private synchronized void sell() {//方法上加一个synchronized关键字,我们叫做同步方法
        //同步方法所使用的对象不是任意对象,它用的锁是this

            while(num>=1){
                try {
                    Thread.sleep(3000);//使用休眠来模拟网路延迟
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String name = Thread.currentThread().getName();
                System.out.println(name+"正在售卖第"+(num--)+"张票");
            }

    }
  1. 静态同步方法
//静态同步方法使用的锁对象,不是任意对象,她用的锁是当前类的字节码类型
     private synchronized static void sellMethod() {
        while(num>=1){
            try {
                Thread.sleep(3000);//使用休眠来模拟网路延迟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String name = Thread.currentThread().getName();
            System.out.println(name+"正在售卖第"+(num--)+"张票");
        }
    }
}

  1. JDK1.5之后的Lock锁
public class MyRunable implements Runnable {
    static int num=100;
    ReentrantLock lock=new ReentrantLock();
    @Override
    public void run() {
        //加锁
        try {
            lock.lock();
            while(num>=1){
                try {
                    Thread.sleep(3000);//使用休眠来模拟网路延迟
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String name = Thread.currentThread().getName();
                System.out.println(name+"正在售卖第"+(num--)+"张票");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //确保锁要释放掉
            lock.unlock();
        }
    }
}
3.死锁
  1. 如果出现了同步嵌套,就很容易产生死锁问题
  2. 是指两个或者两个以上的线程,在抢占CPU的执行权的时候,都处于一种互相等待状态
public class MyTest {
    public static void main(String[] args) {
        MyThread th1 = new MyThread(true);
        MyThread th2 = new MyThread(false);
        th1.start();
        th2.start();
    }
}
public class MyThread extends Thread {

    boolean flag;

    public MyThread(boolean flag) {
        this.flag=flag;
    }

    @Override
    public void run() {
        if(flag){
            synchronized (ObjectUtils.objA){
                System.out.println("objA执行了");
                synchronized (ObjectUtils.objB){
                    System.out.println("objB执行了");
                }
            }//objA不释放
        }else{
            synchronized (ObjectUtils.objB){
                System.out.println("objB执行了");
                synchronized (ObjectUtils.objA){
                    System.out.println("objA执行了");
                }
            }
        }//objB不释放
    }
}
public interface ObjectUtils {
    //创建两把锁对象
    public static final Object objA=new Object();
    Object objB=new Object();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值