Thread类中run()方法和start()方法的区别
①run()方法是描述一个线程具体执行的内容;start()方法是申请系统线程,系统从这里真正开始创建一个线程,让这个新线程来执行run()方法
②run()方法是一个类中的普通方法,是线程的入口,不需要程序员手动调用。主动调用和普通调用一样,会顺序执行一次;start()方法内部会调用Java本地的方法,真正启动线程,并执行run()方法中的代码。
线程的状态:
线程安全问题的原因和解决原因
原因:
①抢占式执行。当多个线程同时执行时,系统调度线程的顺序是不确定。
②多个线程同时修改同一个变量
③修改操作不是原子的
原子性:某个操作的cup指令只有一个
比如“++”操作不是原子的,因为“++”操作的CPU指令有:load:读取内存的变量 Add:使变量执行++操作 save:将变量保存回内存中
④内存的可见性
当系统频繁读取内存时并且读取的结果都是一样的时候,此时编译器就会对代码进行优化。当另外的一个线程对内存变量进行修改的时候,原来的线程由于代码的优化而感知不到内存变量发生的改变即内存可见性
⑤指令重排序
解决办法:
①采用关键字"synchronized",实现给线程加锁
进入synchronized代码块即实现加锁
出了synchronized代码块即实现解锁
一旦某个线程加锁之后,另外的线程也想实现加锁,此时就会发生阻塞。要等上一个线程将锁释放之后,后面的线程才能实现加锁
1.修饰代码块
在synchronized的()中,只要是object的实例即可,此时使用this,即谁调用Add(),谁就是this
2.直接修饰普通方法:锁的是synchronized Counter对象
3.修饰静态方法:锁的是synchronized类的对象
等价于
②采用关键字"volatile",让操作变成原子的
volatile保证内存的可见性和禁止指令重排序
被volatile关键字修饰的变量,此时编译器会禁止JVM对代码进行优化,能够保证每次都是重新从内存读取变量的值
wait()方法:让线程进入等待
使用wait()方法:①先解锁;②阻塞等待;③当时具有符合的条件是再将其唤醒
注意:使用wait()方法必须是对已经被加锁的对象使用,并且被加锁的对象和解锁的对象要一样
notify()方法:唤醒当前正在等待的线程
wait和sleep的区别:
①wait还需要搭配synchronized使用,而sleep可以直接使用
②wait是Object的方法,sleep是Thread的方法
③wait解决的是线程之间的顺序控制,而sleep主要是让线程休眠一段时间