多线程快速入门

一、线程和进程

线程的概述:

是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

进程与线程之间的联系:

当一个程序进入内存运行时,就产生了一个进程,也就是说一个执行要给程序就会产生要给进程,而进程也不是最小的执行单位,一个进程里面还可以有多个线程(至少有一个主线程)。

多线程的优点:
  1. 多线程可以提高程序的执行效率;
  2.进程之间是不能共享内存的,但是线程很容易就可以实现内存共享;
多线程和cpu的关系:

电脑配置
4核4线程:指的是cpu里面有4个物理核心
4核8线程(假8核):指的是cpu里面有4个物理核心,但是通过一定的技术将一个核逻辑上扩展成了两个核心

二、线程的创建和启动

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

创建线程的方式

1.继承Thread类
package com.thread.demo;

/**
 * Created by Administrator on 2019/11/21.
 */
public class ThreadDemo extends Thread {

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

注意:使用这种方式使得无法共享变量,所以输出的结果不是连续的

2.实现Runnable接口创建线程类
package com.thread.demo;

/**
 * Created by Administrator on 2019/11/21.
 */
public class RunnableDemo implements Runnable {
    private int i;

    @Override
    public void run() {
        for(;i<100;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }

    public static void main(String[] args) {
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==20){
                RunnableDemo st=new RunnableDemo();
                new Thread(st,"1").start();
                new Thread(st,"2").start();

            }
        }
    }
}

注意:runnable 接口中只有一个方法,从java8开始,runnable接口使用了@FunctionalInterface修饰。也就是说,Runnable接口是函数式接口,可以使用拉姆达表达式创建对象;使用runnable之后可以发现由两个线程之间可以共享变量。

三、使用callable和future创建

```java
package com.thread.demo;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/**
 * Created by Administrator on 2019/11/21.
 */
public class CallThreadDemo {
    public static void main(String[] args) {
        CallThreadDemo rt=new CallThreadDemo();
        FutureTask<Integer> task=new FutureTask<Integer>((Callable<Integer>)()->{
            int i=0;
            for(;i<100;i++){
                System.out.println(Thread.currentThread().getName()+"的循环变量i的值"+i);
            }
            return i;
        });
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+"的循环变量i的值"+i);
            if(i==20){
                new Thread(task,"有返回值的线程").start();
            }
        }
        try{
            System.out.println("子线程的返回值:"+task.get());
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }
}

三、线程的生命周期

新建状态:当程序使用new关键字创建了一个线程的时候,该线程就属于新建状态
就绪状态:当调用了start()方法的时候,线程进入就绪状态等待cpu的使用权
运行状态:当线程获得cpu的使用权的时候
阻塞状态:当线程调用了sleep()方法主动放弃cpu权限;线程调用一个阻塞式IO方法;
线程试图获取一个同步监听器;调用了suspend()将线程挂起
死亡状态:线程正常结束;线程抛出Exceptino或者error;直接调用了该线程的stop()方法来结束 该线程

四、控制线程

java的线程支持提供了一些便捷的工具方法,通过这些工具方法能控制线程的执行

1.join线程(线程等待)

Thread提供了让一个线程等待另一个线程的方法-join()方法.当某个程序执行流中调用其他线程的join()方法时,调用程序会被阻塞,直到被join方法加入的join线程执行完为止。
join()方法有如下三种重载形式:
1.join():等待被join()的线程执行完成;
2.join(long millis):等待被join的线程的时间;如果在时间内还没有执行结束,则不再等待;
3.join(long millis,int nanos):等待被join的线程的时间;如果在时间内还没有执行结束,则不再等待;

2.后台线程 setDaemon(true)

Thread.setDaemon(true):指定将线程设置为守护线程

3.线程睡眠:sleep

两种重载形式:
static void sleep(long millis):
static void sleep(long mills,int nanos):
会使得线程从运行状态变成阻塞状态,等到睡眠时间到了又会加入就绪队列。

4.线程让步:yield

yield和sleep有点像,不过他不会阻塞线程,它只是将线程转入就绪状态,yield只是让当前线程暂停了一下,把cpu暂时让出来给优先级相同或者更高的线程执行机会。

5.改变线程的优先级

每个线程执行的时候都会有一定的优先级,优先级高的线程获得较多的机会,而优先级 低的线程则获得较少的执行机会。
setPriority(int newPriority);设置优先级别,newPriority可以是 1-10之间,也可以用以下三个静态常量:
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5
getPriority():来设置和返回指定线程的优先级

五、线程同步

1.线程安全问题

由于多个线程对同一个系统资源(变量、文件…)进行操作,各个线程之间无法得知资源是否是最新的,因而产生错误。主要产生原因就是线程调度的随机性,
同时满足以下两个条件时:
1,多个线程在操作共享的数据。
2,操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
 对于线程安全问题,需要注意以下两点:
只存在读数据的时候,不会产生线程安全问题。
在java中,只有同时操作成员(全局)变量的时候才会产生线程安全问题,局部变量不会(每个线程执行时将会把局部变量放在各自栈帧的工作内存中,线程间不共享,故不存在线程安全问题,这里不展开描述内存问题,有兴趣可自行百度)。

package com.thread.demo;

import sun.security.krb5.internal.Ticket;

/**
 * Created by Administrator on 2019/11/21.
 */
public class TicketThread implements Runnable {
    private int ticketCount=10;
    @Override
    public void run() {
        while (ticketCount>0){  //这是线程结束的条件
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sale();
        }
    }
     public synchronized void sale(){
            if(ticketCount>0){  //这是执行的条件
                System.out.println(Thread.currentThread().getName()+"正在出售第"+(10-ticketCount+1)+"张票");
                ticketCount--;
            }


    }

    public static void main(String[] args) {
        TicketThread ticketThread=new TicketThread();
        Thread t1=new Thread(ticketThread,"1");
        Thread t2=new Thread(ticketThread,"2");
        t1.start();
        t2.start();


    }
}
2.同步代码块

同步代码块语法格式:
synchronized(obj){

}
上面语法中的synchronized后括号里面的obj就是同步监听器,上面代码的含义:线程开始执行代码同步代码块之前,必须先获得对同步监听器的锁定。

3.同步方法

同步方法就是使用synchronized关键字来修饰某个方法,该方法被称为同步方法。对于使用synchronized修饰的方法无需要指定同步监听器,同步方法的同步监听器就是this,也就是调用方法的对象。

4.释放同步监视器的锁定

任何线程进入同步代码块、同步方法之前,必须获得同步监视器的锁定,那么何时会释放对
同步监视器的锁定:
1.线程的同步方法、同步代码块执行结束,当前线程释放同步代码块。
2.同步方法遇到了break、return终止了该代码块。
3.出现了Exception、error异常
4.执行了wait()方法,也将失去同步监视器的使用权
注意如下情况是不会释放同步监视器:
1.程序调用Thread.sleep()方法;
2.调用suspend()方法, 程序应当避免使用suspend()方法和resume()方法;

5.synchronized的三种应用方式

1.普通方法(实例方法),锁是当前实例对象,进入同步代码块前要获得当前实例的锁。
2.静态同步方法,锁是当前类的class对象,进入同步代码块前需要获得当前类对象的锁
3.同步方法块,锁是括号里面的对象,对给定的对象加锁,进入同步代码库前要获得给定对象的锁。

6.同步锁

java5开始,java提供了一种强大的线程同步机制-通过显示定义同步锁对象来实现同步,在这种机制下,同步锁由Lock对象充当。
Lock是控制多个线程对共享资源进行访问的工具。通产,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
锁的种类:
接口:Lock、ReadWriteLock
实现类:ReentrantLock(可重入锁)、ReentrantReadWriteLock

7.死锁
当两个线程相互等待对方释放同步监听器时就会发生死锁。

所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
产生死锁可能性的最根本原因是:线程在获得一个锁L1的情况下再去申请另外一个锁L2,也就是锁L1想要包含了锁L2,也就是说在获得了锁L1,并且没有释放锁L1的情况下,又去申请获得锁L2,这个是产生死锁的最根本原因。

六:线程通信

1、传统的线程通信
主要是借助Object类提供的wait()、notify()、notifyAll()三个方法,这三个方法并不属于Thread而是属于Object类,这三个方法必须由同步监视器对象来调用,这可以分为以下几种情况:

1、对于使用synchronized修饰的同步方法,因为该类的默认实例是同步监听器,所以可以在同步方法中直接调用这三个方法。
2、对于使用synchronized修饰的同步代码块,同步监视器是括号里面的对象,所以必须使用该对象调用着三个方法。
wait():导致当前线程等待,直到其他线程调用该同步监听器的notify()方法或者notifyAll方法唤醒该线程,调用wait()方法的当前线程或释放对该同步监听器的锁定。
notify():唤醒在此同步监听器上等待的单个线程。
notifyAll():唤醒在此同步监听器上的所有线程。

2.使用Condition控制线程通信

如果程序不适用sunchronized关键字来保证同步,而是直接使用Lock对象来保证同步,则系统中不存在隐士的同步监听器,也就不能使用wait()、notify()、notifyAll()方法进行线程通信。
Condition类提供了如下三个方法:
await():导致线程等待,直到其他线程调用signal、signalAll方法的时候。
signal():唤醒单个线程。
signalAll():唤醒所有的线程。

3.使用阻塞队列控制线程通信

BlockingQueue提供了如下两个支持阻塞的方法:
put(E e):尝试把E元素放入BlockingQueue中,既可以很好的控制线程的通信。
take():尝试从BlockingQueue的头部取出元素,如果该队列的元素为空,则阻塞该线程。
在队列尾部插入元素:add(E e)、 offer(E e)、put(E e)方法,当该队列已经满了,这三个方法也会抛出异常,阻塞队列。

七、线程池

java5开始新增了一个Executors工厂类来产生线程池,该工厂类包含如下几个静态工厂方法来创建线程池。


```java
ExecutorService newCachedThreadPool():创建一个具有缓存功能的线程池, 系统根据需要创建线程,这些线程将会被缓存到线程池里。
ExecutorService newFixedThreadPool(int nThreads):创建一个可重用的、具有固定线程数的线程池。
ExecutorService newSingleThreadExecutor():创建一个只有单线程的线程池。
ScheduledExecutoService newScheduledThreadPool(int corePoolSize):创建具有指定线程数的线程池,它可以在指定延迟后执行任务。corePoolSize指的是池中所保存的线程数,即使线程是空闲的也被保存在线程池里面。
ScheduledExecutoService newSingleThreadScheduledExecutor():创建只有一个线程的线程池,它可以在指定延迟后执行县线程任务。
ExecutorService newWorkStelingPool(int parallelism):创建持有足够的线程的线程池来支持给定的并行级别,该方法还会使用多个队列来减少竞争.
ExecutorService newWorkStelingPool():该方法是前一个方法的简化版本。如果当前机器由4个cpu,则目标并行级别被设置为4,也就是相当于为前一个方法传入4作为参数。

ExecutorService里面提供了如下三个方法:
Future<?> submit(Runnable task):将一个Runnable对象提交给指定的线程池,线程池再有空闲时候执行Runnable对象代表的任务。其中Future对象代表Runnable任务的返回值————但run()方法没有返回值,所以Future对象将在run()方法执行结束返回null。但可以调用Future的isDone()isCancelled()方法来获得Runnable对象的执行状态。
<T>Future<T> submit(Runnable task,T result):将一个Runnable对象提交给指定的线程池,线程池将在有空闲时执行Runnable对象代表的任务。
<T>Future<T>submit(Callable<T> task):将callable对象提交给线程池。
ScheduledExecutorService代表可在指定延迟后或者周期性地执行线程任务的线程池。
ScheduledFuture<V>schedule(Callable<V>callable,long delay,TimeUnit unit):指定callable任务将在delay延迟后执行。
ScheduleFuture<?> schedule(Runnable command,long delya,TimeUnit unit):指定command任务将在delay延迟后执行。
ScheduledFuture<?> shceduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit):指定command任务将在delay延迟后执行,而且设定频率重复执行,也就是说,在initialDelay后开始执行,一次在initialDelay+period、initialDlay+2*period...处重复执行,依次类推。
ScheduleFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit):







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值