Java多線程編程核心技術---對象及變量的併發訪問(二)

數據類型String的常量池特性

在JVM中具備String常量池緩存的功能。java

public class Service {
    public static void print(String str){
        try {
            synchronized (str) {
                while (true) {
                    System.out.println(Thread.currentThread().getName());
                    Thread.sleep(500);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class ThreadA extends Thread {
    @Override
    public void run() {
        Service.print("AA");
    }
}

public class ThreadB extends Thread {
    @Override
    public void run() {
        Service.print("AA");
    }
}
public class Test {
    public static void main(String[] args) {
        ThreadA a = new ThreadA();
        a.setName("A");
        ThreadB b = new ThreadB();
        b.setName("B");
        a.start();
        b.start();
    }
}

控制檯打印結果以下:編程

...
A
A
A
A
A
A
...

出現這種狀況就是由於Sting的兩個值都是AA,兩個線程持有相同的鎖,因此形成線程B不能執行。所以在大多數狀況下,同步synchronized代碼塊都不實用String做爲鎖對象,而改用其餘,好比new Object()實例化一個Object對象,但他並不放入緩存中。緩存


同步synchronized方法無限等待與解決
public class Service {
    synchronized public void methodA(){
        System.out.println("methodA begin...");
        boolean condition = true;
        while (condition) {
            
        }
        System.out.println("methodA end...");
    }
    
    synchronized public void methodB(){
        System.out.println("methodB begin...");
        System.out.println("methodB end...");
    }
}
public class ThreadA extends Thread {
    private Service service;
    
    public ThreadA(Service service) {
        super();
        this.service = service;
    }
    
    @Override
    public void run() {
        service.methodA();
    }
}

public class ThreadB extends Thread {
    private Service service;
    
    public ThreadB(Service service) {
        super();
        this.service = service;
    }
    
    @Override
    public void run() {
        service.methodB();
    }
}
public class Run {
    public static void main(String[] args) {
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        ThreadB b = new ThreadB(service);
        b.setName("B");
        a.start();
        b.start();
    }
}

控制檯打印結果以下:安全

methodA begin...

線程A處於死循環狀態,線程B永遠沒法拿到Service對象鎖而一直得不到運行。服務器

對Service對象作以下修改:多線程

public class Service {
    Object object1 = new Object();
    Object object2 = new Object();
    public void methodA() {
        synchronized (object1) {
            System.out.println("methodA begin...");
            boolean condition = true;
            while (condition) {

            }
            System.out.println("methodA end...");
        }
    }

    public void methodB() {
        synchronized (object2) {
            System.out.println("methodB begin...");
            System.out.println("methodB end...");
        }
    }
}

此時控制檯打印結果以下:app

methodA begin...
methodB begin...
methodB end...

methodA()和methodB()對不一樣的對象加鎖,因此線程A持有的鎖不會對線程B形成影響。異步


多線程的死鎖

Java線程死鎖是一個經典的多線程問題,由於不一樣的線程都在等待根本不可能被釋放的鎖,從而致使全部的任務都沒法繼續完成。ide

public class DeadThread implements Runnable {

    public String username;
    public Object lock1 = new Object();
    public Object lock2 = new Object();
    
    public void setFlag(String username) {
        this.username = username;
    }
    
    @Override
    public void run() {
        if (username.equals("a")) {
            synchronized (lock1) {
                try {
                    System.out.println("username=" + username);
                    Thread.sleep(3000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("按lock1->lock2代碼順序執行了");
                }
            }
        }
        if (username.equals("b")) {
            synchronized (lock2) {
                try {
                    System.out.println("username=" + username);
                    Thread.sleep(3000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("按lock2->lock1代碼順序執行了");
                }
            }
        }
    }

    public static void main(String[] args) {
        try {
            DeadThread t1 = new DeadThread();
            t1.setFlag("a");
            Thread thread1 = new Thread(t1);
            thread1.start();
            Thread.sleep(200);
            
            t1.setFlag("b");
            Thread thread2 = new Thread(t1);
            thread2.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

控制檯打印結果以下:性能

username=a
username=b

此時程序不結束,處於死鎖狀態。

能夠使用jps命令查看當前線程的id,而後使用jstack -l id來檢查是否存在死鎖。


內置類與靜態內置類
//內置類
public class PublicClass {
    private String username;
    private String password;
    
    class PrivateClass{
        private String age;
        private String address;
        public String getAge() {
            return age;
        }
        public void setAge(String age) {
            this.age = age;
        }
        public String getAddress() {
            return address;
        }
        public void setAddress(String address) {
            this.address = address;
        }
        public void printPublicProperty() {
            System.out.println(username + "-" + password);
        }
    }
    
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    
    public static void main(String[] args) {
        PublicClass publicClass = new PublicClass();
        publicClass.setUsername("admin");
        publicClass.setPassword("123456");
        System.out.println(publicClass.getUsername() + "-" + publicClass.getPassword());
        
        PrivateClass privateClass = publicClass.new PrivateClass();
        privateClass.setAddress("shanghai");
        privateClass.setAge("25");
        System.out.println(privateClass.getAddress() + "-" + privateClass.getAge());
        privateClass.printPublicProperty();
    }
}

控制檯打印結果以下:

admin-123456
shanghai-25
admin-123456
//靜態內置類
public class PublicClass {
    static String username;
    static String password;
    
    static class PrivateClass{
        private String age;
        private String address;
        public String getAge() {
            return age;
        }
        public void setAge(String age) {
            this.age = age;
        }
        public String getAddress() {
            return address;
        }
        public void setAddress(String address) {
            this.address = address;
        }
        public void printPublicProperty() {
            System.out.println(username + "-" + password);
        }
    }
    
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    
    public static void main(String[] args) {
        PublicClass publicClass = new PublicClass();
        publicClass.setUsername("admin");
        publicClass.setPassword("123456");
        System.out.println(publicClass.getUsername() + "-" + publicClass.getPassword());
        
        PrivateClass privateClass = new PrivateClass();
        privateClass.setAddress("shanghai");
        privateClass.setAge("25");
        System.out.println(privateClass.getAddress() + "-" + privateClass.getAge());
        privateClass.printPublicProperty();
    }
}

控制檯打印結果同上。


內置類與同步-實驗1
public class OutClass {
    static class Inner{
        public void method1() {
            synchronized ("其餘的鎖") {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + "i=" + i);
                    try {
                        Thread.sleep(100);
                    } catch (Exception e) {
                    }
                }
            }
        }
        
        public synchronized void method2() {
            for (int i = 11; i < 20; i++) {
                System.out.println(Thread.currentThread().getName() + "i=" + i);
                try {
                    Thread.sleep(100);
                } catch (Exception e) {
                }               
            }
        }
    }
    
    public static void main(String[] args) {
        final Inner inner = new Inner();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                inner.method1();
            }
        }, "A");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                inner.method2();
            }
        }, "B");
        t1.start();
        t2.start();
    }
}

控制檯打印結果以下:

Ai=0
Bi=11
Bi=12
Ai=1
Bi=13
Ai=2
Ai=3
Bi=14
Ai=4
Bi=15
Bi=16
Ai=5
Ai=6
Bi=17
Ai=7
Bi=18
Bi=19
Ai=8
Ai=9

因爲持有不一樣的對象監視器,因此打印結果是亂序的。

內置類與同步-實驗2
public class OutClass {
    static class InnerClass1{
        public void method1(InnerClass2 class2) {
            String threadName = Thread.currentThread().getName();
            synchronized (class2) {
                System.out.println(threadName + "進入InnerClass1的method1方法");
                for (int i = 0; i < 5; i++) {
                    System.out.println("i=" + i);
                    try {
                        Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(threadName + "離開InnerClass1的method1方法");
            }
        }
        
        public synchronized void method2() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + "進入InnerClass1的method2方法");
            for (int j = 0; j < 5; j++) {
                System.out.println("j=" + j);
                try {
                    Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            System.out.println(threadName + "離開InnerClass1的method2方法");
        }
    }
    
    static class InnerClass2{
        public synchronized void method1() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + "進入InnerClass2的method1方法");
            for (int k = 0; k < 5; k++) {
                System.out.println("k=" + k);
                try {
                    Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            System.out.println(threadName + "離開InnerClass2的method1方法");
        }
    }
    
    public static void main(String[] args) {
        final InnerClass1 class1 = new InnerClass1();
        final InnerClass2 class2 = new InnerClass2();
        Thread t1 = new Thread(new Runnable() {
            
            @Override
            public void run() {
                class1.method1(class2);
            }
        }, "T1");
        
        Thread t2 = new Thread(new Runnable() {
            public void run() {
                class1.method2();
            }
        }, "T2");
        
        Thread t3 = new Thread(new Runnable() {
            
            @Override
            public void run() {
                class2.method1();
            }
        }, "T3");
        
        t1.start();
        t2.start();
        t3.start();
    }
}

控制檯打印結果以下:

T2進入InnerClass1的method2方法
T1進入InnerClass1的method1方法
j=0
i=0
j=1
i=1
i=2
j=2
j=3
i=3
i=4
j=4
T2離開InnerClass1的method2方法
T1離開InnerClass1的method1方法
T3進入InnerClass2的method1方法
k=0
k=1
k=2
k=3
k=4
T3離開InnerClass2的method1方法

同步代碼塊synchronized (class2)對class2上鎖後,其餘線程只能以同步方式調用class2中的靜態同步方法。


對象鎖的改變
public class MyService {
    private String lock = "123";
    public void testMethod() {
        try {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + " begin " + System.currentTimeMillis());
                lock = "456";
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + " end " + System.currentTimeMillis());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public class ThreadA extends Thread {
    private MyService service;
    
    public ThreadA(MyService service) {
        super();
        this.service = service;
    }
    
    @Override
    public void run() {
        service.testMethod();
    }
}

public class ThreadB extends Thread {
    private MyService service;
    
    public ThreadB(MyService service) {
        super();
        this.service = service;
    }
    
    @Override
    public void run() {
        service.testMethod();
    }
}

public class Run1 {
    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        ThreadB b = new ThreadB(service);
        b.setName("B");
        a.start();
        Thread.sleep(100);
        b.start();
    }
}

k控制檯打印結果以下:

A begin 1465980925627
B begin 1465980925727
A end 1465980927627
B end 1465980927727

從打印結果看,A線程和B線程是以異步方式執行的,可見A線程與B線程持有的鎖不一樣。

將以上main方法中的代碼作以下修改:

public class Run1 {
    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        ThreadB b = new ThreadB(service);
        b.setName("B");
        a.start();
        //Thread.sleep(100);
        b.start();
    }
}

此時打印結果以下:

A begin 1465981162126
A end 1465981164127
B begin 1465981164128
B end 1465981166128

可見此時A線程和B線程是以同步方式執行的,A線程和B線程共同爭搶的鎖是「123」。

PS:只要對象不變,即便對象的屬性改變,運行的結果仍是同步的。將以上代碼作以下修改:

public class MyService {
    private StringBuilder lock = new StringBuilder("123");
    
    public void testMethod() {
        try {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + " begin " + System.currentTimeMillis());
                lock.append("456");
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + " end " + System.currentTimeMillis());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public class Run1 {
    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        ThreadB b = new ThreadB(service);
        b.setName("B");
        a.start();
        Thread.sleep(100);
        b.start();
    }
}

此時控制檯打印結果以下:

A begin 1465981411980
A end 1465981413980
B begin 1465981413980
B end 1465981415981

可見線程A和線程B是以同步方式執行的。


volatile關鍵字
關鍵字volatile與死循環

死循環例子

public class PrintString {
    private boolean isContinuePrint = true;
    public boolean isContinuePrint() {
        return isContinuePrint;
    }
    public void setContinuePrint(boolean isContinuePrint) {
        this.isContinuePrint = isContinuePrint;
    }
    public void printStringMethod() {
        try {
            while (isContinuePrint) {
                System.out.println("printStringMethod is running...threadName=" + Thread.currentThread().getName());
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        PrintString printString = new PrintString();
        printString.printStringMethod();
        System.out.println("中止線程...");
        printString.setContinuePrint(false);
    }
}

控制檯打印結果以下:

printStringMethod is running...threadName=main
printStringMethod is running...threadName=main
printStringMethod is running...threadName=main
printStringMethod is running...threadName=main
printStringMethod is running...threadName=main
......

main線程在printString.printStringMethod()中陷入了死循環,後面的printString.setContinuePrint(false)得不到執行。

解決同步死循環

修改上面的代碼

public class PrintString implements Runnable {
    private boolean isContinuePrint = true;
    public boolean isContinuePrint() {
        return isContinuePrint;
    }
    public void setContinuePrint(boolean isContinuePrint) {
        this.isContinuePrint = isContinuePrint;
    }
    public void printStringMethod() {
        try {
            while (isContinuePrint) {
                System.out.println("printStringMethod is running...threadName=" + Thread.currentThread().getName());
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public void run() {
        printStringMethod();
    }

    public static void main(String[] args) throws InterruptedException {
        PrintString printString = new PrintString();
        new Thread(printString).start();
        Thread.sleep(5000);
        System.out.println("中止線程...");
        printString.setContinuePrint(false);
    }
}

此時控制檯打印結果以下:

printStringMethod is running...threadName=Thread-0
printStringMethod is running...threadName=Thread-0
printStringMethod is running...threadName=Thread-0
printStringMethod is running...threadName=Thread-0
printStringMethod is running...threadName=Thread-0
中止線程...

此時main線程設置isContinuePrint=false,能夠使另外一個線程中止執行。

++注:《Java多線程編程核心技術》P120講將上面的代碼運行在-server服務器模式中的64bit的JVM上時,會出現死循環。實際測試並未出現死循環,暫未弄清緣由。++

解決異步死循環
package com.umgsai.thread22;

public class RunThread extends Thread {
    volatile private boolean isRunning = true;
    public boolean isRunning() {
        return isRunning;
    }
    public void setRunning(boolean isRunning) {
        this.isRunning = isRunning;
    }
    
    @Override
    public void run() {
        System.out.println("進入run方法...");
        while (isRunning) {
            System.out.println("running....");
            try {
                Thread.sleep(500);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.println("線程被中止...");
    }
    
    public static void main(String[] args) {
        try {
            RunThread runThread = new RunThread();
            runThread.start();
            Thread.sleep(1000);
            runThread.setRunning(false);
            System.out.println("已將isRunning設置爲false");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

控制檯打印結果以下:

進入run方法...
running....
running....
已將isRunning設置爲false
線程被中止...

==使用volatile關鍵字強制從公共內存中讀取變量==。

使用volatile關鍵字增長了實例變量在多個線程之間的可見性,可是volatile關鍵字不支持原子性。

synchronized和volatile的比較

  1. 關鍵字volatile是線程同步的輕量級實現,性能比synchronized好。volatile只能修飾變量,synchronized能夠修飾方法和代碼塊。
  2. 多線程訪問volatile不會發生阻塞,synchronized會出現阻塞。
  3. volatile能保證數據的可見性,但不能保證原子性。synchronized能夠保證原子性,也能夠間接保證可見性,由於它會將私有內存和公共內存中的數據作同步。
  4. 關鍵字volatile解決的是變量在多個線程之間的可見性,synchronized解決的是多個線程之間訪問資源的同步性。
volatile的非原子性
public class VolatileTest extends Thread {

    volatile public static int count;
    private static void addCount(){
        for (int i = 0; i < 100; i++) {
            count++;
        }
        System.out.println("count=" + count);
    }
    
    @Override
    public void run() {
        addCount();
    }
    
    public static void main(String[] args) {
        VolatileTest[] volatileTests = new VolatileTest[100];
        for (int i = 0; i < 100; i++) {
            volatileTests[i] = new VolatileTest();
        }
        for (int i = 0; i < 100; i++) {
            volatileTests[i].start();
        }
    }
}

控制檯打印結果以下:

......
count=5332
count=5232
count=5132
count=5032
count=4932
count=4854
count=4732
count=4732

將以上代碼中的addCount方法加上synchronized關鍵字

public class VolatileTest extends Thread {

    volatile public static int count;
    //必定要加static關鍵字,這樣synchronized與static鎖的內容就是VolatileTest類了,也就達到同步效果了。
    synchronized private static void addCount(){
        for (int i = 0; i < 100; i++) {
            count++;
        }
        System.out.println("count=" + count);
    }
    
    @Override
    public void run() {
        addCount();
    }
    
    public static void main(String[] args) {
        VolatileTest[] volatileTests = new VolatileTest[100];
        for (int i = 0; i < 100; i++) {
            volatileTests[i] = new VolatileTest();
        }
        for (int i = 0; i < 100; i++) {
            volatileTests[i].start();
        }
    }
}

此時控制檯打印結果以下:

......
count=9300
count=9400
count=9500
count=9600
count=9700
count=9800
count=9900
count=10000
使用原子類進行i++操做

i++操做除了使用synchronized關鍵字同步外,還能夠使用AtomicInteger原子類實現。

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerTest extends Thread {
    private AtomicInteger count = new AtomicInteger(0);
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(count.incrementAndGet());
        }
    }
    
    public static void main(String[] args) {
        AtomicIntegerTest atomicIntegerTest = new AtomicIntegerTest();
        Thread t1 = new Thread(atomicIntegerTest);
        Thread t2 = new Thread(atomicIntegerTest);
        Thread t3 = new Thread(atomicIntegerTest);
        Thread t4 = new Thread(atomicIntegerTest);
        Thread t5 = new Thread(atomicIntegerTest);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

控制檯打印結果以下:

......
4992
4993
4994
4995
4996
4997
4998
4999
5000

5個線程成功累加到5000。

原子類也並不徹底安全
public class MyService {
    public static AtomicLong atomicLong = new AtomicLong();
    public void addNum() {
        System.out.println(Thread.currentThread().getName() + " 加了100以後是:" + atomicLong.addAndGet(100));
        atomicLong.addAndGet(1);
    }
}

public class MyThread extends Thread {
    private MyService myService;
    public MyThread(MyService myService) {
        this.myService = myService;
    }
    
    @Override
    public void run() {
        myService.addNum();
    }
}

public class Run {
    public static void main(String[] args) {
        try {
            MyService myService = new MyService();
            MyThread[] array = new MyThread[100];
            for (int i = 0; i < array.length; i++) {
                array[i] = new MyThread(myService);
            }
            for (int i = 0; i < array.length; i++) {
                array[i].start();;
            }
            Thread.sleep(1000);
            System.out.println(myService.atomicLong.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

控制檯打印結果以下:

......
Thread-89 加了100以後是:8987
Thread-96 加了100以後是:9493
Thread-80 加了100以後是:9594
Thread-94 加了100以後是:9695
Thread-97 加了100以後是:9796
Thread-95 加了100以後是:9896
Thread-98 加了100以後是:9998
Thread-99 加了100以後是:10098
10100

累加的結果是正確的,可是打印順序的錯的,這是由於雖然addAndGet方法是原子的,可是方法和方法之間的調用卻不是原子的。

對以上代碼作以下修改:

public class MyService {
    public static AtomicLong atomicLong = new AtomicLong();
    synchronized public void addNum() {
        System.out.println(Thread.currentThread().getName() + " 加了100以後是:" + atomicLong.addAndGet(100));
        atomicLong.addAndGet(1);
    }
}

此時控制檯打印結果以下:

......
Thread-86 加了100以後是:9392
Thread-87 加了100以後是:9493
Thread-88 加了100以後是:9594
Thread-92 加了100以後是:9695
Thread-93 加了100以後是:9796
Thread-94 加了100以後是:9897
Thread-95 加了100以後是:9998
Thread-98 加了100以後是:10099
10100

此時線程以同步方式執行addNum方法,每次先加100再加1.

synchronized代碼塊有volatile同步的功能

關鍵字synchronized能夠使多個線程訪問同一個資源具備同步性,並且還能夠將線程工做內存中的私有變量與公共內存中的變量進行同步。

public class Service {
    private boolean isContinueRun = true;
    public void runMethod() {
        while (isContinueRun) {
            
        }
        System.out.println("stop...");
    }
    
    public void stopMethod() {
        isContinueRun = false;
    }
}
public class ThreadA extends Thread {
    private Service service;
    public ThreadA(Service service) {
        this.service = service;
    }
    
    @Override
    public void run() {
        service.runMethod();
    }
}

public class ThreadB extends Thread {
    private Service service;
    public ThreadB(Service service) {
        this.service = service;
    }
    
    @Override
    public void run() {
        service.stopMethod();
    }
}
public class Run {
    public static void main(String[] args) {
        try {
            Service service = new Service();
            ThreadA a = new ThreadA(service);
            a.start();
            Thread.sleep(1000);
            ThreadB b = new ThreadB(service);
            b.start();
            System.out.println("已經發起中止命令了");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

運行main,控制檯打印結果以下:

已經發起中止命令了

出現死循環。

對Service類作以下修改:

public class Service {
    private boolean isContinueRun = true;
    public void runMethod() {
        String anyString = new String();
        while (isContinueRun) {
            synchronized (anyString) {
                
            }
        }
        System.out.println("stop...");
    }
    
    public void stopMethod() {
        isContinueRun = false;
    }
}

此時控制檯打印結果以下:

已經發起中止命令了
stop...

關鍵字synchronized能夠保證在同一時刻,只有一個線程能夠執行某一個方法或某一個代碼塊。它包含兩個特徵:互斥性和可見性。同步synchronized不只能夠解決一個線程看到對象處於不一致的狀態,還能夠保證進入同步方法或者同步代碼塊的每一個線程都看到由同一個鎖保護以前全部的修改結果。

相關文章
相關標籤/搜索