[转]多线程

 

 

 

多线程

并发和并行

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

线程和进程

  • 进程:进入到内存中的程序就是进程
  • 线程:在某一个应用程序运行某一个功能时,系统会为该功能开启一个通往cpu的通道,cup通过这个通道可以执行该功能。这个通道就是线程。

线程调度

    分时调度
        所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
    抢占式调度
        优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

Thread类

    线程开启我们需要用到了 java.lang.Thread 类。

构造方法:

  • public Thread() :分配一个新的线程对象。
  • public Thread(String name) :分配一个指定名字的新的线程对象。
  • public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
    public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。

常用方法:

  • public String getName() :获取当前线程名称。
  • public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
  • public void run() :此线程要执行的任务在此处定义代码。
  • public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
    public static Thread currentThread() :返回对当前正在执行的线程对象的引用。

创建线程的方式

    继承Thread类
        步骤如下
            1.创建一个类,继承Thread类
            2.重写run方法–>设置线程任务(线程类中要干的事情)
            3.创建Thread的子类对象
            4.调用Thread中的Start()(使该线程开始执行),java虚拟机会自动调用该线程的run方法

//1.创建一个类,继承Thread类
public class Thread01 extends Thread {
	//2.重写run方法–>设置线程任务(线程类中要干的事情)
    @Override
    public void run(){
        for (int i = 0; i < 10; i++) {
            try {
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"执行了"+i);
        }
    }
}
public static void main(String[] args) {
		//3.创建Thread的子类对象
        Thread01 thread01 = new Thread01();
		//  4.调用Thread中的Start()(使该线程开始执行),java虚拟机会自动调用该线程的run方法
        thread01.start();
    }

        注意
              一个线程对象不能连续调用start()
              java程序属于抢占式调度,cup会在多个线程之间做高速切换,会导致每一次运行结果都不一致。

    实现Runnable接口方式演示
        步骤如下:
            1. 定义Runnable接口的实现类。
            2. 重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
            3. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
            4. 调用线程对象的start()方法来启动线程。

// 1. 定义Runnable接口的实现类。
public class Thread02 implements Runnable {
	//2. 重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"执行了"+i);
        }
    }
}

public static void main(String[] args) {
		//3. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
        Thread02 thread02 = new Thread02();
        Thread thread = new Thread(thread02);
		//4. 调用线程对象的start()方法来启动线程。
        thread.start();
    }

创建多线程程序两种方式的区别

  • 使用Runnable接口的方式实现多线程程序,可以避免单继承的局限性。
  • 使用Runnable接口的方式实现多线程程序,可以把设置线程任务和开启线程进行解耦。

    匿名内部类形式创建多线程
        好处
            使用线程的内匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作。
        步骤
            使用匿名内部类的方式实现Runnable接口,重写Runnable接口中的run方法

public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+i);
                }
            }
        }).start();
    }

高并发及线程安全

    高并发:
        是指在某个时间点上,有大量的用户(线程)同时访问同一资源。例如:天猫的双11购物节、12306的
        在线购票在某个时间点上,都会面临大量用户同时抢购同一件商品/车票的情况。
    线程安全:
        在某个时间点上,当大量用户(线程)访问同一资源时,由于多线程运行机制的原因,可能会导致被访问的资源出现"数据污染"的问题。

多线程的安全性问题

可见性
    启动一个线程,在线程中将一个变量的值更改,而主线程却一直无法获得此变量的新值

//线程类
public class Thread04_KeJianXing implements Runnable {
    public static int a = 0;
    @Override
    public void run() {
        System.out.println("先睡两秒再说!");
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("醒了!改变一下a的值!");
        a = 1;
        System.out.println("修改执行完毕!");
    }
}

//测试类
 public static void main(String[] args) {
        Thread04_KeJianXing t = new Thread04_KeJianXing();

        Thread thread = new Thread(t);

        thread.start();


        while(true){
            if(Thread04_KeJianXing.a == 1){
                System.out.println("main线程执行完毕");
                break;
            }
        }
    }

运行结果
在这里插入图片描述
循环没有停止!说明没有读取到改变后的a值。

有序性
    有些时候“编译器”在编译代码时,会对代码进行“重排”,例如:
        int a = 10; //1
        int b = 20; //2
        int c = a + b; //3
    第一行和第二行可能会被“重排”:可能先编译第二行,再编译第一行,总之在执行第三行之前,会将1,2编译完毕。1和2先编译谁,不影响第三行的结果。
    但在“多线程”情况下,代码重排,可能会对另一个线程访问的结果产生影响

原子性

//线程类
public class MyThread extends Thread {
	public static int a = 0;
	@Override
	public void run() {
		for (int i = 0; i < 10000; i++) {
				a++;
		} 
		System.out.println("修改完毕!");
	}
}
//测试类
public class Demo {
	public static void main(String[] args) throws InterruptedException {
		//1.启动两个线程
		MyThread t1 = new MyThread();
		MyThread t2 = new MyThread();
		t1.start();
		t2.start();
		Thread.sleep(1000);
		System.out.println("获取a最终值:" + MyThread.a);//总是不准确的。原因:两个线程访问a的步骤不具有:原子性
	}
}

原因:两个线程访问同一个变量a的代码不具有"原子性"
    1. 线程t1先读取a 的值为:0
    2. t1被暂停
    3. 线程t2读取a的值为:0
    4. t2将a = 1
    5. t2将a写回主内存
    6. t1将a = 1
    7. t1将a写回主内存(将t2更改的1,又更改为1)
所以两次加1,但结果仍为1,少加了一次。

volatile关键字

概念

  • volatile是一个"变量修饰符",它只能修饰"成员变量",它能强制线程每次从主内存获取值,并能保证此变量不
  • 会被编译器优化。
  • volatile能解决变量的可见性、有序性;
  • volatile不能解决变量的原子性

volatile解决可见性

//线程类
public class Thread04_KeJianXing implements Runnable {
    public volatile static int a = 0;//增加volatile关键字
    @Override
    public void run() {
        System.out.println("先睡两秒再说!");
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("醒了!改变一下a的值!");
        a = 1;
        System.out.println("修改执行完毕!");
    }
}

//测试类
 public static void main(String[] args) {
        Thread04_KeJianXing t = new Thread04_KeJianXing();

        Thread thread = new Thread(t);

        thread.start();


        while(true){
            if(Thread04_KeJianXing.a == 1){
                System.out.println("main线程执行完毕");
                break;
            }
        }
    }

运行结果
在这里插入图片描述
当变量被修饰为volatile时,会迫使线程每次使用此变量,都会去主内存获取,保证其可见性

volatile解决有序性

    当变量被修饰为volatile时,会禁止代码重排

volatile不能解决原子性


---------------------
作者:次奥QNMLGB
来源:CSDN
原文:https://blog.csdn.net/weixin_44564242/article/details/104999588?depth_1-utm_source=distribute.pc_category.none-task&request_id=&utm_source=distribute.pc_category.none-task
版权声明:本文为作者原创文章,转载请附上博文链接!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值