线程的状态
线程安全问题
-
案例:售票的例子。
class Ticket implements Runnable
{
//1,描述票的数量。
private int tickets = 100;
//2,售票的动作,这个动作需要被多线程执行,那就是线程任务代码。需要定义run方法中。
//线程任务中通常都有循环结构。
private Object obj = new Object();
public void run()
{
while(true)
{
if(tickets>0)
{
//要让线程在这里稍停,模拟问题的发生。sleep 看到了0 -1 -2 错误的数据,这就是传说中的多线程安全问题。
try{Thread.sleep(1);}catch(InterruptedException e){}
//打印线程名称。
System.out.println(Thread.currentThread().getName()+"....."+tickets--);
}
}
}
}
class ThreadDemo3
{
public static void main(String[] args)
{
//1,创建Runnable接口的子类对象。
Ticket t = new Ticket();
//2,创建四个线程对象。并将Runnable接口的子类对象作为参数传递给Thread的构造函数。
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
//3,开启四个线程。
t1.start();
t2.start();
t3.start();
t4.start();
}
}
售票的动作需要同时执行,所以使用多线程技术。
发生了线程安全问题:出现了错误的数据。0 -1 -2
问题产生的原因;
1,线程任务中在操作共享的数据。
2,线程任务操作共享数据的代码有多条(运算有多个)。
解决思路:
只要让一个线程在执行线程任务时将多条操作共享数据的代码执行完,
在执行过程中,不要让其他线程参与运算。就哦了。
代码体现呢?
Java中解决此问题通过代码块来完成的。
这个代码块:同步代码块 synchronized
格式:
synchronized(对象)
{
//需要被同步的代码。
}
同步好处:
解决多线程安全问题。
同步弊端:
降低了程序的性能。
同步前提:
必须保证多个线程在同步中使用的是同一个锁。
解决了什么问题?
当多线程安全问题发生时,加入了同步后,
问题依旧,就要通过这个同步的前提来判断同步是否写正确。
-
使用同步代码块
class Ticket implements Runnable
{
//1,描述票的数量。
private int tickets = 100;
//2,售票的动作,这个动作需要被多线程执行,那就是线程任务代码。需要定义run方法中。
//线程任务中通常都有循环结构。
private Object obj = new Object();
public void run()
{
while(true)
{
synchronized(obj)
{
if(tickets>0)
{
//要让线程在这里稍停,模拟问题的发生。sleep 看到了0 -1 -2 错误的数据,这就是传说中的多线程安全问题。
try{Thread.sleep(1);}catch(InterruptedException e){/*未写处理方式,后面讲*/}
System.out.println(Thread.currentThread().getName()+"....."+tickets--);//打印线程名称。
}
}
}
}
}
class ThreadDemo3
{
public static void main(String[] args)
{
//1,创建Runnable接口的子类对象。
Ticket t = new Ticket();
//2,创建四个线程对象。并将Runnable接口的子类对象作为参数传递给Thread的构造函数。
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
//3,开启四个线程。
t1.start();
t2.start();
t3.start();
t4.start();
}
}
-
使用同步函数
同步的另一种体现形式:同步函数。
同步函数使用的锁是哪个?
经过分析:大概猜的是this,因为函数必须被对象调用。
验证:
写一个同步代码块,写一个同步函数,如果同步代码块中的锁对象和同步函数中的锁对象是同一个,
就同步了,就没有错误的数据了。如果不是同一个锁对象,就不同步出现错误数据。
让两个线程,一个线程在同步代码块中执行,一个线程在同步函数中执行。
- 非静态同步函数使用的锁是this
class Ticket implements Runnable
{
private int tickets = 100;
private Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag){
while(true){
//验证同步函数使用的锁对象是this
synchronized(this){
if(tickets>0){
try{Thread.sleep(10);}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...obj..."+tickets--);//打印线程名称。
}
}
}
}
else{
while(true){
this.sale();
}
}
}
public synchronized void sale()//同步函数,使用的锁对象 this。
{
if(tickets>0)
{
try{Thread.sleep(10);}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...sale..."+tickets--);//打印线程名称。
}
}
}
class ThreadDemo4
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{Thread.sleep(10);}catch(InterruptedException e){}
//切换标记,之前,让主线程停一会,这时就只有一个t1线程在,它就会执行同步代码块。
t.flag = false;
t2.start();
}
}
- 静态同步函数使用的锁是字节码文件对象, 类名.class
class Ticket implements Runnable
{
private static int tickets = 100;
private Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag){
while(true){
synchronized(Ticket.class){
if(tickets>0){
try{Thread.sleep(10);}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...obj..."+tickets--);//打印线程名称。
}
}
}
}
else{
while(true){
this.sale();
}
}
}
public static synchronized void sale()//
{
if(tickets>0)
{
try{Thread.sleep(10);}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...sale..."+tickets--);//打印线程名称。
}
}
}
class ThreadDemo5
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{Thread.sleep(10);}catch(InterruptedException e){}
//切换标记,之前,让主线程停一会,这时就只有一个t1线程在,它就会执行同步代码块。
t.flag = false;
t2.start();
}
}
- 总结
总结:非静态同步函数使用的锁是this。
静态同步函数使用的锁是字节码文件对象, 类名.class
同步函数和同步代码块有什么区别吗?
同步函数使用的锁是固定的this或 类名.class。当线程任务只需要一个同步时完全可以使用同步函数。
同步代码块使用的锁可以是任意对象。当线程任务中需要多个同步时,必须通过锁来区分,这时必须使用同步代码块。
同步代码块较为常用。
synchronized的一点思考
在run方法上加上synchronized后变成同步函数,就是让整个程序变成单线程,降低了程序的效率,所以只在操作共享数据的代码加上synchronized就可以了。
class Ticket implements Runnable
{
//1,描述票的数量。
private int tickets = 100;
//2,售票的动作,这个动作需要被多线程执行,那就是线程任务代码。需要定义run方法中。
//线程任务中通常都有循环结构。
private Object obj = new Object();
public synchronized void run()
{
while(true)
{
if(tickets>0)
{
//要让线程在这里稍停,模拟问题的发生。sleep 看到了0 -1 -2 错误的数据,这就是传说中的多线程安全问题。
try{Thread.sleep(1);}catch(InterruptedException e){}
//打印线程名称。
System.out.println(Thread.currentThread().getName()+"....."+tickets--);
}
}
}
}
class ThreadDemo3
{
public static void main(String[] args)
{
//1,创建Runnable接口的子类对象。
Ticket t = new Ticket();
//2,创建四个线程对象。并将Runnable接口的子类对象作为参数传递给Thread的构造函数。
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
//3,开启四个线程。
t1.start();
t2.start();
t3.start();
t4.start();
}
}