多线程基础

多线程

并发与并行

并发:指两个或多个时间在同一时间段内发生(交替执行)
并行:指两个或多个事件在同一时刻内发生(同时发生)

线程与进程

进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时执行运行多个进程;系统运行一个程序就是一个进程从创建到消亡的过程。晋城市资源分配的最小单位
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中可以有多个线程。线程是程序执行的最小单位。

线程的调度

分时调度:
抢占式调度

主线程

执行主(main)方法的线程

单线程程序:java程序只有一个线程,执行从main方法开始,从上到下依次执行

JVM执行main方法,main方法会进入带栈内存
JVM会找操作系统开辟一条main方法通向cpu的执行路径
cpu就可以通过这条路径来执行main方法
而这个路径有一个名字。叫mian(主)线程

创建多线程的第一种方法

创建Thread类的子类

实现步骤:
1.创建一个Thread类的子类
2.在Thread类的子类中重写Thread类中的run方法,设置线程任务
3.创建Thread类的子类对象
4.调用Thread类中的start()方法

public class Mytread extends Thread{
    @Override
    public void run() {
        for (int i =0;i<20;i++){
            System.out.println("run"+i);
        }
    }
}
public static void main(String[] args) {
        Mytread n = new Mytread();
        n.start();
        for (int j =0;j<120;j++){
            System.out.println("main"+j);
        }
run0
main0
main1
main2
main3
main4
main5
main6
main7
main8
main9
main10
main11
main12
main13
...
多线程内存图解

在这里插入图片描述

创建多线程的第二种方法

实现Runnable接口
java.lang.Runnable
Runnable接口应该由那些打算通过某一线程执行其实实例的类来实现。类 必须定义一个称为run的无参数方法。
java.lang.Thread类的构造方法
Thread(Runnable target) 分配新的Thread对象。
Thread(Runnable target),String name)分配新的Thread对象

实现步骤:
1.创建一个Runnable 接口的实现类
2.再实现类中重写Runnable 接口的run方法,设置线程任务
3.创建一个Runnable 接口的实现类方法
4.创建一个Thread类对象,构造方法中传递Runnable 接口的实现类对象
5.调用Thread类中的start方法,开启新的线程执行run方法

//1.创建一个Runnable 接口的实现类
public class RunnableImpl implements Runnable  {
//2.再实现类中重写Runnable 接口的run方法,设置线程任务
    @Override
    public void run() {
        for (int i =0;i<20;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
 public static void main(String[] args) {
 //3.创建一个Runnable 接口的实现类方法
    RunnableImpl m = new RunnableImpl();
    //4.创建一个Thread类对象,构造方法中传递Runnable 接口的实现类对象
        Thread t = new Thread(m);
        //5.调用Thread类中的start方法,开启新的线程执行run方法
    t.start();
            for (int i =0;i<20;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
main:0
Thread-0:0
main:1
Thread-0:1
main:2
Thread-0:2
main:3
Thread-0:3
main:4
Thread-0:4
...
Runnable和Thread的区别

Runnable接口创建多线程的好处
1.避免了单继承局限性:一个类只能继承一个类。类继承了Thread类就不能再继承其他的类,但实现Runnable接口还可以实现其他接口
2.增强了程序的扩展性,降低了程序的耦合性(解耦)
实现Runable接口的方式,把设置线程任务和开启新县城进行了分离(解耦)
实现类中,重写run方法“用来设置线程任务
创建Thread对象,调用Start方法:用来开启新线程

匿名内部类方法实现线程创建

匿名内部类作用:简化代码
把子类继承父类,重写父类的方法,创建子类对象和一步完成。
匿名内部类的最终产物:子类/实现类对象,而这个类没有名字

public static void main(String[] args) {
        //线程的父亲是Thread
    new Thread(){
        @Override
        public void run() {
            for (int i =0;i<20;i++){
                System.out.println(Thread.currentThread().getName()+"="+i);

            }
        }
    }.start();
    //线程的接口是RunnableImpl();多态
        RunnableImpl n = new RunnableImpl() {
            @Override
            public void run() {
                for (int i = 0; i < 40; i++) {
                    System.out.println(Thread.currentThread().getName() + "=" + i);

                }
            }
        };
        new Thread(n).start();
            //简化接口方式
        new Thread(new RunnableImpl() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + "=" + i);

                }
            }
        }).start();
    }

Thread类

1.Thread类的常用方法

1.1获取线程名称

获取线程名称“
1.使用Thread类中的方法getName()
String getName()返回该线程名称
2.可以先获取到当前正在执行的线程,使用线程中的getName()获取线程的名称
static Thread currenThread()返回当前正在执行的线程对象的引用。

public class Mytread extends Thread{
    @Override
    public void run() {
            //获取线程名称
        Thread s = Thread.currentThread();
        System.out.println(s);
        String y = getName();
        System.out.println(y);


    }
}
public class Mytread extends Thread{
    @Override
    public void run() {
            //获取线程名称
        //获取当前正在进行的线程
        Thread s = Thread.currentThread();
        System.out.println(s);
        //获取线程名称
        String y = getName();
        System.out.println(y);


    }
}
main
Thread[Thread-0,5,main]
Thread[Thread-2,5,main]
Thread-2
Thread[Thread-1,5,main]
Thread-1
Thread-0
1.2设置线程名称

设置线程名称“
1.使用Thread类中的方法setName(名字)
void setName(String name)改变该线程名称
2.先创建一个带参数的构造方法,参数传递线程的名称;调用父类的带参构造方法,把线程名称传递给父类,让父类给子类线程起一个名字
Thread(String name)分配新的Thread对象。

public class Mytread extends Thread{
    public Mytread(){}
    public Mytread(String name){
        super(name);
    }
    @Override
    public void run() {

            //获取线程名称
        //获取当前正在进行的线程
        Thread s = Thread.currentThread();
        System.out.println(s);
        //获取线程名称
        String y = getName();
        System.out.println(y);


    }
}
  public static void main(String[] args) {
        Mytread n = new Mytread();
        //第一种方式
        n.setName("l流星");
        n.start();

        //第二种方法
        Mytread n2 = new Mytread("白天");
        n2.start();


    }
Thread[白天,5,main]
Thread[l流星,5,main]
l流星
白天
1.3其他常用方法

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

public static void main(String[] args) {
        for(int i =0;i<100;i++){
            System.out.println(i);
            //每隔一秒打印一次数据
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }


    }

线程安全

案例代码

public class RunnableImpl implements Runnable  {
static private int tic = 20;
    @Override
    public void run() {

            while(true){
                if (tic > 0) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在卖票" + tic);

                    tic--;
            }
            }
        }
```java
public class mipaio {
    public static void main(String[] args) {
        RunnableImpl n1= new RunnableImpl();
        Thread m = new Thread(n1);
        Thread m2 = new Thread(n1);
        Thread m3 = new Thread(n1);
        m.start();
        m2.start();
        m3.start();

    }
}
Thread-2正在卖票20
Thread-0正在卖票20
Thread-1正在卖票20
Thread-0正在卖票17
Thread-1正在卖票17
Thread-2正在卖票17
Thread-1正在卖票14
Thread-0正在卖票14
Thread-2正在卖票14
Thread-0正在卖票11
Thread-1正在卖票11
Thread-2正在卖票11
Thread-0正在卖票8
Thread-1正在卖票8
Thread-2正在卖票8
Thread-1正在卖票5
Thread-2正在卖票5
Thread-0正在卖票5
Thread-2正在卖票2
Thread-1正在卖票2
Thread-0正在卖票2
Thread-2正在卖票-1

1. 解决线程安全问题

解决同步问题

1.1同步代码块

synchronized关键字可以用于方法中的某个区域,表示只对这个区块的资源实行互斥访问
格式:
synchronized(同步锁){
需要同步操作的代码
}

注意:
1.通过同步代码块中的锁对象,可以使用任意对象
2.但是必须保证多个线程使用的锁对象是同一个
3.锁对象的作用:把同步代码块锁住,只让一个线程在同步代码块中执行

public class RunnableImpl implements Runnable  {
static private int tic = 20;
//创建一个锁对象
    Object oj = new Object();
    @Override
    public void run() {

            while(true){
            synchronized (oj) {
                if (tic > 0) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在卖票" + tic);

                    tic--;
                }

            }
            }
        }
    }
Thread-0正在卖票20
Thread-0正在卖票19
Thread-0正在卖票18
Thread-0正在卖票17
Thread-0正在卖票16
Thread-0正在卖票15
Thread-0正在卖票14
Thread-0正在卖票13
Thread-0正在卖票12
Thread-0正在卖票11
Thread-0正在卖票10
Thread-1正在卖票9
Thread-1正在卖票8
Thread-1正在卖票7
Thread-1正在卖票6
Thread-1正在卖票5
Thread-1正在卖票4
Thread-1正在卖票3
Thread-1正在卖票2
Thread-1正在卖票1
1.2同步方法

实现步骤:
1.把访问共享数据的代码抽取出来,放到一个方法中
2.把方法上添加synchronized修饰符

格式:
修饰符 synchronized 返回值类型 方法名(参数列表){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}

同步方法的锁对象为synchronized(this)

public class RunnableImpl implements Runnable  {
static private int tic = 20;

    @Override
    public void run() {
    while(true){
    //调用同步方法
        i();
}
        }
    public synchronized void i(){
		//synchronized (this)
       //将使用共享数据的代码发入
                if (tic > 0) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在卖票" + tic);
                    tic--;
                }
    }
1.2.1 静态同步方法

静态同步方法的锁对象不能为this.this是创建对象之后产生的,静态方法优先于对象
静态方法的锁对象是本类的class属性—>class文件对象(反射)

public class RunnableImpl implements Runnable  {
static private int tic = 20;
    @Override
    public void run() {
        while(true){
            i();
        }


        }
    public static synchronized void i(){



            if (tic > 0) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在卖票" + tic);

                tic--;
            }
    }
Lock锁

第三种解决方案:使用Lock锁
在这里插入图片描述

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

public class RunnableImpl implements Runnable  {
static private int tic = 100;
 Lock lock =  new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            //在可能出现安全问题前获取锁
            lock.lock();
            if (tic > 0) {
                try {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName() + "正在卖票" + tic);

                    tic--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //无论是否产生错误,都释放锁
                    lock.unlock();
                }

            }

        }
    }
}
同步技术原理

同步技术使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器
同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步
注意:
1.同步保证了只能有一个线程在同步中执行共享数据,保证安全
2.程序频繁的判断锁,获取锁,释放锁,程序的效率会降低

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值