--1.1方法内的变量是线程安全的
解释:由于方法内的变量是私有的,本体访问的同时别人访问不了,所以是线程安全的。
--1.2实例变量是非线程安全的
解释:由于实例变量可以由多个线程访问,当本体操作变量过程中,别人也可以抢占资源操作变量,使数据不同步了,所以是非线程安全的。(之前博文已经实现过这种情况)
--1.3线程与锁
例:
public class Service {//服务类
private int age=0;
public synchronized void get(String username){//同步方法
try{
if(username.equals("张三")){
age=20;
System.out.println("张三获取年龄");
Thread.sleep(2000);
}else{
age=30;
System.out.println("李四获年龄");
}
System.out.println(username+",age="+age);
}catch(Exception e){
e.printStackTrace();
}
}
}
public class MyThread1 extends Thread{//线程1
private Service service;
public MyThread1(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.get("张三");
}
}
public class MyThread2 extends Thread{//线程2
private Service service;
public MyThread2(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.get("李四");
}
}
----1.3.1多个线程访问同一个锁(前提)
测试类1:
public static void main(String[] args) {
Service service=new Service();
MyThread1 myThread1=new MyThread1(service);
myThread1.start();
MyThread2 myThread2=new MyThread2(service);
myThread2.start();
}
解释:我们的前提是多个线程访问同一个锁,如果当前进来一个线程获得同步方法,那么当前这个线程就持有了该方法所属对象的锁,其他线程就只能处于等待态。
结果:
张三获取年龄
张三,age=20
李四获年龄
李四,age=30
----1.3.2多个线程访问多个锁
public static void main(String[] args) {
Service service1=new Service();
Service service2=new Service();
MyThread1 myThread1=new MyThread1(service1);
myThread1.start();
MyThread2 myThread2=new MyThread2(service2);
myThread2.start();
}
结果:
张三获取年龄
李四获年龄
李四,age=30
张三,age=20
解释:我们的前提是多个线程访问多个锁,在当前线程获取其中一个锁时,对于同步方法内的代码是线程安全的。结果的顺序看出是异步的,在于不同锁之间的线程是不同步的。
----1.3.3总结:
(1).对于多个线程访问共享资源,才需要使用锁来进行同步化访问读写。并且对于synchronized修饰的方法为同步方法,是对象级别上的锁,多个线程访问时,必须"排队运行" 。
(2).对于多个线程访问对象,如果线程1获取了该对象的同步方法的锁,那么其他对象只能排队等到线程1执行完释放锁,才可以再获取锁去执行同步方法。如果该对象中有多个同步方法,那么其他线程只能排队等线程1执行完所有的同步方法释放锁,其他线程才可获取锁再执行同步方法。如果该对象中既有同步方法又有非同步的方法,那么线程1执行同步方法的同时,其他线程是可以以异步的方式执行非同步的方法。
--1.4synchronized锁的重入
解释:就是当前线程已经得到了一个对象锁之后,再次请求此对象的锁可以再次得到该对象的锁。也就是说,synchronized方法体或代码块中再次调用类中其他synchronized方法和块时,同样也可获取锁。
例:
public class Service {
public synchronized void getAge(String username){
if(username.equals("张三")){
System.out.println("张三年龄20");
getSex(username);
}else{
System.out.println("李四年龄30");
getSex(username);
}
}
public synchronized void getSex(String username){
if(username.equals("张三")){
System.out.println("张三性别男!");
}else{
System.out.println("李四性别女!");
}
}
}
public class MyThread1 extends Thread{
private Service service;
private String username;
public MyThread1(Service service ,String username) {
super();
this.service = service;
this.username=username;
}
@Override
public void run() {
service.getAge(username);
}
}
public class Test {
public static void main(String[] args) {
Service service=new Service();
MyThread1 myThread1=new MyThread1(service,"张三");
myThread1.start();
}
}
张三年龄20
张三性别男!
----1.4.1总结:
锁的重入也可用于父子类继承的环境中。子类完全可以通过锁的重入带哦用父类的同步方法。(节省篇幅,例子就不粘了)
--1.5出现异常,锁自动释放
解释:如果当前线程执行代码时出现了异常,那么当前线程所持有的锁也将会自动释放。
--1.6同步不具有继承性
解释:同步不可以继承,虽然父类声明了同步方法,但是子类重写父类的方法不具有同步的性质。
--2.1synchronized同步代码块
synchronized方法对当前对象进行加锁,synchronized代码块是对某一个对象进行加锁。
比较同步方法和同步代码块:
同步方法的特点就是只要是当前对象的同步方法,当前线程没执行完,其他线程只能等着。而同步代码块则是对于不同对象的同步代码块,线程1执行同步代码块1时,线程2也可以执行同步代码块2,这样效率会有所提升。而在我看来实际上两种方式只是细粒度上进一步得到了提升。同步代码块可以进行更加细微的操作。
--2.2半同步半异步
就是在synchronized代码块中就同步执行,不在synchronized代码块中就异步执行。
--2.3synchronized代码块的同步特点
synchronized代码块的同步性与synchronized同步方法是一样的,当一个线程访问对象的synchronized(this)代码块时,说明当前线程获取了对象级别的锁,那么其他的同步代码块synchronized(this)访问将被阻塞,就是不能访问,得需要当前线程释放对象锁,才可以访问。可以看出synchronized(this)代码块监视的是当前对象。
例:
public class Service {
public void getAge(String username){
synchronized (this) {
try {
System.out.println(username);
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(username+"执行完毕!");
}
}
}
public class MyThread1 extends Thread{
private Service service;
private String username;
public MyThread1(Service service ,String username){
this.service=service;
this.username=username;
}
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
service.getAge(username);
}
}
public class MyThread2 extends Thread{
private Service service;
private String username;
public MyThread2(Service service ,String username){
this.service=service;
this.username=username;
}
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
service.getAge(username);
}
}
public class Test {
public static void main(String[] args) {
Service service=new Service();
MyThread1 myThread1=new MyThread1(service,"张三");
myThread1.start();
MyThread2 myThread2=new MyThread2(service,"李四");
myThread2.start();
}
}
结果:
张三
张三执行完毕!
李四
李四执行完毕!
----2.3.1总结:
多个线程调用同一个对象的不同synchronized同步方法或者是synchronized(this)同步代码块时,调用都是顺序执行,也就是所谓的同步的,阻塞的。
<1>synchronized同步方法:
①.对于其他的synchronized同步方法或synchronized(this)调用都是呈阻塞状态。
②.同一个时间只能有一个线程获取锁对象,去执行synchronized同步方法中代码。
<2>synchronized(this)同步代码块:
①.对于其他的synchronized同步方法或synchronized(this)调用都是呈阻塞状态。
②.同一个时间只能有一个线程获取锁对象,去执行synchronized(this)同步代码块中代码。
--2.4将任意对象作为对象监视器(synchronized(Object)形式):
在多个线程持有"对象监视器"为同一对象的前提下,同一时间只能有一个线程执行synchronized(非this对象)同步代码块中的代码。
采用这种方式的优点:
如果一个类中有很多synchronized方法,虽然表面上实现了同步,但是由于同步产生的阻塞,效率很低。但是如果使用synchronized(Object)代码块其实与其他synchronized方法是异步的,不需要与其他锁this同步方法争抢this锁,可以提高效率。
例:
public class Service {
private String keyObject=new String();
public void startupThread1(){
try{
synchronized (keyObject) {
System.out.println("thread1 begin!");
Thread.sleep(3000);
System.out.println("thread1 end");
}
}catch(Exception e){
e.printStackTrace();
}
}
public synchronized void startupThread2(){
System.out.println("thread2 begin!");
System.out.println("thread2 end");
}
}
public class MyThread1 extends Thread{
private Service service;
public MyThread1(Service service){
this.service=service;
}
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
service.startupThread1();
}
}
public class MyThread2 extends Thread{
private Service service;
public MyThread2(Service service){
this.service=service;
}
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
service.startupThread2();
}
}
public class MyThread2 extends Thread{
private Service service;
public MyThread2(Service service){
this.service=service;
}
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
service.startupThread2();
}
}
结果:
thread1 begin!
thread2 begin!
thread2 end
thread1 end
----2.4.1总结
同步代码块放在非同步synchronized方法中进行声明使用,不能保证调用方法的线程的执行同步/顺序性,也就是线程调用方法的顺序是无序的。
--2.5线程同步总结
<1>当多个线程同时执行synchronized(Object非this){......... }同步代码块时呈同步效果。
<2>当其他线程执行当前对象中synchronized同步方法时呈同步效果。
<3>当其他线程执行当前对象方法里面的synchronized(this)代码块时也呈现同步效果。
--2.6静态同步synchronized方法和synchronized(Class)代码块(类锁)
这两种给类上锁效果都是一样的。但是与非静态方法却有所不同。synchronized关键字修饰静态方法是给Class类上锁,而synchronized修饰非静态方法是给对象上锁。
volatile作用使变量在多个线程间可见。
--3.1死循环例子
public class MyThread1 extends Thread{
private boolean isRunning=true;
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
@Override
public void run() {
System.out.println("进入run了");
while(isRunning ==true){
}
System.out.println("线程被停止了!");
}
}
public class Test {
public static void main(String[] args) {
try{
MyThread1 myThread1=new MyThread1();
myThread1.start();
Thread.sleep(1000);
myThread1.setRunning(false);
System.out.println("已经赋值为false");
}catch(Exception e){
e.printStackTrace();
}
}
}
结果:
解释:虽然线程一直在私有堆栈中取得isRunning的值为true并且也执行了setRunning(false);但是却把公共堆栈的isRunning变量值更新为false,所以还是一直死循环。主要由于私有堆栈和公共堆栈里面的值不同步引起的。而volatile关键字修饰的变量,作用就是强制性地在公共堆栈中取值。也就解决上面的死循环。
private volatile boolean isRunning=true;
--3.2synchronized和volatile的区别
<1>volatile关键字是线程轻量级实现,volatile只能修饰于变量。
<2>多线程访问volatile不会阻塞(因为volatile不保证原子性操作),而synchronized会出现阻塞。
<3>volatile不保证数据的原子性操作,可以保证数据的可见性(可以强制从公共堆栈取值)。而synchronized可以保证原子性(前面说同步了),也可以间接的保证可见性,就让公共堆栈和私有堆栈的数据同步。
----3.2.1总结:
我们使用volatile关键字来解决变量在多个线程之间 "可见",而synchronized关键字来保证多个线程之间 "同步"。
--3.3synchronized关键字实现间接可见性
死循环例子:
public class Service {
private boolean isContinueRun=true;
public void runMethod(){
while(isContinueRun){
}
System.out.println("已经停下来了!");
}
public void stopMethod(){
isContinueRun=false;
}
}
public class MyThread1 extends Thread{
private Service service;
public MyThread1(Service service){
this.service=service;
}
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
service.runMethod();
}
}
public class MyThread2 extends Thread{
private Service service;
public MyThread2(Service service){
this.service=service;
}
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
service.stopMethod();
}
}
public class Test {
public static void main(String[] args) {
try{
Service service=new Service();
MyThread1 myThread1=new MyThread1(service);
myThread1.start();
Thread.sleep(1000);
MyThread2 myThread2=new MyThread2(service);
myThread2.start();
System.out.println("已经发起停止命令了!");
}catch(Exception e){
e.printStackTrace();
}
}
}
结果:
解释:这种死循环,虽然MyThread2的调用了stopMethod();方法停止线程,但是根本没起作用。原因是MyThread1线程进入死循环中,一直在循环体未出来,MyThread1没看见MyThread2送过来的false,就是差在各个线程之间没有可见性。
为了保持线程之间的可见性,使用synchronized关键字,让两个线程都持有钥匙进入同步代码块。这样就可以保证两个线程之间可见了。
重新编写Service类:
public class Service {
private boolean isContinueRun=true;
public void runMethod(){
while(isContinueRun){
String keyString =new String();
synchronized (keyString) {
}
}
System.out.println("已经停下来了!");
}
public void stopMethod(){
isContinueRun=false;
}
}
解释:这样就实现了间接可见性。
-----------------------------------------------
关于线程的安全主要就是搞同步和可见两个方面。