0
点赞
收藏
分享

微信扫一扫

java---多线程

小猪肥 2022-03-12 阅读 200

java—多线程

进程与线程的区别:一个进程可以有多个线程

****进程:****是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。

正在运行中的应用程序,通常称为进程。每个进程都有自己独立的地址空间(内存空间),每当用户启动一个进程时,操作系统就会为该进程分配一个独立的内存空间,让应用程序在这个独立的内存空间中运行。

****线程:****是进程的一个执行单元,是进程内科调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程。线程是一个轻量级的子进程,是最小的处理单元;是一个单独的执行路径。可以说:线程是进程的子集(部分)。

​ ****关系:****一个程序至少一个进程,一个进程至少一个线程。

线程的创建方式

java中常见的创建方式有三种:

  1. 继承Thread类创建线程类
  2. 继承Runnable接口创建线程类
  3. 通过Callable和Future创建线程

继承Thread类创建线程类

继承Thread类并重写run方法。

public class Demo1 {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        MyThread thread1 = new MyThread(), thread2 = new MyThread();
        thread1.start();
        thread2.start();
    }
}
class MyThread extends Thread {
    @Override
    public void run() {
        for(int i=0;i<5;i++) {
            System.out.println("这是"+Thread.currentThread().getName()+"第"+i+"次输出");
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

继承Runnable接口创建线程类

继承Runnable接口并实现run方法。然后将该类的对象作为参数传入Thread的构造方法中,然后调用Thread对象的start方法

public class Demo1 {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        MyThread thread1 = new MyThread();
        Thread t1 = new Thread(thread1), t2 = new Thread(thread1);
        t1.start();
        t2.start();
    }
}
class MyThread implements Runnable {

    int i=0;
    @Override
    public void run() {
        for(;i<10;i++) {
            System.out.println(Thread.currentThread().getName()+"正在输出:"+i);
            //Thread.sleep(20);
        }
    }
}

通过Callable和Future创建线程

先创建一个继承了Callable接口的类,将该类的实例对象作为参数传入FutureTask构造函数,然后将FutureTask对象作为参数传入Thread构造方法。最后调用Thread的start方法。

public static void main(String[] args) throws IOException, ClassNotFoundException, ExecutionException, InterruptedException {
        MyThread myThread = new MyThread();
        FutureTask<Integer> futureTask = new FutureTask<>(myThread);
        Thread thread = new Thread(futureTask);
        for(int i=0;i<10;i++) {
            if(i==5) {
                thread.start();
            }
        }
        System.out.println("线程的返回值为:"+futureTask.get());

    }
}
class MyThread implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"正在执行!");
        return 55;
    }
}

三种方式的对比

由于java只实行类的单继承,所以对于继承了Thread类的类而言就不能再继承其他类了,但是实现Runnable或者Callable接口后任可以继承其他类。在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

在开发中,优先选择实现Runnable接口的方式,如果需要有返回值,则实现Callable接口。

注意!!!:关于start和run,thread.run()只是调用一个方法,而thread.start()会启用一个线程。

线程的相关方法

  • start:启动当前线程并调用当前线程的run方法
  • getName() :获取线程的名字
  • setName:设置线程的名字
  • yield:释放当前CPU的执行权,当当前线程任能继续参与执行权的抢夺
  • join:在a线程中调用b.join()后a线程进入阻塞状态,直到b线程结束,也可以在join中加入1个参数p1,代表最多等待p1毫秒。或者是加入两个参数p1,p2,代表最多等待p1毫秒 加 p2 纳秒
  • sleep:让当前线程停止执行一段时间,如果是一个参数,代表毫秒,如果是两个参数代表毫秒和纳秒。
  • isAlive判断这个线程是否还活着
  • activeCount:返回当前程序中的活动线程数 就绪+阻塞+运行
  • currentThread:静态方法,返回执行当前代码的线程
  • wait:这是Object类的方法。使当前线程等待,直到其他线程调用此对象的notify或notifyAll方法,或者是指定的等待时间已过

线程的生命周期

线程的生命周期有五种状态:

  1. 新建 New
  2. 就绪 Runnable
  3. 运行 running
  4. 阻塞 Blocked
  5. 死亡 Dead

多线程的安全问题与解决办法

产生安全问题的原因

试想这样一种情况,假如一共有10张票,两个线程同时售卖,对于每个线程的过程如下所示:

  • 首先判断是否还有票
    • 如果没有票了,直接结束
    • 如果还有票,卖出一张票,然后将票数减一

问题就出在判断的这里,假如已经,卖了9张票了,还剩最后一张,线程A首先进行判断,发现还有票,所以进入第二个判断体。但是A还没将票数减一的时候B也开始进行判断,发现还有票,同样进入卖票的程序,这样就会导致卖出了11张票,最终的票数为-1,这显然是不合理的。

解决办法:

对于共享变量,同时只能让一个线程修改。也就是同步

1)同步代码块

即让某段代码只能同时被一个线程执行

while(true) {
            synchronized (t1) {
                if(tickets>0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets-- + "张票");
                }
                else {
                    Thread.currentThread().interrupt();
                }
            }
        }

​ t1可以是任意一个对象,我这里是Interger t1 = new Integer(0);

由于两个线程的同步对象都是t1,所以这两个线程同时只有一个能够执行这个代码块。

2)同步方法

其实就是用关键词synchronized修饰一个方法。被修饰的方法同时只能被一个线程执行。

3)lock锁

在java.util.concurrent.locks.lock包下有一个Lock接口,ReentrantLock是他的一个实现类。我们主要使用

  • lock 获得锁
  • unlock 释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。

方法就行了,在lock与unlock之间的就是同步代码块。

同步是一种高开销的操作,因此应该尽量减少同步的内容。 通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

举报

相关推荐

0 条评论