java多线程并发及线程池

1、继承Thread类创建线程类

public class FirstThreadTest extends Thread {
    public void run(){
        System.out.println("这里是线程的执行方法");
    }

    public static void main(String[] args) {
        //获得线程
        FirstThreadTest thread = new FirstThreadTest();
        System.out.println("线程名称为:"+thread.getName());
        //启动线程
        thread.start();
        System.out.println("main方法也是一个线程:"+Thread.currentThread().getName());
    }
}

执行结果:

线程名称为:Thread-0
main方法也是一个线程:main
这里是线程的执行方法

2、通过Runnable接口创建线程类
(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体;
(2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象;
(3)调用线程对象的start()方法来启动该线程。

public class RunnableThreadTest implements Runnable {

    public void run() {
        System.out.println("这里是线程方法");
        System.out.println("线程名为:" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        System.out.println("main方法线程:" + Thread.currentThread().getName());
        RunnableThreadTest rtt = new RunnableThreadTest();
        new Thread(rtt, "新线程1").start();
        new Thread(rtt, "新线程2").start();
    }

}

执行结果

main方法线程:main
这里是线程方法
线程名为:新线程1
这里是线程方法
线程名为:新线程2

两种线程创建的具体区别:http://www.cnblogs.com/whgw/archive/2011/10/03/2198506.html(扩展性和资源共享性)

多线程并发

多线程并发出现问题主要涉及到两个方面:多线程共享数据同步问题和数据因并发产生不一致问题;

1、多线程共享数据同步问题
如下代码:

/**
 * 两个工人一起搬砖
 */
public class Banzhuan {
    public static void main(String[] args) {
        // 一个工厂
        Factory factory = new Factory();
         /**
           * p1线程和p2线程都是由factory这个实例创建的
           * 那么p1调用外部类的getZhuanTou()方法就相当于调用的是factory这个实例的getZhuanTou(),同样的,p2调用的也是factory这个实例的getZhuanTou().
           * 那么这里就出现了两个线程同时访问factory的getZhuanTou()方法。
           * 而factory的getZhuanTou()方法又对zhuanTou这个属性进行了zhuanTou--操作。
           * 换句话说,两个线程同时访问了factory的数据zhuanTou.这时候就可能产生线程安全问题。
           */
        // 同一个工厂的两个工人
        Person p1 = factory.getPerson();
        Person p2 = factory.getPerson();
        p1.start();
        p2.start();
    }
}

// 工厂
class Factory {
    int zhuanTou = 20;// 一共20块砖头

    public int getZhuanTou() {
        if (zhuanTou == 0) {
            throw new RuntimeException(Thread.currentThread().getName()+ ",没有砖头搬了!");
        }
        Thread.yield();
        return zhuanTou--;

    }

    // 工人
    class Person extends Thread {
        // 不停的搬砖
        public void run() {
            while (true) {
                // 获取线程名(工人名) 及 剩下砖头数
                System.out.println(getName() + "搬了第" + getZhuanTou() + "块砖头");
                // 当线程的run方法中出现了异常,且我们没有 解决,那么该线程终止并死亡。但不会影响 当前进程中的其他线程。
                Thread.yield();
            }
        }
    }

    // 获取工人
    public Person getPerson() {
        return new Person();
    }
}

多次运行结果:
这里写图片描述

这里并不是每次都会出错,要多运行几次,就会可能碰到多种错误,比如搬到了同一块砖,比如少搬一块砖,比如搬完到了0然后还有继续搬……
原因是:比如现在还剩15块砖头,工人p1搬第15块砖头的时候正拿到手上,但是还没有登记减少一块砖头(即还没有运行zhuanTou–),这个时候工人p2也去拿砖,然后登记的时候一看还剩15块砖头,实际呢只剩14块了……
解决方法:
synchronized:当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
所以将getZhuanTou()方法改成如下就可以了

public int getZhuanTou() {
        synchronized (this) {
            if (zhuanTou == 0) {
                throw new RuntimeException(Thread.currentThread().getName()+",没有砖头搬了!");
            }
            Thread.yield();
            return zhuanTou--;
        }
    }

为啥不将这个关键词锁在方法那呢?尽量将锁在范围最小的地方,这样运行的效率更快。

2、数据因并发产生不一致问题
参考:https://my.oschina.net/clopopo/blog/149368

ThreadLocal:为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象。

首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象;

另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。

再次,注意每一个线程都保存一个对象的时候(非基本类型数据)应该是new一个新对象而不是引用一个对象,如下:

private static ThreadLocal<Index> local = new ThreadLocal<Index>() {
        @Override
        protected Index initialValue() {
            return new Index(); //注意这里
        }
    };

案例代码:

public class ThreadLocalTest {

        //创建一个Integer型的线程本地变量
    public static final ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };
    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[5];
        for (int j = 0; j < 5; j++) {       
               threads[j] = new Thread(new Runnable() {
                @Override
                public void run() {
                                        //获取当前线程的本地变量,然后累加5次
                    int num = local.get();
                    for (int i = 0; i < 5; i++) {
                        num++;
                    }
                                        //重新设置累加后的本地变量
                    local.set(num);
                    System.out.println(Thread.currentThread().getName() + " : "+ local.get());

                }
            }, "Thread-" + j);
        }

        for (Thread thread : threads) {
            thread.start();
        }
    }
}

运行结果:
Thread-0 : 5
Thread-4 : 5
Thread-2 : 5
Thread-1 : 5
Thread-3 : 5

--------------------------------------------------------------------------------------------------

3、总结:
ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别:
1、synchronized关键字主要解决多线程共享数据同步问题,ThreadLocal使用场合主要解决多线程中数据因并发产生不一致问题;
2、synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享;
synchronized和ThreadLocal比较:http://blog.csdn.net/huyongl1989/article/details/8088841

线程池

当有许多请求需要去处理的时候,如果只是单独的一个人去处理,可想而知那会让后面在排队的人等多久,这样就需要线程池,有请求过来了就到线程池里面取出一条线程去处理它,处理完成就把它收回到线程池里面,然而自己实现 一个功能强大的线程池也并非易事,在java1.5之后专门提供了线程池的类库。

Java通过Executors接口提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程;
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待;
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行;
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行;
下面简单看一下newFixedThreadPool这种线程池:

public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            final int index = i;
            fixedThreadPool.submit(new Runnable() {
                public void run() {
                    try {
                        System.out.println(Thread.currentThread().getName()+"---->"+index);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

运行:
pool-1-thread-1—->0
pool-1-thread-3—->2
pool-1-thread-2—->1
pool-1-thread-3—->3
pool-1-thread-2—->5
pool-1-thread-1—->4
pool-1-thread-3—->6
pool-1-thread-1—->8
pool-1-thread-2—->7
pool-1-thread-3—->9

因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。
fixedThreadPool.submit(Runnable task)或者execute方法会调用线程的run方法;

线程池参考地址:http://blog.csdn.net/u012385190/article/details/52486393

猜你喜欢

转载自jieke-zj.iteye.com/blog/2397311