Java 并发

本文介绍了并发问题的概念及其解决方案,包括Java中的synchronized关键字用法、线程池管理和并发定律等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 什么是并发问题。

多个进程或线程同时(或着说在同一段时间内)访问同一资源会产生并发问题。

银行两操作员同时操作同一账户就是典型的例子。比如A、B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户减去 50元,A先提交,B后提交。 最后实际账户余额为1000-50=950元,但本该为 1000+100-50=1050。这就是典型的并发问题。如何解决?可以用锁。

2 java中synchronized的用法

  1. 用法1
    public class Test{
        public synchronized void print(){
            ....;
        } 
    }
    

    某线程执行print()方法,则该对象将加锁。其它线程将无法执行该对象的所有synchronized块。

  2. 用法2
    public class Test{
        public void print(){
            synchronized(this){//锁住本对象
                ...;
            }
        }
    }
    

    同用法1, 但更能体现synchronized用法的本质。

  3. 用法3
    public class Test{
        private String a = "test";
        public void print(){
            synchronized(a){//锁住a对象
                ...;
            }
        }
        public synchronized void t(){
            ...; //这个同步代码块不会因为print()而锁定.
        }
    }
    

    执行print(),会给对象a加锁,注意不是给Test的对象加锁,也就是说 Test对象的其它synchronized方法不会因为print()而被锁。同步代码块执行完,则释放对a的锁。

    为了锁住一个对象的代码块而不影响该对象其它 synchronized块的高性能写法:

    public class Test{
        private byte[] lock = new byte[0];
        public void print(){
            synchronized(lock){
                ...;
            }
        }
        public synchronized void t(){
            ...; 
        }
    }
    
  4. 静态方法的锁
    public class Test{
        public synchronized static void execute(){
            ...;
        }
    }
    

    效果同

    public class Test{
        public static void execute(){
            synchronized(TestThread.class){
                ...;
            }
        }
    }

3 使用Executors、Executor、ExecutorService、ThreadPoolExecutor

可以使用线程管理任务。还可以使用jdk1.5提供的一组类来更方便的管理任务。从这些类里我们可以体会一种面向任务的思维方式。这些类是:

  1. Executor接口。使用方法:
    Executor executor = anExecutor;//生成一个Executor实例。
    executor.execute(new RunnableTask1());
    

    用意:使用者只关注任务执行,不用操心去关注任务的创建、以及执行细节等这些第三方实现者关心的问题。也就是说,把任务的调用执行和任务的实现解耦。

    实际上,JDK1.5中已经有该接口出色的实现。够用了。

  2. Executors是一个如同Collections一样的工厂类或工具类,用来产生各种不同接口的实例。
  3. ExecutorService接口它继承自Executor. Executor只管把任务扔进 executor()里去执行,剩余的事就不管了。而ExecutorService则不同,它会多做点控制工作。比如:
    class NetworkService {
        private final ServerSocket serverSocket;
        private final ExecutorService pool;
    
        public NetworkService(int port, int poolSize) throws IOException {
            serverSocket = new ServerSocket(port);
            pool = Executors.newFixedThreadPool(poolSize);
        }
     
        public void serve() {
            try {
                for (;;) {
                    pool.execute(new Handler(serverSocket.accept()));
                }
            } catch (IOException ex) {
                pool.shutdown(); //不再执行新任务
            }
        }
    }
    
    class Handler implements Runnable {
        private final Socket socket;
        Handler(Socket socket) { this.socket = socket; }
        public void run() {
            // read and service request
        }
    }
    

    ExecutorService(也就是代码里的pool对象)执行shutdown后,它就不能再执行新任务了,但老任务会继续执行完毕,那些等待执行的任务也不再等待了。

  4. 任务提交者与执行者通讯
    public static void main(String args[])throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Callable<String> task = new Callable<String>(){
            public String call()throws Exception{
                return "test";
            }
        };
        Future<String> f = executor.submit(task); 
        String result = f.get();//等待(阻塞)返回结果
        System.out.println(result);
        executor.shutdown();                
    }
    

    Executors.newSingleThreadExecutor()取得的Executor实例有以下特性:

    1. 任务顺序执行. 比如:
      executor.submit(task1);
      executor.submit(task2);
      

      必须等task1执行完,task2才能执行。

    2. task1和task2会被放入一个队列里,由一个工作线程来处理。即:一共有2个线程(主线程、处理任务的工作线程)。
  5. 其它的类请参考Java Doc
  6. 简单例子1
package concurrent; 

import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 


public class TestCachedThreadPool { 
        public static void main(String[] args) { 
//                ExecutorService executorService = Executors.newCachedThreadPool(); 
                ExecutorService executorService = Executors.newFixedThreadPool(5);
//         ExecutorService executorService = Executors.newSingleThreadExecutor();

                for (int i = 0; i < 5; i++) { 
                        executorService.execute(new TestRunnable()); 
                        System.out.println("************* a" + i + " *************"); 
                } 
                executorService.shutdown(); 
        } 
} 

class TestRunnable implements Runnable { 
        public void run() { 
                System.out.println(Thread.currentThread().getName() + "线程被调用了。"); 
                while (true) { 
                        try { 
                                Thread.sleep(5000); 
                                System.out.println(Thread.currentThread().getName()); 
                        } catch (InterruptedException e) { 
                                e.printStackTrace(); 
                        } 
                } 
        } 
}
例子2
import java.util.ArrayList; 
import java.util.List; 
import java.util.concurrent.*; 


public class CallableDemo { 
        public static void main(String[] args) { 
                ExecutorService executorService = Executors.newCachedThreadPool(); 
                List<Future<String>> resultList = new ArrayList<Future<String>>(); 

                //创建10个任务并执行 
                for (int i = 0; i < 10; i++) { 
                        //使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中 
                        Future<String> future = executorService.submit(new TaskWithResult(i)); 
                        //将任务执行结果存储到List中 
                        resultList.add(future); 
                } 

                //遍历任务的结果 
                for (Future<String> fs : resultList) { 
                        try { 
                                System.out.println(fs.get());     //打印各个线程(任务)执行的结果 
                        } catch (InterruptedException e) { 
                                e.printStackTrace(); 
                        } catch (ExecutionException e) { 
                                e.printStackTrace(); 
                        } finally { 
                                //启动一次顺序关闭,执行以前提交的任务,但不接受新任务。如果已经关闭,则调用没有其他作用。 
                                executorService.shutdown(); 
                        } 
                } 
        } 
} 


class TaskWithResult implements Callable<String> { 
        private int id; 

        public TaskWithResult(int id) { 
                this.id = id; 
        } 

        /** 
         * 任务的具体过程,一旦任务传给ExecutorService的submit方法,则该方法自动在一个线程上执行。 
         * 
         * @return 
         * @throws Exception 
         */ 
        public String call() throws Exception { 
                System.out.println("call()方法被自动调用,干活!!!             " + Thread.currentThread().getName()); 
                //一个模拟耗时的操作 
                for (int i = 999999; i > 0; i--) ; 
                return "call()方法被自动调用,任务的结果是:" + id + "    " + Thread.currentThread().getName(); 
        } 
}

4 并发3定律

  1. Amdahl定律. 给定问题规模,可并行化部分占12%,那么即使把并行运用到极致,系统的性能最多也只能提高1/(1-0.12)=1.136倍。即:并行对提高系统性能有上限。
  2. Gustafson定律. Gustafson定律说Amdahl定律没有考虑随着cpu的增多而有更多的计算能力可被使用。其本质在于更改问题规模从而可以把Amdahl定律中那剩下的88%的串行处理并行化,从而可以突破性能门槛。本质上是一种空间换时间。
  3. Sun-Ni定律. 是前两个定律的进一步推广。其主要思想是计算的速度受限于存储而不是CPU的速度. 所以要充分利用存储空间等计算资源,尽量增大问题规模以产生更好/更精确的解.



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值