ShanTianQi

线程(一)

线程


第一章 多线程

学习的程序在没有跳转语句的前提下,都是由上至下依次执行,那么现在详设计一个程序,边打游戏边听歌,要解决这个问题,就需要使用多进程或多线程


1.1并发与并行

20210708223907 20210708224345

注意:单核处理器的计算机肯定是不能并行处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。


1.2线程与进程

进入到内存的程序叫进程

20210708231151

应用程序到CPU的执行路径叫做线程

20210708232046

tips:一个程序运行后至少有一个进程,一个进程可以包含多个线程

线程调度

20210708235938 20210709000000

主线程

执行main方法的线程

单线程程序:java程序中只有一个线程

执行从main方法开始,从上到下依次执行

package Demo04;

public class Demo01MainThread {
    public static void main(String[] args) {
        Person p1=new Person("小明");
        p1.run();

        Person p2=new Person("小林");
        p2.run();
    }
}
package Demo04;

public class Person {
    private String name;

    public void run(){
        for (int i = 0; i < 20; i++) {
            System.out.println(name+"--->"+i);
        }
    }
    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
20210709105448

1.3创建线程方式一


Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。

第一种方法:Java中通过继承Thread类来创建并启动多线程的步骤如下:

void start()使该线程开始执行;java虚拟机调用该线程的run方法。结果是两个线程并发的运行,当前线程(main方法)和另一个线程(创建的新线程,执行其run方法)。多次启动一个线程是非法的,特别是当前线程已经结束执行后,不能再重新启动,java程序属于抢占式调度,哪个线程的优先级高,哪个线程就优先执行,同一优先级,随机选择一个执行。

package Demo04;

public class Demo02Thread {
    public static void main(String[] args) {
      MyThread mt=new MyThread();
      mt.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("main:"+i);
        }
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("run:"+i);
        }
    }
}

第二章 线程


2.1多线程原理

1.随机性打印结果

20210709111740

2.多线程内存图解

20210809181832

2.2Thread类

构造方法:

常用方法:

1.获取当前线程名称

package Demo04;

public class Demo03Thread2 extends Thread {
    public static void main(String[] args) {
        MyThread2 mt=new MyThread2();
        mt.start();//Thread-0
    }
}
  class MyThread2 extends Thread {
    @Override
    public void run() {
        //第一种方法
//        String name = getName();
//        System.out.println(name);
        //第二种方法
//        Thread thread = Thread.currentThread();
//        String name = thread.getName();
//        System.out.println(name);
        //链式编程
        System.out.println(Thread.currentThread().getName());
    }
}

2.设置线程的名称:(了解)

3.sleep

public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。毫秒数结束后,线程继续执行。

package Demo04;

public class Demo04Sleep {
    public static void main(String[] args) {
        //模拟秒表
        for (int i = 1; i <= 60; i++) {
            System.out.println(i);
            //使用Thread类的sleep方法让程序睡眠1秒钟
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

2.3创建线程方式二

采用java.lang.Runnable,我们只需要重写run方法即可。

步骤:

package com.indi.demo04;

public class Demo05Runnable {
    public static void main(String[] args) {
            RunnableImpl run =new RunnableImpl();
            Thread t =new Thread(run);
            t.start();
            for (int i = 0; i < 20; i++) {
                System.out.println(Thread.currentThread().getName()+"--->"+i);
            }
    }
}
class RunnableImpl implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}

2.4Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runnable接口的话,则很容易实现资源共享。

总结:

实现Runnable接口比继承Thread类所具有的优势:

1.适合多个相同的程序代码的线程去共享同一个资源。

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

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

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

扩充:在Java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用Java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实就是在操作系统中启动了一个进程。


2.5匿名内部类方式实现线程的创建

package com.indi.demo04;

public class Demo06InnerClassThread {
    public static void main(String[] args) {
        //线程的父类是Thread
        //new MyThread().start();
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName()+"--->"+i);
                }
            }
        }.start();
        //线程的接口Runnable
        //Runnable r =new RunnableImpl();//多态
        Runnable t=new Runnable(){
            //重写run方法,设置线程任务
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName()+"--->"+i);
                }
            }
        };
       new Thread(t).start();
       //简化接口的方式
        new Thread(new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName()+"--->"+i);
                }
            }
        }).start();
    }
}

第三章 线程安全


3.1线程安全

如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

我们通过一个案例,演示线程的安全问题:

电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是”“战狼三”,本次电影的座位共100个(本场电影只能卖100张票)。

我们来模拟电影院的售票窗口,实现多个窗口同时卖“葫芦娃大战奥特曼”这场电影票(多个窗口一起卖这100张票)

需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟

模拟票︰

20210810131523
package com.indi.demo06.ThreadSafe;

public class Demo01Ticket {
    public static void main(String[] args) {
      RunnableImpl mt =new RunnableImpl();
      Thread t0 =new Thread(mt);
      Thread t1 =new Thread(mt);
      Thread t2 =new Thread(mt);
      t0.start();
      t1.start();
      t2.start();
    }
}
package com.indi.demo07.Synchronized;
/*
   解决线程安全问题的第一种方案:使用同步代码块
 */

public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的票源
     private int ticket=100;
//     设置线程任务:卖票
    @Override
    public void run() {
//        使用死循环,让卖票操作重复执行
        while (true){
//            先判断票是否存在
            if (ticket>0){
                //提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"--->正在卖第"+ticket+"张票");
                ticket--;
            }
        }
    }
}

卖票案例出现了线程安全问题,卖出了不存在的票和重复的票。


3.2线程同步

当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。

要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java提供了同步机制(synchronized)来解决。

根据案例简述︰

窗口1线程进入操作的时候,窗口2和窗口3线程只能在外等着,窗口1操作结束,窗口1和窗口2和窗口3才有机会进入代码去执行。也就是说在某个钱程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。

有三种方式完成同步操作:


3.3同步代码块

格式:

synchronized(同步锁){
    需要同步操作的代码(访问了共享数据的代码)
}

同步锁

对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁。

1.锁对象 可以是任意类型。

2.多个线程对象 要使用同一把锁。

3.锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行。

注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就能进入代码块,其他的线程只能在外等着(BLOCKED)

package com.indi.demo07.Synchronized;

public class Demo01Ticket {
    public static void main(String[] args) {
      RunnableImpl mt =new RunnableImpl();
      Thread t0 =new Thread(mt);
      Thread t1 =new Thread(mt);
      Thread t2 =new Thread(mt);
      t0.start();
      t1.start();
      t2.start();
    }
}
package com.indi.demo07.Synchronized;
/*
   解决线程安全问题的第一种方案:使用同步代码块
 */

public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的票源
     private int ticket=100;
     Object obj = new Object();
//     设置线程任务:卖票
    @Override
    public void run() {
//        使用死循环,让卖票操作重复执行
        while (true){
        synchronized (obj){
            //            先判断票是否存在
            if (ticket>0){
                //提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"--->正在卖第"+ticket+"张票");
                ticket--;
            }
          }
        }
    }
} 

原理

20210810164723

3.4同步方法

格式:

public synchronized void method(){
    可能会产生线程安全问题的代码
}

同步锁是Who?

1.非静态方法

package com.indi.demo08.Synchronized;

public class Demo01Ticket {
    public static void main(String[] args) {
      RunnableImpl mt =new RunnableImpl();
      Thread t0 =new Thread(mt);
      Thread t1 =new Thread(mt);
      Thread t2 =new Thread(mt);
      t0.start();
      t1.start();
      t2.start();
    }
}
package com.indi.demo08.Synchronized;
/*
    解决线程安全问题的第二种方案:使用同步方法
    步骤:
        1.把访问了共享数据的代码抽取出来,放到一个方法中
        2.在方法上添加synchronized修饰符
*/
public class RunnableImpl implements Runnable {
     private int ticket=100;
    @Override
    public void run() {
        while (true){
            payTicket();
        }
    }
    public synchronized void payTicket(){
        if (ticket>0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"--->正在卖第"+ticket+"张票");
            ticket--;
        }
    }
}

2.静态方法

package com.indi.demo08.Synchronized;

public class Demo01Ticket {
    public static void main(String[] args) {
      RunnableImpl mt =new RunnableImpl();
      Thread t0 =new Thread(mt);
      Thread t1 =new Thread(mt);
      Thread t2 =new Thread(mt);
      t0.start();
      t1.start();
      t2.start();
    }
}
package com.indi.demo08.Synchronized;
/*
    解决线程安全问题的第二种方案:使用同步方法
    步骤:
        1.把访问了共享数据的代码抽取出来,放到一个方法中
        2.在方法上添加synchronized修饰符
*/
public class RunnableImpl implements Runnable {
     private static int ticket=100;
    @Override
    public void run() {
        while (true){
            payTicket();
        }
    }
    public static void payTicket(){
        synchronized(RunnableImpl.class){
            if (ticket>0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"--->正在卖第"+ticket+"张票");
                ticket--;
            }
        }
}
}

3.5Lock锁

java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。

Lock锁也称同步锁,加锁与释放锁方法:

java.util.concurrent.locks.ReentrantLock impLements Lock接口

使用步骤:

package com.indi.demo09.Lock;

public class Demo01Ticket {
    public static void main(String[] args) {
      RunnableImpl mt =new RunnableImpl();
      Thread t0 =new Thread(mt);
      Thread t1 =new Thread(mt);
      Thread t2 =new Thread(mt);
      t0.start();
      t1.start();
      t2.start();
    }
}
package com.indi.demo09.Lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/*
* 解决线程安全问题的第三种方案:使用Lock锁
*
* */
public class RunnableImpl implements Runnable {
     private int ticket=100;

     //1.在成员位置创建一个ReentrantLock对象
    Lock l =new ReentrantLock();

    @Override
    public void run() {
        while (true){
            l.lock();
            if (ticket>0){
                try {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName()+"--->正在卖第"+ticket+"张票");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    l.unlock();//无论程序是否异常,都会把锁释放
                }
            }
        }
    }
}

#线程#多线程