IT干货网

java线程同步处理并发问题

sanshao 2022年03月12日 编程设计 204 0

java线程同步处理并发问题

一个对象同时被多个线程读写时,会造成并发问题,线程同步把这些线程排队来解决这个问题。

线程同步有两个元素,队列和锁。

人为对被修改的对象加锁,使对象每次只能被一个线程修改,一个线程获得排他锁的时候获得读写机会,其他线程被挂起,这个线程读写完后释放锁。

只有进行修改操作时才需要锁,其他操作不需要锁,否则低效。

死锁

死锁是锁机制错误使用的情况。多个线程在已经持有一个资源的锁时,还想获得另一个资源的锁,就可能发生死锁。

以下代码展示死锁发生的情况:

package com.cxf.multithread.lock; 
 
public class TestForLock { 
    public static void main(String[] args) { 
        Girl girl1 = new Girl(0,"snow white"); 
        Girl girl2 = new Girl(1,"snow pink"); 
        girl1.start(); 
        girl2.start(); 
    } 
} 
 
class Lipstick{} 
class Mirror{} 
 
class Girl extends Thread { 
    static Lipstick lipstick = new Lipstick(); 
    static Mirror mirror = new Mirror(); 
 
    int choice; 
    String name; 
 
    Girl(int choice, String name) { 
        this.choice = choice; 
        this.name = name; 
    } 
 
    public void run(){ 
        try { 
            get(); 
        } catch (InterruptedException e) { 
            e.printStackTrace(); 
        } 
    } 
 
    private void get() throws InterruptedException { 
        if(choice == 0) { 
            synchronized (lipstick) { 
                System.out.println(name + " get lipstick"); 
                Thread.sleep(1000); 
 
                synchronized (mirror) { 
                    System.out.println(name + " get mirror"); 
                } 
            } 
        }else { 
            synchronized (mirror) { 
                System.out.println(name + " get mirror"); 
                Thread.sleep(1000); 
 
                synchronized (lipstick) { 
                    System.out.println(name + " get lipstick"); 
                } 
            } 
        } 
    } 
} 

输出结果:

snow white get lipstick 
snow pink get mirror 

线程1持有实例1的锁,同时想获得实例2的锁,得不到便无法释放实例1的锁。

线程2持有实例2的锁,同时想获得实例1的锁,得不到便无法释放实例2的锁。

由此产生死锁。

解决办法:

package com.cxf.multithread.lock; 
 
public class TestForLock { 
    public static void main(String[] args) { 
        Girl girl1 = new Girl(0,"snow white"); 
        Girl girl2 = new Girl(1,"snow pink"); 
        girl1.start(); 
        girl2.start(); 
    } 
} 
 
class Lipstick{} 
class Mirror{} 
 
class Girl extends Thread { 
    static Lipstick lipstick = new Lipstick(); 
    static Mirror mirror = new Mirror(); 
 
    int choice; 
    String name; 
 
    Girl(int choice, String name) { 
        this.choice = choice; 
        this.name = name; 
    } 
 
    public void run(){ 
        try { 
            get(); 
        } catch (InterruptedException e) { 
            e.printStackTrace(); 
        } 
    } 
 
    private void get() throws InterruptedException { 
        if(choice == 0) { 
            synchronized (lipstick) { 
                System.out.println(name + " get lipstick"); 
                Thread.sleep(1000); 
            } 
            synchronized (mirror) { 
                System.out.println(name + " get mirror"); 
            } 
        }else { 
            synchronized (mirror) { 
                System.out.println(name + " get mirror"); 
                Thread.sleep(1000); 
 
                synchronized (lipstick) { 
                    System.out.println(name + " get lipstick"); 
                } 
            } 
        } 
    } 
} 

输出结果:

snow white get lipstick 
snow pink get mirror 
snow pink get lipstick 
snow white get mirror 

只要有一个线程不同时想要持有两个锁,死锁就不会产生。

可重入锁

可重入锁是显式锁,开启锁后需要关闭。而synchronized是隐式锁,自动关闭。

不加锁买票

以下代码展示不加锁买票的情况。

package com.cxf.multithread.relock; 
 
public class TestForRelock { 
    public static void main(String[] args) { 
        GetTicket getTicket = new GetTicket(); 
        new Thread(getTicket).start(); 
        new Thread(getTicket).start(); 
        new Thread(getTicket).start(); 
    } 
 
} 
 
class GetTicket implements Runnable { 
    int num = 10; 
    @Override 
    public void run() { 
        while (true) { 
            if (num>0){ 
                try { 
                    Thread.sleep(100); 
                } catch (InterruptedException e) { 
                    e.printStackTrace(); 
                } 
                System.out.println("get NO."+ num-- +" ticket"); 
            }else break; 
        } 
    } 
} 

输出结果:

get NO.10 ticket 
get NO.9 ticket 
get NO.8 ticket 
get NO.7 ticket 
get NO.7 ticket 
get NO.7 ticket 
get NO.6 ticket 
get NO.6 ticket 
get NO.5 ticket 
get NO.4 ticket 
get NO.4 ticket 
get NO.4 ticket 
get NO.3 ticket 
get NO.3 ticket 
get NO.3 ticket 
get NO.2 ticket 
get NO.2 ticket 
get NO.2 ticket 
get NO.1 ticket 
get NO.1 ticket 
get NO.1 ticket 

买到了重复的票。

加锁买票

package com.cxf.multithread.relock; 
 
import java.util.concurrent.locks.ReentrantLock; 
 
public class TestForRelock { 
    public static void main(String[] args) { 
        GetTicket getTicket = new GetTicket(); 
        new Thread(getTicket).start(); 
        new Thread(getTicket).start(); 
        new Thread(getTicket).start(); 
    } 
 
} 
 
class GetTicket implements Runnable { 
    int num = 10; 
    ReentrantLock lock = new ReentrantLock(); 
    @Override 
    public void run() { 
        while (true) { 
 
            try { 
                lock.lock(); 
                if (num>0){ 
                    try { 
                        Thread.sleep(100); 
                    } catch (InterruptedException e) { 
                        e.printStackTrace(); 
                    } 
                    System.out.println("get NO."+ num-- +" ticket"); 
                }else break; 
            }finally { 
                lock.unlock(); 
            } 
 
        } 
    } 
} 

输出结果:

get NO.10 ticket 
get NO.9 ticket 
get NO.8 ticket 
get NO.7 ticket 
get NO.6 ticket 
get NO.5 ticket 
get NO.4 ticket 
get NO.3 ticket 
get NO.2 ticket 
get NO.1 ticket 

不会买到重复的票。

可重入锁使用ReenTrantLock类建立锁实例,方法lock加锁,unlock解锁,一般try finally格式来执行:

try{ 
    lock(); 
    访问对象的语句; 
}finally{unlock} 

不安全的例子

下面3个不安全的例子反映出并发问题。

买票

以下代码展示多个人买同一种票的情况:

package com.cxf.multithread.problem.ticket; 
 
public class TestForTicket { 
    public static void main(String[] args) { 
        ByTicket getByAll = new ByTicket(); 
        Thread thread1 = new Thread(getByAll,"cxf"); 
        Thread thread2 = new Thread(getByAll,"cdf"); 
        Thread thread3 = new Thread(getByAll,"czf"); 
        thread1.start(); 
        thread2.start(); 
        thread3.start(); 
    } 
} 
 
class ByTicket implements Runnable{ 
    private  int num = 10; 
    boolean flag =true; 
    @Override 
    public void run() { 
        while (flag){ 
            try { 
                buy(); 
            } catch (InterruptedException e) { 
                e.printStackTrace(); 
            } 
        } 
    } 
 
    private void buy() throws InterruptedException { 
        if (num <= 0) { 
            flag = false; 
            return; 
        } 
        Thread.sleep(100); 
        System.out.println(Thread.currentThread().getName()+" get NO."+num--+" ticket"); 
    } 
} 

输出结果:

cxf get NO.10 ticket 
cdf get NO.8 ticket 
czf get NO.9 ticket 
czf get NO.7 ticket 
cxf get NO.7 ticket 
cdf get NO.6 ticket 
czf get NO.4 ticket 
cxf get NO.3 ticket 
cdf get NO.5 ticket 
cxf get NO.2 ticket 
czf get NO.0 ticket 
cdf get NO.1 ticket 
 

有人买到了相同的票,也有人买到了不存在的票。

取钱

以下代码演示2个人同时在一个银行账户上取钱的情况:

package com.cxf.multithread.problem.bank; 
 
public class TestForBank { 
    public static void main(String[] args) { 
        Account account = new Account(100,"All"); 
        GetMoney cxf = new GetMoney(account,60,"cxf"); 
        GetMoney cdf = new GetMoney(account,70,"cdf"); 
        cxf.start(); 
        cdf.start(); 
    } 
} 
 
class Account{ 
    int remain; 
    String name; 
    public Account(int remain,String name) { 
        this.remain = remain; 
        this.name =name; 
    } 
} 
 
class GetMoney extends Thread{ 
    Account account; 
    int toGet; 
 
    public GetMoney(Account account,int toGet,String name) { 
        super(name); 
        this.account = account; 
        this.toGet = toGet; 
 
    } 
 
    public void run() { 
        if (account.remain - toGet < 0) { 
            System.out.println(Thread.currentThread().getName()+" not enough remain"); 
            return; 
        } 
 
        try { 
            Thread.sleep(100); 
        } catch (InterruptedException e) { 
            e.printStackTrace(); 
        } 
 
        account.remain = account.remain - toGet; 
        System.out.println(account.remain + " yuan left"); 
        System.out.println(this.getName() + " got " + toGet); 
 
 
    } 
} 

输出结果:

-30 yuan left 
-30 yuan left 
cxf got 60 
cdf got 70 

发生了超额取款的情况。

记录

以下代码演示用列表记录多个线程时会发生的事情:

package com.cxf.multithread.problem.list; 
 
import java.util.ArrayList; 
import java.util.List; 
 
public class TestForList { 
    public static void main(String[] args) throws InterruptedException { 
 
        List<String> list = new ArrayList<String>(); 
 
        for (int i = 0; i < 100000; i++) { 
            new Thread(()->{ 
                 
                list.add(Thread.currentThread().getName()); 
 
            }).start(); 
        } 
        Thread.sleep(500); 
        System.out.println(list.size()); 
    } 
} 
 

输出结果:

99993 
 

正确结果应为100000。实质上也是多个线程操作同一个对象。

使用同步块对例子改进

正确买票

以下代码演示如何正确买票:

package com.cxf.multithread.problem.ticket; 
 
public class TestForTicket { 
    public static void main(String[] args) { 
        ByTicket getByAll = new ByTicket(); 
        Thread thread1 = new Thread(getByAll,"cxf"); 
        Thread thread2 = new Thread(getByAll,"cdf"); 
        Thread thread3 = new Thread(getByAll,"czf"); 
        thread1.start(); 
        thread2.start(); 
        thread3.start(); 
    } 
} 
 
class ByTicket implements Runnable{ 
    private  int num = 10; 
    boolean flag =true; 
    @Override 
    public void run() { 
        while (flag){ 
            try { 
                buy(); 
            } catch (InterruptedException e) { 
                e.printStackTrace(); 
            } 
        } 
    } 
 
    synchronized private void  buy() throws InterruptedException { 
        if (num <= 0) { 
            flag = false; 
            return; 
        } 
        Thread.sleep(100); 
        System.out.println(Thread.currentThread().getName()+" get NO."+num--+" ticket"); 
    } 
} 

输出结果:

cxf get NO.10 ticket 
cxf get NO.9 ticket 
cxf get NO.8 ticket 
cxf get NO.7 ticket 
cxf get NO.6 ticket 
cxf get NO.5 ticket 
cxf get NO.4 ticket 
cxf get NO.3 ticket 
cxf get NO.2 ticket 
cxf get NO.1 ticket 

同步块的关键词synchronized加在buy方法的定义前面。相当于对含有buy方法的实例this加锁。

正确取钱

以下代码演示如何正确取钱:

package com.cxf.multithread.problem.bank; 
 
public class TestForBank { 
    public static void main(String[] args) { 
        Account account = new Account(100,"All"); 
        GetMoney cxf = new GetMoney(account,60,"cxf"); 
        GetMoney cdf = new GetMoney(account,70,"cdf"); 
        cxf.start(); 
        cdf.start(); 
    } 
} 
 
class Account{ 
    int remain; 
    String name; 
    public Account(int remain,String name) { 
        this.remain = remain; 
        this.name =name; 
    } 
} 
 
class GetMoney extends Thread{ 
    Account account; 
    int toGet; 
 
    public GetMoney(Account account,int toGet,String name) { 
        super(name); 
        this.account = account; 
        this.toGet = toGet; 
 
    } 
 
    public void run() { 
        synchronized (account) { 
        if (account.remain - toGet < 0) { 
            System.out.println(Thread.currentThread().getName()+" not enough remain"); 
            return; 
        } 
 
        try { 
            Thread.sleep(100); 
        } catch (InterruptedException e) { 
            e.printStackTrace(); 
        } 
 
            account.remain = account.remain - toGet; 
            System.out.println(account.remain + " yuan left"); 
            System.out.println(this.getName() + " got " + toGet); 
        } 
 
    } 
} 

输出结果:

40 yuan left 
cxf got 60 
cdf not enough remain 

同步块关键字synchronized放在方法内部,synchronized后()内为被加锁的对象,{}内为被锁限制的访问修改动作。

正确记录

以下代码演示如何正确记录:

package com.cxf.multithread.problem.list; 
 
import java.util.ArrayList; 
import java.util.List; 
 
public class TestForList { 
    public static void main(String[] args) throws InterruptedException { 
 
        List<String> list = new ArrayList<String>(); 
 
        for (int i = 0; i < 100000; i++) { 
            new Thread(()->{ 
                synchronized (list) { 
                    list.add(Thread.currentThread().getName()); 
                } 
            }).start(); 
        } 
        Thread.sleep(500); 
        System.out.println(list.size()); 
    } 
} 
 

输出结果:

100000 

这个例子是以列表为操作对象,因此还有另一种办法实现线程同步:

package com.cxf.multithread.problem.list; 
 
import java.util.ArrayList; 
import java.util.List; 
import java.util.concurrent.CopyOnWriteArrayList; 
 
public class TestForList { 
    public static void main(String[] args) throws InterruptedException { 
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); 
        for (int i = 0; i < 100000; i++) { 
            new Thread(()->{ 
                    list.add(Thread.currentThread().getName()); 
            }).start(); 
        } 
        Thread.sleep(500); 
        System.out.println(list.size()); 
    } 
} 
 

输出结果:

100000 

这里不用List生成列表,而是用CopyOnWriteArrayList,自带同步功能。


评论关闭
IT干货网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!