上一篇看这里:JAVA并发编程-8-线程池 如果多线程下使用这个类,不论多线程如何使用和调度这个类,这个类总是表示出正确的行为,这个类就是线程安全的。 类的线程安全表现为: 不做正确的同步,在多个线程之间共享状态(即类的属性)的时候,就会出现线程不安全。 所有的变量都是在方法内部声明的,这些变量都处于栈封闭状态。 线程在执行方法时,会将方法打包成一个栈桢,放到自己的方法栈中去执行,由于栈是线程私有的,不同线程执行方法不会相互影响。 没有任何成员变量的类,就叫无状态的类 线程安全问题是由类在多个线程之间共享状态(即类的属性)产生的,如果类没有属性,即没有状态,则不会产生线程安全问题。 让状态不可变,两种方式: 保证类的可见性,最适合一个线程写,多个线程读的情景, 加锁和CAS前面章节反复提到,是一种线程安全的操作 类中持有的成员变量,特别是对象的引用,如果这个成员对象不是线程安全的,通过get等方法发布出去,会造成这个成员对象本身持有的数据在多线程下不正确的修改,从而造成整个类线程不安全的问题。 TheadLocal线程副本,是保存在每个线程中的key,value结构,线程之间不可见,可以保证线程安全。 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁 资源一定是多于1个,同时小于等于竞争的线程数,资源只有一个,只会产生激烈的竞争。 如上代码,定义了两把锁(多个资源),fisrtToSecond先拿第一个锁再拿第二个锁,SecondToFisrt先拿第二个锁再拿第一个锁。启动两个线程,线程A拿到了第一把锁,线程B拿到了第二把锁,这时线程A去拿第二把锁,但是被线程B占有无法拿到,而线程B也在尝试拿第一把锁,被线程A占有。产生了死锁。 死锁的根本成因:获取锁的顺序不一致导致。 解决方式:要保证加锁的顺序 尝试拿锁的机制中,发生多个线程之间互相谦让,不断发生拿锁,释放锁的过程。 定义一个转账操作,按照顺序对from和to加锁,finally里面解锁 模拟张三to李四,和李四to张三,两个操作,启动两个线程调用转账方法。 ReentrantLock.trylock在拿不到锁的时候不会进行等待,而是在finally中执行解锁操作。这样线程A拿到1锁拿不到2锁时,解锁进行下一次循环,线程B拿到2锁拿不到1锁时,解锁进行下一次循环,不断尝试,知道某个时间差将其错开。 解决办法:每个线程休眠随机数,错开拿锁的时间。 低优先级的线程总是拿不到cpu的执行时间。这种情况在我们的开发过程中比较少碰到。 使用并发的目标是为了提高性能,但是引入多线程后,也会引入额外的开销。但是如果使用的不合理,往往可能使程序运行的效率降低。 衡量应用的程序性能: 多快和多少完全独立,甚至是相互矛盾的。 对服务器应用来说:多少(可伸缩性,吞吐量)比多快更受重视 一个应用程序中,串行的部分总是有的 1、上下文切换 如果当前可运行线程大于cpu的数量,cpu会把它的可执行时间进行分片,分给一个个线程去运行,当一个线程将时间分片用完了以后,操作系统需要将当前运行完了的线程从cpu上拿走,把其他线程load进来,让其他线程来使用cpu,这个过程就是一次上下文切换。上下文切换包括将原线程的各种数据等保存,将新线程所需要的数据读到cpu寄存器,这些都是上下文切换所要耗费的资源,大约需要5000-10000个时钟周期。 2、内存同步 即加锁,需要执行很多内存屏障等额外指令,这会带来性能上的损失 3、阻塞 线程挂起,包括两次额外的上下文切换线程并发安全的理解
一、类的线程安全的定义
二、怎么才能做到类的线程安全
1、栈封闭
2、无状态
3、让类不可变
1,加final关键字,对于一个类,所有的成员变量应该是私有的,同样的只要有可能,所有的成员变量应该加上final关键字,但是加上final,要注意如果成员变量又是一个对象时,这个对象所对应的类也要是不可变,才能保证整个类是不可变的。
2、根本就不提供任何可供修改成员变量的地方,同时成员变量也不作为方法的返回值4、volatile
5、加锁和CAS
6、安全的发布
7、TheadLocal
三、线程不安全会产生的问题
1、死锁
public class NormalDeadLock { private static Object valueFirst = new Object();//第一个锁 private static Object valueSecond = new Object();//第二个锁 //先拿第一个锁,再拿第二个锁 private static void fisrtToSecond() throws InterruptedException { String threadName = Thread.currentThread().getName(); synchronized (valueFirst) { System.out.println(threadName+" get first"); SleepTools.ms(100); synchronized (valueSecond) { System.out.println(threadName+" get second"); } } } //先拿第二个锁,再拿第一个锁 private static void SecondToFisrt() throws InterruptedException { String threadName = Thread.currentThread().getName(); synchronized (valueSecond) { System.out.println(threadName+" get first"); SleepTools.ms(100); synchronized (valueFirst) { System.out.println(threadName+" get second"); } } } }
//先拿第一个锁,再拿第二个锁 private static void fisrtToSecond() throws InterruptedException { String threadName = Thread.currentThread().getName(); synchronized (valueFirst) { System.out.println(threadName+" get first"); SleepTools.ms(100); synchronized (valueSecond) { System.out.println(threadName+" get second"); } } } //先拿第二个锁,再拿第一个锁 private static void SecondToFisrt() throws InterruptedException { String threadName = Thread.currentThread().getName(); synchronized (valueFirst) { System.out.println(threadName+" get first"); SleepTools.ms(100); synchronized (valueSecond) { System.out.println(threadName+" get second"); } } }
2、活锁
/** * *类说明:用户账户的实体类 */ public class UserAccount { //private int id; private final String name;//账户名称 private int money;//账户余额 //显示锁 private final Lock lock = new ReentrantLock(); public Lock getLock() { return lock; } public UserAccount(String name, int amount) { this.name = name; this.money = amount; } public String getName() { return name; } public int getAmount() { return money; } @Override public String toString() { return "UserAccount{" + "name='" + name + ''' + ", money=" + money + '}'; } //转入资金 public void addMoney(int amount){ money = money + amount; } //转出资金 public void flyMoney(int amount){ money = money - amount; } }
public class SafeOperateToo implements ITransfer { @Override public void transfer(UserAccount from, UserAccount to, int amount) throws InterruptedException { Random r = new Random(); while(true) { if(from.getLock().tryLock()) { try { System.out.println(Thread.currentThread().getName() +" get "+from.getName()); if(to.getLock().tryLock()) { try { System.out.println(Thread.currentThread().getName() +" get "+to.getName()); //两把锁都拿到了 from.flyMoney(amount); to.addMoney(amount); break; }finally { to.getLock().unlock(); } } }finally { from.getLock().unlock(); } } } } }
/** * 类说明:模拟支付公司转账的动作 */ public class PayCompany { /*执行转账动作的线程*/ private static class TransferThread extends Thread { private String name;//线程名字 private UserAccount from; private UserAccount to; private int amount; private ITransfer transfer; //实际的转账动作 public TransferThread(String name, UserAccount from, UserAccount to, int amount, ITransfer transfer) { this.name = name; this.from = from; this.to = to; this.amount = amount; this.transfer = transfer; } public void run() { Thread.currentThread().setName(name); try { transfer.transfer(from, to, amount); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { PayCompany payCompany = new PayCompany(); UserAccount zhangsan = new UserAccount("zhangsan", 20000); UserAccount lisi = new UserAccount("lisi", 20000); ITransfer transfer = new SafeOperateToo(); TransferThread zhangsanToLisi = new TransferThread("zhangsanToLisi" , zhangsan, lisi, 2000, transfer); TransferThread lisiToZhangsan = new TransferThread("lisiToZhangsan" , lisi, zhangsan, 4000, transfer); zhangsanToLisi.start(); lisiToZhangsan.start(); } }
可以看到执行结果,尝试加锁了很多次。3、线程饥饿
四、性能和思考
这是架构设计最重要的需要权衡的部分
做应用的时候:
1、先保证程序正确,确实达不到要求,再提高速度(黄金原则)
2、一定要以测试为基准
Amdahl定律:1/(F+(1-N)/N) F-串行部分,N-并行部分,最好的情况 1/F影响性能的因素
减少锁的竞争
对锁的持有快进快出,尽量缩短持有锁的时间
比如sleep操作不会释放锁,不建议在加锁代码中使用
在使用锁的时候,锁保护的对象是多个,多个对象其实是独立变化的,应该使用多个锁一一保护每个对象
比如ConcurrentHashmap
使用读写锁等
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算