Java 多线程

java 多线程

1.线程初步

多线程这块多记录点实例以及重点部分,以下两篇博客讲的很清楚了

https://www.cnblogs.com/lwbqqyumidi/p/3804883.html

https://www.cnblogs.com/wxd0108/p/5479442.html

实现多线程方法:

方法一:直接从Thread类派生,重写其run方法
class MyThread extends Thread { 
    public void run() 
    { 
        // 将要以多线程方式运行的代码 
    } 
} 
调用Thread类的start方法启动运行
方法二:实现Runnable接口方式
将代码封装到Runable接口的run方法内:
public interface Runnable{ 
    void run(); 
}

public MyRunnable implements Runnable{
    public void run(){
        //要以多线程方式运行的代码
    }
}

之后:
创建一个Runable对象: Runnable r = new MyRunnable();
将Runable对象传给Thread对象: Thread t = new Thread(r);
启动多线程运行:t.start();

实例一:

public class TestMain {

    public static void main(String[] args){
        Thread th=new Thread(new Runnable(){
            public void run(){
                System.out.println("Runnable.Run()");
            }
        })
        {
            public void run(){
                //super.run();
                System.out.println("Thread.Run()");
            }
        };
        th.start();
    }    
}
输出:
Thread.Run()

原因:Thread的run方法是调用传入的Runnable类的run方法,如下源代码
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
但是,程序又重写了Thread的run方法,所以以上方法作废,直接输出Thread.Run()

https://www.cnblogs.com/lwbqqyumidi/p/3804883.html文章中还有一种使用Callable和Future接口创建线程的方式具体如下:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class TestMain {

    public static void main(String[] args){
        Callable<Integer> myCallable = new MyCallable();    // 创建MyCallable对象
        FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask来包装MyCallable对象

        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                Thread thread = new Thread(ft);   //FutureTask对象作为Thread对象的target创建新的线程
                thread.start();                      //线程进入到就绪状态
            }
        }

        System.out.println("主线程for循环执行完毕..");

        try {
            int sum = ft.get();            //取得新创建的新线程中的call()方法返回的结果,call()方法未执行完,get()方法一直阻塞
            System.out.println("sum = " + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}
class MyCallable implements Callable<Integer> {
    private int i = 0;

    // 与run()方法不同的是,call()方法具有返回值
    @Override
    public Integer call() {
        int sum = 0;
        for (; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            sum += i;
        }
        return sum;
    }

}

使用工厂创建线程:

  • JDK定义了一个ThreadFactory接口用于定义线程工厂
public interface ThreadFactory { 
    Thread newThread(Runnable r); 
} 
  • 实现此接口,就可以在应用程序中应用“工厂”设计模式,按需创建线程,避免了到处重复书写创建线程代码的麻烦,提升了代码的可维护性
  • 可以依据特定的需求,向外界返回特殊的自定义的Thread子类对象
  • 在需要时,可以实现一个线程池,重用而不是新建对象

示例代码:

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ThreadFactory;

public class TestMain {

    public static void main(String[] args){
        MyThreadFactory factory=new MyThreadFactory("my thread factory");
        factory.newThread(new Runnable() {
            @Override
            public void run() {
                System.out.println("in runnable.");
            }
        }).start();

        factory.printInfor();
    }
}
class MyThreadFactory implements ThreadFactory {
    private int counter;
    private String name;
    private List<String> stats;

    public MyThreadFactory(String name) {
        counter = 0;
        this.name = name;
        stats = new ArrayList<String>();
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r, name + "-Thread_" + counter);
        counter++;
        stats.add(String.format("Created thread %d with name %s on %s\n",
                t.getId(), t.getName(), new Date()));
        return t;
    }

    public void printInfor(){
        for(String s:stats){
            System.out.println(s);
        }
    }
}

Daemon线程:

  • Daemon线程是在后台执行的“背景”线程,当主线程结束时 JVM会自动结束这个线程
  • 默认情况下,创建的线程均为“前台”线程,需要调用setDeamon方法设置为“背景”线程:thread.setDaemon(true);

让线程暂停的方法thread.sleep()和TimeUnit.SECONDS.sleep(),第二种方式能直接指定各种不同的时间单位:纳秒、微秒、毫秒、秒、分钟、小时、天

import java.util.concurrent.TimeUnit;

public class TestMain {

    public static void main(String[] args){
        Thread th=new Thread(new Runnable(){

            private int counter=0;

            public void run() {
                for(int i=0;i<10;i++){
                    try {
                        Thread.sleep(500);//毫秒
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                    e.printStackTrace();
                    }
                    counter++;
                    System.out.println(counter);
                }
            }
        });
        th.start();
    }
}

终止线程:

外界通过调用线程的interrupt方法向线程发出中断工作的请求,这时,线程对象内部的一个“中断状态”标记被设置。线程对象应该定期检查这一标记,以合适的方式中断已经运行的工作。

import java.util.concurrent.TimeUnit;

public class TestMain {

    public static void main(String[] args){
        Thread th=new PrimerGenerator();
        th.start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        th.interrupt();//终止线程
    }
}

class PrimerGenerator extends Thread{
    public void run() {
        long number=1l;
        while(true){
            if (isPrime(number)) {
                System.out.printf("Number %d is Prime\n",number);
            }
            if (isInterrupted()) {//终止判断
                System.out.printf("The Prime Generator has been Interrupted\n");
                return;
            }
            number++;
        }
    }

    /**
     * 判断是不是primer
     * @param number
     * @return
     */
    private boolean isPrime(long number) {
        if (number <=2) {
            return true;
        }
        for (long i=2; i<number; i++){
            if ((number % i)==0) {
                return false;
            }
        }
        return true;
    }
}

如果被调用线程本身需要使用sleep()方法,由于此方法在运行时会清除掉“中断状态标记”,并激发一 个InterruptedException,所以,应该采用以下代码模板:

public void run(){ 
    try{ 
        … while( more work to do) { 
            do more work 
            Thread.sleep(dealy); 
        } 
    } catch(InterruptedException e) { 
        //thread was interrupted during sleep 
    } finally { 
        // clean up, if required 
    } //exiting the run method terminates the thread 
}
import java.util.concurrent.TimeUnit;

public class TestMain {

    public static void main(String[] args){
        Thread th=new Thread(new Runnable(){
            public void run(){
                try{
                    Thread.sleep(5000);
                }catch(InterruptedException e){
                    System.out.println("Thread is interrupted");
                }
            }
        });
        th.start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        th.interrupt();//终止线程
    }
}

定时任务,使用Timer类的schedule方法可以定时执行一个任务。此任务是TimerTask类型的实例,注意TimerTask派生自Runnable。因此,会在另外一个线程中运行这个定时任务。

import java.util.Timer;
import java.util.TimerTask;

public class TestMain {

    public static void main(String[] args){
        //将要被定时执行的任务,需要派生自TimerTask抽象类,并实现其抽象方法run()
        TimerTask task=new TimerTask(){
            private int counter=0;
            public void run() {
                counter++;
                System.out.println(counter+":invoked!");
            }
        };
        Timer timer=new Timer();
        //过2秒钟后首次运行,以后每隔3秒运行一次
//      timer.schedule(task, 2000,3000);
        //过2秒钟后运行一次,但线程并没有死掉
        timer.schedule(task,2000);
    }
}

线程执行框架:(Thread Executor Framework)

JDK中提供了一个Executor framework,其目的是将多线程任务的创建与执行分离开来,将要多线程执行的任务封装为一个Runnable对象,将其传给一个Executor对象,Executor从线程池中选择线程执行工作任务。使用Executor framework的好处在能简化多线程代码同时提升程序性能。

顶层接口:

public interface Executor { 
    void execute(Runnable command); 
}

ThreadPoolExecutor类实现了Executor接口,在实际开发中,我们通常通过Executors类的一些静态方法来实例化ThreadPoolExecutor对象

  • Executors.newSingleThreadExecutor:单线程,如果出了异步死掉后,自动重新创建后一个新的线程
  • Executors.newFixedThreadPool:固定数目的线程池
  • Executors.newCachedThreadPool:动态地按需求增加线程,重用己创建的线程,长时不用的线程被销毁
  • Executors.newScheduledThreadPool:固定数目,定期执行
  • Executors. newSingleThreadScheduledExecutor:单线程,定期执行
  • Executors.newWorkStealingPool:可以依据CPU核数创建多个任务队列,实现线程之间的工作负荷均衡。
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TestMain {

    public static void main(String[] args){
        Server server=new Server();

        //创建100个任务,让server执行
        for (int i=0; i<100; i++){
            Task task=new Task("Task_"+i);
            server.executeTask(task);
        }

        server.endServer();
    }
}
class Server {

    private ThreadPoolExecutor executor;

    //创建一个线程池执行者
    public Server(){
        executor=(ThreadPoolExecutor)Executors.newCachedThreadPool();
    }
    //executor处理任务
    public void executeTask(Task task){
        System.out.printf("Server: A new task has arrived\n");
        executor.execute(task);//正式处理
        System.out.printf("Server: Pool Size: %d\n",executor.getPoolSize());
        System.out.printf("Server: Active Count: %d\n",executor.getActiveCount());
        System.out.printf("Server: Completed Tasks: %d\n",executor.getCompletedTaskCount());
    }

    public void endServer() {
        System.out.println("----------------------------------------------------------------------------");
        System.out.printf("Server: Finally Pool Size: %d\n",executor.getPoolSize());//由于线程被重用,最后pool size不是100
        executor.shutdown();

    }

}

class Task implements Runnable {

    private Date initDate;

    private String name;

    public Task(String name){
        initDate=new Date();
        this.name=name;
    }

    @Override
    public void run() {
        System.out.printf("%s: Task %s: Created on: %s\n",Thread.currentThread().getName(),name,initDate);
        System.out.printf("%s: Task %s: Started on: %s\n",Thread.currentThread().getName(),name,new Date());

        try {
            Long duration=(long)(Math.random()*10);
            System.out.printf("%s: Task %s: Doing a task during %d seconds\n",Thread.currentThread().getName(),name,duration);
            TimeUnit.SECONDS.sleep(duration);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.printf("%s: Task %s: Finished on: %s\n",Thread.currentThread().getName(),name,new Date());
    }

}

Executor Framework与Callable接口和Future接口合用,这样可以调取线程得到的结果:

import java.util.*;
import java.util.concurrent.*;

public class TestMain {

    public static void main(String[] args){
        ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);//初始一个容量为2的线程池
        List<Future<Integer>> resultList = new ArrayList<>();

        Random random = new Random();
        //生成10个任务
        for (int i = 0; i < 10; i++) {
            Integer number = new Integer(random.nextInt(10));
            FactorialCalculator calculator = new FactorialCalculator(number);
            Future<Integer> result = executor.submit(calculator);//提交任务,返回的result放在Future的list中
            resultList.add(result);
        }

        do {
            System.out.printf("Main: Number of Completed Tasks: %d\n", executor.getCompletedTaskCount());
            for (int i = 0; i < resultList.size(); i++) {
                Future<Integer> result = resultList.get(i);
                System.out.printf("Main: Task %d: %s\n", i, result.isDone());
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } while (executor.getCompletedTaskCount() < resultList.size());//一直执行到所有task完成

        System.out.printf("Main: Results\n");
        for (int i = 0; i < resultList.size(); i++) {
            Future<Integer> result = resultList.get(i);
            Integer number = null;
            try {
                number = result.get();//阻塞式等待结果
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            System.out.printf("Core: Task %d: %d\n", i, number);
        }

        executor.shutdown();
        System.out.println("------------done------------");
    }
}


class FactorialCalculator implements Callable<Integer> {

    private Integer number;

    public FactorialCalculator(Integer number) {
        this.number = number;
    }

    /**
     * 计算阶乘
     */
    @Override
    public Integer call() throws Exception {
        int num, result;

        num = number.intValue();
        result = 1;

        if ((num == 0) || (num == 1)) {
            result = 1;
        } else {
            for (int i = 2; i <= number; i++) {
                result *= i;
                Thread.sleep(10);
            }
        }
        System.out.printf("%s: %d\n", Thread.currentThread().getName(), result);

        return result;
    }
}

Future还提供cancel()方法用于取消任务。

任务同步一:如果有多个并行执行的任务,其中只要有一个完成了就行,这时,可以使用invokeAny()方法

任务同步二:使用invokeAll()等待所有任务都运行结束,再输出结果

2.线程同步

  • 添加了synchronized关键字的方法称为“同步方法”。
  • 每次只允许一个线程执行同步方法。其余的线程阻塞,直到此方法执行完毕
  • 当方法执行完毕,JVM调度下一个最高优先级的线程运行
class Sequence { 
    private int nextValue; 
    public synchronized int getNext() { 
        return nextValue++; 
    } 
}

亦可使用同步代码块达到相同的目的:

synchronized(object) { 
    //需要同步的代码 
}

上述代码中的object代表同步对象,通常我们会将要访问同步对象的代码放入到此同步块中(这并非是必须遵守的规则,完全可以放置任意的代码)。JVM会保证只有一个线程执行代码块中的这些代码

在实际开发中,通常在内部放置一个专用于同步的对象,在同步方法内部锁定它,示例如下:

public class TestMain {

    public static void main(String[] args){
        SynchroizedClass e = new SynchroizedClass();
        TestThread1 t1 = new TestThread1(e);
        TestThread2 t2 = new TestThread2(e);
        //hello,world顺序输出,没有synchroized关键字就乱序输出
        t1.start();
        t2.start();
    }
}
class SynchroizedClass
{
    private Object object = new Object();

    public void printHello()
    {
        synchronized (object)
        {
            for (int i = 0; i < 10; i++)
            {
                try{
                    Thread.sleep((long) (Math.random() * 1000));
                }catch (InterruptedException e){
                    e.printStackTrace();
                }

                System.out.println("hello: " + i);
            }
        }
    }

    public void printWorld()
    {
        synchronized(object)
        {
            for (int i = 0; i < 10; i++)
            {
                try{
                    Thread.sleep((long) (Math.random() * 1000));
                }catch (InterruptedException e){
                    e.printStackTrace();
                }

                System.out.println("world: " + i);
            }
        }
    }
}

class TestThread1 extends Thread
{
    private SynchroizedClass example;

    public TestThread1(SynchroizedClass example)
    {
        this.example = example;
    }

    @Override
    public void run()
    {
        this.example.printHello();
    }
}

class TestThread2 extends Thread
{
    private SynchroizedClass example;

    public TestThread2(SynchroizedClass example)
    {
        this.example = example;
    }

    @Override
    public void run()
    {
        this.example.printWorld();
    }
}

synchronized方法是一种粗粒度的并发控制手段,某一时刻只能有一个线程执行该方法。synchroized块则是一种细粒度的并发控制,只会将块中的代码同步,位于方法内synchroized块之外的代码是可以被多个线程同时访问到

线程之间的协作:

A线程正在运行,希望插入一个B线程,要求B先执行完毕,A才继续运行,可以使用join()方法,Join的含义是:将某一线程加入成为另一个线程的流程之一。

public class TestMain {

    public static void main(String[] args)throws InterruptedException{
        System.out.println("主线程执行");

        Thread otherThread = new Thread(new Runnable() { 
            public void run() { 
                try { 
                    System.out.println("辅助线程开始.."); 
                    for(int i = 1; i <= 5; i++) {
                        Thread.sleep(1000); 
                        System.out.println(i+":辅助线程执行..");
                    }
                    System.out.println("辅助线程执行结束"); 
                } 
                catch(InterruptedException e) { 
                    e.printStackTrace(); 
                } 
            } 
        });

        otherThread.start(); //启动线程

        try {
             //辅助线程加入主线程
            otherThread.join();
        }
        catch(InterruptedException e) {
            e.printStackTrace();
        }

        for(int i=1;i<=5;i++){
            Thread.sleep(500);
            System.out.println(i+": 主线程正在执行...");
        }

        System.out.println("主线程 执行完毕");
    }
}
经典的“生产者—消费者”问题:

生产者生产5个数,消费者依次消费5个数,生产者不生产数,消费者需等待,消费者未消费数,生产者需等待,以下两个代码分别是无同步和有同步。

无同步:
public class TestMain {

    public static void main(String[] args)throws InterruptedException{
        NoSyncClerk clerk = new NoSyncClerk(); 

        // 消费者线程
        Thread consumerThread = new Thread(new Consumer(clerk)); 
        // 生产者线程
        Thread producerThread = new Thread(new Producer(clerk)); 

        consumerThread.start(); 
        producerThread.start(); 
    }
}

class NoSyncClerk {
    // -1 表示目前没有产品
    private int product = -1; 

     // 这个方法由生产者呼叫
    public void setProduct(int product) { 
        this.product = product; 
        System.out.printf("生产者设定 (%d)%n", this.product); 
    }

    // 这个方法由消费者呼叫
    public int getProduct() {
        int p = this.product; 
        System.out.printf("消费者取走 (%d)%n", this.product);
        return p; 
    } 
}

class Consumer implements Runnable {
    private NoSyncClerk clerk;

    public Consumer(NoSyncClerk clerk) {
        this.clerk = clerk;
    }

    public void run() {
        System.out.println("消费者开始消耗整数......");

        // 消耗5个整数
        for(int i = 1; i <= 5; i++) {
            try {
                 // 等待随机时间
                Thread.sleep((int) (Math.random() * 1000));
            }
            catch(InterruptedException e) {
                e.printStackTrace();
            }

            // 从店员处取走整数
            clerk.getProduct();
        }
    }
 }

class Producer implements Runnable {
    private NoSyncClerk clerk;

    public Producer(NoSyncClerk clerk) {
        this.clerk = clerk;
    }

    public void run() {
        System.out.println("生产者开始生产整数......");

        // 生产1到5的整数
        for(int product = 1; product <= 5; product++) {
            try {
                // 暂停随机时间
                Thread.sleep((int) (Math.random() * 1000));
            }
            catch(InterruptedException e) {
                e.printStackTrace();
            }
            // 将产品交给店员
            clerk.setProduct(product);
        }
    } 
}

wait方法阻塞本线程,等待其他线程调用notify方法通知自己可以继续执行。

有同步:
public class TestMain {

    public static void main(String[] args)throws InterruptedException{
        SyncClerk clerk = new SyncClerk(); 

        // 消费者线程
        Thread consumerThread = new Thread(new Consumer(clerk)); 
        // 生产者线程
        Thread producerThread = new Thread(new Producer(clerk)); 

        consumerThread.start(); 
        producerThread.start(); 
    }
}

class SyncClerk {
    // -1 表示目前没有产品
    private int product = -1; 

     // 这个方法由生产者呼叫
    public synchronized void setProduct(int product) { 
        if(this.product!=-1){
            try{
                //无空间存放,需等待取走
                wait();
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }

        this.product = product; 
        System.out.printf("生产者设定 (%d)%n", this.product); 

        //通知消费者取走
        notify();
    }

    // 这个方法由消费者呼叫
    public synchronized int getProduct() {
        if(this.product==-1){
            try{
                wait();//缺货需等待
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }

        int p = this.product; 
        System.out.printf("消费者取走 (%d)%n", this.product);
        this.product=-1;

        notify();//通知生产者生产
        return p; 
    } 
}

class Consumer implements Runnable {
    private SyncClerk clerk;

    public Consumer(SyncClerk clerk) {
        this.clerk = clerk;
    }

    public void run() {
        System.out.println("消费者开始消耗整数......");

        // 消耗5个整数
        for(int i = 1; i <= 5; i++) {
            try {
                 // 等待随机时间
                Thread.sleep((int) (Math.random() * 1000));
            }
            catch(InterruptedException e) {
                e.printStackTrace();
            }

            // 从店员处取走整数
            clerk.getProduct();
        }
    }
 }

class Producer implements Runnable {
    private SyncClerk clerk;

    public Producer(SyncClerk clerk) {
        this.clerk = clerk;
    }

    public void run() {
        System.out.println("生产者开始生产整数......");

        // 生产1到5的整数
        for(int product = 1; product <= 5; product++) {
            try {
                // 暂停随机时间
                Thread.sleep((int) (Math.random() * 1000));
            }
            catch(InterruptedException e) {
                e.printStackTrace();
            }
            // 将产品交给店员
            clerk.setProduct(product);
        }
    } 
}
基于锁实现线程同步:

JDK提供了一个ReentrantLock锁:

Lock myLock =new ReentrantLock();
  • 使用ReentrantLock锁可以保证一次只有一个线程执行以下代码:
myLock.lock(); // a ReentrantLock object 
try{ 
    critical section 
} finally { 
    myLock.unlock(); 
}
  • 一个线程可以多次申请ReentrantLock锁,但必须保证它必须释放同样多次的锁。所以称它为“可重入”的锁

读写锁:ReentrantReadWriteLock:可以同时读,不能同时写

Lock对象提供了一个Condition对象,可以取代 Object类的notify和wait方法,Condition最大的好处在于可以仅使用一个Lock对象就能支持“多 路等待”。

参考:

金旭亮Java编程系列(大部分内容和代码)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值