线程安全
当多个线程同时操作一个数据结构的时候,可能会发生一些奇妙的情况(比如相互串行或者相互修改),这种情况发生后就无法保证数据的一致性,这也是不安全的线程,为了保证数据的一致性,我们在这里讨论线程安全。
1.首先我们新建一个线程
package ThreadSecure;
public class Thread_A extends Thread{
private work work; //这是工作
public Thread_A(work work)
{
this.work = work;
}
@Override
public void run()
{
work.add();
}
}
这是一个不包含任何线程安全写法的线程,这在单例模式中是非常危险的。
这个线程主要实现一个功能就是运行一个“任务”,然后在任务结束后使得num获得++,
2.然后我们编辑工作内容
package ThreadSecure;
public class work {
public int number = 0;
public void add()
{
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
number = number + 1 ;
System.out.println(Thread.currentThread().getName() + "-" + number);
}
}
3.然后我们编辑主函数,使得主函数创建三个线程,用来执行work.add()这个功能,然后观察结果
package ThreadSecure;
public class ThreadMain {
public static void main(String[] args)
{
work work = new work();
for(int i = 0 ; i < 5 ; i++)
{
Thread_A p1 = new Thread_A(work);
p1.start();
}
try {
Thread.sleep(1001);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(work.number);
}
}
4.输出结果
//第一次
Thread-0-1
Thread-4-2
Thread-1-3
Thread-3-3
Thread-2-2
3
//第二次
Thread-4-3
Thread-0-3
Thread-3-3
Thread-2-3
Thread-1-3
3
可以看到运行几次的结果都是不同的,能不能按照我们所想,用number累计做了多少次work.add()完全是凭借运气!
所以在这里使用JAVA的先进功能隐式锁修改代码
//关键字
synchronized(object);
隐式锁
synchronzied是JAVA的一个关键字,在用其进行修饰代码块或者方法的时候,可以保证同一时刻最多只有一个线程执行该代码段,从此访问需要讲究顺序,凭一个先来后到。
示例1:修饰于方法上,放在public等之后、返回类型之前
public synchronized void synMethod()
{
//method
}
示例2:修饰于代码块上
public int synMethod(int al){
synchronized (this){//synchronized(Object)
}//一次只有一个线程可以进入
//这里面的object可以为一个对象、参数本身、this,不建议对非对象使用
}
规则
- 当两个进程并发地访问一个对象的隐式锁同步代码块时,同一个时间内只能有一个代码块得到执行
- 当一个线程访问object的一个隐式锁同步代码块时,另外一个线程可以访问该对象中的非锁条件(所以不建议对非对象使用,因为容易混淆)
- 尤其关键的是当一个线程访问object中的synchronized(this)方法块时,其他线程对object中的所有其他synchronized(this)同步代码块的访问将会被阻塞
- 也就是说,当一个线程访问被隐式锁锁住的对象中的某个被锁的代码块,其他所有线程对这个对象中的被锁代码的访问都会被堵塞
- 以上规则适用于其他对象锁
1.利用隐式锁修改代码
了解了隐式锁后我们就尝试用隐式锁修改代码,以完成我们之前的目的
package ThreadSecure;
public class work {
public int number = 0;
public synchronized void add()
{
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
number = number + 1 ;
System.out.println(Thread.currentThread().getName() + "-" + number);
}
}
可见我们在add方法上加入了一个隐式锁
2.运行结果
Thread-0-1
Thread-1-2
2
Thread-4-3
Thread-3-4
Thread-2-5
程序走到add处得以一步一步地执行
三种隐式锁的写法
1.锁方法体
class A{
public synchronized void method()
{
//method
}
}
2.锁this
class A{
publicvoid method()
{
synchronized(this){
//method
}
}
}
3.锁小东西
class A{
private byte [] lock = new byte[1];
public void method()
{
synchronized(lock){
//method
}
}
}
三种方法的速度大约是3>2>1
因为锁和解锁一个对象是需要此对象的资源,所以锁的对象越小,那么效率也越高,与此同时,一个线程进入对象时调用资源也是需要一定时间的,在最关键的时候锁当然比把线程“关”在门外面速度更快一点。造一个一字节的byte对象最小,所以在工作中经常这样写。