CPU 的切换是随机的同时在每个线程停留的时间也是随机的,每一次在线程中的状态是会被保存的,等到下一次这个线程得到CPU执行权程序会从上次离开的地方开始执行
这样的运行机制就可能会导致多线程的安全问题:
就这段代码而言,开启四个线程,假设此时ticket = 1 ,当CPU执行线程0 判断ticket 的数目合理之后CPU切换到线程1,判断后CPU切换,如此在线程2中也是这样,在线程三中ticket -1 = 0 此时票已经为零。CPU切换到线程0,接着执行就该输出,此时输出的值就为0,CPU继续切换到线程1 输出的值为-1,这样的之都是不合法的,这样的问题就叫做——————多线程的安全问题
多线程安全问题产生的原因:
1.多个线程操作共享数据
2.在一个线程中有多行代码操作共享数据(改变共享数据)
如何让解决多线程的安全问题:
解决思路:一个线程在执行线程任务的时候将多条操作共享数据的代码一起执行,在执行过程中不允许其他线程执行
代码体现:
package Thread;
class Tickets implements Runnable{
private int ticket=100;
Object obj = new Object();
public void run(){
while(ticket!=0) {//多线程里要有循环
synchronized(obj) {
if (ticket > 0) {
//使CPU在这里停一下,使用sleep()函数,Thread 中的静态函数。使其出现错误的数据
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}//没有解决方案
System.out.println(Thread.currentThread().getName() + " " + --ticket);
}
}
}
}
}
public class ThreadSellTickets {
public static void main(String[] args){
Tickets tc = new Tickets();
Thread T1 =new Thread(tc);
Thread T2 =new Thread(tc);
// Thread T3 =new Thread(tc);
//Thread T4 =new Thread(tc);
T1.start();
T2.start();
//T3.start();
//T4.start();
}
}
将多行代码封装成代码块来解决,这个代码块叫同步代码块synchronized
使用格式:
synchronized(对象)//对象的任意的(一般使用object obj = new object() )
{
////需要同步进行的代码
}
运行解析://还是根据售票那个例子
比如线程1 得到了CPU的执行权,读到synchronized 会判断对象是否存在,如果对象存在,会拿取对象再执行代码块中的代码。在执行代码块中的代码时CPU有可能会切换出去,切换到其他线程,其他线程也会执行到synchronized 判断对象是否存在,但是对象已经被线程一拿走,对象不存在,此线程就不会执行代码块中的代码。当线程1 中同步代码块执行完成,获取的对象才会被释放,其他的线程才能执行代码块中的代码。
同步的好处:解决多线程的安全问题
同步的缺点:降低了程序的性能,因为每一个线程都会去判断synchronized 的对象是否存在
关于同步的总结:锁!!!!!!!!!!!!!!!!!!!!
synchronized(对象)相当于一个锁,线程判断对象是否存在,存在就拿走就相当于将这段代码块的门锁上(钥匙被拿走),虽然CPU的执行权会切换出去但是其他线程到这判断后发现对象不存在,门被锁起来了,无法继续执行代码块中的代码,只有当线程执行完同步代码块,对象才会被释放(钥匙被释放),这段代码块的门才能被其他线程打开
线程产生安全问题的根本原因:CPU的切换,一个线程没有将任务执行完(一次)CPU就切换出去,另一个线程又会修改共享数据,从而产生了数据的错误。而锁机制解决这一问题的方式就是线程来执行这个任务时就将任务锁起来,无论CPU如何切换,只有一条路径将这个任务执行完(操作共享数据的代码执行完),锁才会打开)
同步的前提:(什么时候使用同步)
有时候加了同步问题也没有办法解决。
1.存在多个线程。没有多个线程也不存在CPU执行权切换的事
2.多线程使用的锁要是一样的,对象要是一样的,不是一样的锁你也不存在锁住,锁不一样你也锁不住
如果加上了同步锁还是无法解决多线程的安全问题,就要从同步锁的前提去思考是否写错了
同步的代码行,是对共享数据操作(修改)的代码行。首先一定要明确共享数据在哪里