bugstar

导航

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下提供大量的线程安全集合类

posted on 2018-03-02 13:50  bugstar  阅读(112)  评论(0编辑  收藏  举报