Java多线程之线程安全二

时间:2021-07-29 18:37:16

线程的同步

线程的安全问题:当多个线程访问共享数据的时候,就有可能出现线程安全的问题。 

产生的原因:       如果当一个线程正在访问一个数据的时候,另一个线程也参与了进来,那么就会出现线程安全问题。

 

解决思路:我们可以将操作共享数据的代码封装起来,当有一个线程正在执行这部分代码的时候,其他线程不能参加执行

 

在Java中可以通过同步代码块和同步方法来实现这个操作

语法:

                   1.synchronized(对象){//同步锁,通常会使用this作为同步锁

                            //被同步的代码块

}

 

2.同步方法:直接用synchronized修饰方法

同步方法使用的同步锁是默认的

         1)非静态方法使用的同步锁是当前类的对象(this)

         2)静态方法使用的同步锁是当前类的Class对象(类名.class)

 

 

问:同步处理能够有效的解决多线程的数据安全问题,那么是不是所有的方法或代码块都进行同步处理?

答:不能这么做,因为使用同步处理虽然能够解决多线程的数据安全问题,但是同时会降低代码的执行效率

 

问:在什么情况下需要使用同步?

答:当多线程操作共享数据的时候,需要进行同步处理

         关键词:多线程、共享数据

        

同步的前提:多个线程必须使用同一把锁

注意事项:不要乱用同步处理

1:

public classListThread extends Thread {

         private Vector<Integer> list;

         public ListThread(Vector<Integer>list){

                   this.list = list;

         }

         public void run(){

                   for(int i=1;i<=10000;i++){

                            list.add(i);

                   }

         }

}

         public static void main(String[] args){

                   Vector<Integer> list =new Vector<Integer>(); //共享资源

                   ListThread t1 = newListThread(list);         //构造线程对象,传入list

                   ListThread t2 = newListThread(list);         //t1t2共享一个list

                   t1.start();

                   t2.start();

                   try {

                            t1.join();

                            t2.join();//等待子线程执行结束

                   } catch (InterruptedExceptione) {

                            // TODOAuto-generated catch block

                            e.printStackTrace();

                   }

                   System.out.println(list.size());

         }

2:

 

线程安全问题多个线程对同一资源进行争用时产生的问题(关于线程同步问题)

 

异步 多线程,你走你的,我走我的

同步 多线程执行到某一环节,要进行协调,让某一线程先执行,另一线程后执行,避免对同一资源的争抢

 

1.1    同步对象锁synchronized

public classDressingRoom {

         public void use(){

                   System.out.println(Thread.currentThread().getName()+"进入试衣间");

                   try {

                            Thread.sleep(1000);

                   } catch (InterruptedExceptione) {

                            // TODOAuto-generated catch block

                            e.printStackTrace();

                   }

                   System.out.println(Thread.currentThread().getName()+"离开试衣间");

         }

}

public classCustomerRunnable implements Runnable {

         private DressingRoom room;

        

         public CustomerRunnable(DressingRoomroom) {

                   this.room = room;

         }

         @Override

         public void run() {

                   // TODO Auto-generated methodstub

                   for(int i=0;i<10;i++){

                            synchronized (room) {//获取试衣间的锁,其他顾客、线程不能再进入

                                     room.use();//使用试衣间

                            }//释放锁,其他顾客可以进入

                            try {

                                     Thread.sleep(1000);//挑选、休息

                            } catch(InterruptedException e) {

                                     // TODOAuto-generated catch block

                                     e.printStackTrace();

                            }

                   }

         }

}

DressingRoom room =new DressingRoom();

                   CustomerRunnable r = newCustomerRunnable(room);

                   Thread zhangsan = newThread(r);

                   Thread lisi = new Thread(r);

                   zhangsan.setName("张三");

                   lisi.setName("李四");

                   zhangsan.start();

                   lisi.start();

1.2    线程同步

1.2.1  多线程安全问题

         当run()方法体内的代码操作到了成员变量(共享数据)时,就可能会出现多线程安全问题

         产生的原因

                   当一个线程执行操作共享数据的相关的代码块时,其他线程也参与了运算,就会导致线程安全问题的产生

         解决思路

                   将操作共享数据的所有代码封装起来,当有线程在执行这些代码的时候,其他线程不能参与运算

 

1.2.2  同步的手段

         同步代码块

                   synchronized(锁对象){ // 需要同步的代码 }

         同步方法

                   publicsynchronized void 方法名(参数列表){ // 方法体 }

         Lock(JDK1.5新特性)

 

1.2.3  同步的好处和弊端

         好处:解决了线程的安全问题

         弊端:降低了效率

 

1.2.4  同步的前提和准则

         同步的前提

                   必须是多线程并且使用了同一把锁

                   切记:不要乱用同步处理

         同步的准则

                   1、使用代码保持剪短,把不随线程变化的预处理和后处理移出同步块

                   2、不要阻塞,比如InputStream.read()

                   3、在持有锁额时候,不要对其他对象调用同步方法

 

1.2.5  验证同步锁

         非静态方法默认同步锁是当前对象(this

         静态方法默认同步锁是当前类的类对象(Class对象)

1.2.6  同步对象锁

public classDressingRoom {

         public void use(){

                   synchronized (this) {

                            System.out.println(Thread.currentThread().getName()+"进入试衣间");

                            try {

                                     Thread.sleep(1000);

                            } catch (InterruptedExceptione) {

                                     // TODOAuto-generated catch block

                                     e.printStackTrace();

                            }

                            System.out.println(Thread.currentThread().getName()+"离开试衣间");

                   }

         }

}

public classCustomerRunnable implements Runnable {

         private DressingRoom room;

 

         public CustomerRunnable(DressingRoomroom) {

                   this.room = room;

         }

         @Override

         public void run() {

                   for (int i = 0; i < 10;i++) {

                            room.use();// 使用试衣间

                            try {

                                     Thread.sleep(1000);//挑选、休息

                            } catch(InterruptedException e) {

                                     // TODOAuto-generated catch block

                                     e.printStackTrace();

                            }

                   }

         }

}

DressingRoom room =new DressingRoom();

                   CustomerRunnable r = newCustomerRunnable(room);

                   Thread zhangsan = newThread(r);

                   Thread lisi = new Thread(r);

                   zhangsan.setName("张三");

                   lisi.setName("李四");

                   zhangsan.start();

                   lisi.start();

同步静态类:

public class Singleton {

         privatestatic Singleton s =null;

         privateSingleton(){

                   System.out.println("构造方法");

         }

         publicstatic Singleton getInstance(){

                   synchronized(Singleton.class) {

                            if(s==null){

                                     try{

                                               Thread.sleep(3000);//将问题极端化

                                     }catch (InterruptedException e) {

                                               //TODO Auto-generated catch block

                                               e.printStackTrace();

                                     }

                                     s= new Singleton();

                            }

                   }

                   returns;

         }

}

public class SingleThread extends Thread {

         public voidrun(){

                   Singleton.getInstance();

         }

}

SingleThread t1 = new SingleThread();

                   SingleThreadt2 = new SingleThread();

                   t1.start();

                   t2.start();

同步类:【

//即使张三和王五,对他们来说图纸是同一张,描述类对象是同一个,所以是在同一个锁上进行同步的

                            synchronized(Microphone.class) {  //在话筒的描述类对象进行同步

                                     microphone.speakWith();

                            }

同步对象锁:

synchronized(锁对象){

         语句

           

这样我称为: 语句在锁对象上进行了同步,在多线程中,多个在同一锁对向上同步的语句快,同时只能有一份进行执行;而且执行是原子的,一定要执行完才能结束,不能切换到具有相同对象锁的语句块去执行(有例外)

 

synchronized(this){

         语句

}

public classMicrophone {

         public void speakWith(){

                 synchronized(this){//在当前话筒对象上进行同步,一个线程抢到当前话筒后,其他线程就不能再抢

                            System.out.println(Thread.currentThread().getName()+"拿起话筒");

                            try {

                                     Thread.sleep(1000);

                            } catch(InterruptedException e) {

                            }

                            System.out.println(Thread.currentThread().getName()+"放下话筒");

                   }//将当前话筒对象释放掉,其他线程可以抢到它

         }

}

语句在当前对象上进行了同步,如果多个线程都在同一个当前对象上同步,那么同一时间只能有一个线程执行这个同步的代码块

 

1.2.7  同步方法

public classMicrophone {

         public synchronized void speakWith(){

                   System.out.println(Thread.currentThread().getName()+"拿起话筒");

                   try {

                            Thread.sleep(1000);

                   } catch (InterruptedExceptione) {

                            // TODOAuto-generated catch block

                            e.printStackTrace();

                   }

                   System.out.println(Thread.currentThread().getName()+"放下话筒");

         }

}

同步方法:

[修饰符] synchronized返回类型方法名(参数列表){

         语句

}

相当于:

[修饰符] 返回类型 方法名(参数列表){

         synchronized(this){

                   语句

         }

}

 

归根结底一句话,能不能同步成功,要看同步锁是否是同一个对象

 

在描述类对象上进行同步,在图纸上进行同步

synchronized(类.class){

         语句

}

所有在类.class上进行同步的代码块都会受影响,根本原因是描述类对象只有一份拷贝,所以这些线程都是在同一个对象锁上同步,都会受影响。

 

[修饰符] staticsynchronized 返回类型方法名(参数){

         语句

}

相当于:

[修饰符] static 返回类型 方法名(参数列表){

         synchronized(类名.class){

                   语句

         }

}

 

1.3    同步引起的死锁

多线程的死锁问题:线程卡死的情况

死锁产生的情况有很多,我们这里先例举一个比较常见的情况,就是同步锁的嵌套

如果因为同步锁的嵌套使用导致死锁,那么一般就是同步锁的顺序不一致,只要保证锁的顺序是一样的,就可以避免死锁问题】

public class DinnerRunnableimplements Runnable {

         private Object knife;

         private Object fork;

        

         public DinnerRunnable(Object knife,Object fork) {

                   this.knife = knife;

                   this.fork = fork;

         }

 

         @Override

         public void run() {

                   synchronized (knife) {

                            System.out.println(Thread.currentThread().getName()+"拿到刀");

                            try {

                                     Thread.sleep(100);//问题极端化

                            } catch(InterruptedException e) {

                                     // TODOAuto-generated catch block

                                     e.printStackTrace();

                            }

                            synchronized (fork){

                                     System.out.println(Thread.currentThread().getName()+"拿到叉");

                                     System.out.println(Thread.currentThread().getName()+"");

                            }

                   }

         }

 

}

public classDinnerRunnable2 implements Runnable {

         private Object knife;

         private Object fork;

        

         public DinnerRunnable2(Object knife,Object fork) {

                   this.knife = knife;

                   this.fork = fork;

         }

 

         @Override

         public void run() {

                   synchronized (fork) {

                            System.out.println(Thread.currentThread().getName()+"拿到叉");

                            try {

                                     Thread.sleep(100);//问题极端化

                            } catch(InterruptedException e) {

                                     // TODOAuto-generated catch block

                                     e.printStackTrace();

                            }

                            synchronized (knife){

                                     System.out.println(Thread.currentThread().getName()+"拿到刀");

                                     System.out.println(Thread.currentThread().getName()+"");

                            }

                   }                

         }

}

Object knife = newObject();

                   Object fork = new Object();

                   Thread zhangsan = newThread(new DinnerRunnable(knife, fork));

                   Thread lisi = new Thread(newDinnerRunnable2(knife, fork));

                   zhangsan.setName("张三");

                   lisi.setName("李四");

                   zhangsan.start();

                   lisi.start();

同步块引起的死锁

两个对象锁A、B,一个线程拿到A等待B,另一个拿了B等待A,形成死锁

解决方式,让这些线程获取对象锁的顺序一致