Java多线程之线程安全二


线程的同步

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

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

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

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

语法:

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

//被同步的代码块

}

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

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

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

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

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

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

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

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

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

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

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

【1:

public classListThread extends Thread {

private Vector list;

public ListThread(Vectorlist){

this.list = list;

}

public void run(){

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

list.add(i);

}

}

}

public static void main(String[] args){

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

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

ListThread t2 = newListThread(list);         //t1和t2共享一个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,形成死锁

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

你可能感兴趣的:(Java多线程之线程安全二)