如果有读者看了上一篇文章的栗子会出现线程安全的问题,如果还没有看上篇请点击多线程详解,这一篇是承接上一篇接着讲的。
那为什么会出现线程安全问题呢?原因是:由于一个线程在操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来了,导致共享数据存在了安全问题。解决的办法:必须让一个线程操作共享数据完毕后,其它线程才有机会参与共享数据的操作。这就出现了线程的同步机制。
同步代码块
synchronized(同步监视器){
//需要被同步的代码块(即为操作共享数据的代码)
}
1.共享数据:多个线程共同操作的同一个数据(变量)
2.同步监视器(锁):由任何类的对象来充当,那个线程获取此监视器,就执行大括号里被同步的代码。
3.要求所有线程必须共用同一把锁。
4.在实现Runnnable中,考虑同步可以使用this当锁。但是在继承Thread中慎用this当锁。
利用同步代码块修改实现Runnable上节栗子中出现安全问题
class SubThread implements Runnable{
int ticket=100;
Object obj=new Object();
public void run(){
Object obj=new Object();//局部变量,放进来的话就没效果
while (true){
synchronized (this){//当前对象,即为st
if(ticket>0){
try{
Thread.currentThread().sleep(1000);
}catch (Exception e){}
System.out.print(Thread.currentThread().getName()+"已售出票号:"+ticket--);
}
}
}
}
}
class TestThread{
public static void main(String []args){
SubThread st=new SubThread();
Thread th1=new Thread(st);
Thread th2=new Thread(st);
Thread th3=new Thread(st);
th1.start();
th2.start();
th3.start();
}
}
注意:在代码块中的锁的对象不能在run()方法里创建,否则3个线程都会创建锁。要求所有线程必须共用同一把锁。
利用同步代码块修改继承Thread上节栗子中出现安全问题
class SubThread extends Thread{
static int ticket=100;
static Object object=new Object();
public void run(){
while (true){
//synchronized (this){//当前对象,为st1,st2,st3。所以在继承中锁为this是没效果的,也会出现安全问题。改为
synchronized (Object){
if(ticket>0){
try{
Thread.currentThread().sleep(1000);
}catch (Exception e){}
System.out.print(Thread.currentThread().getName()+"已售出票号:"+ticket--);
}
}
}
}
}
class TestThread{
public static void main(String []args){
SubThread st1=new SubThread();
SubThread st2=new SubThread();
SubThread st3=new SubThread();
st1.start();
st2.start();
st3.start();
}
}
注意:在继承Thread中,慎用this。
同步方法
- 将操作(共享数据)的方法声明为synchronized,即此为同步方法,能够保证其中一个线程执行此方法时,
其它线程在外等待直至此线程完成。
- 同步方法的锁为:this。
利用同步方法修改实现Runnable上节栗子中出现安全问题
class SubThread implements Runnable{
int ticket=100;
public void run(){
while (true){
show(); //当前对象的show方法
}
}
public synchronized void show(){
if(ticket>0){
try{
Thread.currentThread().sleep(1000);
}catch (Exception e){}
System.out.print(Thread.currentThread().getName()+"已售出票号:"+ticket--);
}
}
}
class TestThread{
public static void main(String []args){
SubThread st=new SubThread();
Thread th1=new Thread(st);
Thread th2=new Thread(st);
Thread th3=new Thread(st);
th1.start();
th2.start();
th3.start();
}
}
不能利用同步方法修改继承Thread上节栗子中出现安全问题
class SubThread extends Thread{
static int ticket=100;
static Object object=new Object();
public void run(){
while (true){
//show(); //这种创建3个对象,也会出现安全问题
synchronized (object){
if(ticket>0){
try{
Thread.currentThread().sleep(1000);
}catch (Exception e){}
System.out.print(Thread.currentThread().getName()+"已售出票号:"+ticket--);
}
}
}
}
public synchronized void show(){ //this充当的锁,表示的是st1,st2,st3
,
if(ticket>0){
try{
Thread.currentThread().sleep(1000);
}catch (Exception e){}
System.out.print(Thread.currentThread().getName()+"已售出票号:"+ticket--);
}
}
}
class TestThread{
public static void main(String []args){
SubThread st1=new SubThread();
SubThread st2=new SubThread();
SubThread st3=new SubThread();
st1.start();
st2.start();
st3.start();
//解决此同步方法,相当于只创建一个对象
new Thread(st1).start();
new Thread(st1).start();
new Thread(st1).start();
}
}
注意:同步方法不能使用在继承Thread(在多个线程),因为上个例子中show()方法是this充当的锁。对于同步方法需要特别注意。
补充(单例懒汉式线程安全)
class Singleton{
private static Singleton instance=null;
public static Singleton getInstance(){
if(instance==null){
synchronized(Singleton.class){
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}
线程的死锁
产生的原因: 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。也就相当于分别都占用了对方的资源,并且双方都没有放弃。
解决的办法:需要专门的算法、原则。尽量减少同步资源的定义。
举个例子:
class TestThread{
static StringBuffer sb1=new StringBuffer();
static StringBuffer sb2=new StringBuffer();
public static void main(String []args){
new Thread(){
@Override
public void run() {
synchronized (sb1){
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
sb1.append("A");
synchronized (sb2){
sb2.append("B");
}
}
}
}.start();
new Thread(){
@Override
public void run() {
synchronized (sb2){
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
sb1.append("C");
synchronized (sb1){
sb2.append("D");
}
}
}
}.start();
}
}
如果哪些地方有疑问请在下方留言,下一篇讲解线程之间的通信 线程之间通信,附上我的github地址 悟静,欢迎各位Star;