1、线程安全
2、多个线程多个锁
3、对象锁同步和异步
4、脏读
5、synchronized其他概念
6、volatile关键字
7、线程通信
8、ThreadLocal
1、线程安全
1.1、线程安全概念:当多个线程访问某一个类(对象或方法)时,这个类始终都能保持正确的行为,那么这个类(对象或方法)是线程安全的。
1.2、synchronized:可以在任意对象及方法上加上锁,而加锁的这段代码成为‘互斥区’或‘临界区’。
举一个简单例子
import java.util.concurrent.atomic.AtomicInteger;
public class MyThread extends Thread{
private int count = 5 ;
//synchronized加锁
public void run(){
count--;
System.out.println(this.currentThread().getName() + " count = "+ count);
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread t1 = new Thread(myThread,"t1");
Thread t2 = new Thread(myThread,"t2");
Thread t3 = new Thread(myThread,"t3");
Thread t4 = new Thread(myThread,"t4");
Thread t5 = new Thread(myThread,"t5");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
打印出来的结果是
t2 count = 3
t4 count = 1
t3 count = 2
t1 count = 3
t5 count = 0
这个明显不是我想要的。
我们只需要在run方法加上一把锁(synchronized)就可以
t1 count = 4
t5 count = 3
t4 count = 2
t2 count = 1
t3 count = 0
总结:我们代码中线程写的是t1、t2、t3、t4、t5,但是我们打印出来的结果并没有按照这样的顺序去走。
分析:当多个线程访问myThread的run方法时,以排队的方式进行处理(这里排对是按照CPU分配的先后顺序而定的)。一个线程想要执行synchronized修饰的方法里的代码:1 尝试获得锁 2 如果拿到锁,执行synchronized代码体内容;拿不到锁,这个线程就会不断的尝试获得这把锁,直到拿到为止, 而且是多个线程同时去竞争这把锁。(也就是会有锁竞争的问题)
2、多个线程多个锁
多个线程,每个线程可以拿到自己指定的锁,分别获得锁之后,执行synchroized方法体的内容。
举一个简单例子
public class MultiThread {
private int num = 0;
/** static */
public synchronized void printNum(String tag){
try {
if(tag.equals("a")){
num = 100;
System.out.println("tag a, set num over!");
Thread.sleep(1000);
} else {
num = 200;
System.out.println("tag b, set num over!");
}
System.out.println("tag " + tag + ", num = " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//注意观察run方法输出顺序
public static void main(String[] args) {
//俩个不同的对象
final MultiThread m1 = new MultiThread();
final MultiThread m2 = new MultiThread();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
m1.printNum("a");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
m2.printNum("b");
}
});
t1.start();
t2.start();
}
}
打印出来的结果是
tag a, set num over!
tag b, set num over!
tag b, num = 200
tag a, num = 100
出现以上结果的原因就是因为m1 m2是2个不同的对象。互不影响
按照正常思维的话代码打印结果应该是tag a, set num over!
tag a, num = 100
tag b, set num over!
tag b, num = 200
才符合我们的逻辑。如果你想要这种结果,就需要在 printNum()前面使用static修饰。如果在synchronized加上static,那么以后使用printNum()所获的的锁就是MultiThread这个类级别的锁
总结: 关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)当做锁, 所以代码中哪个线程先执行synchronized关键字的方法,哪个线程就持有该方法所属对象的锁(Lock), 在静态方法上加synchronized关键字,表示锁定.class类,类一级别的锁(独占.class类)。
3、对象锁同步和异步
同步(synchroized):同步的概念就是共享,我们要牢牢记住‘共享’这2个字,如果不是共享的资源,就没有必要进行同步。
异步(asynchroized):异步的概念就是独立,互相之间不受到任何限制。好像页面发起http请求一样,我们还可以继续浏览或者操作页面的内容。
同步的目的是为了线程安全,其实对于线程安全来说,需要满足2个特性:
原子性(同步)
可见性
举个例子
public class MyObject {
public synchronized void method1(){
try {
System.out.println(Thread.currentThread().getName());
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/** synchronized */
public void method2(){
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
final MyObject mo = new MyObject();
/**
* 分析:
* t1线程先持有object对象的Lock锁,t2线程可以以异步的方式调用对象中的非synchronized修饰的方法
* t1线程先持有object对象的Lock锁,t2线程如果在这个时候调用对象中的同步(synchronized)方法则需等待,也就是同步
*/
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
mo.method1();
}
},"t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
mo.method2();
}
},"t2");
t1.start();
t2.start();
}
}
打印结果为:
t1
t2
这2个同时打印。
如果我们在method2()加了synchronized方法,则会出现t1打印完秒钟后,才打印t2,因为进入method2()需要method1()释放对象锁。
4、脏读
对于对象的同步和异步的方法,编码的时候,就必须要考虑问题的整体,不然会出现数据不一致的情况,很经典的错误就是脏读(dirtyread)
举个例子
public class DirtyRead {
private String username = "xjd";
private String password = "123";
public synchronized void setValue(String username, String password) {
this.username = username;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.password = password;
System.out.println("setValue最终结果:username = " + username + " , password = " + password);
}
public void getValue() {
System.out.println("getValue方法得到:username = " + this.username + " , password = " + this.password);
}
public static void main(String[] args) throws Exception {
final DirtyRead dr = new DirtyRead();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
dr.setValue("z3", "456");
}
});
t1.start();
Thread.sleep(1000);
dr.getValue();
}
}
getValue方法得到:username = z3 , password = 123
setValue最终结果:username = z3 , password = 456
结果明显不符合我们期望的,这就是脏读。
我们只需要在getValue()加上synchronized即可 。
5、synchronized其他概念
关键字synchronized拥有锁重入的功能,也就是在使用synchronized的时候,当一个线程得到一个对象后,再次请求此对象的时候可以再次得到该对象的锁。
举个例子:
形式一、
public class SyncDubbo1 {
public synchronized void method1(){
System.out.println("method1..");
method2();
}
public synchronized void method2(){
System.out.println("method2..");
method3();
}
public synchronized void method3(){
System.out.println("method3..");
}
public static void main(String[] args) {
final SyncDubbo1 sd = new SyncDubbo1();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
sd.method1();
}
});
t1.start();
}
}
形式二(父子继承关系的)
public class SyncDubbo2 {
static class Main {
public int i = 10;
public synchronized void operationSup(){
try {
i--;
System.out.println("Main print i = " + i);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class Sub extends Main {
public synchronized void operationSub(){
try {
while(i > 0) {
i--;
System.out.println("Sub print i = " + i);
Thread.sleep(100);
this.operationSup();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
Sub sub = new Sub();
sub.operationSub();
}
});
t1.start();
}
}
这就是锁重入。
synchronized异常:对于web应用程序,异常释放锁的情况,如果不及时处理,很可能对你的应用程序产生严重的逻辑错误,比如很多对象都在等待第一个对象的正确执行完毕再去释放对象锁,但是第一个对象由于异常出现,导致业务代码无法被正常的执行,就释放了锁。那么可想而知后续的对象执行的全部都是错误的逻辑。这里的处理方案有好几种,比如:比如你有20个任务,这些任务都是没有关系的。那么可以使用try catch去捕获然后写入日志。如果这20个任务是有关联关系的,那么就要停止这个线程,记录日志,不同的业务有不同的处理方案。
public class SyncException {
private int i = 0;
public synchronized void operation(){
while(true){
try {
i++;
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + " , i = " + i);
if(i == 20){
//Integer.parseInt("a");
throw new RuntimeException();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
final SyncException se = new SyncException();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
se.operation();
}
},"t1");
t1.start();
}
}
输出结果为:
t1 , i = 1
t1 , i = 2
t1 , i = 3
t1 , i = 4
t1 , i = 5
t1 , i = 6
t1 , i = 7
t1 , i = 8
t1 , i = 9
t1 , i = 10
t1 , i = 11
t1 , i = 12
t1 , i = 13
t1 , i = 14
t1 , i = 15
t1 , i = 16
t1 , i = 17
t1 , i = 18
t1 , i = 19
Exception in thread "t1" java.lang.RuntimeException
at com.eshine.base.sync005.SyncException.operation(SyncException.java:18)
at com.eshine.base.sync005.SyncException$1.run(SyncException.java:32)
at java.lang.Thread.run(Thread.java:724)
t1 , i = 20
synchronized代码块其他细节
对象锁
public class ObjectLock {
public void method1(){
synchronized (this) { //对象锁
try {
System.out.println("do method1..");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void method2(){ //类锁
synchronized (ObjectLock.class) {
try {
System.out.println("do method2..");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private Object lock = new Object();
public void method3(){ //任何对象锁
synchronized (lock) {
try {
System.out.println("do method3..");
Thread.sleep(2000);
} catch (InterruptedException 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.method1();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
objLock.method2();
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
objLock.method3();
}
});
t1.start();
t2.start();
t3.start();
}
}
同一对象属性的修改不会影响锁的情况
public class ModifyLock {
private String name ;
private int age ;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public synchronized void changeAttributte(String name, int age) {
try {
System.out.println("当前线程 : " + Thread.currentThread().getName() + " 开始");
this.setName(name);
this.setAge(age);
System.out.println("当前线程 : " + Thread.currentThread().getName() + " 修改对象内容为: "
+ this.getName() + ", " + this.getAge());
Thread.sleep(2000);
System.out.println("当前线程 : " + Thread.currentThread().getName() + " 结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
final ModifyLock modifyLock = new ModifyLock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
modifyLock.changeAttributte("张三", 20);
}
},"t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
modifyLock.changeAttributte("李四", 21);
}
},"t2");
t1.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
死锁:死锁问题,在设计程序时就应该避免双方相互持有对方的锁的情况
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();
}
}
6、volatile关键字
volatile关键字的主要作用是使变量在多个线程中可见
public class RunThread extends Thread{
private boolean isRunning = true;
private void setRunning(boolean isRunning){
this.isRunning = isRunning;
}
public void run(){
System.out.println("进入run方法..");
int i = 0;
while(isRunning == true){
//..
}
System.out.println("线程停止");
}
public static void main(String[] args) throws InterruptedException {
RunThread rt = new RunThread();
rt.start();
Thread.sleep(1000);
rt.setRunning(false);
System.out.println("isRunning的值已经被设置了false");
}
}
打印结果为:
进入run方法..
isRunning的值已经被设置了false
但是线程并没有停止,原因:在线程执行的时候为了提高性能,虚拟机会分配多一块内存空间出来存放线程中使用的变量,线程每次使用到变量就直接读取,而不是从外部读取,导致了这种情况。所以我们需要在private volatile boolean isRunning = true; 这句代码加上volatile 。
进入run方法..
isRunning的值已经被设置了false
线程停止
volatile关键字不具备synchronized关键字的原子性(同步)建议使用AtomicInteger
public class VolatileNoAtomic extends Thread{
//private static volatile int count;
private static AtomicInteger count = new AtomicInteger(0);
private static void addCount(){
for (int i = 0; i < 1000; i++) {
//count++ ;
count.incrementAndGet();
}
System.out.println(count);
}
public void run(){
addCount();
}
public static void main(String[] args) {
VolatileNoAtomic[] arr = new VolatileNoAtomic[100];
for (int i = 0; i < 10; i++) {
arr[i] = new VolatileNoAtomic();
}
for (int i = 0; i < 10; i++) {
arr[i].start();
}
}
}
最终结果为10000. 当然也可以在addCount() 加 synchronized;最终结果也是一样的
volatie关键字只具有可见性,没有原子性。要实现原子性建议使用atomic类的系列对象,支持原子性操作(注意:atomic类只保证本身方法原子性,并不保证多次操作的原子性)
举个栗子
public class AtomicUse {
private static AtomicInteger count = new AtomicInteger(0);
// 多个addAndGet在一个方法内是非原子性的,需要加synchronized进行修饰,保证4个addAndGet整体原子性
/** synchronized */
public synchronized int multiAdd() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count.addAndGet(1);
count.addAndGet(2);
count.addAndGet(3);
count.addAndGet(4); // +10
return count.get();
}
public static void main(String[] args) {
final AtomicUse au = new AtomicUse();
List<Thread> ts = new ArrayList<Thread>();
for (int i = 0; i < 100; i++) {
ts.add(new Thread(new Runnable() {
@Override
public void run() {
System.out.println(au.multiAdd());
}
}));
}
for (Thread t : ts) {
t.start();
}
}
}
这里我们只能添加synchronized关键字来保证一数据的最终一致。
7、线程通信
使用wait/notify方法实现线程间的通信,这2个方法都是Object的类的方法,也就是说,java为所有类都提供了这2个方法
7.1、wait和notify必须配合synchronized关键字使用
7.2、wait方法释放对象锁,notify不会释放对象锁
这里有2个例子,先看第一个
public class ListAdd1 {
private volatile static List list = new ArrayList();
public void add() {
list.add("xjd");
}
public int size() {
return list.size();
}
public static void main(String[] args) {
final ListAdd1 list1 = new ListAdd1();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
list1.add();
System.out.println("当前线程:" + Thread.currentThread().getName() + "添加了一个元素..");
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
if (list1.size() == 5) {
System.out.println("当前线程收到通知:" + Thread.currentThread().getName() + " list size = 5 线程停止..");
throw new RuntimeException();
}
}
}
}, "t2");
t1.start();
t2.start();
}
}
打印结果:
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程收到通知:t2 list size = 5 线程停止..
Exception in thread "t2" java.lang.RuntimeException
at com.eshine.base.conn008.ListAdd1$2.run(ListAdd1.java:43)
at java.lang.Thread.run(Thread.java:724)
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
这种方式非常的不好,因为t2一直在轮询。导致性能会比较差
第二种方式则更好一点
public class ListAdd2 {
private volatile static List list = new ArrayList();
public void add() {
list.add("xjd");
}
public int size() {
return list.size();
}
public static void main(String[] args) {
final ListAdd2 list2 = new ListAdd2();
// 1 实例化出来一个 lock
// 当使用wait 和 notify 的时候 , 一定要配合着synchronized关键字去使用
// final Object lock = new Object();
final CountDownLatch countDownLatch = new CountDownLatch(1);
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
// synchronized (lock) {
for (int i = 0; i < 10; i++) {
list2.add();
System.out.println("当前线程:" + Thread.currentThread().getName() + "添加了一个元素..");
Thread.sleep(500);
if (list2.size() == 5) {
System.out.println("已经发出通知..");
countDownLatch.countDown();
// lock.notify();
}
}
// }
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// synchronized (lock) {
if (list2.size() != 5) {
try {
// System.out.println("t2进入...");
// lock.wait();
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("当前线程:" + Thread.currentThread().getName() + "收到通知线程停止..");
throw new RuntimeException();
// }
}
}, "t2");
t2.start();
t1.start();
}
}
打印结果:
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
已经发出通知..
Exception in thread "t2" java.lang.RuntimeException
at com.eshine.base.conn008.ListAdd2$2.run(ListAdd2.java:71)
at java.lang.Thread.run(Thread.java:724)
当前线程:t1添加了一个元素..
当前线程:t2收到通知线程停止..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
7.3使用wait/notify模拟Queue
BlockingQueue:顾名思义,一个支持阻塞式机制的队列,他有put、take方法
put:把一个object放入到BlockingQueue中,如果BlockingQueue没有空间,则调用此方法的线程被阻断,直到BlockingQueue有空间再继续
take:取走BlockingQueue的首个对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据加入
public class MyQueue {
//1 需要一个承装元素的集合
private LinkedList<Object> list = new LinkedList<Object>();
//2 需要一个计数器
private AtomicInteger count = new AtomicInteger(0);
//3 需要制定上限和下限
private final int minSize = 0;
private final int maxSize ;
//4 构造方法
public MyQueue(int size){
this.maxSize = size;
}
//5 初始化一个对象 用于加锁
private final Object lock = new Object();
//put(anObject): 把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断,直到BlockingQueue里面有空间再继续.
public void put(Object obj){
synchronized (lock) {
while(count.get() == this.maxSize){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//1 加入元素
list.add(obj);
//2 计数器累加
count.incrementAndGet();
//3 通知另外一个线程(唤醒)
lock.notify();
System.out.println("新加入的元素为:" + obj);
}
}
//take: 取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入.
public Object take(){
Object ret = null;
synchronized (lock) {
while(count.get() == this.minSize){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//1 做移除元素操作
ret = list.removeFirst();
//2 计数器递减
count.decrementAndGet();
//3 唤醒另外一个线程
lock.notify();
}
return ret;
}
public int getSize(){
return this.count.get();
}
public static void main(String[] args) {
final MyQueue mq = new MyQueue(5);
mq.put("a");
mq.put("b");
mq.put("c");
mq.put("d");
mq.put("e");
System.out.println("当前容器的长度:" + mq.getSize());
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
mq.put("f");
mq.put("g");
}
},"t1");
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
Object o1 = mq.take();
System.out.println("移除的元素为:" + o1);
Object o2 = mq.take();
System.out.println("移除的元素为:" + o2);
}
},"t2");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
打印结果为:
新加入的元素为:a
新加入的元素为:b
新加入的元素为:c
新加入的元素为:d
新加入的元素为:e
当前容器的长度:5
新加入的元素为:f
移除的元素为:a
移除的元素为:b
新加入的元素为:g
8、ThreadLocal
概念:线程局部变量,是一种多线程间并发访问变量的解决方案。与synchronized加锁方式不同,TheadLocal不提供锁,而是使用空间换时间的手段,为每一个线程提供变量的独立副本,以保证线程安全。
public class ConnThreadLocal {
public static ThreadLocal<String> th = new ThreadLocal<String>();
public void setTh(String value){
th.set(value);
}
public void getTh(){
System.out.println(Thread.currentThread().getName() + ":" + this.th.get());
}
public static void main(String[] args) throws InterruptedException {
final ConnThreadLocal ct = new ConnThreadLocal();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
ct.setTh("张三");
ct.getTh();
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
ct.getTh();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t2");
t1.start();
t2.start();
}
}
结果:
t1:张三
t2:null