【写在前面】笔记都是看黑马基础班整理的
1、线程安全
- 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
- 当我们使用多线程访问同一资源的时候,且对多线程中对资源有写的操作,就容易出现线程安全问题。
写一个三个线程同时计数(100—1):
主方法(线程同步代码一样):
//多线程实现从100到1
public class demon01Count {
public static void main(String[] args) {
//创建Runnable对象(四个情况的实现类)
RunnableImpl runnable = new RunnableImpl();
RunnableImplSyn1 runnable = new RunnableImplSyn1();
RunnableImplSyn2 runnable = new RunnableImplSyn2();
RunnableImplLock runnable = new RunnableImplLock();
//创建Thread类对象,构造方法中传入参数
Thread thread01 = new Thread(runnable);
Thread thread02 = new Thread(runnable);
Thread thread03 = new Thread(runnable);
//使用start开启多线程
thread01.start();
thread02.start();
thread03.start();
}
}
先定义一个Runnable实现类
public class RunnableImpl implements Runnable {
//定义一个多线程的共享资源
private int number=100;
@Override
public void run(){
//使用死循环,使计数重复执行
while(true){
if(number>0){ //判断是否到0了
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+number);
number--;
}
}
}
}
运行结果(部分):
Thread-0--->100
Thread-2--->100
Thread-1--->100
Thread-0--->97
Thread-1--->97
Thread-2--->97
Thread-1--->91
Thread-0--->91
Thread-2--->91
Thread-1--->88
Thread-2--->88
Thread-0--->88
Thread-1--->85
Thread-0--->85
Thread-2--->85
Thread-0--->5
Thread-2--->3
Thread-1--->2
Thread-0--->1
Thread-2--->0
Thread-1--->-1
- 运行结果中出现了0和-1,并且有重复出现的数据,这种问题就是线程不安全。
- 线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
2、线程同步
解决多线程访问同一资源引起的线程安全问题,用同步机制(synchronized)来解决。
- 线程1进入操作的时候,线程2和线程3只能在外等着,线程1操作结束,线程1和线程2和线程3才有机会进入代码去执行。也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。
2.1 同步代码块
同步代码块的格式:
synchronized(锁对象){
可能出现线程安全问题的代码(访问了共享数据的代码)
}
- 注意:
1、 通过代码块中的所对象,可以使任意的对象。
2、必须保证多个线程使用的锁对象是同一个。
3、锁对象的作用是把同步代码块锁住,只让一个线程在同步代码块中执行。
public class RunnableImplSyn1 implements Runnable {
private int number=100;
//创建一个锁对象
Object obj =new Object();
@Override
public void run(){
while(true){
synchronized (obj){
if(number>0){ //判断是否到0了
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+number);
number--;
}
}
}
}
}
2.2 同步方法
- 使用synchronized修饰方法,保证线程1在执行该方法的时候,其他线程只能在方法外等待。
格式:
public synchronized void method(){
可能产生线程安全问题的代码
}
public class RunnableImplSyn2 implements Runnable{
//定义一个多线程的共享资源
private int number=100;
@Override
public void run(){
//使用死循环,使技术重复执行
while(true) {
count();
}
}
private synchronized void count(){
if(number>0){ //判断是否到0了
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+number);
number--;
}
}
}
2.3 Lock锁(同步锁)
- java.util.concurrent.locks 提供的接口Lock。有lock和unlock方法,用来加同步锁和释放锁。
使用步骤:
1、在成员位置创建一个ReentrantLock对象
2、在可能会出现安全问题的代码前调用Lock接口中lock()获取锁
3、在可能会出现安全问题的代码后调用Lock接口中unlock()释放锁
public class RunnableImplLock implements Runnable {
//定义一个多线程的共享资源
private int number=100;
Lock lock = new ReentrantLock();
@Override
public void run(){
//使用死循环,使技术重复执行
while(true){
lock.lock();
if(number>0){ //判断是否到0了
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+number);
number--;
}
lock.unlock();
}
}
}