一、关于线程安全
1.是什么决定的线程安全问题?
线程安全问题基本是由全局变量及静态变量引起的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
2.可以解决多线程并发访问资源的方法有哪些?
主要有三种方式:分别是同步代码块 、同步方法和锁机制(Lock)
其中同步代码块和同步方法是通过关键字synchronized实现线程同步
本文主要是将synchronized关键字用法作为例子来去解释Java中的对象锁和类锁
二、synchronized关键字各种用法与实例
事实上,synchronized修饰非静态方法、同步代码块的synchronized (this)用法和synchronized (非this对象)的用法锁的是对象,线程想要执行对应同步代码,需要获得对象锁。
synchronized修饰静态方法以及同步代码块的synchronized (类.class)用法锁的是类,线程想要执行对应同步代码,需要获得类锁。
因此,事实上synchronized关键字可以细分为上面描述的五种用法。
1.同步块synchronized (this)
public class Ticket1 extends Thread{ private int nums = 0; //出票数 private int count =20; //剩余 @Override public void run() { while (true) { synchronized (this) { if(count <= 0) { break; } nums++; count--; try { Thread.sleep(550); } catch (Exception e) { e.printStackTrace(); } System.out.println("显示出票信息:"+Thread.currentThread().getName()+ "抢到第"+nums+"张票,剩余"+count+"张"); } } } public static void main(String[] args) { Ticket1 ticket1 = new Ticket1(); Thread anni = new Thread(ticket1,"安妮"); Thread jack = new Thread(ticket1,"jack"); anni.start(); jack.start(); } }
这样是同步的,线程获取的是同步块synchronized (this)括号()里面的对象实例的对象锁,这里就是Ticket1 实例对象的对象锁了。
需要注意的是synchronized (){}的{}前后的代码依旧是异步的
2.synchronized (非this对象)的用法锁的是对象
例1:
public class Ticket2 implements Runnable{ private int nums = 0; //出票数 private int count =25; //剩余 //实现线程安全三种方法 private final Object lock = new Object();//1.使用私有不变对象锁,使得攻击者无法获取到锁对象(推荐使用) @Override public void run() { while (true) { //2.this 使用对象自身的锁(隐式锁)--对象锁 //3.Ticket2.class 给Ticket2加锁 --类锁->使用说明:静态方法则一定会同步,非静态方 //法需在单例模式才生效(本例为单例),但是也不能都用静态同步方法,总之用得不好可能会给性能带来极大的影响。 synchronized (lock) { if(count <= 0) { break; } nums++; count--; try { Thread.sleep(750); } catch (Exception e) { e.printStackTrace(); } System.out.println("显示出票信息:"+Thread.currentThread().getName()+ "抢到第"+nums+"张票,剩余"+count+"张"); } } } public static void main(String[] args) { Ticket2 ticket2 = new Ticket2(); Thread zhangsan = new Thread(ticket2,"张三"); Thread zhaoyun = new Thread(ticket2,"赵云"); zhangsan.start(); zhaoyun.start(); } }
例2:
public class Run2 { public static void main(String[] args) { Service service = new Service("xiaobaoge"); ThreadA2 a = new ThreadA2(service); a.setName("A"); a.start(); ThreadB2 b = new ThreadB2(service); b.setName("B"); b.start(); } } class Service { String anyString = new String(); public Service(String anyString){ this.anyString = anyString; } public void setUsernamePassword(String username, String password) { try { synchronized (anyString) { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入同步块"); Thread.sleep(3000); System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开同步块"); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class ThreadA2 extends Thread { private Service service; public ThreadA2(Service service) { super(); this.service = service; } @Override public void run() { service.setUsernamePassword("a", "aa"); } } class ThreadB2 extends Thread { private Service service; public ThreadB2(Service service) { super(); this.service = service; } @Override public void run() { service.setUsernamePassword("b", "bb"); } }
3.synchronized (class)
public class Run { public static void main(String[] args) { ThreadA a = new ThreadA(); a.setName("A"); a.start(); ThreadB b = new ThreadB(); b.setName("B"); b.start(); } } 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(); } } } public static void printB() { synchronized (Service.class) { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB"); System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB"); } } }
4.静态synchronized同步方法
public class Run { public static void main(String[] args) { ThreadA a = new ThreadA(); a.setName("A"); a.start(); ThreadB b = new ThreadB(); b.setName("B"); b.start(); } } class Service { synchronized public static void printA() { 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"); } } class ThreadA extends Thread { @Override public void run() { Service.printA(); } } class ThreadB extends Thread { @Override public void run() { Service.printB(); } }
5.synchronized修饰非静态方法
public class Run { public static void main(String[] args) { HasSelfPrivateNum numRef = new HasSelfPrivateNum(); ThreadA athread = new ThreadA(numRef); athread.start(); ThreadB bthread = new ThreadB(numRef); bthread.start(); } } class HasSelfPrivateNum { private int num = 0; synchronized public void addI(String username) { try { if (username.equals("a")) { num = 100; System.out.println("a set over!"); Thread.sleep(2000); } else { num = 200; System.out.println("b set over!"); } System.out.println(username + " num=" + num); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class ThreadA extends Thread { private HasSelfPrivateNum numRef; public ThreadA(HasSelfPrivateNum numRef) { super(); this.numRef = numRef; } @Override public void run() { super.run(); numRef.addI("a"); } } class ThreadB extends Thread { private HasSelfPrivateNum numRef; public ThreadB(HasSelfPrivateNum numRef) { super(); this.numRef = numRef; } @Override public void run() { super.run(); numRef.addI("b"); } }
实验结论:两个线程访问同一个对象中的同步方法是一定是线程安全的。本实现由于是同步访问,所以先打印出a,然后打印出b
这里线程获取的是HasSelfPrivateNum的对象实例的锁——对象锁。