zoukankan      html  css  js  c++  java
  • Java多线程编程核心技术---学习分享

    继承Thread类实现多线程

    public class MyThread extends Thread {
    
        @Override
        public void run() {
            super.run();
            System.out.println("MyThread...");
        }
        
        public static void main(String[] args) {
            MyThread myThread = new MyThread();
            myThread.start();
            System.out.println("运行结束..");
        }
    }
    

    运行结果如下:

    运行结束..
    MyThread...
    

    实现Runnable接口实现多线程

    如果创建的线程类已经有一个父类了,就不能再集成Thread类,因为Java不支持多继承,这时就需要实现Runnable接口。

    public class MyRunnable implements Runnable {
    
        @Override
        public void run() {
            System.out.println("MyRunnable is running...");
        }
        
        public static void main(String[] args) {
            Runnable runnable = new MyRunnable();
            Thread thread = new Thread(runnable);
            thread.start();
            System.out.println("运行结束..");
        }
    }
    

    运行结果如下:

    运行结束..
    MyRunnable is running...
    

    实例变量与线程安全

    1. 不共享数据的情况
    public class MyThread3 extends Thread {
        private int count = 5;
        public MyThread3(String name){
            super();
            this.setName(name);
        }
        
        @Override
        public void run() {
            super.run();
            while (count > 0) {
                count--;
                System.out.println("执行者:" + this.currentThread().getName() + ",count=" + count);
            }
        }
        
        public static void main(String[] args) {
            MyThread3 t1 = new MyThread3("A");
            MyThread3 t2 = new MyThread3("B");
            MyThread3 t3 = new MyThread3("C");
            t1.start();
            t2.start();
            t3.start();
        }
    }
    

    某一次运行结果如下:

    执行者:B,count=4
    执行者:B,count=3
    执行者:B,count=2
    执行者:C,count=4
    执行者:A,count=4
    执行者:A,count=3
    执行者:C,count=3
    执行者:C,count=2
    执行者:B,count=1
    执行者:B,count=0
    执行者:C,count=1
    执行者:A,count=2
    执行者:A,count=1
    执行者:A,count=0
    执行者:C,count=0
    
    1. 共享数据的情况
    public class MyThread4 extends Thread {
        private int count = 5;
    
        @Override
        public void run() {
            super.run();
            count--;
            System.out.println("执行者:" + Thread.currentThread().getName() + ",count=" + count);
        }
    
        public static void main(String[] args) {
            MyThread4 myThread = new MyThread4();
            Thread t1 = new Thread(myThread, "A");
            Thread t2 = new Thread(myThread, "B");
            Thread t3 = new Thread(myThread, "C");
            Thread t4 = new Thread(myThread, "D");
            Thread t5 = new Thread(myThread, "E");
            t1.start();
            t2.start();
            t3.start();
            t4.start();
            t5.start();
        }
    }
    

    某一次的运行结果如下:

    执行者:A,count=3
    执行者:D,count=1
    执行者:E,count=2
    执行者:B,count=3
    执行者:C,count=0
    

    i--分成如下三步:

    取得原有的i值

    计算i-1

    对i进行赋值


    synchronized同步方法

    方法中的变量不存在非线程安全问题,永远都是线程安全的。这是方法内部的变量是私有的特性造成的。

    如果多个线程共同访问一个对象中的实例变量,则有可能出现“非线程安全”问题。用线程访问的对象中如果有多个实例变量,则运行的结果有可能出现交叉的情况。
    package com.umgsai.thread.thread73;

    多个对象多个锁

    public class Run {
        public static void main(String[] args) {
            Service service1 = new Service();
            Service service2 = new Service();
            ThreadA threadA = new ThreadA(service1);
            threadA.start();
            ThreadB threadB = new ThreadB(service2);
            threadB.start();
        }
    }
    

    上面的代码是两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,效果却是以异步方式运行的。synchronized关键字取得的锁是对象锁,而不是把一段代码或方法当做锁,所以在以上的代码中,哪个线程先执行带synchronized关键字的方法,哪个线程就持有改方法所属对象的锁Lock,那么其他线程只能处于等待状态,前提是多个线程访问的是同一个对象。如果多个线程访问多个对象,则JVM会创建多个锁。


    synchronized方法的弊端

    同步方法里的长任务。

    package com.umgsai.thread.thread74;

    当一个线程访问object的一个synchronized同步代码块时,另一个线程仍然可以访问该object对象中的非synchronized(this)同步代码块。

    package com.umgsai.thread.thread74;

    synchronized同步方法

    1. 对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态。
    2. 同一时间只有一个线程可以执行synchronized同步方法中的代码。

    synchronized(this)同步代码块

    1. 对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态。
    2. 同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码。

    数据类型String的常量池特性

    package com.umgsai.thread.thread76;

    出现这种情况就是因为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...");
    		}
    	}
    }
    
    

    此时控制台打印结果如下:

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

    methodA()和methodB()对不同的对象加锁,所以线程A持有的锁不会对线程B造成影响。

    多线程死锁

    package com.umgsai.thread.thread17

    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();
    		}
    	}
    }
    

    可以使用jps命令查看当前线程的id,然后使用jstack -l id来检查是否存在死锁。


    对象锁的改变

    package com.umgsai.thread.thread21;

    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();
        }
    }
    

    关键字volatile与死循环

    package com.umgsai.thread.thread22

    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);
        }
    }
    

    以上同步代码出现死循环,无法停止

    public class NewPrintString 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 {
        	NewPrintString printString = new NewPrintString();
            new Thread(printString).start();
            Thread.sleep(5000);
            System.out.println("停止线程...");
            printString.setContinuePrint(false);
        }
    }
    

    注:《Java多线程编程核心技术》P120讲将上面的代码运行在-server服务器模式中的64bit的JVM上时,会出现死循环。实际测试并未出现死循环,暂未弄清原因。

    解决可能出现的死循环

    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();
            }
        }
    }
    

    使用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
    

    使用synchronized关键字
    ·```java
    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++操作
    
    ```java
    package com.umgsai.thread.thread23;
    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
    

    原子类也并不完全安全

    package com.umgsai.thread.thread24
    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方法是原子的,但是方法和方法之间的调用却不是原子的。

    解决方法:加上synchronized即可。

    关键字synchronized可以使多个线程访问同一个资源具有同步性,而且还可以将线程工作内存中的私有变量与公共内存中的变量进行同步。

    package com.umgsai.thread.thread25;
    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();
            }
        }
    }
    

    出现死循环。

    对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;
        }
    }
    

    关键字synchronized可以保证在同一时刻,只有一个线程可以执行某一个方法或某一个代码块。它包含两个特征:互斥性和可见性。同步synchronized不仅可以解决一个线程看到对象处于不一致的状态,还可以保证进入同步方法或者同步代码块的每个线程都看到由同一个锁保护之前所有的修改结果。

  • 相关阅读:
    2018-4-17-软件设计-白话依赖注入
    2018-2-13-wpf-PreviewTextInput-在鼠标输入获得-_u0003
    2018-5-23-为何-987654321_123456789-的值是-8.0000000729
    寄存器位写操作
    Linux多IP配置
    Kconfig和Makefile
    linux设置网卡速率
    Winmanager,NERDTree和MiniBufExplorer
    SuperTab
    ping
  • 原文地址:https://www.cnblogs.com/umgsai/p/5884876.html
Copyright © 2011-2022 走看看