hadoop学习序曲之java基础篇--java多线程

来源:互联网 发布:配电网数据采集与监控 编辑:程序博客网 时间:2024/04/30 15:20
信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施, 它负责协调各个线程, 以保证它们能够正确、合理的使用公共资源。比如:停车场每辆车是一个线程,看门的大爷起到了信号量的作用。


工作内存和主内存之间有八种操作。
read(读取) load(载入) use(使用) assign(赋值) store(存储) write(写入) lock(锁定) unlock(解锁)

1、线程的属性:

默认情况下,每一个线程都有一个默认的名称 操作对象是线程对象。
格式:Thread-编号,编号从0开始。在run()方法里通过this.getName()调用。 获取id :getId
设置名称:
1)、在Thread的start()之前调用setName(),getName 自定义名称。
2)、在类中创建一个String参数的构造函数,将此参数传给父类。
    public Hello(String name) {
                super(name);
            }
3)、获取当前执行的对象线程:Thread.currentThread()
4)、线程的优先级
setPriority():设置优先级 1-10 包含1和10  针对于处理就绪状态但是还没有运行的线程
5)、是否为守护线程:setDaemon
守护线程:要在start()之前执行是否为守护线程 否则:
Exception in thread "main" java.lang.IllegalThreadStateException

创建线程的两种方法
方法一:继承Thread类。
1、继承Thread并且重写Thread的run(),将线程运行的代码实现在run方法里。
2、创建类的实例。
3、调用start()方法是线程使线程进入就绪状态,等待cpu分配资源

方法二:实现Runnable。
1、实现Runnable并且重写Runnable的run(),将线程运行的代码实现在run方法里。
2、创建类的实例。
3、将上述的示例传入Thread的构造函数来创建Thread实例;
4、调用Thread实例的start()方法是线程使线程进入就绪状态,等待cpu分配资源。
注意:如果直接调用这个对象的run方法,这时底层资源并没有完成资源的创建和请求分配,仅仅是简单的对象调用



线程的静态方法:
Thread.yield() 让行:交出cpu的执行权限,进入就绪状态
Thread.sleep() 线程阻塞一定时间后接着执行。单位为毫秒。
线程对象方法:
t.interrupt() :打断t线程。t用 isInterrupted() InterruptedException
t.join():在一个线程里调用线程t的join。相当于确保当前线程之后的代码在t线程执行完毕之后运行。

区别:
1、在继承Thread中可以直接使用this来调用thread的方法。比如 this.getName()获取线程名字,而继承Runnable只能先获取当前的进程对象,Thread.currentThread().getName()。
2、如果线程类只是实现了Runnable接口,那么该类还可以继承其他类。但是继承了thread就不能继承其它类了。
3、实现Runnable。可以多个线程共享一个Runnable target对象的资源,所以非常适合多个相同线程来处理同一份资源。

推荐实现Runnable 接口。

用户线程和守护线程

java中的守护线程,在其运行之前将Thread实例设置了setDaemon(true)
区别:
守护线程和用户线程的唯一区别是:当程序中没有活动的用户线程时,守护线程会被jvm中断,退出程序
Thread常用方法
Thread静态方法:

1、Thread.yield():使当前线程让出CPU占有权,并执行其他线程,但让出的时间是不可设定的,执行后转到就绪状态。礼让其它线程!
public static native void yield()
2、Thread.sleep(long millis):让当前正在执行的线程休眠,转到阻塞状态;如果参数为0则相当于yield()。
public static native void sleep

Thread对象的方法:
1、t.interrupt() :打断正在运行的线程,被打断的线程在内部可以根据isInterrupted()来查看是否被别人打断,来响应中断操作,做出处理。
2、t.join():当前线程加入到指定的线程t中,等指定的线程完毕后再执行该线程。要在start()之后join()

什么时候需要同步:
1、读取上一次可能是由另一个线程写入的变量
2、写入下一次可能由另一个线程读取的变量

保证多线程下同步问题的方法:
Java 语言提供了两个关键字:synchronized 和volatile、一个对象ReetrantLock。
同步策略:
synchronized 修饰代码块和方法。
Volatile:它是一个类型修饰符,线程在获取用volatile声明的变量时,直接从主存读取,修改后直接写入主存。

ReetrantLock 是在代码的任何地方都可以获得锁、释放锁、但是为了安全推荐在finally块中释放锁。

性能:
在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
有一些类由硬件提供原子操作指令实现的。在非激烈竞争的情况下,开销更小,速度更快。 在java.util.concurrent.atomic包中;


包括:AtomicBoolean、 AtomicInteger、 AtomicLong、 AtomicReference等等。
多线程的缺点
1、线程不是越多越好。大量的线程,需要更多的内存空间,需要cpu在它们之间频繁切换,在一定程度上影响运行性能。另外:单cpu系统中的多线程效率还不如单线程,因为有线程切换的性能消耗。

2、安全问题。多个线程使用同一个变量会导致数据不一致。

3、死锁问题。解决安全问题就要使用锁,只要使用锁就有可能造成死锁。不按顺序的资源竞争,造成死锁。

线程池
Executors类里面提供了一些静态工厂,生成一些常用的线程池。

SingleThreadExecutor

FixedThreadPool

CachedThreadPool

ScheduledThreadPool 

SingleThreadScheduledExecutor
1. new SingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
2.new FixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
3. new CachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
4.new ScheduledThreadPool 
创建一个执行延时、周期行执行任务的线程池。

多线程:new ScheduledThreadPool
单线程:new SingleThreadScheduledExecutor。
前三个返回的都是:ExecutorService
后一个返回的是:ScheduledExecutorService

ExecutorService对象:
一般使用流程:

1、Executors创建一个线程池

2、调用submit/execute 添加线程任务。(周期线程:schedule、scheduleAtFixedRate、scheduleWithFixedDelay)

3、调用shutdown等任务完成后关闭线程池。
ScheduledExecutorService  executorService= Executors.newScheduledThreadPool(3);

executorService.schedule(new SayHello(), 1, TimeUnit.SECONDS);
延迟执行一次,一个线程。
executorService.scheduleWithFixedDelay(new SayHello(),2,1,TimeUnit.SECONDS);
三个线程随机执行,初始延迟2s之后每1s执行1次。
executorService.scheduleAtFixedRate(new SayHello(),2,1, TimeUnit.SECONDS);
三个线程随机执行,初始延迟2s之后每1s执行1次。
表面上看第二和第三个差不多:
但是记住:scheduleWithFixedDelay、scheduleAtFixedRate两者虽然都是从上一个任务开始时间计算周期但是:fixedrate按照上次任务开始时间计算,但是还是会等上一个任务执行完之后,fixeddelay是按照上次任务完成时时间计算。

多线程注意事项
1、创建线程时一定要有线程的名字。

2、要响应线程中断操作。

3、使用ThreadLocal 。

4、注意同步准则的使用。
ThreadLocal 
它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。

ThreadLocal<String> local=new ThreadLocal<String>();

local.set("ssss");

System.out.println(local.get());

当编写 synchronized 块时,
1、使代码块保持简短。Synchronized 块应该简短 — 在保证相关数据操作的完整性的同时,尽量简短。把不随线程变化的预处理和后处理移出 synchronized 块。

2、不要阻塞。不要在 synchronized 块或方法中调用可能引起阻塞的方法,如InputStream.read()。

3、在持有锁的时候,不要对其它对象调用方法。这听起来可能有些极端,但它消除了最常见的死锁源头

4、要避免死锁,应该确保在获取多个锁时,在所有的线程中都以相同的顺序获取锁。

0 0
原创粉丝点击