Java学习笔记—多线程

1.进程和线程

1.1概述

(1)进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
(2)线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
(3)多进程的意义:单进程计算机只能做一件事情。而我们现在的计算机都可以一边玩游戏(游戏进程),一边听音乐(音乐进程),
所以我们常见的操作系统都是多进程操作系统。比如:Windows,Mac和Linux等,能在同一个时间段内执行多个任务。
(4)多进程的意义:多线程的作用不是提高执行速度,而是为了提高应用程序的使用率。

1.2多线程程序实现

多线程的一些方法:

方法功能
public void run()run方法中封装应该是必须被线程执行的代码
public void start()开启线程
public final void setDaemon(Daemon(boolean on)把该线程标记为守护线程
public final void stop()停止线程的运行
public void interrupt()清除线程阻塞的状态
public final void join()等待该线程执行完毕了以后,其他线程才能再次执行
public final int getPriority()获取线程的优先级
public final void setPriority(int newPriority)设置线程的优先级
public static void sleep(long millis)线程休眠
public static void yield()暂停当前正在执行的线程对象,并执行其他线程

(1)多线程程序实现方法1:
Thread
1.直接继承Thread类
2.重写run方法
3.创建Thread的子类对象,启动子线程
正确开启线程的方法是 调用start()方法,线程开启后,会去调用run()方法;

public class MyTest {
    public static void main(String[] args) {
        //获取的是当前的线程对象
        Thread thread = Thread.currentThread();
        String name = thread.getName();
        System.out.println(name);

        MyThread th = new MyThread();
        //设置线程的名称
        th.setName("线程一");
        th.start();
    }
}
public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //this.getName() 获取当前线程的名称
            System.out.println(this.getName() + ":" + i);
            // System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

(2)多线程程序实现方法2:
Runnable
1.创建线程的另一种方法是声明实现 Runnable 接口的类。
2.该类然后实现 run 方法。然后可以分配该类的实例,
3.在创建 Thread 时作为一个参数来传递并启动

public class MyTest {
    public static void main(String[] args) {
      
        //Runnable任务
        MyRunnable myRunnable = new MyRunnable();
        Thread th = new Thread(myRunnable, "线程A");
        th.start();
        
    }
}
public class MyRunnable implements Runnable {

    //让线程来执行的方法
    @Override
    public void run() {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            // System.out.println(Thread.currentThread().getName() + ":" + i);
            sum += i;
        }
    }
}

实现Runnable接口的好处:因为Java是单继承,实现这个接口后还可以去继承其他的类或接口,避免由于Java单继承带来的局限性。
(3)多线程程序实现方法3:
Callable

  1. 创建一个类实现Callable 接口
  2. 创建一个FutureTask类将Callable接口的子类对象作为参数传进去
  3. 创建Thread类, 将FutureTask对象作为参数传进去
  4. 开启线程
public class MyTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
      
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> task = new FutureTask<>(myCallable);
        Thread thread = new Thread(task);
        thread.start();
        Integer integer = task.get();
        System.out.println(integer);

        //Runnable run() 没有返回值 没有抛出异常
        //Callable call() 有返回值的 可以抛出异常
    }
}
public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        Thread.sleep(200);
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum += i;
        }

        return sum;
    }
}

Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。

2.线程安全问题

出现线程安全问提要满足下面这些条件
1.是不是多线程。
2.是不是有共享数据
3.是不是有多条语句再操作这个共享变量 读 改 写
解决办法:我们可以加锁,也就是 使用同步代码块来解决

锁对象:任意一个Java对象就可以用来做一把锁 注意多个线程要用同一个锁对象
synchronized (锁对象){
                        需要同步代码,你认为有可能出现安全问题代码
}
我们来看以下的案例:
需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
如果我们不加锁,看看下面的代码。

public class MyTest {
    public static void main(String[] args) {
        SellTiketRunnable sellTiketRunnable = new SellTiketRunnable();
        Thread th1 = new Thread(sellTiketRunnable, "窗口1");
        Thread th2 = new Thread(sellTiketRunnable, "窗口2");
        Thread th3 = new Thread(sellTiketRunnable, "窗口3");
        th1.start();
        th2.start();
        th3.start();
     
    }
}
public class SellTiketRunnable implements Runnable {
    //共享数据
    static int num = 100;

    @Override
    public void run() {
        while (true) {
            if (num > 0) {
               try {
                    Thread.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售:" + (num--) + "张票");
            }
        }
    }
}

结果:
在这里插入图片描述
我们看到都有负数票和0票的出现,有时候还会有相同票。
解决办法就是加锁,就上保证只有一个线程去操作这个共享变量。

加锁的方式有两种:

锁对象:任意一个Java对象就可以用来做一把锁 注意多个线程要用同一个锁对象
synchronized (锁对象){
                        需要同步代码,你认为有可能出现安全问题代码
}
我们来实现那个需求:

public class MyTest {
    public static void main(String[] args) {
        SellTiketRunnable sellTiketRunnable = new SellTiketRunnable();
        Thread th1 = new Thread(sellTiketRunnable, "窗口1");
        Thread th2 = new Thread(sellTiketRunnable, "窗口2");
        Thread th3 = new Thread(sellTiketRunnable, "窗口3");
        th1.start();
        th2.start();
        th3.start();
     
    }
}
public class SellTiketRunnable implements Runnable {
    //共享数据
    static int num = 100;

    @Override
    public void run() {
        while (true) {
            sell();
        }
    }


    public synchronized void sell() {
        if (num > 0) {
            System.out.println(Thread.currentThread().getName() + "正在出售:" + (num--) + "张票");
        }
    }

Lock锁:虽然我们可以理解同步代码块和同步方法的锁对象问题,
但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
void lock() 加锁
void unlock() 释放锁

public class MyTest {
    public static void main(String[] args) {
        SellTiketRunnable sellTiketRunnable = new SellTiketRunnable();
        Thread th1 = new Thread(sellTiketRunnable, "窗口1");
        Thread th2 = new Thread(sellTiketRunnable, "窗口2");
        Thread th3 = new Thread(sellTiketRunnable, "窗口3");
        th1.start();
        th2.start();
        th3.start();
     
    }
}
public class SellTiketRunnable implements Runnable {
    //共享数据
    static int num = 1000;
    Lock lock = new ReentrantLock();

    @Override
    //th1 th2 th3
    public void run() {
        while (true) {

            //加锁
            lock.lock();
            try {
                // th1
                if (num > 0) { //1
                    //模拟网络延迟
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(1 / 0);
                    System.out.println(Thread.currentThread().getName() + "正在出售:" + (num--) + "张票");
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //不管有没有出现异常都要释放锁
                lock.unlock();
            }
        }
    }
}

3.死锁

死锁: 两个或者两个以上的线程,在抢占CPU的执行权的时候,都处于等待状态

public interface LockUtils {
    //定义两把锁对象
     Object objA = new Object();
     Object objB = new Object();
}
public class MyThread extends Thread {
    private boolean flag;

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

    @Override
    public void run() {
        if (flag) {
            synchronized (LockUtils.objA) {
                System.out.println("true线程进来了持有 objA锁");
                synchronized (LockUtils.objB) {
                    System.out.println("true线程进来了持有 objB锁");
                } 
            }
        } else {
            synchronized (LockUtils.objB) {
                System.out.println("false线程进来了持有 objB锁");
                synchronized (LockUtils.objA) {
                    System.out.println("false线程进来了持有 objA锁");
                }
            }
        }
    }
}
public class MyTest {
    public static void main(String[] args) {
        MyThread th1 = new MyThread(false);
        MyThread th2 = new MyThread(true);
        th1.start();
        th2.start();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值