一、synchronized 同步方法
synchronized方法的缺点,假如有两个线程一个线程A调用同步方法后获得锁,那么另一个线程B就需要等待A执行完,但是如果说A执行的是一个很费时间的任务的话这样就会很耗时。
1、非线程安全
如果两个线程同时操作对象中的实例变量,则会出现“非线程安全”
2、synchronized方法与锁对象
synchronized取得的锁都是对象锁,而不是把一段代码或方法当做锁,如果多个线程访问的是同一个对象,哪个线程先执行带synchronized关键字的方法,则哪个线程就持有该方法,那么其他线程只能呈等待状态。如果多个线程访问的是多个对象则不一定,因为多个对象会产生多个锁。
当多个线程访问的是同一个对象中的非synchronized类型方法会发生脏读的情况
3、synchronized锁重入
(1)、“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。
public class Service {
synchronized public void service1() {
System.out.println("service1");
service2();
}
synchronized public void service2() {
System.out.println("service2");
service3();
}
synchronized public void service3() {
System.out.println("service3");
}
}
public class MyThread implements Runnable {
@Override
public void run() {
Service service = new Service();
service.service1();
}
}
public class Run {
public static void main(String[] args) {
MyThread myThread =new MyThread ();
Thread thread1 = new Thread(myThread ,"thread-1");
t.start();
}
}
(2)可重入锁也支持在父子类继承的环境中
public class Father{
public int i = 10;
synchronized public void operateIFatherMethod() {
try {
i--;
System.out.println("main print i=" + i);
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class Sub extends Father{
synchronized public void operateISubMethod() {
try {
while (i > 0) {
i--;
System.out.println("sub print i=" + i);
Thread.sleep(100);
this.operateIFatherMethod();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class MyThread implements Runnable {
@Override
public void run() {
Sub sub = new Sub();
sub.operateISubMethod();
}
}
public class Run {
public static void main(String[] args) {
MyThread myThread =new MyThread ();
Thread thread1 = new Thread(myThread ,"thread-1");
t.start();
}
}
4、同步不具有继承性
如果父类有一个带synchronized关键字的方法,子类继承并重写了这个方法。但是同步不能继承,所以还是需要在子类方法中添加synchronized关键字。
5、synchronized 同步方法实例
public class SyncMethod {
public static void main(String[] args) {
/**
* 同步代码注意点:
* 1、不跟随线程变化的预处理和后护理放在同步代码的外面,是同步的代码尽量简短
* 2、不要阻塞线程,如inputStream.read()
* 3、在持有锁的时候,不要对其他对象调用同步方法(即不要调用别的对象的同步方法)。
*/
// 案例:假设某个景点有 4个售票员在同时售票
// 实例化2个线程, 用2个线程来模拟2个售票员
TicketCenterMethod ticketCenterMethod =new TicketCenterMethod();
Thread thread1 = new Thread(ticketCenterMethod,"thread-1");
Thread thread2 = new Thread(ticketCenterMethod,"thread-2");
thread1.start();
thread2.start();
}
}
class TicketCenterMethod implements Runnable {
// 剩余的票的数量
private static int restTicketCount = 100;
@Override
public void run() {
for (int i = 0; i <300 ; i++) {
saleTicketMethod();
}
}
// 同步方法,this表示锁,可以是任意对象,但是不同线程之间此处必须要使用同一个对象
public synchronized void saleTicketMethod(){
if (TicketCenterMethod.restTicketCount > 0){
System.out.println(Thread.currentThread().getName() + " 卖出第 "
+TicketCenterMethod.restTicketCount+" 张票,剩余票数 = "+
--TicketCenterMethod.restTicketCount+" 张 ");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
二、synchronized 同步代码块
对于synchronized同步方法可能存在非常耗时的问题。可以使用synchronized同步块来解决。但是要注意synchronized同步块的使用方式,如果synchronized同步块使用不好的话并不会带来效率的提升。
1、synchronized(this)同步代码块实例
/**临界资源问题
* 1、 多线程共享数据时,会发横线程不安全的情况
* 2、 多线程共享数据必须使用同步
* */
public class SyncCodeblock {
public static void main(String[] args) {
/**
* 同步代码注意点:
* 1、不跟随线程变化的预处理和后护理放在同步代码的外面,是同步的代码尽量简短
* 2、不要阻塞线程,如inputStream.read()
* 3、在持有锁的时候,不要对其他对象调用同步方法(即不要调用别的对象的同步方法)。
*/
// 案例:假设某个景点有 4个售票员在同时售票
// 实例化2个线程, 用2个线程来模拟2个售票员
TicketCenterThread tickitCenterThread =new TicketCenterThread();
Thread t1 = new Thread(tickitCenterThread,"thread-1");
Thread t2 = new Thread(tickitCenterThread,"thread-2");
t1.start();
t2.start();
}
}
class TicketCenterThread implements Runnable {
// 剩余的票的数量
public static int restTicketCount = 100;
private Object object = new Object();
@Override
public void run() {
while (TicketCenterThread.restTicketCount>0){
// 同步代码块,this表示锁,可以是任意对象,但是不同线程之间此处必须要使用同一个对象
synchronized (object){
System.out.println(Thread.currentThread().getName() + " 卖出第 "
+TicketCenterThread.restTicketCount+" 张票,剩余票数 = "+
--TicketCenterThread.restTicketCount+" 张 ");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
2、synchronized代码块间的同步性
- 当一个对象访问synchronized(this)代码块时,其他线程对同一个对象中所有其他synchronized(this)代码块代码块的访问将被阻塞,这说明synchronized(this)代码块使用的“对象监视器”是一个。
- 也就是说和synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。
- 其他线程执行对象中synchronized同步方法(上一节我们介绍过,需要回顾的可以看上一节的文章)和synchronized(this)代码块时呈现同步效果;
- 如果两个线程使用了同一个“对象监视器”,运行结果同步,否则不同步。
3、静态synchronized同步方法与synchronized(class)代码块
synchronized关键字加到static静态方法和synchronized(class)代码块上都是是给Class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁。
下面例子说明,静态同步synchronized方法与synchronized(class)代码块持有的锁一样,都是Class锁,Class锁对对象的所有实例起作用。synchronized关键字加到非static静态方法上持有的是对象锁。
线程A,B和线程C持有的锁不一样,所以A和B运行同步,但是和C运行不同步。
public class Service {
public static void printA() {
synchronized (Service.class) {
try {
System.out.println(
"线程名称为:" + Thread.currentThread().getName() +
"在" + System.currentTimeMillis() + "进入printA");
Thread.sleep(3000);
System.out.println(
"线程名称为:" + Thread.currentThread().getName() +
"在" + System.currentTimeMillis() + "离开printA");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
synchronized public static void printB() {
System.out.println("线程名称为:" + Thread.currentThread().getName() +
"在" + System.currentTimeMillis() + "进入printB");
System.out.println("线程名称为:" + Thread.currentThread().getName() +
"在" + System.currentTimeMillis() + "离开printB");
}
synchronized public void printC() {
System.out.println("线程名称为:" + Thread.currentThread().getName() +
"在" + System.currentTimeMillis() + "进入printC");
System.out.println("线程名称为:" + Thread.currentThread().getName() +
"在" + System.currentTimeMillis() + "离开printC");
}
}
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.printA();
}
}
public class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.printB();
}
}
public class ThreadC extends Thread {
private Service service;
public ThreadC(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.printC();
}
}
public class Run {
public static void main(String[] args) {
Service service = new Service();
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
ThreadB b = new ThreadB(service);
b.setName("B");
b.start();
ThreadC c = new ThreadC(service);
c.setName("C");
c.start();
}
}
4、数据类型String的常量池属性
在Jvm中具有String常量池缓存的功能
字符串常量池中的字符串只存在一份! 即执行完第一行代码后,常量池中已存在 “a”,那么s2不会在常量池中申请新的空间,而是直接把已存在的字符串内存地址返回给s2。
//因为数据类型String的常量池属性,所以synchronized(string)在使用时某些情况下会出现一些问题,比如//两个线程运行
synchronized(“abc”){
}和
synchronized(“abc”){
}
//修饰的方法时,这两个线程就会持有相同的锁,导致某一时刻只有一个线程能运行。所以尽量不要使用
//synchronized(string)而使用synchronized(object)