您的位置:首页 > 编程语言 > Java开发

浅谈Java多线程

2018-05-29 11:15 176 查看

Java多线程的概念及创建方法

一、首先我们需要明白几个概念:程序、进程和线程

程序:指令集,是一个静态概念,比如说桌面上的一个应用就是一个程序。不管它运不运行都是一个程序

进程:操作系统调度程序,是一个动态概念。还是拿上面那个例子,当我们点击运行的时候,操作系统开始调度,这就启动了一个进程。

线程:线程是程序运行 的最小单位。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。(百度上copy过来的,哈哈),它和进程的关系是在一个进程内可以有多个线程,实际上它就是程序运行之后,进程执行的多条路径。多线程就是指一个进程可以有多条执行路径。比如说:一个人在桌子上吃饭,这是单进程单线程。多个人在一桌吃饭,这是单进程多线程。简而言之就是,有多个人同时使用同一个资源,这就是多线程。多线程涉及到的主要有并发、死锁、线程安全等问题。接下来就来具体说一下多线程是怎么实现的

二、线程实现的几种方法

在Java里面实现线程的方法主要由三种:

1、继承Thread类+实现run()方法贴上代码:

/**
* 创建多线程,继承Thread,重写run方法
*/
public class ThreadPra extends Thread {
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println("跑了"+i +"步");
}
}

public static void main(String[] args) {
//        新生线程
Thread t1=new Thread();
Thread t2= new Thread();
//        启动线程
t1.start();
t2.start();
}
}
2、实现Runnable接口+run方法
public class Ticket implements Runnable {
private int num= 50;
@Override
public void run() {
while (true){
if (num<=0){
break;
}
System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
}
}

public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t,"路人甲");
Thread t2= new Thread(t,"黄牛");
Thread t3 = new Thread(t,"程序员");//线程新生
t1.start();//线程就绪
t2.start();
t3.start();
}
}
运行结果:


可以看到这个抢占资源的顺序是随机的。谁抢到了资源谁就先运行。

由于继承Java的单继承,因此推荐使用Runnable接口,优点1避免单继承和2便于资源共享

启动:使用静态代理

1)创建真实角色
2)创建代理角色Thread+引用

3)代理角色.start()

缺点:不能抛出异常,没有返回值!!

这样的实现是不是看起来很简单!!!

3、实现Callable接口,重写call方法,用Future获取返回值

使用这种方式。我们需要借助任务调度线程池

ScheduledThreadPoolExecutor
,比如我们如果需要运行两个线程,我们可以用:

ExecutorService exe = new ScheduledThreadPoolExecutor(2);指定线程的个数为2,

 然后创建新的线程:(这里的Race实现了Callable接口,重写了call方法)

Race l =new Race("tortoise",1000); Race r = new Race("rabbit",500);通过result.get()获取到返回值

//        获取返回值
Future<Integer> res1 = exe.submit(l);
Future<Integer> res2 = exe.submit(r);
获取返回值,最后停止服务exe.shutdownNow();完整的代码段:ExecutorService exe = new ScheduledThreadPoolExecutor(2); Race l =new Race("tortoise",1000); Race r = new Race("rabbit",500); // 获取返回值 Future<Integer> res1 = exe.submit(l); Future<Integer> res2 = exe.submit(r); Thread.sleep(4000);//跑4秒就停 l.setFlag(false); r.setFlag(false); System.out.println("乌龟跑了"+ res1.get()+"步"); System.out.println("兔子跑了"+ res2.get()+"步"); // 停止服务 exe.shutdownNow();这种方式的优点是:1)可以对外声明异常2)有返回值。缺点是比较繁琐

三、线程的状态

简单来说线程一个有五个主要的状态:新生、就绪、运行、阻塞、死亡

那么这几个状态之间怎么转换?以下图来说明,注意:阻塞解除后会进入就绪状态


1、新生状态(new )
2、就绪状态(线程准备就绪,待CPU分配时间片即可运行)
3、CPU给了时间片,线程进入运行状态
4、阻塞状态 如sleep或者等待I/O设备等资源

5、死亡状态(两种情况:一种是正常死亡,线程体正常执行完毕。一种是外部干涉,调用stop或者destroy强行终止,不会释放锁)

涉及到同步的具体的几种状态:


这里的重点是怎么样创建线程终止线程。创建线程前面已经说过了。这里我们看一下终止线程的几种方法。

第一种是线程运行完毕,自然终止(死亡)。

第二种是外部干涉。外部干涉也有几种方式。

1)线程类中定义线程体使用的标识如flag标识 2)线程体使用该标识 3)提供对外的方法改变该标识 4)外部根据条件调用该方法即可

线程阻塞的方法:1)join合并线程 2)yield暂停当前正在执行的线程对象,它是一个静态方法,并执行其他线程 3)sleep休眠,这个是用的比较多的一个方法。调用这个方法后,线程进入休眠状态,不会释放锁,此时若有新的线程则会进行等待。

sleep方法示例:public class sleepDemo { public static void main(String[] args) throws InterruptedException { Date endTime = new Date(System.currentTimeMillis()+10*1000); long end =endTime.getTime(); while (true){ // 输出 System.out.println(new SimpleDateFormat("hh:mm:ss").format(endTime)); // 休眠 Thread.sleep(1000); // 构建下一秒的时间 endTime = new Date(endTime.getTime()+1000); if(endTime.getTime() -10000> end){ break; } } } }其他的线程的信息:

1、Thread.currentThread获得当前线程。2、获取名称、设置名称、优先级、判断状态

proxy.setPriority(Thread.MIN_PRIORITY);
proxy.isAlive()
四、线程同步(并发)

同步(并发):多个线程访问同一个资源时,我们要确保这份资源的安全性。比如说银行取钱的时候,多个人操作相同的账户,存取钱。如果处理不好就可能会发生不安全的问题。为了避免这种问题,我们需要采取相应的措施。

解决并发问题所采取的主要由几种方法:

1、给线程加锁。java里面采取使用synchronized关键字的方式。synchronized关键字 ->同步

Java里面同步有同步块和同步方法

同步块:

synchronize(引用类型|this类.class){
//方法体
}
同步方法:

在方法中加入synchronized关键字,保证线程安全。

难点:线程范围不能太大,否则浪费资源,造成等待,也不能过小,否则造成线程不安全

五、死锁

死锁是由什么引起的呢?

过多的并发,造成资源等待,比如我们操作系统书里面提到的筷子问题,每个人都在等待旁边的人释放筷子。这就造成了无限等待,也就造成了死锁问题。这个时候我们就需要采取相应的措施。

解决死锁问题的两个个典型的方法是生产者---消费者模式、管程法

生产者消费者模式:也称有限缓冲问题,让生产者在缓冲区满时休眠,等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒。

代码:

场景:比如我们定义一个场景Movie,Player和Watch都共同使用这个资源,这就涉及到了同步问题。

生产者使用资源Movie:

/** * 生产者 */ public class Player implements Runnable{ private Movie m; public Player(Movie m){ super(); this.m=m; } @Override public void run() { for(int i= 0;i<10 ;i++){ if(0 == i%2){ m.play("左"); }else{ m.play("右"); } } } }消费者(也使用Movie):

/** * 消费者 */ public class Watch implements Runnable { private Movie m; public Watch(Movie m){ super(); this.m=m; } @Override public void run() { for(int i= 0;i<10 ;i++){ m.watch(); } } }定义两个类共用movie资源,我们定义flag信号灯。

/** * 一個場景,共同的资源 * 生产者消费者模式,信号灯法 * wait() */ public class Movie { private String pic; // 信号灯 // flag -->T生产者生产,消费者等待,生产完成后通知消费者 // flag -->F 消费者消费,生产者等待,消费完成后通知生产者 private boolean flag = true; public synchronized void play(String pic){ if (!flag){//生产者等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 开始生成 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } // 生产完毕 System.out.println("生产了:"+pic); this.pic = pic; // 通知消费 this.notify(); // 生产者停下 this.flag = false; } public synchronized void watch(){ if(flag){//消费者等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 开始消费 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } // 消费完毕 System.out.println("消费了了:"+pic); // 通知生产 this.notifyAll(); // 消费者停下 this.flag = true; } }main方法:

public class App { public static void main(String[] args) { Movie m =new Movie(); // 多线程 Player p = new Player(m); Watch w = new Watch(m); // 访问同一份资源 new Thread(p).start(); new Thread(w).start(); } }结果:

这里如果不使用信号灯的话,输出的结果是一样的。

注意:wait方法和notify和同步一起使用的!!

六、任务调度

最后提一下Timer定时器

public class TimerDemo1{ public static void main(String[] args) { Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { System.out.println("哈哈...."); } },new Date(System.currentTimeMillis()+1000), 2000); } }我们可以用Timer来实现任务定时,比如说上面的例子,1秒之后每隔2秒打印“哈哈....”同样还可以定其他的时间。


!!!好了,以上就是我对线程的一些相关的理解和梳理,重点就是Java中线程的创建、终止、还有线程的几种状态。。具体项目中用到的线程肯定不止这么简单,会涉及到线程池,任务调度相关的东西。未完待续。。。。

阅读更多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: