概述
什么是线程安全?
当多个线程访问某一个类(对象或方法)时,这个对象始终都能表现出正确的行为, 那么这个类(对象或方法)就是线程安全的。
java内存模型
在说明多线程安全问题前,要明白java内存模型。
为什么有线程安全问题?
当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。
经典售票问题我们可以一起写一下!!
/**
* @classDesc: 演示多线程的安全问题
* @author: hj
* @date:2018年12月12日 上午10:49:59
*/
public class ThreadTrain implements Runnable {
private int trainCount = 100;
@Override
public void run() {
synchronized (this) {
while (trainCount > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
sale();
}
}
}
// 模仿卖票
public void sale() {
if (trainCount > 0) {
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票");
trainCount--;
}
}
public static void main(String[] args) {
ThreadTrain threadTrain = new ThreadTrain();
Thread t0 = new Thread(threadTrain, "t0");
Thread t1 = new Thread(threadTrain, "t1");
Thread t2 = new Thread(threadTrain, "t2");
t0.start();
t1.start();
t2.start();
}
}
如果在run方法中不加入同步代码块就会出现线程不安全的问题。
线程安全解决办法:
问:如何解决多线程之间线程安全问题
答:使用多线程之间同步synchronized或使用锁(lock)。
问:为什么使用线程同步或使用锁能解决线程安全问题呢?
答:将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。代码执行完成后释放锁,让后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。
问:什么是多线程之间同步
答:当多个线程共享同一个资源,不会受到其他线程的干扰。
问:什么是多线程同步
答:当多个线程共享同一个资源,不会受到其他线程的干扰。
下面先看看一个通过synchronized实现的线程安全例子
/**
* 线程安全:当多个线程访问某一个类(对象或方法)时,这个对象始终都能表现出正确的行为, 那么这个类(对象或方法)就是线程安全的。
* synchronized:可以在任意对象及方法上加锁,而加锁的这段代码称为"互斥区"或"临界区"
*
* @author 4 Todo:演示线程安全与不安全,通过synchronized关键字控制
*/
public class ThreadSafe extends Thread {
private int count = 0;
/* synchronized */
@Override
public synchronized void run() {
count++;
System.out.println("thread: " + Thread.currentThread().getName() + " do: " + count);
}
public static void main(String[] args) {
/**
* 分析:当多个线程访问myThread的run方法时,以排队的方式进行处理(这里排对是按照CPU分配的先后顺序而定的),
* 一个线程想要执行synchronized修饰的方法里的代码: 1 尝试获得锁 2
* 如果拿到锁,执行synchronized代码体内容;拿不到锁,这个线程就会不断的尝试获得这把锁,直到拿到为止,
* 而且是多个线程同时去竞争这把锁。(也就是会有锁竞争的问题)
*/
ThreadSafe threadSafe = new ThreadSafe();
Thread t1 = new Thread(threadSafe, "t1");
Thread t2 = new Thread(threadSafe, "t2");
Thread t3 = new Thread(threadSafe, "t3");
Thread t4 = new Thread(threadSafe, "t4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
也可以试着把synchronized去掉看看结果。
接下来我们看看java锁的感念!
内置的锁
Java提供了一种内置的锁机制来支持原子性
每一个Java对象都可以用作一个实现同步的锁,称为内置锁,线程进入同步代码块之前自动获取到锁,代码块执行完成正常退出或代码块中抛出异常退出时会释放掉锁
内置锁为互斥锁,即线程A获取到锁后,线程B阻塞直到线程A释放锁,线程B才能获取到同一个锁
内置锁使用synchronized关键字实现,synchronized关键字有两种用法:
1.修饰需要进行同步的方法(所有访问状态变量的方法都必须进行同步),此时充当锁的对象为调用同步方法的对象
2.同步代码块和直接使用synchronized修饰需要同步的方法是一样的,但是锁的粒度可以更细,并且充当锁的对象不一定是this,也可以是其它对象,所以使用起来更加灵活
现在我们着重对synchronized做说明!
同步代码块synchronized
就是将可能会发生线程安全问题的代码,给包括起来。 synchronized(同一个数据){ 可能会发生线程冲突问题 } 就是同步代码块 synchronized(对象)//这个对象可以为任意对象 { 需要被同步的代码 } |
synchronized (this) {
while (trainCount > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
sale();
}
}
对象如同锁,持有锁的线程可以在同步中执行
没持有锁的线程即使获取CPU的执行权,也进不去
同步的前提:
1,必须要有两个或者两个以上的线程
2,必须是多个线程使用同一个锁
必须保证同步中只能有一个线程在运行
好处:解决了多线程的安全问题
弊端:多个线程需要判断锁,较为消耗资源、抢锁的资源。
同步方法:
什么是同步方法?
答:在方法上修饰synchronized 称为同步方法
public synchronized void run() {
count++;
System.out.println("thread: " + Thread.currentThread().getName() + " do: " + count);
}
同步方法使用的是什么锁?
答:同步函数使用this锁。
补充几点琐碎的知识点
1.原子操作(业务整体需要使用完整的synchronized,保持业务的原子性。)
/**
* 业务整体需要使用完整的synchronized,保持业务的原子性。
*/
public class DirtyRead {
private String name = "";
private int age = 0;
/* synchronized */
public synchronized void getvalue() {
System.out.println("name is:" + name + " age is:" + age);
}
public synchronized void setvalue(String name, int age) {
this.name = name;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.age = age;
System.out.println("修改完成 " + "name is:" + name + " age is:" + age);
}
public static void main(String[] args) {
DirtyRead dirtyRead = new DirtyRead();
new Thread(new Runnable() {
@Override
public void run() {
dirtyRead.setvalue("hj", 100);
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
dirtyRead.getvalue();
}
}
2.锁的占有问题(关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)当做锁,所以代码中哪个线程先执行synchronized关键字的方法,哪个线程就持有该方法所属对象的锁(Lock),在静态方法上加synchronized关键字,表示锁定.class类,类一级别的锁(独占.class类)。)
/**
* 关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)当做锁,
* 所以代码中哪个线程先执行synchronized关键字的方法,哪个线程就持有该方法所属对象的锁(Lock),
* 在静态方法上加synchronized关键字,表示锁定.class类,类一级别的锁(独占.class类)。
*
* @author 4
*
*/
public class MultiThread {
private static int num = 0;
/* static */
private synchronized void print(String tag) {
try {
if (tag.equals("a")) {
num = 100;
System.out.println("tag set a,num is: " + num);
Thread.sleep(2000);
} else if (tag.equals("b")) {
num = 200;
System.out.println("tag set b,num is: " + num);
Thread.sleep(2000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
MultiThread m1 = new MultiThread();
new Thread(new Runnable() {
@Override
public void run() {
m1.print("a");
}
}, "t1").start();
new Thread(new Runnable() {
@Override
public void run() {
m1.print("b");
}
}, "t2").start();
}
}
3.对象锁的同步和异步问题(只有synchronized方法或者需要同步的代码才会同步执行,否则都是异步的)
/* *
* 对象锁的同步和异步问题
* */
public class Myobject {
public synchronized void method1() {
try {
Thread.sleep(2000);
System.out.println("method1被执行了。。。。");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/** synchronized */
public void method2() {
System.out.println("method2被执行了。。。。");
}
public static void main(String[] args) {
Myobject objectlock = new Myobject();
new Thread(new Runnable() {
@Override
public void run() {
objectlock.method1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
objectlock.method2();
}
}).start();
}
}
4.synchronized的重入(1.synchronized的重入 2.synchronized通过继承可以传递子类)
/**
* 1.synchronized的重入 <br>
* 2.synchronized通过继承可以传递子类(省略不写了)
*/
public class SyncDubbo1 {
public synchronized void method1() {
System.out.println("method1..");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
method2();
}
public synchronized void method2() {
System.out.println("method2..");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
method3();
}
public synchronized void method3() {
System.out.println("method3..");
}
public static void main(String[] args) {
SyncDubbo1 syncDubbo1=new SyncDubbo1();
new Thread(new Runnable() {
@Override
public void run() {
syncDubbo1.method1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
syncDubbo1.method2();
}
}).start();
}
}
5.锁的粒度大小
这一块本来是在lock这一块才说明现在先提一句。锁的粒度大小会影响运行的效率,粒度越大效率越差
LOCK使用
首先看看所得类型(对象锁,类锁,任意对象锁)
/***
* 对象锁,类锁,任意对象锁都可以枷锁
* @author 4
*/
public class ObjectLock {
public void method1() {
synchronized (this) {// 对象锁
try {
System.out.println("do method1");
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void method2() {
synchronized (this.getClass()) {// 类锁
try {
System.out.println("do method2");
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Object objectlock = new Object();
public void method3() {
synchronized (objectlock) {// 任意对象锁
try {
System.out.println("do method3");
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
final ObjectLock objLock = new ObjectLock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
objLock.method3();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
objLock.method3();
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
objLock.method2();
}
});
t1.start();
t2.start();
t3.start();
}
}
再说说所得粒度大小问题:
1.同步方法<同步代码块:因为一个是进入方法内加锁的,一个是在排队方法外加锁,这样即使获得了锁,进入方法中还得分配资源需要一定的时间。
2.减小锁的大小,byte数组对象锁,推荐的写法,效率好。
private byte []lock=new byte [1]
3.减小同步代码块的大小
/**
* 使用synchronized代码块减小锁的粒度,提高性能, synchronized包含的业务要小
*/
public class Optimize {
public void doLongTimeTask() {
try {
System.out.println("当前线程开始:" + Thread.currentThread().getName() + ", 正在执行一个较长时间的业务操作,其内容不需要同步");
Thread.sleep(2000);
synchronized (this) {
System.out.println("当前线程:" + Thread.currentThread().getName() + ", 执行同步代码块,对其同步变量进行操作");
Thread.sleep(1000);
}
System.out.println("当前线程结束:" + Thread.currentThread().getName() + ", 执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
final Optimize otz = new Optimize();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
otz.doLongTimeTask();
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
otz.doLongTimeTask();
}
}, "t2");
t1.start();
t2.start();
}
}
锁对象改变锁失效
/*
* 锁对象的改变,锁失效6
* */
public class ChangeLock {
private String lock = new String("lock");
private void method() {
synchronized (lock) {
try {
System.out.println("当前线程 : " + Thread.currentThread().getName() + "start.....");
// lock = new String("change lock");
Thread.sleep(2000);
System.out.println("当前线程 : " + Thread.currentThread().getName() + "end.....");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ChangeLock changeLock = new ChangeLock();
new Thread(new Runnable() {
@Override
public void run() {
changeLock.method();
}
}, "t1").start();
new Thread(new Runnable() {
@Override
public void run() {
changeLock.method();
}
}, "t2").start();
new Thread(new Runnable() {
@Override
public void run() {
changeLock.method();
}
}, "t3").start();
}
}
读者可以放开注释,体验锁失效的效果
ps:不要在锁的代码块中修改锁的内容。
显示锁reentrantlock
reentrantlock主要有4种方法lock()ulock()newCondition() getHoldCount()
1.reentrantlock入门lock()ulock()方法
/**
* @classDesc:ReentrantLock lock unlock演示
* @author: hj
* @date:2018年12月12日 下午1:07:27
*/
public class UseReentrantLock {
public Lock lock = new ReentrantLock();
public void method1() {
try {
lock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method1..");
Thread.sleep(1000);
System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method1..");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void method2() {
try {
lock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method2..");
Thread.sleep(2000);
System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method2..");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final UseReentrantLock ur = new UseReentrantLock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
ur.method1();
ur.method2();
}
}, "t1");
t1.start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2.reentrantlock的newCondition()方法
/**
* @classDesc:lock.newCondition()方法演示
* @author: hj
* @date:2018年12月12日 下午1:16:02
*/
public class UseCondition {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void method1() {
try {
lock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + "进入等待状态..");
Thread.sleep(3000);
System.out.println("当前线程:" + Thread.currentThread().getName() + "释放锁..");
condition.await(); // Object wait
System.out.println("当前线程:" + Thread.currentThread().getName() + "继续执行...");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void method2() {
try {
lock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + "进入..");
Thread.sleep(3000);
System.out.println("当前线程:" + Thread.currentThread().getName() + "发出唤醒..");
condition.signal(); // Object notify
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final UseCondition uc = new UseCondition();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
uc.method2();
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
uc.method2();
}
}, "t2");
t1.start();
t2.start();
}
}
3. reentrantlock的多个newCondition()方法
/**
* @classDesc: lock.newCondition()多condition的用法
* @author: hj
* @date:2018年12月12日 下午1:17:48
*/
public class UseManyCondition {
private ReentrantLock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
public void m1() {
try {
lock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + "进入方法m1等待..");
c1.await();
System.out.println("当前线程:" + Thread.currentThread().getName() + "方法m1继续..");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void m2() {
try {
lock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + "进入方法m2等待..");
c1.await();
System.out.println("当前线程:" + Thread.currentThread().getName() + "方法m2继续..");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void m3() {
try {
lock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + "进入方法m3等待..");
c2.await();
System.out.println("当前线程:" + Thread.currentThread().getName() + "方法m3继续..");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void m4() {
try {
lock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + "唤醒..");
c1.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void m5() {
try {
lock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + "唤醒..");
c2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final UseManyCondition umc = new UseManyCondition();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
umc.m1();
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
umc.m2();
}
}, "t2");
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
umc.m3();
}
}, "t3");
Thread t4 = new Thread(new Runnable() {
@Override
public void run() {
umc.m4();
}
}, "t4");
Thread t5 = new Thread(new Runnable() {
@Override
public void run() {
umc.m5();
}
}, "t5");
t1.start(); // c1
t2.start(); // c1
t3.start(); // c2
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t4.start(); // c1
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t5.start(); // c2
}
}
4.holdCount锁的持有次数
/**
* lock.getHoldCount()方法:只能在当前调用线程内部使用,不能再其他线程中使用
* 那么我可以在m1方法里去调用m2方法,同时m1方法和m2方法都持有lock锁定即可 测试结果holdCount数递增
*/
public class TestHoldCount {
// 重入锁
private ReentrantLock lock = new ReentrantLock();
public void m1() {
try {
lock.lock();
System.out.println("进入m1方法,holdCount数为:" + lock.getHoldCount());
// 调用m2方法
m2();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void m2() {
try {
lock.lock();
System.out.println("进入m2方法,holdCount数为:" + lock.getHoldCount());
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
TestHoldCount thc = new TestHoldCount();
thc.m1();
thc.m2();
System.out.println("holdCount数为:" + thc.lock.getHoldCount());
}
}
显示锁readwritelock
主要记住:RR共享 WW、RW、WW排斥
/**
* @classDesc: 读写锁的使用
* @author: hj
* @date:2018年12月12日 下午1:21:30
*/
public class UseReentrantReadWriteLock {
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private ReadLock readLock = rwLock.readLock();
private WriteLock writeLock = rwLock.writeLock();
public void read() {
try {
readLock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + "进入...");
Thread.sleep(3000);
System.out.println("当前线程:" + Thread.currentThread().getName() + "退出...");
} catch (Exception e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
}
public void write() {
try {
writeLock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + "进入...");
Thread.sleep(3000);
System.out.println("当前线程:" + Thread.currentThread().getName() + "退出...");
} catch (Exception e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
public static void main(String[] args) {
final UseReentrantReadWriteLock urrw = new UseReentrantReadWriteLock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
urrw.read();
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
urrw.read();
}
}, "t2");
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
urrw.write();
}
}, "t3");
Thread t4 = new Thread(new Runnable() {
@Override
public void run() {
urrw.write();
}
}, "t4");
t1.start(); // RR共享
t2.start();
// t1.start(); // R 互斥
// t3.start(); // W
// t3.start();//互斥
// t4.start();
}
}
多线程死锁
什么是多线程死锁?
答:同步中嵌套同步,导致锁无法释放
/**
* @classDesc: 死锁问题,在设计程序时就应该避免双方相互持有对方的锁的情况 演示死锁
* @author: hj
* @date:2018年12月12日 下午12:13:01
*/
public class DeadLock implements Runnable {
private String tag;
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public void setTag(String tag) {
this.tag = tag;
}
@Override
public void run() {
if (tag.equals("a")) {
synchronized (lock1) {
try {
System.out.println("当前线程 : " + Thread.currentThread().getName() + " 进入lock1执行");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("当前线程 : " + Thread.currentThread().getName() + " 进入lock2执行");
}
}
}
if (tag.equals("b")) {
synchronized (lock2) {
try {
System.out.println("当前线程 : " + Thread.currentThread().getName() + " 进入lock2执行");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("当前线程 : " + Thread.currentThread().getName() + " 进入lock1执行");
}
}
}
}
public static void main(String[] args) {
DeadLock d1 = new DeadLock();
d1.setTag("a");
DeadLock d2 = new DeadLock();
d2.setTag("b");
Thread t1 = new Thread(d1, "t1");
Thread t2 = new Thread(d2, "t2");
t1.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
面试题
1.什么是线程安全
2.java内存模型
3.线程安全实现
4.同步代码的优化
5.锁的加锁、释放,以及读写锁的使用特点
6.死锁的概念
最后
以上就是优美芒果为你收集整理的水滴石穿--多线程安全同步与锁什么是线程安全?java内存模型线程安全解决办法:LOCK使用 锁对象改变锁失效显示锁reentrantlock显示锁readwritelock 多线程死锁面试题的全部内容,希望文章能够帮你解决水滴石穿--多线程安全同步与锁什么是线程安全?java内存模型线程安全解决办法:LOCK使用 锁对象改变锁失效显示锁reentrantlock显示锁readwritelock 多线程死锁面试题所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复