Java高级—多线程
一、线程的概念
再讲线程之前,那么又又又又又又又要提起进程。(这个又字看多了,好像不认识了)
进程:
程序的一次的执行过程。
线程:
操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
二、线程的实现和方法
Thread thread = new Thread("线程1");//线程创建
System.out.println(thread.getName());//获取线程名称
thread.start(); //线程启动
thread.setPriority(8); //设置优先级 1~10
thread.getState(); //获取线程状态
thread.isAlive(); //查看线程是否运行
三、多线程的实现方式
实现多线程的方式:
1、继承Thread类
2、实现Runnable接口
3、实现Callable接口
4、线程池
1、继承Thread类
重写run方法
public class MyThread extends Thread{
public void run(){ //线程运行的主体
for (int i = 1; i <= 20; i++) {
System.out.println(i+".你好,来自线程"+Thread.currentThread().getName());
}
}
public static void main(String[] args) {
MyThread thread = new MyThread();
MyThread thread2 = new MyThread();
thread.start(); //真正意义上的多线程,因为run还是main主线程调用
thread2.start();
}
}
2、实现Runnable接口
重写run方法
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
System.out.println(i+".你好,来自线程"+Thread.currentThread().getName());
Thread.yield(); //礼让:放弃本次就会,重写竞争
}
}
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread thread = new Thread(mr,"子线程1");
Thread thread2 = new Thread(mr,"子线程2");
thread.setPriority(1); //设置优先级
thread.setPriority(10);
thread.start();
thread2.start();
}
}
3、实现Callable接口
重写call方法
由于run方法没有返回值,想要将返回值返回给客户程序就需要通过call方法来实现
使用Callable接口实现多线程的步骤
1.第一步:创建Callable子类的实例化对象
2.第二步:创建FutureTask对象,并将Callable对象传入FutureTask的构造方法中(注意:FutureTask实现了Runnable接口和Future接口)
3.第三步:实例化Thread对象,并在构造方法中传入FurureTask对象
4.第四步:启动线程
FutureTask ft = new FutureTask(Callable接口的实现类)
Thread thread = new Thread(ft);
thread.start();
ft.get();//获取call()方法的返回值
public class MyCallable implements Callable {
@Override
public String call() throws Exception {
System.out.println("执行call方法");
return "999";
}
public static void main(String[] args) {
FutureTask<String> ft = new FutureTask(new MyCallable());
Thread thread1 = new Thread(ft);
thread1.start();
}
}
4、线程池
1.newFixedThreadPool
固定大小的线程池,可以指定线程池的大小,该线程池corePoolSize和maximumPoolSize相等,阻塞队列使用的是LinkedBlockingQueue,大小为整数最大值。
该线程池中的线程数量始终不变,当有新任务提交时,线程池中有空闲线程则会立即执行,如果没有,则会暂存到阻塞队列。对于固定大小的线程池,不存在线程数量的变化。同时使用无界的LinkedBlockingQueue来存放执行的任务。当任务提交十分频繁的时候,LinkedBlockingQueue
迅速增大,存在着耗尽系统资源的问题。而且在线程池空闲时,即线程池中没有可运行任务时,它也不会释放工作线程,还会占用一定的系统资源,需要shutdown。
public class FixPoolDemo {
private static Runnable getThread(final int i) {
return new Runnable() {
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
};
}
public static void main(String args[]) {
ExecutorService fixPool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
fixPool.execute(getThread(i));
}
fixPool.shutdown();
}
}
2.newSingleThreadExecutor
单个线程线程池,只有一个线程的线程池,阻塞队列使用的是LinkedBlockingQueue,若有多余的任务提交到线程池中,则会被暂存到阻塞队列,待空闲时再去执行。按照先入先出的顺序执行任务。
public class CachePool {
private static Runnable getThread(final int i){
return new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
}catch (Exception e){
}
System.out.println(i);
}
};
}
public static void main(String args[]){
ExecutorService cachePool = Executors.newCachedThreadPool();
for (int i=1;i<=10;i++){
cachePool.execute(getThread(i));
}
}
}
3.newCachedThreadPool
缓存线程池,缓存的线程默认存活60秒。线程的核心池corePoolSize大小为0,核心池最大为Integer.MAX_VALUE,阻塞队列使用的是SynchronousQueue。是一个直接提交的阻塞队列, 他总会迫使线程池增加新的线程去执行新的任务。在没有任务执行时,当线程的空闲时间超过keepAliveTime(60秒),则工作线程将会终止被回收,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销。如果同时又大量任务被提交,而且任务执行的时间不是特别快,那么线程池便会新增出等量的线程池处理任务,这很可能会很快耗尽系统的资源。
public class SingPoolDemo {
private static Runnable getThread(final int i){
return new Runnable() {
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
};
}
public static void main(String args[]) throws InterruptedException {
ExecutorService singPool = Executors.newSingleThreadExecutor();
for (int i=0;i<10;i++){
singPool.execute(getThread(i));
}
singPool.shutdown();
}
4.newScheduledThreadPool
定时线程池,该线程池可用于周期性地去执行任务,通常用于周期性的同步数据。
scheduleAtFixedRate:是以固定的频率去执行任务,周期是指每次执行任务成功执行之间的间隔。
schedultWithFixedDelay:是以固定的延时去执行任务,延时是指上一次执行成功之后和下一次开始执行的之前的时间。
public class ScheduledExecutorServiceDemo {
public static void main(String args[]) {
ScheduledExecutorService ses = Executors.newScheduledThreadPool(10);
ses.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(4000);
System.out.println(Thread.currentThread().getId() + "执行了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 0, 2, TimeUnit.SECONDS);
}
}
三、同步机制
synchronized
举个例子:
买票:
非同步
public class BuyTicket extends Thread{
int left = 15; //总共15张票,余票
int used = 0; //已卖票数
public synchronized void buy(){
}
public void run(){
//取票
while (left>0){
left--;
used++;
try {
Thread.sleep(100); //模拟网络延时
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print(Thread.currentThread().getName()+"成功购买");
System.out.println("当前已卖"+used+"张"+"余票数:"+left);
}
}
public static void main(String[] args) {
BuyTicket bt = new BuyTicket();
Thread t1 = new Thread(bt,"奥网城");
Thread t2 = new Thread(bt,"ssssssssssss");
Thread t3 = new Thread(bt,"Tom");
t1.start();
t2.start();
t3.start();
}
}
【结果:会出现多个人买同一张票】
同步
public class BuyTicket extends Thread{
int left = 15; //总共15张票,余票
int used = 0; //已卖票数
public synchronized boolean buy(){
if(left<=0)return false;
left--;
used++;
System.out.print(Thread.currentThread().getName()+"成功购买");
System.out.println(",当前已卖"+used+"张"+",余票数:"+left);
return true;
}
public void run(){
//取票
while (true){
if(!buy())
break;
try {
Thread.sleep(100); //网络延时
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果显示
1、synchronized和lock的用法区别
synchronized:在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。
lock:一般使用ReentrantLock类做为锁。在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。
2、synchronized和lock性能区别
synchronized是托管给JVM执行的,
而lock是java写的控制锁的代码。
在Java1.5中,synchronize是性能低效的。因为这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用Java提供的Lock对象,性能更高一些。
但是到了Java1.6,发生了变化。synchronize在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在Java1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地。
2种机制的具体区别:
synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。
而Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是CAS操作(Compare and Swap)。我们可以进一步研究ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法是compareAndSetState。这里其实就是调用的CPU提供的特殊指令。
3、synchronized和lock用途区别
synchronized原语和ReentrantLock在一般情况下没有什么区别,但是在非常复杂的同步应用中,请考虑使用ReentrantLock,特别是遇到下面2种需求的时候。
1.某个线程在等待一个锁的控制权的这段时间需要中断
2.需要分开处理一些wait-notify,ReentrantLock里面的Condition应用,能够控制notify哪个线程
3.具有公平锁功能,每个到来的线程都将排队等候