1 产生的原因:当某个线程在执行的过程中,尚未结束操作,其他线程参与进来。
2. 解决:当一个线程操作共享数据的时候,其他线程不能参与进来。
3.解决办法:
方法一:同步代码块。
synchronized (同步监视器){
//需要被同步的代码
}
class SubThread implements Runnable {
private int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (this) {
if (ticket > 0) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +"的票号为" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public static void main(String[] args) {
SubThread s1 = new SubThread();
Thread t1 = new Thread(s1);
Thread t2 = new Thread(s1);
Thread t3 = new Thread(s1);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
注意:①操作共享数据的代码,即为需要被同步的代码
②同步监视器,即:锁。任何一个类的随对象都可以作为锁。
要求:多个线程必须公用一把锁。所以需要注意声名的位置。
补充:在对于实现Runnable创建的多线程中,可以考虑使用this来作为同步监视器。
在对于继承Thread类来创建的而多线程中,慎用this作为同步监视器。
③注意使用synchronized的方式去同步包装同步代码块的时候,对于解决实现Runnable接口来创建的多线程
和继承Threaad类来创建多线程时候,对于共享数据的处理不一样。最主要的是判断锁是不是同一把。
方法二:同步方法。
1.同步方法任然涉及到同步监视器,只是不需要显示的申明。
2.非静态的同步方法,同步监视器是:this
静态的同步方法的同步监视器:当前类本身。
/**
*使用同步方法来解决实现Runnable接口的创建的多线程的安全问题。
*
*/
public class Test1 {
public static void main(String[] args) {
SubThread s1 = new SubThread();
Thread t1 = new Thread(s1);
Thread t2 = new Thread(s1);
Thread t3 = new Thread(s1);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
class SubThread1 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private synchronized void show() {
if (ticket > 0) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +"的票号为" + ticket);
ticket--;
}
}
}
/**
*使用同步方法来解决继承Thread创建的多线程的安全问题。
*
*/
public class Test2 {
public static void main(String[] args) {
SubThread2 s1 = new SubThread2();
SubThread2 s2 = new SubThread2();
SubThread2 s3 = new SubThread2();
s1.setName("窗口一");
s2.setName("窗口二");
s3.setName("窗口三");
s1.start();
s2.start();
s3.start();
}
}
class SubThread2 extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private static synchronized void show() {//同步监视器:SubThread2.class
//private synchronized void show() {//此时默认的锁时调用该方法的线程,因为通过
//继承Thread的方式创建的多线程,调用的时候
//每个线程都会调用该方法,所以锁不是唯一的
if (ticket > 0) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +"的票号为" + ticket);
ticket--;
}
}
解决线程安全问题方式三:Lock锁
/**
* 解决线程安全问题方式三:Lock锁
*
* java.util.concurrent.locks.Lock接口是控制对个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问
* ,每次只能有一个线程对lock对象加锁,线程在获取共享资源之前要先获得锁对象。
*
* 常用的实现类:ReentrantLock
*
* 1.实例化ReentrantLock
* 2.调用lock.lock();加锁
* 3.调用lock.unlock();解锁
*
* 问:synchronized和lock的区别?
* 同:都可以解决线程的安全问题。
* 不同:synchronized机制在执行相关的代码同步代码后,自动释放同步监视器。
* lock需要手动启动同步,同时结束时也需要手动实现。
*
* 问:如何解决线程安全问题?
*/
class Window implements Runnable {
private int ticket = 100;
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
//调用lock.lock();加锁
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ ":票号为 " + ticket);
ticket--;
} else {
break;
}
} finally {
//调用lock.unlock();解锁
lock.unlock();
}
}
}
}
public class TestLock2 {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
4.同步的方式,解决了线程的安全问题。
局限性:操作同步代码块的时候,只能有一个线程参与。其他线程等待,相当于一个单线程的过程,效率低。