代碼理解多線程(一)

 

多線程訪問會有什麼問題??

 

public class TestMyThread extends Thread{
    

    //共享變量
    private int a = 5;
    
    //重寫run方法
    @Override
    public  void  run(){        
        a--;
        System.out.println("a is :"+ a);    
    }
    
    public static void main(String[] args) {
        
        
        //實列需要多線程的對象
        TestMyThread myThread = new TestMyThread();
        
        
        //創建多個線程實列
        Thread t1 = new Thread(myThread,"t1");
        Thread t2 = new Thread(myThread,"t2");
        Thread t3 = new Thread(myThread,"t3");
        Thread t4 = new Thread(myThread,"t4");
        Thread t5 = new Thread(myThread,"t5");
        
        
        
        //運行
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        
        
    }
    
    
    
    
    
}

結果:

a is :3
a is :3
a is :2
a is :1
a is :0

a is :4
a is :3
a is :2
a is :1
a is :0

等等,你會發現每次出來的結果都不一樣,這就是多線程訪問的問題

 怎樣解決???

package test19218;

import test517designpattern.test;

/**
 * @Description
 * @Author zengzhiqiang
 * @Date 2019年2月18日
 */

public class TestMyThread extends Thread{
    

    //共享變量
    private int a = 5;
    
    //重寫run方法
    @Override
    public synchronized void  run(){        
        a--;
        System.out.println("a is :"+ a);    
    }
    
    public static void main(String[] args) {
        
        
        //實列需要多線程的對象
        TestMyThread myThread = new TestMyThread();
        
        //創建多個線程實列
        Thread t1 = new Thread(myThread,"t1");
        Thread t2 = new Thread(myThread,"t2");
        Thread t3 = new Thread(myThread,"t3");
        Thread t4 = new Thread(myThread,"t4");
        Thread t5 = new Thread(myThread,"t5");
        
        
        
        //運行
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        
        
    }
    
    
    
    
    
}

 

 我們加了synchronize關鍵字

a is :4
a is :3
a is :2
a is :1
a is :0

 結果是我們想要的。

那麼synchronize是鎖的對象myThread ,還是那一段代碼呢?

package test19218;

import test517designpattern.test;

/**
 * @Description
 * @Author zengzhiqiang
 * @Date 2019年2月18日
 */

public class TestMyThread extends Thread{
    
    //共享變量
    private int a = 5;
    
    //重寫run方法
    @Override
    public synchronized void  run(){        
        a--;
        System.out.println("a is :"+ a);    
    }
    
    public static void main(String[] args) {
        
        
        //實列需要多線程的對象
        TestMyThread myThread = new TestMyThread();
        TestMyThread myThread2 = new TestMyThread();
        
        //創建多個線程實列
        Thread t1 = new Thread(myThread,"t1");
        Thread t2 = new Thread(myThread2,"t2");
        
        //運行
        t1.start();
        t2.start();
        
    }
    
}

 

 結果

a is :4
a is :4

 說明鎖住的是代碼。如果是鎖住的對象,那麼就應該減兩次纔對!!!

 /**
 * 關鍵字synchronized取得的鎖都是對象鎖,而不是把一段代碼(方法)當做鎖,
 * 所以代碼中哪個線程先執行synchronized關鍵字的方法,哪個線程就持有該方法所屬對象的鎖(Lock),
 *
 * 在靜態方法上加synchronized關鍵字,表示鎖定.class類,類一級別的鎖(獨佔.class類)。
 * @author alienware
 *
 */

 如果同一個對象,兩個方法,一個用synchronize修飾,另一個不用,我訪問非synchronize修飾的方法是否需要等待,其實就是是否會阻塞?

package test19218;

/**
 * @Description
 * @Author zengzhiqiang
 * @Date 2019年2月18日
 */

public class TestMyThread02 {

    String tag;

    public TestMyThread02(String tag) {
        this.tag = tag;
    }

    public TestMyThread02() {
    }

    // 共享變量
    private int a = 5;

    // 方法
    public synchronized void deNum(String tag) throws InterruptedException {
        a--;
        Thread.sleep(1000);
        System.out.println(tag + "---a is :" + a);
    }

    // 方法
    public  void adNum(String tag) {
        a++;
        System.out.println(tag + "---a is :" + a);
    }

    public static void main(String[] args) {

        final String t1 = "t1";
        final String t2 = "t2";

        // 實列需要多線程的對象
        final TestMyThread02 myThread01 = new TestMyThread02();
        // final TestMyThread02 myThread02 = new TestMyThread02();

        // 創建多個線程實列
        Thread th = new Thread(new Runnable() {

            @Override
            public void run() {

                try {
                    myThread01.deNum(t1);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

            }
        });

        Thread th1 = new Thread(new Runnable() {

            @Override
            public void run() {

                myThread01.adNum(t2);

            }
        });

        // 運行
        th.start();
        th1.start();
    }

}

 

 結果

t2---a is :5
t1---a is :5

 t2先執行了,所以並不會阻塞。

如果兩個方法都加上synchronize呢?

package test19218;

/**
 * @Description
 * @Author zengzhiqiang
 * @Date 2019年2月18日
 */

public class TestMyThread02 {

    String tag;

    public TestMyThread02(String tag) {
        this.tag = tag;
    }

    public TestMyThread02() {
    }

    // 共享變量
    private int a = 5;

    // 方法
    public synchronized void deNum(String tag) throws InterruptedException {
        a--;
        Thread.sleep(1000);
        System.out.println(tag + "---a is :" + a);
    }

    // 方法
    public synchronized  void adNum(String tag) {
        a++;
        System.out.println(tag + "---a is :" + a);
    }

    public static void main(String[] args) {

        final String t1 = "t1";
        final String t2 = "t2";

        // 實列需要多線程的對象
        final TestMyThread02 myThread01 = new TestMyThread02();
        // final TestMyThread02 myThread02 = new TestMyThread02();

        // 創建多個線程實列
        Thread th = new Thread(new Runnable() {

            @Override
            public void run() {

                try {
                    myThread01.deNum(t1);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

            }
        });

        Thread th1 = new Thread(new Runnable() {

            @Override
            public void run() {

                myThread01.adNum(t2);

            }
        });

        // 運行
        th.start();
        th1.start();
    }

}

 

 t1---a is :4
t2---a is :5

 進行阻塞

所以,當同一個對象中的兩個方法有邏輯聯繫的時候,你其中一個加了synchronize,那麼你另一個也應該加上,才能保證返回結果的正確。

 

鎖對象改變問題

package test19218;

/**
 * @Description
 * @Author zengzhiqiang
 * @Date 2019年2月18日
 */

public class TestMyThread02 {

    private String tag = "tag";

    public TestMyThread02() {
    }

    // 共享變量
    private int a = 5;

    // 方法
    public void deNum() throws InterruptedException {

        synchronized (tag) {
            tag = "change tag";

            System.out
                    .println(Thread.currentThread().getName() + "sleep begin");
            Thread.sleep(6000);
            System.out.println(Thread.currentThread().getName() + "sleep over");
        }

    }

    // 方法
    public synchronized void adNum(String tag) {
        a++;
        System.out.println(tag + "---a is :" + a);
    }

    public static void main(String[] args) {

        final String t1 = "t1";
        final String t2 = "t2";

        // 實列需要多線程的對象
        final TestMyThread02 myThread01 = new TestMyThread02();
        // final TestMyThread02 myThread02 = new TestMyThread02();

        // 創建多個線程實列
        Thread th = new Thread(new Runnable() {

            @Override
            public void run() {

                try {
                    myThread01.deNum();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

            }
        }, t1);

        Thread th1 = new Thread(new Runnable() {

            @Override
            public void run() {

                try {
                    myThread01.deNum();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

            }
        }, t2);

        // 運行
        th.start();

        try {
            th1.sleep(100);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        th1.start();
    }

}

 

 th進入鎖以後改變對象,th1立刻就進入,沒有等t1執行完成

如果不改變鎖對象,那麼th1將等th執行完成後才進入。

 死鎖問題,

在設計程序時就應該避免雙方相互持有對方的鎖的情況

package com.bjsxt.base.sync006;

/**
 * 死鎖問題,在設計程序時就應該避免雙方相互持有對方的鎖的情況
 * @author alienware
 *
 */
public class DeadLock implements Runnable{

    private String tag;
    private static Object lock1 = new Object();
    private static Object lock2 = new Object();
    
    public void setTag(String tag){
        this.tag = tag;
    }
    
    @Override
    public void run() {
        if(tag.equals("a")){
            synchronized (lock1) {
                try {
                    System.out.println("當前線程 : "  + Thread.currentThread().getName() + " 進入lock1執行");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("當前線程 : "  + Thread.currentThread().getName() + " 進入lock2執行");
                }
            }
        }
        if(tag.equals("b")){
            synchronized (lock2) {
                try {
                    System.out.println("當前線程 : "  + Thread.currentThread().getName() + " 進入lock2執行");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("當前線程 : "  + Thread.currentThread().getName() + " 進入lock1執行");
                }
            }
        }
    }
    
    public static void main(String[] args) {
        
        DeadLock d1 = new DeadLock();
        d1.setTag("a");
        DeadLock d2 = new DeadLock();
        d2.setTag("b");
        
        Thread t1 = new Thread(d1, "t1");
        Thread t2 = new Thread(d2, "t2");
        
        t1.start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
    

    
}

 

同一對象屬性的修改不會影響鎖的情況

 

package com.bjsxt.base.sync006;
/**
 * 同一對象屬性的修改不會影響鎖的情況
 * @author alienware
 *
 */
public class ModifyLock {
    
    private String name ;
    private int age ;
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    
    public synchronized void changeAttributte(String name, int age) {
        try {
            System.out.println("當前線程 : "  + Thread.currentThread().getName() + " 開始");
            this.setName(name);
            this.setAge(age);
            
            System.out.println("當前線程 : "  + Thread.currentThread().getName() + " 修改對象內容爲: "
                    + this.getName() + ", " + this.getAge());
            
            Thread.sleep(2000);
            System.out.println("當前線程 : "  + Thread.currentThread().getName() + " 結束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        final ModifyLock modifyLock = new ModifyLock();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                modifyLock.changeAttributte("張三", 20);
            }
        },"t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                modifyLock.changeAttributte("李四", 21);
            }
        },"t2");
        
        t1.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
    
}

 

 使用synchronized代碼塊加鎖,比較靈活

 

package com.bjsxt.base.sync006;

/**
 * 使用synchronized代碼塊加鎖,比較靈活
 * @author alienware
 *
 */
public class ObjectLock {

    public void method1(){
        synchronized (this) {    //對象鎖
            try {
                System.out.println("do method1..");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public void method2(){        //類鎖
        synchronized (ObjectLock.class) {
            try {
                System.out.println("do method2..");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    private Object lock = new Object();
    public void method3(){        //任何對象鎖
        synchronized (lock) {
            try {
                System.out.println("do method3..");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    
    public static void main(String[] args) {
        
        final ObjectLock objLock = new ObjectLock();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                objLock.method1();
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                objLock.method2();
            }
        });
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                objLock.method3();
            }
        });
        
        t1.start();
        t2.start();
        t3.start();
        
        
    }
    
}

 

 synchronized代碼塊對字符串的鎖,注意String常量池的緩存功能

package com.bjsxt.base.sync006;
/**
 * synchronized代碼塊對字符串的鎖,注意String常量池的緩存功能
 * @author alienware
 *
 */
public class StringLock {

    public void method() {
        //new String("字符串常量")
        synchronized ("字符串常量") {
            try {
                while(true){
                    System.out.println("當前線程 : "  + Thread.currentThread().getName() + "開始");
                    Thread.sleep(1000);        
                    System.out.println("當前線程 : "  + Thread.currentThread().getName() + "結束");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String[] args) {
        final StringLock stringLock = new StringLock();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                stringLock.method();
            }
        },"t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                stringLock.method();
            }
        },"t2");
        
        t1.start();
        t2.start();
    }
}

 

 使用synchronized代碼塊減小鎖的粒度,提高性能

package com.bjsxt.base.sync006;

/**
 * 使用synchronized代碼塊減小鎖的粒度,提高性能
 * @author alienware
 *
 */
public class Optimize {

    public void doLongTimeTask(){
        try {
            
            System.out.println("當前線程開始:" + Thread.currentThread().getName() +
                    ", 正在執行一個較長時間的業務操作,其內容不需要同步");
            Thread.sleep(2000);
            
            synchronized(this){
                System.out.println("當前線程:" + Thread.currentThread().getName() +
                    ", 執行同步代碼塊,對其同步變量進行操作");
                Thread.sleep(1000);
            }
            System.out.println("當前線程結束:" + Thread.currentThread().getName() +
                    ", 執行完畢");
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        final Optimize otz = new Optimize();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                otz.doLongTimeTask();
            }
        },"t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                otz.doLongTimeTask();
            }
        },"t2");
        t1.start();
        t2.start();
        
    }
    
    
}

 

 多個addAndGet在一個方法內是非原子性的,需要加synchronized進行修飾,保證4個addAndGet整體原子性

package other;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Description
 * @Author zengzhiqiang
 * @Date 2019年2月19日
 */

public class AtomicTest {

    //變量爲atomic系列
    private  AtomicInteger num = new AtomicInteger(0);
    
    //多次自增 synchronized
    public   void addNum(){
     int i =    num.incrementAndGet();
     System.out.println("now  num is :" + i);
    
     num.incrementAndGet();
     num.incrementAndGet();
     num.incrementAndGet();
     num.incrementAndGet();
    
    }
    
    
    public static void main(String[] args) {
        
        
        //實列需要多線程對象
        final AtomicTest test  = new AtomicTest();
        
        
        //起多個線程運行
        for(int i = 0;i<=100;i++){
            
            //System.out.println("now time is : "+ i);
            Thread t = new Thread(new Runnable() {                
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    test.addNum();
                }
            });
            
            //啓動
            t.start();
        }
    }
    
}

 

結果:

now  num is :418
now  num is :433
now  num is :432

 結果是亂的,加上synchronize之後

    //多次自增 synchronized
    public  synchronized   void addNum(){
     int i =    num.incrementAndGet();
     System.out.println("now  num is :" + i);
    
     num.incrementAndGet();
     num.incrementAndGet();
     num.incrementAndGet();
     num.incrementAndGet();
    
    }

 now  num is :486
now  num is :491
now  num is :496
now  num is :501

 輸出結果總是一直且有序

 volatile關鍵字不具備synchronized關鍵字的原子性(同步)

 

package other;

import java.util.ArrayList;
import java.util.List;

import com.mysql.fabric.xmlrpc.base.Array;

/**
 * @Description
 * @Author zengzhiqiang
 * @Date 2019年2月19日
 */

public class VolatileTest {

    //變量
    private volatile int num  = 0;
    
    
    //變量的自增   synchronized
    public  void addNum(){
        num++;
        num++;
        System.out.println("now num is :" + num);
    }
    
    public static void main(String[] args) {
        
        //多線程實列對象
        final VolatileTest test =  new VolatileTest();
        
        //定義多線程列表
        List<Thread> list = new  ArrayList<Thread>();
        
        
        //初始化多個線程
        for(int i = 0;i<100;i++){
            Thread t = new Thread(new Runnable() {
                
                @Override
                public void run() {
                test.addNum();
                    
                }
            });
            
            list.add(t);
        }
        
        
        //啓動多線程
        for(Thread th:list){
            th.start();
        }
        
    }
    
}

 

 結果

now num is :188
now num is :188
now num is :186
now num is :186

 無序,顯示不正確,但是結果爲200,正確

使用synchronize修飾

    //變量的自增   synchronized
    public  synchronized  void addNum(){
        num++;
        num++;
        System.out.println("now num is :" + num);
    }

結果

now num is :194
now num is :196
now num is :198
now num is :200

 有序,顯示正確,且結果正確。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章