先举个例子线程不安全的
package a;
class Test {
public int a=0;
public void setValue(String name) throws InterruptedException {
if(name.equals("a")){
a=100;
Thread.sleep(1000);
System.out.println("a= "+a);
}
else {
a=200;
System.out.println("b= "+a);
}
}
}
class RunA extends Thread{
Test a;
public RunA(Test a){
this.a=a;
}
@Override
public void run() {
super.run();
try {
a.setValue("a");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class RunB extends Thread{
Test a;
public RunB(Test a){
this.a=a;
}
@Override
public void run() {
super.run();
try {
a.setValue("b");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Sender extends Thread {
public static void main(String[] args) throws InterruptedException {
Test a= new Test();
RunA A=new RunA(a);
A.start();
RunB B=new RunB(a);
B.start();
A.setName("A");
System.out.println("end");
}
}
打印结果:
end
b= 200
a= 200
原因是: 多个线程同时访问一个对象变量,有可能会出现线程安全。
解决方法:
在访问的方法加个关键字 synchronized 就变成了同步访问了。
synchronized public void setValue(String name) throws // 添加了关键字InterruptedException {
if(name.equals("a")){
a=100;
Thread.sleep(1000);
System.out.println("a= "+a);
}
else {
a=200;
System.out.println("b= "+a);
}
}
这样就变成了同步访问。
同步和异步的区别:
同步: 按照顺序执行业务,先执行A 业务,然后执行B 业务
异步: A业务执行的时候,B业务也继续执行。
打印结果如下:
end
a= 100
b= 200
经常犯的错误是 如果是多个对象一定是多个对象锁,他们之间是异步的情况,没有锁的竞争, 例子验证如下:
package a;
class Test {
public int a=0;
synchronized public void setValue(String name) throws InterruptedException {
if(name.equals("a")){
a=100;
Thread.sleep(1000);
System.out.println("a= "+a);
}
else {
a=200;
System.out.println("b= "+a);
}
}
}
class RunA extends Thread{
Test a;
public RunA(Test a){
this.a=a;
}
@Override
public void run() {
super.run();
try {
a.setValue("a");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class RunB extends Thread{
Test a;
public RunB(Test a){
this.a=a;
}
@Override
public void run() {
super.run();
try {
a.setValue("b");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Sender extends Thread {
public static void main(String[] args) throws InterruptedException {
Test a= new Test();
RunA A=new RunA(a);
A.start();
RunB B=new RunB(a);
B.start();
A.setName("A");
Test c= new Test();
RunA C=new RunA(c);
C.start();
RunB D=new RunB(c);
D.start();
A.setName("A");
System.out.println("end");
}
}
打印的结果为:
end
a= 100
a= 100
b= 200
b= 200
从结果分析是 异步的, 如果是同步的结果应该是如下:
end
a= 100
b= 200
a= 100
b= 200
大家记住两条就行了同步的条件是
1.synchronized 方法 和对象(同一个)生成一个对象锁
2. 有了对象锁还需要一个条件是 共享 ,只有共享资源的读写访问才需要同步化,如果不是共享资源,那么就没有同步的必要。
对象锁只是针对有同步关键字的(synchronized)方法做同步,其他方法没有同步关键字是不做处理的,关注下边的例子
package a;
class Test {
public int a=0;
synchronized public void setValue(String name) throws InterruptedException {
if(name.equals("a")){
a=100;
System.out.println("A对象同步方法开始运行了 ");
Thread.sleep(1000);
System.out.println("a= "+a);
}
else {
System.out.println("B对象开始同步");
a=200;
System.out.println("b= "+a);
}
}
public void method2() throws InterruptedException {
System.out.println("没有同步关键字method2 begin");
Thread.sleep(500);
System.out.println("method2 开始了,执行的线程为: "+Thread.currentThread().getName());
}
}
class RunA extends Thread{
Test a;
public RunA(Test a){
this.a=a;
}
@Override
public void run() {
super.run();
try {
a.setValue("a");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class RunB extends Thread{
Test a;
public RunB(Test a){
this.a=a;
}
@Override
public void run() {
super.run();
try {
a.method2();
a.setValue("B");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Sender extends Thread {
public static void main(String[] args) throws InterruptedException {
Test a= new Test();
RunA A=new RunA(a);
RunB B=new RunB(a);
A.setName("A");
A.start();
B.setName("B");
B.start();
System.out.println("end");
}
}
打印的结果如下:
end
A对象同步方法开始运行了
没有同步关键字method2 begin
method2 开始了,执行的线程为: B
a= 100
B对象开始同步
b= 200
通过上边的例子可以看出, method2 没有同步, 如果想同步可以的, 直接加上关键字 修改如下:
synchronized public void method2() throws InterruptedException {
运行结果如下,他们的按照顺序进行运行,达到了同步:
end
A对象同步方法开始运行了
a= 100
没有同步关键字method2 begin
method2 开始了,执行的线程为: B
B对象开始同步
b= 200
同步运行总结如下:
通过以上实例可以得出, 在读取同一个实例变量的时候, 写入的方法虽然是 同步 ,但是如果 读取的不是同步,就会出现脏数据。解决方法就是在 读取数据的时候也要加入关键字。
synchronized关键字同步方法,是有局限的, 比如我同步方法里有耗时的操作, 所有线程只能一直等着。 解决方法 同步语句块, 同步语句块可以传入任意对象,并且把任意对象当成锁对象,哪个线程持有这个锁,就可以执行同步方法。
咱们验证下不用同步语句块时间消耗对比,代码如下:
class CommonUtils{
public static long beginTime1;
public static long beginTime2;
public static long endTime1;
public static long endTime2;
}
class Test {
public int a=0;
synchronized public void test() throws InterruptedException {
Thread.sleep(3000);
a++;
System.out.println(Thread.currentThread().getName()+" a的值 "+a);
}
}
class RunA extends Thread{
Test a;
public RunA(Test a){
this.a=a;
}
@Override
public void run() {
CommonUtils.beginTime1=System.currentTimeMillis();
super.run();
try {
a.test();
} catch (InterruptedException e) {
e.printStackTrace();
}
CommonUtils.endTime1=System.currentTimeMillis();
}
}
class RunB extends Thread{
Test a;
public RunB(Test a){
this.a=a;
}
@Override
public void run() {
super.run();
CommonUtils.beginTime2=System.currentTimeMillis();
try {
a.test();
} catch (InterruptedException e) {
e.printStackTrace();
}
CommonUtils.endTime2=System.currentTimeMillis();
}
}
public class Sender extends Thread {
public static void main(String[] args) throws InterruptedException {
Test a= new Test();
RunA A=new RunA(a);
RunB B=new RunB(a);
A.setName("A");
A.start();
B.setName("B");
B.start();
Thread.sleep(10000);
long begin=CommonUtils.beginTime1;
if (begin>CommonUtils.beginTime2){
begin=CommonUtils.beginTime2;
}
long end=CommonUtils.endTime1;
if(end
打印的结果如下:
A a的值 1
B a的值 2
耗时:6031
end
如果改成同步语句块的方式实现同步,修改方法为:
public void test() throws InterruptedException {
Thread.sleep(3000);
synchronized (this){
a++;
}
System.out.println(Thread.currentThread().getName()+" a的值 "+a);
}
打印的结果为:
B a的值 1
A a的值 2
耗时:3030
end
从结果看明显已经缩短了很多, 说明task 的sleep 方法是异步 , a++ 是同步的 ,方法内的其他是异步的,这就是半同步半异步的效果。
咱们证明下同步语句块是否真的就是通不了 ,修改方法为:
public void test() throws InterruptedException {
Thread.sleep(3000);
synchronized (this){
for (int i = 0; i < 50000; i++) {
a++;
}
}
System.out.println(Thread.currentThread().getName()+" a的值 "+a);
}
打印结果如下:
B a的值 50000
A a的值 100000
耗时:3034
end
确实是同步了,计算完全正确。
注意: 除了 synchronized(this) 格式来创建同步代码块,其实java还支持讲 任意对象作为锁来实现同步的功能, 这个任务对象 大多数是实例变量以及方法的参数,使用格式为synchronized(非this对象),
示例代码演示如下
修改下Test 类
class Test {
public int a=0;
Integer anything= 0;
public void test() throws InterruptedException {
Thread.sleep(3000);
synchronized (anything){
for (int i = 0; i < 50000; i++) {
a++;
}
}
System.out.println(Thread.currentThread().getName()+" a的值 "+a);
}
}
Integer anything= 0;
anything 作为锁对象, 任何对象都可以作为锁对象,你也可以写 String anything=new String(), 好处是当有多个锁方法的时候, 他们运行是同步的极大的消耗了时间, 如果把场景分一下, 运行不同的锁对象就极大的提高了我们的运行效率,因为不同的锁对象之间是异步运行的。
打印结果也是同步了 结果如下:
A a的值 100000
B a的值 50000
耗时:3038
end
大家猜猜如果 想使用 synchronized(非this对象)同步代码块和 synchronized 方法 同时存在的效果,
答案是 : 这两个方法由于不是同一个锁,所以是异步的验证代码如下:
package a;
class Test {
public int a=0;
Integer anything= 0;
public void test() throws InterruptedException {
System.out.println(Thread.currentThread().getName()+" a初始值 "+a);
synchronized (anything){
for (int i = 0; i < 50000; i++) {
a++;
}
}
System.out.println(Thread.currentThread().getName()+" a的值 "+a);
}
synchronized public void test2() throws InterruptedException {
System.out.println("线程"+Thread.currentThread().getName()+"Test 2 开始运行");
Thread.sleep(10);
System.out.println("线程"+Thread.currentThread().getName()+"Test 2 结束运行");
}
}
class RunA extends Thread{
Test a;
public RunA(Test a){
this.a=a;
}
@Override
public void run() {
super.run();
try {
a.test();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class RunB extends Thread{
Test a;
public RunB(Test a){
this.a=a;
}
@Override
public void run() {
super.run();
try {
a.test2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}}
public class Sender extends Thread {
public static void main(String[] args) throws InterruptedException {
Test a= new Test();
RunA A=new RunA(a);
RunB B=new RunB(a);
A.setName("A");
A.start();
B.setName("B");
B.start();
Thread.sleep(10000);
System.out.println("end");
}
}
打印的结果如下:
线程BTest 2 开始运行
A a初始值 0
线程BTest 2 结束运行
A a的值 50000
end
从结果显示是异步的。
如果是不同的锁对象(synchronized(非this对象))依然是异步的。 请看下边的例子:
package a;
class Test {
public int a=0;
Integer anything= 0;
String string=new String();
public void test() throws InterruptedException {
synchronized (anything){ //anything 是锁对象
System.out.println(Thread.currentThread().getName()+" a初始值 "+a);
for (int i = 0; i < 50000; i++) {
a++;
}
System.out.println(Thread.currentThread().getName()+" a的值 "+a);
}
}
public void test2() throws InterruptedException {
synchronized (string){ //string 是锁对象
System.out.println("线程"+Thread.currentThread().getName()+"Test 2 开始运行");
Thread.sleep(10);
System.out.println("线程"+Thread.currentThread().getName()+"Test 2 结束运行");
}
}
}
class RunA extends Thread{
Test a;
public RunA(Test a){
this.a=a;
}
@Override
public void run() {
super.run();
try {
a.test();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class RunB extends Thread{
Test a;
public RunB(Test a){
this.a=a;
}
@Override
public void run() {
super.run();
try {
a.test2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}}
public class Sender extends Thread {
public static void main(String[] args) throws InterruptedException {
Test a= new Test();
RunA A=new RunA(a);
RunB B=new RunB(a);
A.setName("A");
A.start();
B.setName("B");
B.start();
Thread.sleep(10000);
System.out.println("end");
}
}
打印结果:
线程BTest 2 开始运行
A a初始值 0
A a的值 50000
线程BTest 2 结束运行
end
如果换成同一个锁,一定是同步的, 修改如下:
public void test2() throws InterruptedException {
synchronized (anything){ // 换成同一个锁
System.out.println("线程"+Thread.currentThread().getName()+"Test 2 开始运行");
Thread.sleep(10);
System.out.println("线程"+Thread.currentThread().getName()+"Test 2 结束运行");
}
}
打印结果如下:
A a初始值 0
A a的值 50000
线程BTest 2 开始运行
线程BTest 2 结束运行
下边的例子挺高级的,请注意看了
用synchronized(非this对象) 和 其他线程 实例方法相结合进行同步。
关键点是 同步语句块的对象 就是 其他线程的实例对象,这样可以达到同步的效果 。
代码如下
package a;
class MyObject{
synchronized public void test2() throws InterruptedException { //这个实例对象的同步方法
System.out.println("线程"+Thread.currentThread().getName()+"Test 2 开始运行");
Thread.sleep(10);
System.out.println("线程"+Thread.currentThread().getName()+"Test 2 结束运行");
}
}
class Test {
public int a=0;
Integer anything= 0;
MyObject object;
public void test(MyObject object) throws InterruptedException {
synchronized (object){
System.out.println(Thread.currentThread().getName()+" a初始值 "+a);
for (int i = 0; i < 50000; i++) {
a++;
}
System.out.println(Thread.currentThread().getName()+" a的值 "+a);
}
}
}
class RunA extends Thread{
Test a;
MyObject object;
public RunA(Test a,MyObject object){
this.a=a;
this.object= object;
}
@Override
public void run() {
super.run();
try {
a.test(object);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class RunB extends Thread{
MyObject object;
public RunB(MyObject object){
this.object=object;
}
@Override
public void run() {
super.run();
try {
object.test2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}}
public class Sender extends Thread {
public static void main(String[] args) throws InterruptedException {
MyObject object=new MyObject();
Test a=new Test();
RunA A=new RunA(a,object);
RunB B=new RunB(object);
A.setName("A");
A.start();
B.setName("B");
B.start();
Thread.sleep(10000);
System.out.println("end");
}
}
打印结果为:
A a初始值 0
A a的值 50000
线程BTest 2 开始运行
线程BTest 2 结束运行
end
达到了同步效果。
同样也可以这么修改 用synchronized(this),依然同样同步的效果:
synchronized(this){
System.out.println("线程"+Thread.currentThread().getName()+"Test 2 开始运行");
Thread.sleep(10);
System.out.println("线程"+Thread.currentThread().getName()+"Test 2 结束运行");
}
静态同步synchronized 方法 与synchronized(class)语句块
Class 类对象是单例的,在静态static方法上使用synchronized 关键字声明同步方法时,使用当前静态方法所在类对象Class类的单例对象作为锁。
看下下边的例子,就是类对象锁 ,两个方法对应同一个Class 类对象。
package a;
class Test {
public static int a=0;
synchronized public static void test() throws InterruptedException {
System.out.println(Thread.currentThread().getName()+" a初始值 "+a);
for (int i = 0; i < 50000; i++) {
a++;
}
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+" a的值 "+a);
}
synchronized public static void test2() throws InterruptedException {
System.out.println("线程"+Thread.currentThread().getName()+"Test 2 开始运行");
Thread.sleep(10);
System.out.println("线程"+Thread.currentThread().getName()+"Test 2 结束运行");
}
}
class RunA extends Thread{
@Override
public void run() {
super.run();
try {
Test.test();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class RunB extends Thread{
@Override
public void run() {
super.run();
try {
Test.test2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}}
public class Sender extends Thread {
public static void main(String[] args) throws InterruptedException {
RunA A=new RunA();
RunB B=new RunB();
A.setName("A");
A.start();
B.setName("B");
B.start();
Thread.sleep(10000);
System.out.println("end");
}
}
达到了同步效果如下:
A a初始值 0
A a的值 50000
线程BTest 2 开始运行
线程BTest 2 结束运行
end
注意区分一下的观点
同步 syn static 方法可以对类的所有对象实例起作用
说的通俗点 对象实例 调用类的静态方法 和 类名字.method()是一样的, 所以他们都是调用了同一个锁 单例类锁
验证代码如下:
package a;
class Test {
public static int a=0;
synchronized public static void test() throws InterruptedException {
System.out.println(Thread.currentThread().getName()+" a初始值 "+a);
for (int i = 0; i < 50000; i++) {
a++;
}
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+" a的值 "+a);
}
synchronized public static void test2() throws InterruptedException {
System.out.println("线程"+Thread.currentThread().getName()+"Test 2 开始运行");
Thread.sleep(10);
System.out.println("线程"+Thread.currentThread().getName()+"Test 2 结束运行");
}
}
class RunA extends Thread{
Test a;
public RunA(Test a){
this.a=a;
}
@Override
public void run() {
super.run();
try {
a.test();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class RunB extends Thread{
Test a;
public RunB(Test a){
this.a=a;
}
@Override
public void run() {
super.run();
try {
a.test2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}}
public class Sender extends Thread {
public static void main(String[] args) throws InterruptedException {
Test a =new Test();
Test b=new Test();
RunA A=new RunA(a);
RunB B=new RunB(b);
A.setName("A");
A.start();
B.setName("B");
B.start();
Thread.sleep(10000);
System.out.println("end");
}
}
同步结果;
A a初始值 0
A a的值 50000
线程BTest 2 开始运行
线程BTest 2 结束运行
end
Process finished with exit code 0
同步 syn(class) 代码块 和 synchronized static 方法 的作用是一样
修改方法如下:
public static void test2() throws InterruptedException {
synchronized (Test.class){
System.out.println("线程"+Thread.currentThread().getName()+"Test 2 开始运行");
Thread.sleep(10);
System.out.println("线程"+Thread.currentThread().getName()+"Test 2 结束运行");
}
运行的结果依然是同步的
A a初始值 0
A a的值 50000
线程BTest 2 开始运行
线程BTest 2 结束运行
end
不要修改锁对象,否则就变成异步了
实例代码如下:
package a;
class Test {
public static int a=0;
public Object ojbect=new Object();
public void test() throws InterruptedException {
synchronized (ojbect){
System.out.println(Thread.currentThread().getName()+" a初始值 "+a);
for (int i = 0; i < 50000; i++) {
a++;
}
ojbect=new Object();//修改锁对象
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+" a的值 "+a);
}
}
public void test2() throws InterruptedException {
synchronized (ojbect){
System.out.println("线程"+Thread.currentThread().getName()+"Test 2 开始运行");
Thread.sleep(10);
System.out.println("线程"+Thread.currentThread().getName()+"Test 2 结束运行");
}
}
}
class RunA extends Thread{
Test a;
public RunA(Test a){
this.a=a;
}
@Override
public void run() {
super.run();
try {
a.test();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class RunB extends Thread{
Test a;
public RunB(Test a){
this.a=a;
}
@Override
public void run() {
super.run();
try {
a.test2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}}
public class Sender extends Thread {
public static void main(String[] args) throws InterruptedException {
Test a =new Test();
Test b=new Test();
RunA A=new RunA(a);
RunB B=new RunB(b);
A.setName("A");
A.start();
B.setName("B");
B.start();
Thread.sleep(10000);
System.out.println("end");
}
}
打印结果:
线程BTest 2 开始运行
A a初始值 0
线程BTest 2 结束运行
A a的值 50000
end
最后总结:
同步的方法一共可以写出这几种
参考资料: Java多线程编程核心技术(第2版) 作者 高洪岩



