文章目录
1.安全问题举例
1.1 介绍
-
1.死锁
多个线程互相占用对方资源,互不释放,一直等待
检查工具:jconsole -
2.饥饿
涉及线程优先级,就是高级别的线程一直在执行,低级别的线程一直得不到资源来执行 -
3.活锁
比如两个线程,都需要用到某个资源的时候,看到互相都需要,就各自退出来继续等待,当正要获取资源的时候,看到对方也正需要资源,于是又退出来继续等待,反反复复这样下去
1.2 多个线程操作一个对象,一个对象操作一个资源
public class MyNumber {
private Integer num;
public MyNumber() {
num = 10;
}
public MyNumber(Integer num) {
this.num = num;
}
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
}
public class UserManagerSingle {
private MyNumber num;
public UserManagerSingle() {
this.num = new MyNumber();
}
public Integer getNumFromMyNumber(){
Integer tmp = num.getNum();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num.setNum(tmp - 1);
return tmp;
}
}
public class MyThread extends Thread{
private UserManagerSingle user;
public MyThread(String name, UserManagerSingle user) {
super(name);
this.user = user;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("线程名:【"+this.getName() +"】 获取序号:"+user.getNumFromMyNumber() + " 时间:"+System.currentTimeMillis());
}
}
}
public class TestThread {
public static void main(String[] args) {
testSingle();
}
public static void testSingle(){
UserManagerSingle userManagerSingle = new UserManagerSingle();
//下面模拟三个线程,同时访问一个后台应用类
new MyThread("t1",userManagerSingle).start();
new MyThread("t2",userManagerSingle).start();
new MyThread("t3",userManagerSingle).start();
}
}
1.3 多个线程操作多个对象,多个对象操作一个资源
2.同步
2.1 同步块
注意:任何时刻只有一条线程可以或得同步监视器的锁定,当同步代码块执行结束后,该线程自然释放了对该同步监视器的锁定
通常推荐使用可能被并发访问的共享资源充当同步监视器
- 1.控制整个方法(除此以外其他类代码不变)
public class MyThread extends Thread{
private UserManagerSingle user;
public MyThread(String name, UserManagerSingle user) {
super(name);
this.user = user;
}
@Override
public void run() {
synchronized (user){
for (int i = 0; i < 5; i++) {
System.out.println("线程名:【"+this.getName() +"】 获取序号:"+user.getNumFromMyNumber() + " 时间:"+System.currentTimeMillis());
}
}
}
}
如上,
1.加锁是用user这个对象来加锁的,因为user是共享资源,所有线程都会访问这个类,若这个类对象不释放锁,那么所有线程将等待
2.注意synchronized 是加载for之前,那么就表示必须等待当前这个线程所有操作全部执行完后,才能开始下一个线程
- 2.控制方法中部分代码(除此以外其他类代码不变)
public class MyThread extends Thread{
private UserManagerSingle user;
public MyThread(String name, UserManagerSingle user) {
super(name);
this.user = user;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
synchronized (user){
System.out.println("线程名:【"+this.getName() +"】 获取序号:"+user.getNumFromMyNumber() + " 时间:"+System.currentTimeMillis());
}
}
}
}
如上,
synchronized 存放在for之内,那么当循环执行过一次后,会释放锁,这时其他等待的线程就有机会获得锁而进行各自线程里的操作,这样就不只单单先执行完一个线程的事了,而是相互竞争执行
2.2 同步方法
同步方法无需显示指定同步监视器,同步方法监视器是this,即就是对象本身自己
若修饰静态方法,那么同步锁的对象是class对象
任何对象都可以作为锁,那么锁信息是存在对象头中
(除此以外其他类代码不变)
public class MyThread extends Thread{
private UserManagerSingle user;
public MyThread(String name, UserManagerSingle user) {
super(name);
this.user = user;
}
@Override
public void run() {
print();
}
//这样写同步方法,是不会实现同步功能的,因为每个MyThread对象是不一样的
public synchronized void print(){
for (int i = 0; i < 5; i++) {
System.out.println("线程名:【"+this.getName() +"】 获取序号:"+user.getNumFromMyNumber() + " 时间:"+System.currentTimeMillis());
}
}
}
由上可知,因为每次都会new MyThread(“t1”,userManagerSingle).start();则在MyThread中同步方法的监视器this都是不一样的,都是指向新生成的不同对象,所以并不能实现同步功能
同步方法要写到同步资源本身类里即可,如下:
public class MyThread extends Thread{
private UserManagerSingle user;
public MyThread(String name, UserManagerSingle user) {
super(name);
this.user = user;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
user.printNum();
}
}
}
public class UserManagerSingle {
private MyNumber num;
public UserManagerSingle() {
this.num = new MyNumber();
}
public Integer getNumFromMyNumber(){
Integer tmp = num.getNum();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num.setNum(tmp - 1);
return tmp;
}
//同步资源这里写同步方法
public synchronized void printNum(){
System.out.println("线程名:【"+Thread.currentThread().getName() +"】 获取序号:"+getNumFromMyNumber() + " 时间:"+System.currentTimeMillis());
}
}
2.3 同步锁
2.3.1 可重入锁ReentrantLock
(除此以外其他类代码不变)
public class MyThread extends Thread{
private UserManagerSingle user;
//lock写在这里并不能实现线程同步,因为MyThread是不同类,而lock也会生成不同对象,并不唯一
private final ReentrantLock lock = new ReentrantLock();
public MyThread(String name, UserManagerSingle user) {
super(name);
this.user = user;
}
@Override
public void run() {
try {
lock.lock();
for (int i = 0; i < 5; i++) {
System.out.println("线程名:【"+this.getName() +"】 获取序号:"+user.getNumFromMyNumber() + " 时间:"+System.currentTimeMillis());
}
}catch (Exception e){
System.out.println("e = " + e);
}finally {
lock.unlock();
}
}
}
lock必须在多个线程中保持唯一,才能控制同步,所以Lock一般也是放在共享资源里创建的,和同步方法比较像,如下:
public class MyThread extends Thread{
private UserManagerSingle user;
public MyThread(String name, UserManagerSingle user) {
super(name);
this.user = user;
}
@Override
public void run() {
user.printNum();
}
}
public class UserManagerSingle {
private MyNumber num;
//共享资源处声明Lock
private final ReentrantLock lock = new ReentrantLock();
public UserManagerSingle() {
this.num = new MyNumber();
}
public Integer getNumFromMyNumber(){
Integer tmp = num.getNum();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num.setNum(tmp - 1);
return tmp;
}
//同步Lock
public void printNum(){
try {
lock.lock();
for (int i = 0; i < 5; i++) {
System.out.println("线程名:【"+Thread.currentThread().getName() +"】 获取序号:"+getNumFromMyNumber() + " 时间:"+System.currentTimeMillis());
}
}catch (Exception e){
System.out.println("e = " + e);
}finally {
lock.unlock();
}
}
}
2.4 原子类操作
public class TestThread {
public static void main(String[] args) {
testSingle();
}
public static void testSingle(){
UserManagerSingle userManagerSingle = new UserManagerSingle(new MyNumber());
//下面模拟三个线程,同时访问一个后台应用类
new MyThread("t1",userManagerSingle).start();
new MyThread("t2",userManagerSingle).start();
new MyThread("t3",userManagerSingle).start();
}
}
public class MyThread extends Thread{
private UserManagerSingle user;
public MyThread(String name, UserManagerSingle user) {
super(name);
this.user = user;
}
@Override
public void run() {
user.getNumFromMyNumber();
}
}
public class UserManagerSingle {
private MyNumber num;
public UserManagerSingle(MyNumber num) {
this.num = num;
}
public void getNumFromMyNumber(){
for (int i = 0; i < 5; i++) {
System.out.println("线程名:【"+Thread.currentThread().getName() +"】 获取序号:"+ num.getNum() + " 时间:"+System.currentTimeMillis());
}
}
}
public class MyNumber {
//增加原子类操作
private AtomicInteger num = new AtomicInteger(0);
public MyNumber() {
}
public Integer getNum() {
return num.getAndIncrement();
}
}
3.锁
3.1 可重入锁
广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁
ReentrantLock和synchronized都是可重入锁
public class MyReentrantLock {
public synchronized void a(){
System.out.println("线程:" + Thread.currentThread().getName()+" 执行a()" + " 当前实例"+this.hashCode());
b();
}
public synchronized void b(){
System.out.println("线程:" + Thread.currentThread().getName()+" 执行b()" + " 当前实例"+this.hashCode());
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
MyReentrantLock rl = new MyReentrantLock();
rl.a();
}
}).start();
}
}
总结:
1.有上可知,虽然a和b方法的锁都在同一个对象spinLock上,但是一个线程调用该对象a方法后,依然还能调用该对象的b方法
3.2 自旋锁
自旋锁可以使线程在没有取得锁的时候,不被挂起,而转去执行一个空循环,(即所谓的自旋,就是自己执行空循环),若在若干个空循环后,线程如果可以获得锁,则继续执行。若线程依然不能获得锁,才会被挂起。
使用自旋锁后,线程被挂起的几率相对减少,线程执行的连贯性相对加强。因此,对于那些锁竞争不是很激烈,锁占用时间很短的并发线程,具有一定的积极意义,但对于锁竞争激烈,单线程锁占用很长时间的并发程序,自旋锁在自旋等待后,往往毅然无法获得对应的锁,不仅仅白白浪费了CPU时间,最终还是免不了被挂起的操作 ,反而浪费了系统的资源。
public class SpinLock {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + Thread.currentThread().getName()+" 执行" + " 当前实例"+this.hashCode());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(new Random().nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + Thread.currentThread().getName()+" 执行" + " 当前实例"+this.hashCode());
}
}).start();
getAllRunThread();
//这里必须是2,因为java运行会多一个Monitor Ctrl-Break线程生成
while(Thread.activeCount() != 2){
//自旋
}
System.out.println("所有线程执行完");
}
//获取当前线程下所有活动线程数
public static void getAllRunThread(){
ThreadGroup currentGroup =
Thread.currentThread().getThreadGroup();
int noThreads = currentGroup.activeCount();
Thread[] lstThreads = new Thread[noThreads];
currentGroup.enumerate(lstThreads);
for (int i = 0; i < noThreads; i++)
System.out.println("线程号:" + i + " = " + lstThreads[i].getName());
}
}
总结:
1.自旋就是当该线程没有取得锁时,不停执行空循环,知道后面获取后再执行下一步
3.3 死锁
所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
public class MyDeadLock {
Object o1 = new Object();
Object o2 = new Object();
public void a() throws InterruptedException {
synchronized (o1){//获取o1锁
Thread.sleep(100);
synchronized (o2){
System.out.println("执行a()方法!!");
}
}
}
public void b() throws InterruptedException {
synchronized (o2){//获取O2锁
Thread.sleep(100);
synchronized (o1){
System.out.println("执行a()方法!!");
}
}
}
public static void main(String[] args) {
final MyDeadLock myDeadLock = new MyDeadLock();
//线程1
new Thread(new Runnable() {
@Override
public void run() {
try {
myDeadLock.a();//这里获取了o1锁,但o2锁并未获取
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
//线程2
new Thread(new Runnable() {
@Override
public void run() {
try {
myDeadLock.b();//这里获取了o2锁,但o1锁并未获取
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
总结:
1.由上可知,一个线程获取o1锁,一个线程获取o2锁,但是他们都没有彼此释放自己获取的锁,故一直等待
3.4 Volatile
1.Volatile称之为轻量级锁,被volatile修饰的变量,在线程之间都是可见的
2.一个线程修改了这个变量的值,那么在其他所有线程中都能够读取到这个修改后的值
3.Synchronized除了线程之间互斥以外,还有一个非常大的作用,就是保证可见性
一个可见性的典型应用
public class MyVolatile {
public volatile boolean run = false;
public static void main(String[] args) {
final MyVolatile myVolatile = new MyVolatile();
//线程1
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
System.out.println("i = " + i+" 当前线程"+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
myVolatile.run = true;
}
}).start();
//线程2
new Thread(new Runnable() {
@Override
public void run() {
while (!myVolatile.run){
//当run没有为true时,一直等待
}
System.out.println("现在才运行执行这个线程"+Thread.currentThread().getName());
}
}).start();
}
}
4. 通讯
场景:
启动三个线程,让他们依次顺序执行各自的方法
4.1 wait与notify
public class MyWaitNotify {
private int signal;
public synchronized void a(){
if(signal != 0){//=0的时候才执行
try {
wait();//用wait必须要加synchronized,notify()和notifyAll()的线程在调用这些方法前必须"拥有"对象的锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("执行方法a()");
signal++;
notifyAll();//notify()不能用,因为这个是随机唤醒某个线程
}
public synchronized void b(){
if(signal != 1){//=1,才执行
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("执行方法b()");
signal++;
notifyAll();
}
public synchronized void c(){
if(signal != 2){//=2才执行
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("执行方法c()");
signal = 0;
notifyAll();
}
public static void main(String[] args) {
MyWaitNotify mwn = new MyWaitNotify();
A a = new A(mwn);
B b = new B(mwn);
C c = new C(mwn);
new Thread(a).start();
new Thread(b).start();
new Thread(c).start();
}
}
class A implements Runnable{
MyWaitNotify wn;
public A(MyWaitNotify wn) {
this.wn = wn;
}
@Override
public void run() {
while (true){
wn.a();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class B implements Runnable{
MyWaitNotify wn;
public B (MyWaitNotify wn) {
this.wn = wn;
}
@Override
public void run() {
while (true){
wn.b();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class C implements Runnable{
MyWaitNotify wn;
public C (MyWaitNotify wn) {
this.wn = wn;
}
@Override
public void run() {
while (true){
wn.c();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4.2 condition
public class MyWaitNotify {
private int signal;
Lock lock = new ReentrantLock();
Condition a = lock.newCondition();
Condition b = lock.newCondition();
Condition c = lock.newCondition();
public void a(){
lock.lock();
if(signal != 0){//=0的时候才执行
try {
a.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("执行方法a()");
signal++;
b.signal();
lock.unlock();
}
public void b(){
lock.lock();
if(signal != 1){
try {
b.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("执行方法b()");
signal++;
c.signal();
lock.unlock();
}
public void c(){
lock.lock();
if(signal != 2){
try {
c.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("执行方法c()");
signal = 0;
a.signal();
lock.unlock();
}
public static void main(String[] args) {
MyWaitNotify mwn = new MyWaitNotify();
A a = new A(mwn);
B b = new B(mwn);
C c = new C(mwn);
new Thread(a).start();
new Thread(b).start();
new Thread(c).start();
}
}
class A implements Runnable{
MyWaitNotify wn;
public A(MyWaitNotify wn) {
this.wn = wn;
}
@Override
public void run() {
while (true){
wn.a();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class B implements Runnable{
MyWaitNotify wn;
public B (MyWaitNotify wn) {
this.wn = wn;
}
@Override
public void run() {
while (true){
wn.b();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class C implements Runnable{
MyWaitNotify wn;
public C (MyWaitNotify wn) {
this.wn = wn;
}
@Override
public void run() {
while (true){
wn.c();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
5. ThreadLocal
每个线程的局部变量
public class MyThreadlocal {
private ThreadLocal<Integer> count = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return new Integer(0);
}
};
public Integer getNext(){
Integer i = count.get();
i++;
count.set(i);
return i;
}
public static void main(String[] args) {
final MyThreadlocal myThreadlocal = new MyThreadlocal();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + Thread.currentThread().getName() + "序号:" + myThreadlocal.getNext());
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + Thread.currentThread().getName() + "序号:" + myThreadlocal.getNext());
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + Thread.currentThread().getName() + "序号:" + myThreadlocal.getNext());
}
}
}).start();
}
}