(一)线程安全问题诱因
- 存在有共享数据
- 存在有多个线程访问这些共享数据
解决方法:同一时刻共享数据只能由一个线程访问,其他线程要等到该线程处理完共享数据后才能对该线程进行操作
public class Ticket{
int ticket=0;
}
public class User implements Runnable(){
Ticket tk;
public User(Ticket tk){
this.tk=tk;
}
public run(){
for(int i=0;i<50;i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程买了"+tk.ticket+"号票");
tk.ticket--;
}
}
}
public class Test{
Ticket tk=new Ticket();
User user=new User();
new Thread(user,"用户A").start();
new Thread(user,"用户B").start();
}
如图:
出现了线程错误
(二)线程锁-synchronized
线程锁的两个特性
- 互斥性:同一时间只能有一个对象持有线程锁,通过这种特性实现多线程的协调机制。同一时间只有一个线程对需要同步的代码块进行访问,也成为原子性
- 可见性:必须确保在锁释放之前,其对于共享变量的修改对下一个获得该锁的线程是可见的,否则另一个线程可能是在本地缓存的某个副本上进行,从而引起不一致
锁的不是代码,而是对象
1.对象锁
获取对象锁的两种方法:同步代码块,同步非静态方法
同步代码块:synchronized(this),synchronized(类实例对象)
同步非静态方法:synchronized(method)
同步代码块
代码示例:
public class Ticket{
int ticket=0;
}
public class User implements Runnable(){
Ticket tk;
public User(Ticket tk){
this.tk=tk;
}
public run(){
for(int i=0;i<50;i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Synchronized(tk){ //**使用对象锁**
System.out.println(Thread.currentThread().getName()+"线程买了"+tk.ticket+"号票");
tk.ticket--;
}
}
}
public class Test{
Ticket tk=new Ticket();
User user=new User();
new Thread(user,"用户A").start();
new Thread(user,"用户B").start();
}
tk即为需要访问的对象,使用synchronized来对tk对象上锁。使得同一时间只有一个User对象访问tk,而另一个User只能在锁被释放后访问。
即使用synchronized(tk)来同步代码块
此时我们可以的得到正确的访问顺序:
synchronized锁住的必须为同一个对象,而不能是不同的对象;
同步非静态方法
方法示例:
public class Ticket{
int ticket=0;
}
public class User implements Runnable(){
Ticket tk;
public User(Ticket tk){
this.tk=tk;
}
public run(){
for(int i=0;i<50;i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ticket();
}
}
public synchronized void ticket(){ //**同步非静态方法**
System.out.println(Thread.currentThread().getName()+"购买了"+tk.ticket+"号票");
tk.ticket--;
}
public class Test{
Ticket tk=new Ticket();
User user=new User();
new Thread(user,"用户A").start();
new Thread(user,"用户B").start();
}
同样使用public synchronized void ticket()来同步非静态方法也可以实现线程锁
2.类锁
同步代码块(synchronized (类Class) )
代码示例:
public class User implements Runnable(){
Ticket tk;
public User(Ticket tk){
this.tk=tk;
}
public run(){
for(int i=0;i<50;i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Synchronized(Ticket.class){ //**使用类锁**
System.out.println(Thread.currentThread().getName()+"线程买了"+tk.ticket+"号票");
tk.ticket--;
}
}
}
即对于Ticketk类整体使用类锁
synchronized(Ticket.class){
}
类锁可以使同一时间只有一个线程持有此类锁
同步静态方法(synchronized static method)
代码示例:
public class Ticket{
static int ticket=0;
public synchronized static void ticket(){ //同步静态方法
System.out.println(Thread.currentThread().getName()+"线程买了"+tk.ticket+"号票");
tk.ticket--;
}
}
public class User implements Runnable(){
Ticket tk;
public User(Ticket tk){
this.tk=tk;
}
public run(){
for(int i=0;i<50;i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
tk.ticket();
}
}
}
public class Test{
Ticket tk=new Ticket();
User user=new User();
new Thread(user,"用户A").start();
new Thread(user,"用户B").start();
}
即对Ticket类中的静态方法进行同步
public synchronized static void method()
(三)对象锁与类锁的总结
- 线程访问该对象的同步代码块时,其他线程可以访问该线程的非同步代码块
- 若锁住的是对象,则当一个线程访问该对象的同步代码块(非静态方法),其它线程不能访问这个对象的同步代码块(非静态方法)
- 若锁住的是对象,则当一个线程访问该对象的同步代码块(非静态方法),其它线程不能访问这个对象的非静态方法(同步代码块)
- 若锁住的是类,则当一个线程访问该类的同步代码块(静态方法),其它线程不能访问这个类的同步代码块(静态方法)
- 若锁住的是类,则当一个线程访问该类的同步代码块(静态方法),其它线程不能访问这个类的静态方法(同步代码块)
- 同一个类不同的对象锁互不干扰
- 类锁和对象锁互不干扰
- 同一个类的不同对象使用类锁是也会同步