java多线程

参考及转载:

https://blog.youkuaiyun.com/tornado886/article/details/4524346

http://www.wityx.com/post/666_1_1.html


线程的状态及生命周期

多线程的两种实现方式

1、继承Thread类

重写run方法->创建对象,调用start()方法

 

2、实现Runnable接口

重写run方法->创建对象->创建线程对象->线程对象调用start方法

基于java的单继承性,推荐使用第二种Runnable接口方法

 

终止线程(未完)

线程的优先级(未完)

线程类中的主要方法(未完)

线程休眠(未完)

Object类中线程的方法(未完)

线程同步

有时两个或多个线程可能会试图同时访问一个资源,为了确保在任何时间点一个共享的资源只被一个线程使用必须使用“同步”

实现线程同步的五种方法:

1、使用synchronized关键字

分为两种:

              1)给方法加synchronized修饰符

              2)给代码块加synchronized修饰符

1)给方法加:

给一个方法增加synchronized修饰符之后就可以使它成为同步方法,这个方法可以是静态方法和非静态方法,但是不能是抽象类的抽象方法,也不能是接口中的接口方法。

         由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

         注: synchronized关键字修饰静态方法时,此时如果调用该静态方法,将会锁住整个类。

2)给代码块加:

需指定对象

同步方法和同步块之间的相互制约只限于同一个对象之间,所以静态同步方法只受它所属类的其它静态同步方法的制约,而跟这个类的实例(对象)没有关系。

        注:同步是高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可

 

注:synchronized 关键字是用于保护“共享数据”。

以下为错误案例:

 

2、使用wait()和notify()

wait()使一个线程处于等待状态,并且释放所持有的对象的lock

sleep()使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常

notify()唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级;

notifyAll()唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争;

 

3、使用特殊域变量(volatile)实现线程同步

Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。

在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制

         当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步

 

4、使用可重入锁实现线程同步

JDK中独占锁的实现除了使用关键字synchronized外,还可以使用ReentrantLock。虽然在性能上ReentrantLocksynchronized没有什么区别,但ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。

ReentrantLocksynchronized的区别:

1)ReentrantLock和synchronized都是独占锁,只允许线程互斥的访问临界区。但是实现上两者不同:synchronized加锁解锁的过程是隐式的,用户不用手动操作,优点是操作简单,但显得不够灵活。一般并发场景使用synchronized的就够了;ReentrantLock需要手动加锁和解锁,且解锁的操作尽量要放在finally代码块中,保证线程正确释放锁。ReentrantLock操作较为复杂,但是因为可以手动控制加锁和解锁过程,在复杂的并发场景中能派上用场。

2)ReentrantLock和synchronized都是可重入的。synchronized可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁;而ReentrantLock在重入时要确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致其他线程无法获得该锁。

ReentrantLock的额外功能:

1)ReentrantLock可以实现公平锁;

         公平锁是指当锁可用时,在锁上等待时间最长的线程将获得锁的使用权。而非公平锁则随机分配这种使用权。和synchronized一样,默认的ReentrantLock实现是非公平锁,因为相比公平锁,非公平锁性能更好。当然公平锁能防止饥饿,某些情况下也很有用。

2)ReentrantLock可以响应中断

         当使用synchronized实现锁时,阻塞在锁上的线程除非获得锁,否则将一直等待下去,也就是说这种无限等待获取锁的行为无法被中断。而ReentrantLock给我们提供了一个可以响应中断的获取锁的方法lockInterruptibly(),该方法可以用来解决死锁问题。

3)获取锁限时等待;

        提供了获取锁限时等待的方法tryLock(),可以选择传入时间参数,表示等待指定的时间,可以使用该方法配合失败重试机制来更好的解决死锁问题。

 

5、使用局部变量(ThreadLocal)实现线程同步

ThreadLocal从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal

概括来说,对于多线程资源共享的问题,其他方法采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

ThreadLocal接口类方法

void set(T value)设置当前线程的线程局部变量的值。

public T get()该方法返回当前线程所对应的线程局部变量。

public void remove():将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

initialValue():返回该线程局部变量的初始值,这个方法是一个延迟调用方法,在线程第1次调用get()set(T)时才执行,并且仅执行1

 

 

 


thread中的run()和start()

1.start()方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码:

通过调用Thread类的start()方法来启动一个线程,
这时此线程是处于就绪状态,
并没有运行。
然后通过此Thread类调用方法run()来完成其运行操作的,
这里方法run()称为线程体,
它包含了要执行的这个线程的内容,
Run方法运行结束,
此线程终止,
而CPU再运行其它线程,

 2.run()方法当作普通方法的方式调用,程序还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码:

而如果直接用Run方法,
这只是调用一个方法而已,
程序中依然只有主线程--这一个线程,
其程序执行路径还是只有一条,
这样就没有达到写线程的目的。

 

例:

对应第一种:

 

对应第二种:

public static void main(String[] args) {
Thread t=new Thread(){
public void run(){
pong();
}
};
t.run();
System.out.println("ping");
}
static void pong(){
System.out.println("pong");
}
A. pingpong
B. pongping
C. pingpong和pongping都有可能
D. 都不输出
答案:B
分析:启动线程需要调用start()方法,而t.run()方法,则是使用对象名.分析:启动线程需要调用start()方法,而t.run()方法,则是使用对象名.

 

关于sleep()和wait(),以下描述错误的一项是()
A. sleep是线程类(Thread)的方法,wait是Object类的方法
B. Sleep不释放对象锁,wait放弃对象锁
C. Sleep暂停线程、但监控状态任然保持,结束后会自动恢复
D. Wait后进入等待锁定池,只针对此对象发出notify方法后获取对象锁进入运行状态。
答案:D
分析:针对此对象的notify方法后获取对象锁并进入就绪状态,而不是运行状态。另外针对此对象的notifyAll方法后也可能获取对象锁并进入就绪状态,而不是运行状态

 

以下锁机机制中,不能保证线程安全的是()
A. Lock
B. Synchronized
C. Volatile
答案:C

 

下面所述步骤中,是创建进程做必须的步骤是()
A. 由调度程序为进程分配CPU
B. 建立一个进程控制块
C. 为进程分配内存
D. 为进程分配文件描述符
答案:BC
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值