2022-08-02 第8组 韩文清 多线程

一.创建线程

    在Java中,创建线程有3种方式

(1)继承Thread类,并且重写run方法

   (2) Thread类中的方法不是抽象方法,Thread也不是抽象类

 (3)MyThread类继承了Thread之后,他就是一个线程类

     **启动线程,执行重写的run方法·4

 //重写run方法
class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("重写的run方法。。。");
    }
}

**要想让线程启动,调用线程的start方法

public class Ch01 {
    public static void main(String[] args) {
        System.out.println(1);
        MyThread myThread=new MyThread();
        //当调用start方法启动一个线程时会执行重写的run方法的代码
        //调用的是start,执行的是run,为什么不直接调run
        myThread.start();
        //普通的对象调方法,没有启动线程
        //myThread.run();
        //线程的优先级,概率问题,做不到百分百
        //90%会先跑主方法,10%的几率会跑MyThread中的run方法

        System.out.println(3);
        System.out.println(4);
    }
}

思考:调用的是start,执行的是run,那为什么不直接调run呢?

结论:用普通的对象调方法,不会启动线程。

           如果想让线程启动,必须调用Thread中的start方法。

(4)线程的优先级:

         概率问题,做不到百分百,有90%的会先跑主方法,10%的几率会跑MyThread中的run方法

二.实现Runnable接口:

     创建实现类的线程对象

     调用start方法

public class Ch02 {
    public Ch02() {
    }

    public static void main(String[] args) {
        System.out.println(1);
        MyThread2 myThread2 = new MyThread2();
        Thread t = new Thread(myThread2);
        t.start();
        System.out.println(3);
        System.out.println(4);
    }
}

三.使用箭头函数(lambda)

Lambda表达式格式:()-> {}

                                 (参数列表) -> {代码}

                  格式说明:

                - 小括内的语法与传统方法参数列表一致,没有参数就留空,有多个参数就用逗号分隔

                -  【->】  是新引入的语法格式,代表指向动作

                - 大括号内的语法与传统方法体要求一致

                1.小括号中书写的内容和接口中的抽象方法的参数列表一致

                2.大括号中书写的内容和实现接口中的抽象方法体一致

                3.箭头是固定的

 

public class Ch03 {
    public Ch03() {
    }

    public static void main(String[] args) {
        System.out.println(1);
        (new Thread(() -> {
            System.out.println(2);
        })).start();

        try {
            Thread.sleep(1000L);
        } catch (InterruptedException var2) {
            var2.printStackTrace();
        }

        System.out.println(3);
        System.out.println(4);
    }
}

   四.实现Callable接口

、定义一个线程任务类实现Callable接口,声明线程执行的结果类型。                                                         
2、重写线程任务类的call()方法,这个方法可以直接返回执行的结果。
3、创建一个Callable的线程任务对象。
4、把Callable的线程任务对象包装成一个未来任务对象。
5、把未来任务对象包装成线程对象。
6、调用线程start()方法,启动线程
7、获取线程执行结果。
————————————————

优点:

线程任务只是实现了Callable接口,可以继续继承其他类,而且可以继续实现其他接口。
同一个线程任务对象可以被包装成多个线程对象
适合多个线程去共享同一个资源。
实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立。
线程池可以放入Runnable接口和Callable接口
可以得到线程执行结果。
————————————————

 

package com.zjl.study.多线程;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/**
 * Created by zjl 2022/5/28
 **/
public class 创建线程方式3 {

  public static void main(String[] args) {

    // 3、创建一个Callable的线程任务对象。
    MyCallable myCallable = new MyCallable();

    // 4、把Callable的线程任务对象包装成一个未来任务对象。
    FutureTask<String> futureTask = new FutureTask<>(myCallable);

    // 5、把未来任务对象包装成线程对象。
    Thread thread = new Thread(futureTask);

    // 6、调用线程start()方法,启动线程。
    thread.start();

    // 7、获取线程执行结果。如果此时获取结果的任务还未执行完成,会让出CPU,直至任务执行完成才获取结果。
    try {
      String s = futureTask.get();
      System.out.println(s);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

}


// 1、定义一个线程任务类实现Callable接口,声明线程执行的结果类型。
class MyCallable implements Callable<String> {

  // 2、重写线程任务类的call()方法,这个方法可以直接返回执行的结果。
  @Override
  public String call() throws Exception {
    return "子线程任务执行,线程名称为:" + Thread.currentThread().getName();
  }
}

五.守护线程

     java里面提供两种类型的线程:用户线程 , 守护线程

1.用户线程(我们创建线程时,默认的一类线程,属性 daemon = false

1.守护线程(属性 daemon = true 

     用户线程和守护线程的关系:

用户线程就是运行在前台的线程,

守护线程就是运行在后台的线程

一般情况下,守护线程是为用户线程提供一些服务 (典型的守护线程是垃圾回收线程 )

当不存在非守护线程时,守护线程自动销毁。

public class Ch05 extends Thread {

    @Override
    public void run() {
        super.run();
    }

    public static void main(String[] args) {

        Ch05 ch05 = new Ch05();
        // ch05就变成了守护线程
        ch05.setDaemon(true);
        ch05.start();
    }
}

六.线程的生命周期

  NEW:这个状态主要是线程未被start()调用执行
* RUNNABLE:线程正在JVM中被执行,等待来自操作系统的调度
* BLOCKED:阻塞。因为某些原因不能立即执行需要挂起等待。
* WAITING:无限期等待。Object类。如果没有唤醒,则一直等待。
* TIMED_WAITING:有限期等待,线程等待一个指定的时间
* TERMINATED:终止线程的状态,线程已经执行完毕。
*
* 等待和阻塞两个概念有点像,阻塞因为外部原因,需要等待,
* 而等待一般是主动调用方法,发起主动的等待。等待还可以传入参数确定等待时间。
public class Ch06 {

    public static void sleep(int i) {
        try {
            // 线程休眠1秒
            Thread.sleep(i);
            System.out.println("哈哈哈...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        sleep(3000);
    }
}

七.网络安全

1.cpu多核缓存结构

   cpu缓存为了提高程序运行的性能,现在cpu在很多方面对程序进行优化

    cpu处理速度最快,内存次之,硬盘速度最低

     在cpu处理内存数据时,如果内存运行速度太慢,就会拖累cpu的速度,为了解决这样的问题,cpu设计了多种缓存数据

2.cpu的缓存:L1 ,L2 ,L3 

    每个cpu都有L1 L2 缓存,但是L3缓存是多核公用的

    cpu查找数据时,cpu->L1->L2->L3->内存->硬盘

     从cpu到内存 60~80纳秒    从cpu到L3 15纳秒 从cpu到L2 3纳秒 从cpu到L1 1纳秒 到寄存器 0.3纳秒

3.指令重排

我们发现测试结果中大部分感觉是正确的(0,1)或(1,0)
按道理来说绝对不会出现(0,0),如果出现(0,0)代表存在指令重排,乱序执行
使用volatile关键字保证一个变量在一次读写操作时,避免指令重排
我们在读写操作之前加入一条指令,当CPU碰到这条指令时必须等到前面的指令执行完,才能执行下一条指令

public class Ch02 {
    private static int x=0,y=0;
    private static int a=0,b=0;
    private static int count =0;

    private static volatile int NUM=1;
    public static void main(String[] args) throws InterruptedException {
        long start=System.currentTimeMillis();
        for(;;){
            Thread t1=new Thread(()->{
                a=1;
                x=b;
            });
            Thread t2=new Thread(()->{
                b=1;
                y=a;
            });
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println("一共执行了"+count+++"次");
            if(x==0&&y==0){
                long end = System.currentTimeMillis();
                System.out.println("耗时"+(end-start)+"毫秒"+x+","+y);
                break;
            }
            a=0;b=0;x=0;y=0;
            /*
            我们发现测试结果中大部分感觉是正确的(0,1)或(1,0)
            按道理来说绝对不会出现(0,0),如果出现(0,0)代表存在指令重排,乱序执行
            使用volatile关键字保证一个变量在一次读写操作时,避免指令重排
            我们在读写操作之前加入一条指令,当CPU碰到这条指令时必须等到前面的指令执行完,才能执行下一条指令
            【内存屏障】

            * */
        }
    }

}

4.可见性

可见性
thread线程一直在高速读取缓存中的isOver,不能感知主线程已经把isOver
这就是线程的可见性的问题

怎么解决?
volatile能够强制改变变量的读写直接在内存中操作

5.线程争抢

说到解决多线程中资源争抢的问题,大多数第一个想到的关键字就是synchronized,它能够保证多个线程之间资源访问的同步性(即它修饰的方法或者代码块在任意时刻只能有一个线程执行)。

一个座位一个人 两个电影窗口卖票 不加锁会造成什么结果?

public class Seat implements Runnable {
	private int seatNumber = 100;
 
	@Override
	public void run() {
		while (true) {
			if (seatNumber > 0) {
				try {
					Thread.sleep(30);
					--seatNumber;
					System.out.println(Thread.currentThread().getName() + "占用1个座位,还剩余 " + seatNumber + "个座位");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			} else {
				System.out.println(Thread.currentThread().getName() + ":不好意思,票卖完了!");
				break;
			}
		}
	}
 
	public static void main(String[] args) {
		Seat mr = new Seat();
		Thread t1 = new Thread(mr, "A窗口");
		Thread t2 = new Thread(mr, "B窗口");
		t1.start();
		t2.start();
	}
}

6.线程安全的实现方法 

第一种实现线程安全的方式:同步代码块,即用synchronized关键字

第二种方法:同步方法,也是用synchronized关键字,只是这个关键字用在方法上了,把线程共享的数据块抽象成方法,在方法上加了同步锁。

第三种方法:使用Lock锁机制,对线程不安全的代码块采用lock()加锁,使用unlock()解锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值