Java多线程(一)

一.多线程基础 

1.  线程和进程 

进程:

        是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用
程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基
本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。 

线程:

        进程内部的一个独立执行单元;一个进程可以同时并发的运行多个线程,可以理
解为一个进程便相当于一个单 CPU 操作系统,而线程便是这个系统中运行的多个任
务。 

2. 多线程的创建方式

第一种继承Thread类 重写run方法 (无法设置返回值)

实现Runnable接口,重写run方法 (无法设置返回值)

实现 implements Callable接口(可以存在线程返回值 Object)

实现Runnable接口比继承Thread类所具有的优势:
  1. 适合多个相同的程序代码的线程去共享同一个资源。

  2. 可以避免java中的单继承的局限性。

  3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和数 据独立。

  4. 线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread 的类。

3.多线程常用方法

3.1  设置和获取线程名称

方法名说明
void setName(String name)将此线程的名称更改为等于参数name
String getName()返回此线程的名称
Thread currentThread()返回对当前正在执行的线程对象的引用

代码演示

public class MyThread extends Thread {
    public MyThread() {}
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+":"+i);
        }
    }
}
public class MyThreadDemo {
    public static void main(String[] args) {
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();

        //void setName(String name):将此线程的名称更改为等于参数 name
        my1.setName("高铁");
        my2.setName("飞机");

        //Thread(String name)
        MyThread my1 = new MyThread("高铁");
        MyThread my2 = new MyThread("飞机");

        my1.start();
        my2.start();

        //static Thread currentThread() 返回对当前正在执行的线程对象的引用
        System.out.println(Thread.currentThread().getName());
    }
}

3.2 线程休眠和守护线程

方法名说明
static void sleep(long millis)使当前正在执行的线程停留(暂停执行)指定的毫秒数
void setDaemon(boolean on)将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出

代码演示

/**
 * 线程休眠
 */
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        /*System.out.println("睡觉前");
        Thread.sleep(3000);
        System.out.println("睡醒了");*/

        MyRunnable mr = new MyRunnable();

        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);

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

/**
 * 守护线程演示
 */
public class MyThread1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "---" + i);
        }
    }
}
public class MyThread2 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "---" + i);
        }
    }
}
public class Demo {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        MyThread2 t2 = new MyThread2();

        t1.setName("女神");
        t2.setName("备胎");

        //把第二个线程设置为守护线程
        //当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.
        t2.setDaemon(true);

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

3.3 线程优先级

3.3.1两种调度方式

(1)分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片

(2)抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些

Java使用的是抢占式调度模型

        假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的。

方法名说明
final int getPriority()返回此线程的优先级
final void setPriority(int newPriority)更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10

 

代码演示

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
        return "线程执行完毕了";
    }
}
public class Demo {
    public static void main(String[] args) {
        //优先级: 1 - 10 默认值:5
        MyCallable mc = new MyCallable();

        FutureTask<String> ft = new FutureTask<>(mc);

        Thread t1 = new Thread(ft);
        t1.setName("飞机");
        t1.setPriority(10);
        //System.out.println(t1.getPriority());//5
        t1.start();

        MyCallable mc2 = new MyCallable();

        FutureTask<String> ft2 = new FutureTask<>(mc2);

        Thread t2 = new Thread(ft2);
        t2.setName("坦克");
        t2.setPriority(1);
        //System.out.println(t2.getPriority());//5
        t2.start();
    }
}

二.线程的生命周期

1.状态描述

NEW(新建) : 线程刚被创建,但是并未启动。

RUNNABLE(可运行) :线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。

BLOCKED(锁阻塞) :当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。

WAITING(无限等待) :一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。

TIMED_WAITING(计时等待) : 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。

TERMINATED(被终止)  : 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

注意:

wait() 让线程处于等待状态,并且释放当前锁资源 需要手动唤醒

sleep() 不会释放锁 让线程处于等待状态 自然醒来

  • 对于sleep()方法,首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。

  • sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。

    wait()是把控制权交出去,然后进入等待此对象的等待锁定池处于等待状态,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。

  • 在调用sleep()方法的过程中,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁。

三.线程同步

1. 线程同步简介

        即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该地址进行操作,而其他线程又处于等待状态,实现线程同步的方法有很多种。

        在一般情况下,创建一个线程是不能提高程序执行效率的,所以要创建多个线程。多个线程同时运行的时候可能调用线程函数,在多个线程同时对同一个内存地址进行写入,由于CPU时间调度上的问题,写入数据会被多次的覆盖,所以就要使线程同步。

2. 线程同步的作用 

线程有可能和其他线程共享一些资源,比如:内存、文件、数据库等
当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢作一团。
线程同步的真是意思其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。

3. Synchronized关键字

        如果对一个变量加上互斥锁,那么在同一时刻,该变量只能有一个线程能访问,即当一个线程访问临界资源时,其他线程只能等待。
        在Java中,每一个对象都有一个锁标记(monitor),也被称为监视器,当多个线程访问对象时,只有获取了对象的锁才能访问。
        在我们编写代码的时候,可以使用synchronized修饰对象的方法或者代码块,当某个线程访问这个对象synchronized方法或者代码块时,就获取到了这个对象的锁,这个时候其他对象是不能访问的,只能等待获取到锁的这个线程执行完该方法或者代码块之后,才能执行对象的方法。

4.线程安全问题

        线程安全是多线程领域的问题,(线程安全问题就是在多线程环境中 , 并且存在数据共享 (即多个线程操作同一个数据))线程安全可以简单理解为一个方法或者一个实例可以在多线程环境中使用而不会出现问题。 在同一程序中运行多个线程本身不会导致问题,问题在于多个线程访问了相同的资源。

4.1引发原因

        在 Java 程序中,存储数据的内存空间分为共享内存和本地内存。线程在读写主存的共享变量时,会先将该变量拷贝一份副本到自己的本地内存,然后在自己的本地内存中对该变量进行操作,完成操作之后再将结果同步至主内存。主内存数据和本地内存的不同步,导致多个线程同时操作主内存里的同一个变量时,变量数据可能会遭到破坏。可以举个例子来解释:

        在存在多线程的环境中,并且有共享的数据,这中情况下就可能会出现线程安全问题:即一个线程访问的共享数据被其他线程修改了, 那么此时就发生了线程安全问题 。我们举一个例子:现有以int value=0;有A、B两个线程操作value,A线程进行+1操作,B线程进行+3操作同一时刻,AB、同时访问value的值,获取的结果都是0;然后同时进行操作,这个时候就会出现bug,value的值就可能是1或3 了,这就是出现了线程安全问题。

4.2 如何解决

        当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。 要解决上述多线程并发访问一个资源的安全问题,Java中提供了同步机制 (synchronized)来解决。

4.2.1 同步代码块
synchronized( 同步监视器){
//需要被同步的代码}

说明:

1.操作共享数据的代码,即为需要被同步的代码;要包的不多不少

2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。

3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁,我们常用object对象。

要求:多个线程必须要共用同一把锁。

补充:根据实际条件我们可以用this充当锁(继承Thread实现多线程时)我们也可以用类充当监视器,类也是对象。

4.2.1 同步方法

        如果操作共享数据的代码我们封装在一个方法内,我们不妨将此方法声明为同步的(使用synchronized关键字修饰方法),此方法也有锁,锁是this。

4.2.3 lock锁
ReentrantLock lock = new ReentrantLock();
 lock.lock();
 sell(name);
 lock.unlock();

5.案例演示

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

public class ThreadDemo {
    public static void main(String[] args) {
      
        //创建线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        //起名字
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

    }
}

5.1 同步代码块演示

public class MyThread extends Thread {

    //表示这个类所有的对象,都共享ticket数据
    static int ticket = 0;//0 ~ 99

    @Override
    public void run() {
            while (true) {
                synchronized (MyThread.class) {
                //同步代码块
                if (ticket < 100) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket++;
                    System.out.println(getName() + "正在卖第" + ticket + "张票!!!");
                } else {
                    break;
                }
            }
        }
    }
}

5.2 同步方法演示

public class MyRunnable implements Runnable {

    int ticket = 0;

    //同步方法的锁是this
    private synchronized boolean method() {
        //3.判断共享数据是否到了末尾,如果到了末尾
        if (ticket == 100) {
            return true;
        } else {
            //4.判断共享数据是否到了末尾,如果没有到末尾
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticket++;
            System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票!!!");
        }
        return false;
    }
}

5.3 lock锁演示

public class MyThread extends Thread{

    static int ticket = 0;

    static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        //1.循环
        while(true){
            //2.同步代码块
            //synchronized (MyThread.class){
            lock.lock(); //2 //3
            try {
                //3.判断
                if(ticket == 100){
                    break;
                    //4.判断
                }else{
                    Thread.sleep(10);
                    ticket++;
                    System.out.println(getName() + "在卖第" + ticket + "张票!!!");
                }
                //  }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值