文章目录
0.synchronized关键字
- synchronized关键字锁定的是对象不是代码块
- 锁定的对象有两种:1.类的实例 2.类对象(类锁)
- 加synchronized关键字之后不一定能实现线程安全,具体还要看锁定的对象是否唯一。
1. 锁定的对象为类的实例举例
public class Demo {
private int count = 10;
private Object object = new Object();
public void test(){
synchronized (object){
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
public class Demo {
private int count = 10;
public void test(){
//synchronized(this)锁定的是当前类的实例,这里锁定的是Demo类的实例
synchronized (this){
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
public class Demo {
private int count = 10;
//直接加在方法声明上,相当于是synchronized(this)
public synchronized void test(){
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
2.锁定的是类对象
public class Demo {
private static int count = 10;
//synchronize关键字修饰静态方法锁定的是类的.class文件
//静态方法中synchronize锁定代码块,锁定的对象不能是类的实例,只能是类的.class文件。
public synchronized static void test(){
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void test2(){
synchronized (Demo4.class){//这里不能替换成this
count--;
}
}
}
3.锁对象的改变
- 锁对象的改变
- 锁定某对象o,如果o的属性发生改变,不影响锁的使用
- 但是如果o变成另外一个对象,则锁定的对象发生改变
- 应该避免将锁定对象的引用变成另外一个对象
public class Demo {
Object o = new Object();
public void test(){
synchronized (o) {
while (true) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
public static void main(String[] args) {
Demo1 demo = new Demo();
new Thread(demo :: test, "t1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t2 = new Thread(demo :: test, "t2");
demo.o = new Object();
//t2能否执行?
t2.start();//t1,t2交替输出
}
}
4.不要以字符串常量作为锁定的对象
- 在下面,test1和test2其实锁定的是同一个对象
public class Demo {
String s1 = "hello";
String s2 = "hello";
public void test1(){
synchronized (s1) {
System.out.println("t1 start");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 end");
}
}
public void test2(){
synchronized (s2) {
System.out.println("t2 start");
}
}
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(demo :: test1,"test1").start();
new Thread(demo :: test2,"test2").start();
}
}
5.锁的粒度
同步代码快中的语句越少越好
- 比较test1和test2
- 业务逻辑中只有count++这句需要sync,这时不应该给整个方法上锁
- 采用细粒度的锁,可以使线程争用时间变短,从而提高效率
public class Demo{
int count = 0;
public synchronized void test1(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
count ++;
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void test2(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (this) {
count ++;
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
6.线程重入
问题就在于线程重入的问题,
- 第一个线程减了个1变成9了,还没打印,第二个线程又减了个1,第三个线程又减了个1,
- 这时候虽然第一个线程只减了一个1但是却打印出来一个7(这里情况是不一定的)
- 可以给方法加上synchronized
public class Demo1 implements Runnable{
private int count = 10;
@Override
public/* synchronized*/ void run() {
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void main(String[] args) {
Demo1 demo = new Demo1();
for (int i = 0; i < 5; i++) {
new Thread(demo,"THREAD" + i).start();
}
}
}
public class Demo2 implements Runnable{
private int count = 10;
@Override
public synchronized void run() {
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
//相比较Demo1,这里是new了五个对象,每个线程对应都拿到各自的锁标记,可以同时执行。
Demo2 demo = new Demo2();
new Thread(demo,"THREAD" + i).start();
}
}
}
7.同步方法和非同步方法是否可以同时调用
同步方法和非同步方法是否可以同时调用?可以
public class Demo{
public synchronized void test1(){
System.out.println(Thread.currentThread().getName() + " test1 start...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " test1 end...");
}
public void test2(){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " test2");
}
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(demo :: test1,"test1").start();
new Thread(demo :: test2,"test2").start();
}
}
8.脏读问题
脏读问题
- 实际业务当中应该看是否允许脏读,不允许的情况下对读方法也要加锁
public class Demo {
String name;
double balance;
public synchronized void set(String name,double balance){
this.name = name;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = balance;
}
public /*synchronized*/ double getBalance(String name){
return this.balance;
}
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(()->demo.set("dd",100.0)).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(demo.getBalance("dd"));//
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(demo.getBalance("dd"));
}
}
9.一个同步方法调用另外一个同步方法,能否得到锁
一个同步方法调用另外一个同步方法,能否得到锁?可以,synchronized本身可支持重入
public class Demo {
synchronized void test1(){
System.out.println("test1 start.........");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
test2();
}
synchronized void test2(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test2 start.......");
}
public static void main(String[] args) {
Demo demo= new Demo();
demo.test1();
}
}
10.重入锁的另外一种情况,继承
public class Demo {
synchronized void test(){
System.out.println("demo test start........");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("demo test end........");
}
public static void main(String[] args) {
new Demo2().test();
}
}
class Demo2 extends Demo {
@Override
synchronized void test(){
System.out.println("demo2 test start........");
super.test();
System.out.println("demo2 test end........");
}
}
11.T2线程能否执行?
public class Demo {
int count = 0;
synchronized void test(){
System.out.println(Thread.currentThread().getName() + " start......");
while (true) {
count ++;
System.out.println(Thread.currentThread().getName() + " count = " + count);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) {
//碰到异常的情况,如果没有处理,会自动释放锁,所以T2可以执行。
int i = 1/0;
}
}
}
public static void main(String[] args) {
Demo demo11 = new Demo();
Runnable r = () -> demo11.test();
new Thread(r, "t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r, "t2").start();
}
}