“非线程安全”有可能导致“脏读”,即取到的数据被更改过的。
当多个线程共同访问1个对象的实例变量,可能会不安全。“线程安全”——获得的实例变量的值经过同步处理
把变量放在方法里面,这时变量是私有的,就是安全的。synchronized()生成的是对象锁因为当多个线程访问多个对象时,JVM会创建多个锁,比如,A线程要调用对象X的锁的方法,B线程要调用对象M的锁的方法,B不用等A执行完,就可以调用M的锁。
如果没有多个线程对同一个对象的实例变量操作,就不会出现线程不安全问题,不需要用同步。
读取实例变量时,此值已经被其他线程更改过了。
用synchronized解决在锁内部调用本类的其他synchronized方法,永远可以得到锁。
例如:
public class Service { PRivate String usernameParam; private String passWordParam; public void setUsernamePassword (String username,String password) { try { String anyString = new String(); synchronized (anyString) { System.out.println ("线程名称为:"+Thread.currentThread().getName()+"在"+System.currentTimeMillis()+"进入同步块"); usernameParam = username; Thread.sleep(3000); passwordParam = password; System.out.println("线程名称为:"+Thread.currentThread().getName()+"在"+System.currentTimeMillis()+"离开同步块"); } } catch (InterruptedException e) { e.printStackTrace(); } }}运行结果
线程名称为:A在1403594007194进入同步块 线程名称为:B在1403594007194进入同步块 线程名称为:B在1403594010194离开同步块 线程名称为:A在1403594010194离开同步块
从结果可看出是异步的,因为不是同一个锁,这时候很容易出现“脏读”
synchronized用在static静态方法上就是对当前的*.java文件对应的Class类进行持锁(整个类锁上了)。
与加到非static方法的使用效果是一样的 本质上的不同是synchronized加到static方法是给Class类上锁,synchronized加到非静态方法上是给对象上锁
例如:
public class Service { synchronized public static void printA(){ try{ System.out.println("进入printA"); Thread.sleep(3000); System.out.println("离开printA"); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public static void printB(){ System.out.println("进入printB"); System.out.println("离开printB"); } synchronized public void printC() { System.out.println("进入printC"); System.out.println("离开printC"); }}上面的printA方法和printB方法都是对Service类上锁的,而printC方法是对对象上锁的,所以同样运行时,printC是异步的,printA和printB是同步的。 Class锁对类的所有对象实例起作用,即不同对象但是静态的同步方法仍是同步运行 同步synchronized(class)代码块的作用和synchronized static 方法的作用一样,看下面例子如何上锁——
public class Service { public static void printA(){ synchronized (Service.class) { try{ System.out.println("进入printA"); Thread.sleep(3000); System.out.println("离开printA"); } catch (InterruptedException e) { e.printStackTrace(); } } }}运行结果:
A B B A A B B A A B B A 交替打印,持有的锁不是一个。
上面代码会造成死循环使其他同步方法无法运行,修改成用同步块
Object object1 = new Object();public void methodA() { synchronized (object1) { System.out.println("methodA begin"); boolean isContinueRun = true; while (isContinueRun) { } System.out.println("methodA end"); }}Object object2 = new Object();public void methodB() { synchronized (object2) { System.out.println("methodB begin"); System.out.println("methodB end"); }}“死锁”必须避免,不同线程在等待不可能被释放的锁会导致所有任务无法完成,造成线程的“假死”。
用JDK的工具监测是否有死锁现象 1. 进入CMD工具 2. 进入JDK安装文件夹的bin目录 3. 执行jps命令 4. 执行jstack命令
什么是内置类 看例子:
public class PublicClass { private String username; class PrivateClass { private String age; public String getAge(){ return age; } public void setAge(String age){ this.age = age; } } public String getUsername(){ return username; } public void setUsername(String username){ this.username = username; }}public class Run { public static void main(String[] args) { PublicClass publicClass = new PublicClass(); publicClass.setUsername("a"); System.out.println(publicClass.getUsername()); PrivateClass privateClass = publicClass.new PrivateClass(); //是这样实例化的,如果PublicClass.java和Run.java不在同一个包中,则需要将PrivateClass内置声明成public privateClass.setAge("18"); System.out.println(privateClass.getAge()); }}内置类中还有一种叫静态内置类,看以下例子:
public class PublicClass { static private String username; static class PrivateClass { static private String age; public String getAge(){ return age; } public void setAge(String age){ this.age = age; } } public String getUsername(){ return username; } public void setUsername(String username){ this.username = username; }}public class Run { public static void main(String[] args) { PublicClass publicClass = new PublicClass(); publicClass.setUsername("a"); System.out.println(publicClass.getUsername()); PrivateClass privateClass = new PrivateClass(); //实例化 privateClass.setAge("18"); System.out.println(privateClass.getAge()); }}结果,要在method1()运行完之后method2()才能运行。
作用:(使变量在多个线程间可见)强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。
如果有“多继承”的情况,则也要用实现Runnable接口的方式来处理多线程的问题。
P121 在JVM设置为Server服务器的环境中,线程会一直在私有堆栈中取得值为true,而thread.setRunning(false)更新的是公共堆栈的变量值false,所以会出现死循环。 问题是–私有堆栈中的值和公共堆栈中的值不同步造成的。 解决问题–要用volatile关键字 当线程访问isRunning时,强制性从公共堆栈中取值
volatile最致命的缺点是不支持原子性。
线程安全包含原子性和可见性两个方面。
volatile不具备同步性,也就不具备原子性。 主要用于多线程读取共享变量时,获取最新值使用。
注意:如果改变实例变量中的数据,比如i++,也就是i=i+1 这样的操作不是一个原子操作,也就是非线程安全。 步骤分解如下: 1. 从内存中取出i的值 2. 计算i的值 3. 将i的值写到内存
假如在第二步计算值时,另一个线程也修改i的值,这时会出现“脏读”,这是需要用synchronized来解决
多线程环境中,use和assign是多次出现,这一操作不是原子性,所以在read和load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会发生对应的变化,也就是私有内存和公共内存的变量不同步,所以计算出来的和预期的不一样,就出现非线程安全问题。
一个原子类型就是一个原子操作可用的类型,它可以在没有锁的情况下做到线程安全。
public class AddCountThread extends Thread { private AtomicInteger count = new AtomicInteger(0); @Override public void run(){ for(int i = 0;i < 10000; i++) { System.out.println(count.incrementAndGet()); } }}public class Run { public static void main (String[] args) { AddCountThread countService = new AddCountThread(); Thread t1 = new Thread(countService); t1.start(); Thread t2 = new Thread(countService); t2.start(); Thread t3 = new Thread(countService); t3.start(); Thread t4 = new Thread(countService); t4.start(); Thread t5 = new Thread(countService); t5.start(); }}运行结果成功累加到50000
原子类在具有逻辑性的情况下输出结果也具有随机性。 - addAndGet()这些原子类的方法是原子的,但是方法与方法之间的调用不是原子的。
新闻热点
疑难解答