线程安全问题
私有数据不需要考虑线程安全问题
共享数据才是导致线程不安全的罪魁祸首
线程之间会因为调度的原因,穿插执行
数据的方法有一定的特殊规则
线程不安全的因素:
1. 代码的原子性:automic一段代码运行期间不能被分割
2. 内存的可见性:程序运行过程中:Java只保证了,单线程情况下,工作内存中的数据是正确的,如果要保证多线程情况下,可以看到线程的工作情况(内存的变化)就需要保证变量的可见性问题
3. 代码的重排序
解决线程安全问题的方法:
1.同步代码块
public class RunnableImpl implements Runnable{
private int ticket=100;
Object obj=new Object();
public void run(){
while(true){
synchronized(obj){
if(ticket>0){
try{
Thread.sleep(10);
}catch(TnterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
同步中的线程,没有执行完毕不会释放,同步外的线程没有锁进不去同步
2同步方法
使用步骤:
-
把访问了共享数据的代码抽取出来,放到一个方法中
-
在方法上添加synchronized修饰符
public class RunnableImpl implements Runnable{
private int ticket=100;
Object obj=new Object();
public void run(){
while(true){
payTicket();
}
}
}
public synchronized void payTicket(){
if(ticket>0){
try{
Thread.sleep(10);
}catch(TnterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->正在卖第"+ticket+"张票");
ticket--;
}
}
}
3.静态同步方法
静态方法的所对象是本类的class属性–》class文件对象(反射)
public class RunnableImpl implements Runnable{
private static int ticket=100;
Object obj=new Object();
public void run(){
while(true){
payTicket();
}
}
}
public static synchronized void payTicket(){
if(ticket>0){
try{
Thread.sleep(10);
}catch(TnterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->正在卖第"+ticket+"张票");
ticket--;
}
}
}
4.Lock锁
java.util.concurrent.locks.Lock接口
Lock实现提供了比使用synchronized方法和语句可获得更广泛的锁定操作
Lock接口中的方法:
void lock() 获取锁
void unlock()释放锁
java.util.concurrent.locks.ReentrantLock implements Lock接口
使用步骤:
1. 在成员位置创建一个ReentrantLock对象
2. 在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
3. 在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
public class RunnableImpl implements Runnable{
private static int ticket=100;
Lock l=new ReentrantLock();
Object obj=new Object();
public void run(){
while(true){
l.lock();
if(ticket>0){
try{
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"--->正在卖第"+ticket+"张票");
ticket--;
}catch(TnterruptedException e){
e.printStackTrace();
}finally{
l.unlock();
}
}
}
}
}
生产消费案例
Object类中的方法
1. void wait() 在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待
2. void notify()唤醒在此对象监视器上等待的单个线程,会继续执行wait方法之后的代码
public class ProduceExpend {
public static void main(String[] args) {
Object obj=new Object();
new Thread(){
public void run(){
while(true) {
synchronized (obj) {
System.out.println("告知老板要的包子数量");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("包子做好,开吃");
System.out.println("----------------------");
}
}
}
}.start();
new Thread(){
public void run(){
while(true){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
System.out.println("5秒钟包子做好了,可以吃包子了");
obj.notify();
}
}
}
}.start();
}
}
进入到TimeWaiting(计时等待)有两种方式:
1. 使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
2. 使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态
唤醒方法:
1. void notify()唤醒此对象监视器上等待的单个线程
2. void notifyAll()唤醒在此对象监视器上等待的所有线程
线程池
java.util.concurrent.Executors:线程池的工厂类,用来生成线程池
Executors类中的静态方法:
static ExecrtorService newFixedThreadPool(Int nThreads)
:创建一个可重用固定线程数的线程池
java.util.concurrent.ExecutorSevice:线程池接口
用来从线程池中获取线程,调用start方法,执行线程任务
submit(Runnable task)
提交一个Runnable任务用于执行
void shutdown()
关闭/销毁线程池的方法
线程池的使用步骤:
1. 使用线程池的工厂类Executors里面提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
2. 创建一个类,实现Runnable接口,重写run方法,设置线程任务
3. 调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
4. 调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
Lambda表达式
public class Lambda {
public static void main(String[] args) {
new Thread(()->{
System.out.println("创建了一个新线程");
}).start();
}
}
可以省略书写(大括号,分号,return,参数类型相同可以省略,参数只有一个也可以省略 )
public class Lambda {
public static void main(String[] args) {
new Thread(()->System.out.println("创建了一个新线程")).start();
}
}
volatile变量修饰符
1. 可见性
2. 可以保证重排序
对象中的等待集(wait set)
Object.wait()
Object.notify()
Object.notifyAll()
用来做线程通信的