多线程基础

线程基础

一、线程与进程的区别

1.什么是进程

进程就是在运行的程序,他是线程的集合  (进程中有多个b不同的执行路径,多个线程的集合,进程中一定有一个线程,这个线程就是主线程)

2. 什么是线程

线程就是进程的一个正在独立运行的一条执行路径(一个执行顺序,一个执行流程,执行路径)

3.什么是多线程

就是为了提高程序的i效率

总:使用多线程,是为了提高程序效率,每个线程互不影响,都是自己在独立运行(例:同时下载多个东西)

CPU处理进程的时候是采用时间片轮转的方式

二、多线程创建方式(五种)

1.继承Thread类

class CreateThread extends Thread {
	// run方法中编写 多线程需要执行的代码
	publicvoid run() {}
}
publicclass ThreadDemo {
	publicstaticvoid main(String[] args) {
		CreateThread createThread = new CreateThread();
		createThread.start();	
	}

}

2.实现Runnable接口

class CreateRunnable implements Runnable {
	@Override
	publicvoid run() {}

}
publicclass ThreadDemo2 {
	publicstaticvoid main(String[] args) {
		CreateRunnable createThread = new CreateRunnable();
		Thread thread = new Thread(createThread);
		thread.start();
	}
}

3.使用匿名内部类方式

public class ThreadDemo3 {
 
    public static void main(String[] args) {
        //1)通过thread子类创建匿名内部类
        new Thread() {
            public void run() {
                System.out.println("线程开始执行......");
            };
        }.start();
        
        //2)通过线程任务的方式创建匿名内部类
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程开始执行......");
                
            }
        }).start();
    }

4.通过Callable和Future/FutureTask创建线程 (获取有返回值线程)

    a. 创建Callable接口的实现类,并实现call()方法;
    b. 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callback对象的call()方法的返回值;
    c. 使用FutureTask对象作为Thread对象的target创建并启动新线程;
    d. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
 

public class CallableDemo implements Callable{     
    @Override  
     public Object call() {
      //处理逻辑
   }
}  
public class CallableTest {     
        public static void main(String[] args) {  
//--------------Future---------
            //创建线程池  
    //      ExecutorService es = Executors.newSingleThreadExecutor();  
    //      //创建Callable对象任务  
    //      CallableDemo calTask=new CallableDemo();  
    //      //提交任务并获取执行结果  
    //      Future future =es.submit(calTask);  
    //      //关闭线程池  
    //      es.shutdown();  
//--------------FutureTask---------              
            //创建线程池  
            ExecutorService es = Executors.newSingleThreadExecutor();  
            //创建Callable对象任务  
            CallableDemo calTask=new CallableDemo();  
            //创建FutureTask  
            FutureTask  futureTask=new FutureTask<>(calTask);  
            //执行任务  
            es.submit(futureTask);  
            //关闭线程池  
            es.shutdown();  
       
            if(future.get()!=null){  
                //输出获取到的结果  
                System.out.println("future.get()-->"+future.get());  
            }else{  
                //输出获取到的结果  
                System.out.println("future.get()未获取到结果");  
            }         
            } catch (Exception e) {  
                e.printStackTrace();  
            }  
            System.out.println("主线程在执行完成");  
        }  
    } 

5.通过线程池创建线程

 ExecutorService pool = Executors.newFixedThreadPool(5);
 Future future1 = pool.submit(new MyTask());
  pool.shutdown();

//submit和execute区别
 主要有三个区别:
1、接收的参数不一样。
        ExecutorService的submit(Callable<T> task)参数是一个Callable对象,
submit(Runnable task)也可以接受Runnable对象;
executorService.execute(Runnable command)的参数是一个Runnable对象。

2、submit有返回值,而execute没有。
     Method submit extends base method Executor.execute by creating and returning 
a Future that can be used to cancel execution and/or wait for completion.
 用到返回值的例子,比如说我有很多个做validation的task,我希望所有的task执行完,
 然后每个task告诉我它的执行结果,是成功还是失败,
 如果是失败,原因是什么。然后我就可以把所有失败的原因综合起来发给调用者。
 个人觉得cancel execution这个用处不大,很少有需要去取消执行的,而最大的用处应该是第二点。

3、submit方便Exception处理。
--------------------- 

注:继承Thread和实现Runnable接口哪个好?

       使用实现实现Runnable接口好,原因实现了接口还可以继续继承,继承了类不能再继承

 

常用线程api方法

start()

启动线程

currentThread()

获取当前线程对象

getID()

获取当前线程ID      Thread-编号  该编号从0开始

getName()

获取当前线程名称

sleep(long mill)

休眠线程

Stop()

停止线程,

常用线程构造函数

Thread()

分配一个新的 Thread 对象

Thread(String name)

分配一个新的 Thread对象,具有指定的 name正如其名。

Thread(Runable r)

分配一个新的 Thread对象

Thread(Runable r, String name)

分配一个新的 Thread对象

三、守护线程

 Java中有两种线程,一种是User Thread用户线程,另一种是Daemon Thread守护线程。

通俗的来说:任何一个守护线程都是整个JVM中所有非守护线程的保姆

 用户线程是指用户自定义创建的线程,主线程停止,用户线程不会停止(非守护线程,和主线程互不影响)

守护线程当进程不存在或主线程停止,守护线程也会被停止。

 使用setDaemon(true)方法设置为守护线程

Daemon 的作用是为其他线程提供便利服务,守护线程最典型的应用就是GC(垃圾回收)

四、运行状态

 

join作用

是让其他线程变为等待,    t1.join();// 让其他线程变为等待,直到当前t1线程执行完毕,才释放。

thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程AJoin()方法,直到线程A执行完毕后,才会继续执行线程B

优先级

通过一个int priority来控制优先级,范围为1-10,其中10最高,默认值为5

           Thread t1 = new Thread();

           t1.start();

           // 注意设置了优先级, 不代表每次都一定会被执行。 只是CPU调度会有限分配

           t1.setPriority(10);

           t2.start();

 

六、多线程之间实现同步

 

1.为什么有线程安全问题?

当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题

2.如何解决多线程之间线程安全问题?

使多线程之间同步,即保证数据的原子性

synchronized  自动挡

lock  jdk1.5并发包  手动

3.为什么使用线程同步或使用锁能解决线程安全问题呢?

答:将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。代码执行完成后释放锁,让后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。

4.问:什么是多线程之间同步?

答:当多个线程共享同一个资源,不会受到其他线程的干扰。

 

方式:

 同步代码块      

可能会发生线程安全问题的代码,给包括起来。

private static Object oj = new Object();   	
public void sale() {
		// 前提 多线程进行使用、多个线程只能拿到一把锁。
		// 保证只能让一个线程 在执行 缺点效率降低
		 synchronized (oj) {
		if (count > 0) {
			System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
			count--;
		}
		 }
	}

对象如同锁,持有锁的线程可以在同步中执行 

没持有锁的线程即使获取CPU的执行权,也进不去 

同步的前提: 

1,必须要有两个或者两个以上的线程 

2,必须是多个线程使用同一个锁 

必须保证同步中只能有一个线程在运行 

原理:有一个线程已经拿到锁了其他线程已经有了cpu的执行权一直在排队 等待其他线程释放资源,

锁的释放  是在代码执行完毕或者程序抛出异常

好处:解决了多线程的安全问题 

弊端:多个线程需要判断锁,较为消耗资源、抢锁的资源,效率低

同步函数

在方法上修饰synchronized 称为同步函数

public synchronized void sale() {
        
    }

同步函数使用this锁。

静态同步函数

什么是静态同步函数?

 方法上加上static关键字,使用synchronized 关键字修饰 或者使用类.class文件。

静态的同步函数使用的锁是  该函数所属字节码文件对象

可以用 getClass方法获取,也可以用当前  类名.class 表示。

synchronized (ThreadTrain.class) {

          }

 

注:

synchronized 修饰方法使用锁是当前this锁。

synchronized 修饰静态方法使用锁是当前类的字节码文件

 

 什么是多线程死锁?

   :同步中嵌套同步,导致锁无法释放

 

七、多线程的三大特性

1.原子性、

即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

独一无二,一致性,保证线程安全问题

2.可见性、(java内存模型)

当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。

Java提供了volatile关键字来保证可见性。(volatile保证线程之间的可见性,但不保证原子性

jdk1.5并发包提供了很多原子类 ,可以通过AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减。保证原子性

3.有序性

程序执行的顺序按照代码的先后顺序执行。

join,wait,notfi(多线程之间通讯)

:Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序

注:并发编程中两大核心:JMM抽象内存模型以及happens-before规则、三大特性(原子性、有序性、可见性)

八、java内存模型

Java内存模型-----属于 多线程可见性 jmm

java内存结构------属于 java内存分配

1.java内存模型 决定了一个线程与另一个线程 是否可见

2.java内存模型 主内存、(主要存放共享的全局变量)、私有本地内存(本地线程私有变量)

 内容:---  共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。

从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:

1. 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。

2. 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。

下面通过示意图来说明这两个步骤

 

如上图所示,本地内存A和B有主内存中共享变量x的副本。假设初始时,这三个内存中的x值都为0。线程A在执行时,把更新后的x值(假设值为1)临时存放在自己的本地内存A中。当线程A和线程B需要通信时,线程A首先会把自己本地内存中修改后的x值刷新到主内存中,此时主内存中的x值变为了1。随后,线程B到主内存中去读取线程A更新后的x值,此时线程B的本地内存的x值也变为了1。

从整体来看,这两个步骤实质上是线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。

总结:什么是Java内存模型:java内存模型简称jmm,定义了一个线程对另一个线程可见。共享变量存放在主内存中,每个线程都有自己的本地内存,当多个线程同时访问一个数据的时候,可能本地内存没有及时刷新到主内存,所以就会发生线程安全问题。

 

九、多线程之间实现通讯

多线程之间通讯、其实就是多个线程在操作同一个资源,但操作的动作不同

 

wait()、notify、notifyAll()方法

wait()、notify()、notifyAll()是三个定义在Object类里的方法,可以用来控制线程的状态。

这三个方法最终调用的都是jvm级的native方法。随着jvm运行平台的不同可能有些许差异。

 如果对象调用了wait方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。

如果对象调用了notify方法就会通知某个正在等待这个对象的控制权的线程可以继续运行。

如果对象调用了notifyAll方法就会通知所有等待这个对象控制权的线程继续运行。

wait与sleep区别?

wait 用于同步中,可以释放锁资源,sleep不会释放锁资源 ,wait 需要notify唤醒 ,sleep 时间到期,从休眠变到运行状态

相同 都是在做休眠

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

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

在调用sleep()方法的过程中,线程不会释放对象锁。

而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备

获取对象锁进入运行状态

注意:一定要在线程同步中使用,并且是同一个锁的资源

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值