• 22.多线程.md


    1.线程的创建和启动

    1.1继承Thread类创建线程

    package com.liyue.studycode.thread;
     
    public class FirstThread extends Thread {
        private int sum = 0;
        public void run(){
            for(; sum<100; sum++){
                System.out.println(this.getName() + "" + sum);
            }
        }
    }
    
    
    package com.liyue.studycode.thread;
     
    public class ThreadPrint {
     
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            System.out.println("主线程: " + Thread.currentThread().getName());
             
            for(int i=0; i<100; i++){
                System.out.println(Thread.currentThread().getName() + " " + i);
                if(i == 90){
                    //definition first thread
                    new FirstThread().start();
                    //definition second thread named H
                    Thread t = new FirstThread();
                    t.setName("H");
                    t.start();
                }
            }
        }
     
    }
    

    1.2继承Runnable接口实现创建线程

    package com.liyue.studycode.thread;
     
    public class SecondThread implements Runnable {
        private int sum = 0;
         
        @Override
        public void run() {
            for(; sum<10; sum++){
                //can not use 'this.getName()' 
                System.out.println(Thread.currentThread().getName() + "" + sum);
            }
             
        }
     
    }
    
    
    package com.liyue.studycode.thread;
     
    public class ThreadPrint {
     
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            System.out.println("主线程: " + Thread.currentThread().getName());
             
            for(int i=0; i<20; i++){
                System.out.println(Thread.currentThread().getName() + " " + i);
                if(i == 10){
                    //definition class SecondThread
                    SecondThread t = new SecondThread();
                    //
                    new Thread(t, "one").start();
                }
            }
        }
     
    }
    

    1.3使用Callable和Future创建

    package com.liyue.studycode.thread;
     
    import java.util.concurrent.Callable;
    import java.util.concurrent.FutureTask;
     
    public class ThreadPrint {
     
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            System.out.println("主线程: " + Thread.currentThread().getName());
            /*pack all object to thread*/
            //create a new object
            ThreadPrint p = new ThreadPrint();
            //use Lambda create Callable<String>
            //use FutureTask<String> pack Callable<String>
            FutureTask<String> f = new FutureTask<String>((Callable<String>)()->{
                int sum = 0;
                for(; sum<20; sum++){
                    System.out.println(Thread.currentThread().getName() + "" + sum);
                }
                //
                System.out.println("子线程执行完成");
                return sum+"";
            });
             
            for(int i=0; i<30; i++){
                System.out.println("主线程: " + Thread.currentThread().getName() + " " + i);
                if(i == 10){
                    new Thread(f, "有返回值的线程").start();
                }
            }
             
            //
            try {
                System.out.println("子线程的返回值:" + f.get());
            } catch (Exception e) {
                e.printStackTrace();
            }
             
        }
     
    }
    

    下面是一个拆分的例子:

    package per.liyue.code.theradtest;
    import java.util.concurrent.Callable;
    public class CallableClass implements Callable<Integer>{
        private int age;
        
        public CallableClass(Integer age) {
            // TODO Auto-generated constructor stub
            this.age = age;
        }
        
        @Override
        public Integer call() throws Exception {
            // TODO Auto-generated method stub
            age += age;
            return this.age;
        }
    }  
    
    
    
    
    //...
    public static void main(String[] args) {
            // TODO Auto-generated method stub
            
            //Thread
            //ThreadClass t = new ThreadClass();
            //t.start();
            
            //Runnable
            //RunnableClass r = new RunnableClass();
            //new Thread(r).start();
        
            //FutureTask
            //先创建一个Callable对象
            CallableClass c = new CallableClass(15);
            //再创建一个FutureTask来接收Callable对象
            FutureTask<Integer> f = new FutureTask<>(c);
            //执行函数,本质还是Callable对象来执行
            new Thread(f, "有返回值的线程").start();
            //获取返回值
    
            try {
                System.out.println("Callable的返回值:" + f.get()); 
            } catch (Exception e) {
                // TODO: handle exception
            }
            
        }  
    
    

    2.控制线程

    2.1 Thread类的join方法

    使用当线程类调用join方法时候,此线程优先执行,正在执行的线程暂停等待此线程执行完成后继续:

    package per.liyue.code.theradtest;
    public class ThreadClass extends Thread {
        @Override
        public void run() {
            // TODO Auto-generated method stub
            for (int i = 0; i < 10; i++) {
                System.out.println("ThreadClass Number 1 to 10:" + i);
            }
        }
    }  
    
    
    package per.liyue.code.theradtest;
    import java.util.concurrent.FutureTask;
    public class MainPrint {
        public static void main(String[] args) throws InterruptedException {
            // TODO Auto-generated method stub       
            for (int i = 0; i < 5; i++) {
                System.out.println("main num:" + i);
                if(i == 2){
                    ThreadClass t = new ThreadClass();
                    t.start();
                    //调用join后,主线程暂停执行。等待t执行完成后才能继续
                  t.join();
                }            
            }     
        }
    }
    

    2.2后台线程

    • 调用Thread的setDaemon方法,可以是得对应线程类成为后台线程
    • 调用方法setDaemon,必须在调用start方法之前
    • 后台线程和前台线程没有明显的区别,但是可以调用isDaemon来判断是否是后台线程
    • 前台线程死亡后后台线程也会死亡
    package per.liyue.code.theradtest;
    public class DeamonThread extends Thread{
        @Override
        public void run() {
            // TODO Auto-generated method stub
            for (int i = 0; i < 100; i++) {
                System.out.println("Deamon num:" + i);
            }
        }    
    }  
    
    
    package per.liyue.code.theradtest;
    import java.util.concurrent.FutureTask;
    public class MainPrint {
        public static void main(String[] args) throws InterruptedException {
            // TODO Auto-generated method stub
            
            DeamonThread d = new DeamonThread();
            d.setDaemon(true);
            d.start();
           //main主线程执行完成后d就结束了,不会再继续
    
            for (int i = 10; i > 0; i--) {
                System.out.println("关闭倒计时:" + i);
            }
        }
    }  
    
    

    2.3线程的睡眠和让步

    • 线程的睡眠通过Thread的sleep方法实现;
    • 线程的让步通过Thread的yield方法实现,不过现在不推荐使用

    3.线程同步

    Demo:售票,有一个票仓库。两个线程同时卖票。

    package per.liyue.code.threadtest;
    import javax.xml.crypto.dsig.keyinfo.RetrievalMethod;
    public class TicketDb {
        //总的票数
        private Integer totalTicketNum;
        
        //初始化总的票数
        public TicketDb(Integer totalNum){
            totalTicketNum = totalNum;
        }
        
        private TicketDb(){
            
        }
        public Integer getTotalTicketNum() {
            return totalTicketNum;
        }
        
        //希望卖的票数
        public Integer setTotalTicketNum(Integer SellTicketNum) throws NullPointerException{
            //有余票则售出
            if(0 < (this.totalTicketNum-SellTicketNum)){
                this.totalTicketNum -= SellTicketNum;
                return SellTicketNum;
            }
            //卖出剩余票
            else if((0 < (this.totalTicketNum-totalTicketNum)) &&
                    (0 < this.totalTicketNum)){
                Integer returnNum = this.totalTicketNum;
                this.totalTicketNum = 0;
                return returnNum;
            }
            //否则返回没票了
            else {
                return 0;
            }
        }  
    }
    
    package per.liyue.code.threadtest;
    import java.util.Random;
    public class SellTicketThread implements Runnable {
        //票库对象
        private TicketDb tickdb;
        
        public void setTickdb(TicketDb tickdb) {
            this.tickdb = tickdb;
        }
        
        //模拟要买的票数,假设每次限购最多10张票
        public Integer RandomTicketNum(){
            Random random = new Random();
            return random.nextInt(10);
        }
        
        private int totalSell = 0;
        
        @Override
        public void run() throws NullPointerException{
            // TODO Auto-generated method stub
            while(true){
                Integer sellNum = tickdb.setTotalTicketNum(RandomTicketNum());
                if(0 != sellNum){
                    System.out.println("售票窗口:" + Thread.currentThread().getName() + "售出票:" + sellNum + "张");
                    totalSell += sellNum;
                }
                else{
                    System.out.println("售票窗口:" + Thread.currentThread().getName() + "没有余票了!!!" + "此窗口总工卖票:" + totalSell);
                    break;
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                    break;
                }
            }//while
            
        }
        
    }
    
    package per.liyue.code.threadtest;
    public class MainPrint {
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            TicketDb totalDb = new TicketDb(100);
            
            //创建两个线程还是售票
            SellTicketThread s = new SellTicketThread();
            s.setTickdb(totalDb);
            new Thread(s, "售票点1").start();
            new Thread(s, "售票点2").start();
        }
    }
    

    输出代码:
    售票窗口:售票点2售出票:2张
    售票窗口:售票点1售出票:9张
    售票窗口:售票点2售出票:9张
    售票窗口:售票点1售出票:1张
    售票窗口:售票点1售出票:7张
    售票窗口:售票点2没有余票了!!!此窗口总工卖票:21
    售票窗口:售票点1售出票:2张
    售票窗口:售票点1售出票:1张
    售票窗口:售票点1售出票:8张
    售票窗口:售票点1售出票:1张
    售票窗口:售票点1售出票:6张
    售票窗口:售票点1售出票:4张
    售票窗口:售票点1没有余票了!!!此窗口总工卖票:50

    3.1同步代码块

    同步代码块使用关键字synchronized来同步代码,格式为:

    synchronized (Object) {
          //code...      
            }  
    

    这里的Object可以是**任何对象*

    3.2同步方法

    3.3同步锁

    4.线程通信

    4.1传统的线程通信

    传统的线程通信,也就是用synchronized关键字来执行,使用同步方法或者同步代码块的时候,可以通过Object的wait()、notifyAll()来控制。

    4.2使用Condition控制

    如果没有使用synchronized关键字,使用了Lock锁。那么这里就需要使用一个Condition变量关联这个Lock锁,来保证线程间的通信。
    Demo:
    使用两个线程来实现交替打印:12A34B....5152Z

    package per.liyue.code.printinfo;
    /*
     * 数字打印线程类
     */
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    
    
    public class PrintNum implements Runnable{
    	//这里的锁要公用
    	private Lock lock;
    	public void setLock(Lock lock) {
    		this.lock = lock;
    	}
    	
    	//字母线程的控制量
    	private Condition conditionLetter;
    	public void setConditionLetter(Condition conditionLetter) {
    		this.conditionLetter = conditionLetter;
    	}
    	
    	//本线程的控制量
    	private Condition conditionNum;	
    	public void setConditionNum(Condition conditionNum) {
    		this.conditionNum = conditionNum;
    	}
    
    
    	private final int maxNum = 52;
    	private int sum = 0;
    	private int count = 1;
    	public void PirntNumByCondition() throws InterruptedException {
    		//加锁保证同步有效
    		lock.lock();
    		//开始打印
    		while(count <= maxNum){
    			if(2 == sum){
    				sum = 0;		
    				//先启动对方,否则当前线程停止就不能启动对方了
    				conditionLetter.signal();
    				//对方启动后, 本线程等待
    				conditionNum.await();
    			}
    			else{
    				//正常打印
    				System.out.print(count);
    				count++;
    				sum++;
    			}
    		}
    		//数字先执行完,字母还得再执行一次
    		conditionLetter.signal();
    		//这里一定不要忘记解锁,可以用try将前面的执行包含,这里用finally来包含解锁
    		lock.unlock();
    	}
    	
    	@Override
    	public void run() {
    		// TODO Auto-generated method stub
    		try {
    			PirntNumByCondition();
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    
    
    }
    
    
    
    package per.liyue.code.printinfo;
    /*
     * 字母打印线程类
     */
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    public class PrintLetter implements Runnable {
        //这里的锁要公用
        private Lock lock;
        public void setLock(Lock lock) {
            this.lock = lock;
        }
        //数字线程的控制量
        private Condition conditionNum;    
        public void setConditionNum(Condition conditionNum) {
            this.conditionNum = conditionNum;
        }
        //本线程的控制量
        private Condition conditionLetter;
        public void setConditionLetter(Condition conditionLetter) {
            this.conditionLetter = conditionLetter;
        }
        private int sum = 0;
        public void PirntNumByCondition() throws InterruptedException {
            lock.lock();
            
            char charFirst = 'A';
            char charEnd = 'Z';
            int firstNum = (int)charFirst;
            int endNum = (int)charEnd;
            while(firstNum <= endNum){
                if (1 == sum) {
                    sum = 0;                
                    //先启动对方,否则当前线程停止就不能启动对方了
                    conditionNum.signal();
                    //对方启动后, 本线程等待
                    conditionLetter.await();
                    
                } else {
                    
                    char ch = (char)firstNum;
                    System.out.print(ch);
                    firstNum++;
                    sum++;
                }
            }
            //这里一定不要忘记解锁,可以用try将前面的执行包含,这里用finally来包含解锁
            lock.unlock();
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            try {
                PirntNumByCondition();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    
    package per.liyue.code.printinfo;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    /*
     * 模拟的主线程类
     */
    public class Main {
        public static void main(String []args){
            System.out.println("开始打印");
            //创建公用锁对象
            Lock lock = new ReentrantLock();
            //将锁和两个控制量相关联
            Condition conNum = lock.newCondition();
            Condition conLetter = lock.newCondition();
            //传入初始化值
            PrintLetter pL = new PrintLetter();
            pL.setLock(lock);
            pL.setConditionNum(conNum);
            pL.setConditionLetter(conLetter);
            //传入初始化值
            PrintNum pN = new PrintNum();
            pN.setLock(lock);
            pN.setConditionNum(conNum);
            pN.setConditionLetter(conLetter);
            
            //两个线程中,通过对自己和对方的控制量的调用,达到相互交替执行的目的
            
            //启动线程
            new Thread(pN).start();
            new Thread(pL).start();
            
            
        }
    }
    

    5.线程池

    5.1线程池

    5.2分解任务ForkJoinPool;

    6.线程相关类

    6.1ThreadLocal类

    6.2不安全的线程集合

    在Java中,ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap都是线程不安全的!如果多线程并发访问这些集合,那么需要用**Collections类包装后才是线程安全的。

    Collections提供静态方法:
    Collection:

    public static Collection synchronizedCollection(Collection c)

    Map:
    public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)

    List:
    public static List synchronizedList(List list)

    Set:
    public static Set synchronizedSet(Set s)

    Demo:

    HashMap m = (HashMap) Collections.synchronizedMap(new HashMap<>());  
    
    

    6.3线程安全集合类

    Java5开始,在java.util.concurrent下提供大量的线程安全集合类

  • 相关阅读:
    Easyui-datagrid显示时间的格式化代码
    JSP页面与JSP页面之间传输参数出现中文乱码的解决方案
    SpringMVC中在web.xml中添加中文过滤器的写法
    SpringMVC的实现过程
    BeanFactory 和 ApplicationContext的区别
    Spring中的IoC(控制反转)具体是什么东西
    Spring/AOP框架, 以及使用注解
    面向切面编程
    Spring的属性注入, byName和byType还有注入List属性
    反射, getClass(), 和something.class以及类型类(转)
  • 原文地址:https://www.cnblogs.com/bugstar/p/8492822.html
Copyright © 2020-2023  润新知