1. 多线程概述

  要实现多线程可以通过继承Thread和实现Runnable接口。不过这两者之间存在一些区别。其中最重要的区别就是,如果一个类继承Thread类,则不适合于多个线程共享资源,而实现了Runnable接口,就可以方便地实现资源的共享。其实细说起来并不是能不能资源共享的事情,是因为继承Thread和实现Runnable接口这两种方式新建的任务数本身就是不同的,线程与任务的对应机制也是不同的。

范例1:继承Thread类

创建了多个thread,相当于创建了多个任务,每个任务交由一个thread来完成。

 package test;

 public class MyThreadDemo1 {
public static void main(String args[]) {
MyThread1 mt1 = new MyThread1();
MyThread1 mt2 = new MyThread1();
MyThread1 mt3 = new MyThread1();
mt1.start();
mt2.start();
mt3.start();
} static class MyThread1 extends Thread {
private int ticket = 5; @Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++)
if (ticket > 0)// 当余票大于0则买票
{
System.out.println("卖一张票:剩余ticket=" + --ticket); // 这里--ticket表示卖了一张票后的余票
}
}
}
}

程序运行结果:

卖票:剩余ticket=5
卖票:剩余ticket=4
卖票:剩余ticket=3
卖票:剩余ticket=2
卖票:剩余ticket=1
卖票:剩余ticket=5
卖票:剩余ticket=4
卖票:剩余ticket=3
卖票:剩余ticket=5
卖票:剩余ticket=4
卖票:剩余ticket=3
卖票:剩余ticket=2
卖票:剩余ticket=1
卖票:剩余ticket=2
卖票:剩余ticket=1

以上程序通过继承Thread类实现多线程,程序中启动了三个线程,但是三个线程分别买了各自的5张票,并没有达到资源(Ticket)共享的目的。

范例2:实现Runable接口

相当于只创建了一个任务,并把这个任务交给三个线程来完成。

 package test;

 public class MyRunableThreadDemo1 {
public static void main(String args[]) {
MyRunableThread1 mrt = new MyRunableThread1();
Thread t1 = new Thread(mrt);
Thread t2 = new Thread(mrt);
Thread t3 = new Thread(mrt);
t1.start();
t2.start();
t3.start();
} static class MyRunableThread1 implements Runnable {
private int ticket = 5; @Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++)
if (ticket > 0)// 当余票大于0则买票
{
System.out.println("卖一张票:剩余ticket=" + --ticket); // 这里--ticket表示卖了一张票后的余票
}
}
}
}

程序运行结果:

卖票:剩余ticket=5
卖票:剩余ticket=4
卖票:剩余ticket=3
卖票:剩余ticket=2
卖票:剩余ticket=1

从程序的运行结果中可以清楚地发现,虽然启动了3个线程, 但是三个线程一共才卖出去5张票,即ticket属性是被所有线程所共享的。

可见,实现Runnable接口相对于继承Thrad类来说,有如下显著优势:
1.适合多个相同程序代码的线程去处理同一资源的情况。
2.可以避免由于java单继承特性带来的局限
3.增强了程序的健壮性,代码能够被多个线程共享,代码与数据时独立的。

2. 多线程的同步

多次运行范例2我们发现得到的结果可能都不相同。

范例2可能的输出结果1

卖票:剩余ticket=4
卖票:剩余ticket=5
卖票:剩余ticket=2
卖票:剩余ticket=3
卖票:剩余ticket=1

范例2可能的输出结果2

卖票:剩余ticket=4
卖票:剩余ticket=2
卖票:剩余ticket=1
卖票:剩余ticket=5
卖票:剩余ticket=1

范例2可能的输出结果3

卖票:剩余ticket=4
卖票:剩余ticket=5
卖票:剩余ticket=3
卖票:剩余ticket=2
卖票:剩余ticket=1
卖票:剩余ticket=0
卖票:剩余ticket=-1

出现票数为负的情况是因为:

线程1在执行 ticket--之前,线程2 进入了 if (ticket > 0) 这个判断,这样当线程1 ticket--之后ticket==0了,线程2再次执行ticket--那么ticket==-1。相当于多执行了一次 ticket--

3.两种线程同步方法

为了解决范例2中出现的问题,我们通过引入同步机制来解决问题。同步又分为同步代码块和同步方法两种类型。

 package test;

 public class MyRunableThreadDemo3 {
public static void main(String args[]) {
MyRunableThread1 mrt = new MyRunableThread1();
Thread t1 = new Thread(mrt, "t1");
Thread t2 = new Thread(mrt, "t2");
Thread t3 = new Thread(mrt, "t3");
t1.start();
t2.start();
t3.start();
} static class MyRunableThread1 implements Runnable {
private int ticket = 200; @Override
public void run() { //错误
// synchronized (this) {
// while (ticket > 0) {
//
//// try {
//// Thread.sleep(3);
//// } catch (InterruptedException e) {
//// e.printStackTrace();
//// }
// System.out.println(Thread.currentThread().getName()
// + "卖了一张票票,剩余ticket=" + --ticket);// 这里--ticket表示卖了一张票后的余票
// }
// }
/**
* 同步代码块 之前说到ticket出现负数的原因是线程1在执行 ticket--之前,线程2 进入了 if (ticket > 0) 这个判断,
* 这样当线程1执行 ticket--之后ticket==0了,线程2再去执行ticket--,那么ticket==-1。相当于多执行了一次ticket--,
* 因此我们将synchronized(this)放在了if(ticket>0)之前。for(int i=0;i<100;i++)用来表示连续执行100次。这里synchronized在
* for循环后面,因此每个线程都执行100次,彼此都有可能锁冲突。
* */
for(int i=0;i<100;i++)
synchronized(this)
{
if(ticket>0)
{
try{
Thread.sleep(30);
}catch(InterruptedException e)
{
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "卖了一张票票,剩余ticket=" + --ticket);// 这里--ticket表示卖了一张票后的余票
}
}
}
}
}

之前说到ticket出现负数的原因是“线程1在执行 --ticket之前,线程2 进入了 if (ticket > 0) 这个判断, 这样当线程1执行 ticket--之后ticket==0了,线程2再去执行ticket--,那么ticket==-1,相当于多执行了一次ticket--”,

因此我们将synchronized(this)放在了if(ticket>0)之前。我们还可以发现外部有一个执行一般次的for循环for(int i=0;i<100;i++),这用来表示run方法中的这synchronized代码块会被执行100次。需要注意的是只有执行完synchronized代码块才会释放锁。因此每一个线程都有100次可能出现锁冲突,一个线程需要等待另外一个线程执行完synchronized代码块中的内容以后才可以访问这个代码块。

范例4:同步方法

 package test;

 public class MyRunableThreadDemo4 {
public static void main(String args[]) {
MyRunableThread1 mrt = new MyRunableThread1();
Thread t1 = new Thread(mrt, "t1");
Thread t2 = new Thread(mrt, "t2");
Thread t3 = new Thread(mrt, "t3");
t1.start();
t2.start();
t3.start();
} static class MyRunableThread1 implements Runnable {
private int ticket = 200; @Override
public void run() {
for (int i = 0; i < 100; i++) {
sale();
}
} public synchronized void sale() {
if (ticket > 0) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "卖了一张票票,剩余ticket=" + --ticket);// 这里--ticket表示卖了一张票后的余票
}
}
}
}

这里将操作ticket资源的内容单独抽取出来作为一个方法来调用,然后同步该方法,保证同一时间只有一个进程调用该方法。

4.生产者消费者案例

 package test;

 public class ThreadDeadLock {
public static void main(String args[]) {
Info info = new Info();
// info作为参数传入两个线程当中
ProducerThread pt = new ProducerThread(info);
ConsumerThread ct = new ConsumerThread(info); Thread producer = new Thread(pt, "producer");
Thread consumer = new Thread(ct, "consumer");
producer.start();
consumer.start();
} //资源类
static class Info {
private String name = "name";
private String content = "content"; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public String getContent() {
return content;
} public void setContent(String content) {
this.content = content;
}
} // 生产者线程
static class ProducerThread implements Runnable {
private Info info = null; // 构造函数,其参数是资源
public ProducerThread(Info info) {
this.info = info;
} @Override
public void run() {
// boolean flag=false;
for (int i = 0; i < 10; i++) {
this.info.setName("name" + i);
try {
Thread.sleep(90);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.info.setContent("content" + i);
}
}
} static class ConsumerThread implements Runnable {
private Info info = null; // 构造函数,其参数是资源
public ConsumerThread(Info info) {
this.info = info;
} @Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.info.getName() + ":-->"
+ this.info.getContent());
}
}
} }

程序输出:

name1:-->content0
name2:-->content1
name3:-->content2
name4:-->content3
name5:-->content4
name6:-->content5
name7:-->content6
name8:-->content7
name9:-->content8
name9:-->content9

范例5存在两个问题:
1.问题1:假设ProducerThread线程刚设置了name信息,还没有设置content信息,此时程序就切换到了ConsumerThread线程,那么ConsumerThread线程获取的是当前name与之前的content,所以输出结果会出现(比如:name1:-->content0)。这是因为name与content没有一起设置的原因,或者是说name与content信息不同步。
2.问题2:生产者放了若干次数据,消费者才开始取数据,或者是,消费者取完一个数据后,还没有等到生产者放入新的数据,又重复取出已去过的数据。(比如出现name1:-->content0 name1:-->content0,这个可以通过调节sleep来控制)

问题1 解决:加入同步

如果要为操作加入同步,可以通过定义同步方法的方式完成,即将设置名称和内容定义在一个方法里面,代码如范例6所示。

范例6

 package test;

 public class ThreadDeadLock2 {
public static void main(String args[]) {
Info info = new Info();
// info作为参数传入两个线程当中
ProducerThread pt = new ProducerThread(info);
ConsumerThread ct = new ConsumerThread(info); Thread producer = new Thread(pt, "producer");
Thread consumer = new Thread(ct, "consumer");
producer.start();
consumer.start();
} // 资源类
static class Info {
private String name;
private String content; //getter and setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
} // 获取name与content信息
public synchronized void get() { try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.getName() + ":-->" + this.getContent());
} // 设置name与content信息
public synchronized void set(String name, String content) { this.setName(name);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.setContent(content);
}
} // 生产者线程
static class ProducerThread implements Runnable {
private Info info = null; // 构造函数,其参数是资源
public ProducerThread(Info info) {
this.info = info;
} @Override
public void run() { for (int i = 0; i < 10; i++) {
this.info.set("name" + i, "content" + i);
}
}
} static class ConsumerThread implements Runnable {
private Info info = null; // 构造函数,其参数是资源
public ConsumerThread(Info info) {
this.info = info;
} @Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.info.get();
}
}
}
}

程序运行结果:

name-0:-->content-0
name+1:-->content+1
name-2:-->content-2
name+3:-->content+3
name-4:-->content-4
name-6:-->content-6
name+7:-->content+7
name-8:-->content-8
name+9:-->content+9
name+9:-->content+9

从程序的运行结果中可以发现,问题1:信息错乱的问题已经解决,但是依然存在问题2:重复读取的问题,以及漏读信息的问题(比如上述输出中name9:-->content9重复读取,而name5:-->content5被漏读了)。既然有重复读取,则肯定会有重复设置的问题,那么对于这样的问题,该如何解决呢?此时,就需要使用Object类。Object类是所有类的父类,在此类中wait、notify是对线程操作有所支持的。

如果想让生产者不重复生产,消费者不重复消费,可以设置有一个标志位,假设标志位为boolean型变量,如果标志位内容为true,则表示可以生产,但是不能取走,如果标志位内容为false,则表示可以取走,不能生产。操作流程如下:

问题解决2——加入等待与唤醒

 package edu.sjtu.erplab.thread;

 class Info{
private String name="name";
private String content="content";
private boolean flag=true;
public synchronized void set(String name,String content)
{
if(!flag)//标志位为false,不可以生产
{
try {
super.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.setName(name);
try {
Thread.sleep(30);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.setContent(content);
flag=false;//修改标志位为false,表示生产者已经完成资源,消费者可以消费。
super.notify();//唤醒消费者进程
} public synchronized void get()
{
if(flag)
{
try {
super.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
Thread.sleep(30);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.getName()+":-->"+this.getContent());
flag=true;//修改标志位为true,表示消费者拿走资源,生产者可以生产。
super.notify();//唤醒生产者进程。
} public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
} } class Producer implements Runnable{
private Info info=null;
public Producer(Info info)
{
this.info=info;
} @Override
public void run() {
boolean flag=false;
for(int i=0;i<10;i++)
if(flag)
{
this.info.set("name+"+i, "content+"+i);
flag=false;
}
else
{
this.info.set("name-"+i, "content-"+i);
flag=true;
}
}
} class Consumer implements Runnable{
private Info info=null;
public Consumer(Info info)
{
this.info=info;
}
@Override
public void run() {
for(int i=0;i<10;i++)
{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.info.get();
} }
} public class ThreadDeadLock {
public static void main(String args[])
{
Info info=new Info();
Producer pro=new Producer(info);
Consumer con=new Consumer(info);
new Thread(pro).start();
new Thread(con).start();
} }

程序运行结果:

name-0:-->content-0
name+1:-->content+1
name-2:-->content-2
name+3:-->content+3
name-4:-->content-4
name+5:-->content+5
name-6:-->content-6
name+7:-->content+7
name-8:-->content-8
name+9:-->content+9

参考:http://www.cnblogs.com/xwdreamer/archive/2011/11/20/2296931.html

java线程小结3的更多相关文章

  1. java线程小结2

    本文我们来总结一下可以改变线程状态的若干方法. 一. Thread类中的方法 1.sleep sleep方法属于Thread类,它相当于让线程睡眠,交出CPU,让CPU去执行其他的任务. 但是slee ...

  2. java线程小结1

    1.创建线程的两种方法 新线程的创建和启动都是通过java代码触发的.除了第一个线程(也就是启动程序的.运行main()方法的线程)是由java平台直接创建的之外,其余的线程都是在java代码中通过“ ...

  3. Java线程:概念与原理

    Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...

  4. Java线程:线程状态的转换

    Java线程:线程状态的转换   一.线程状态   线程的状态转换是线程控制的基础.线程状态总的可分为五大状态:分别是生.死.可运行.运行.等待/阻塞.用一个图来描述如下:   1.新状态:线程对象已 ...

  5. java线程详解

    Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...

  6. Java线程详解----借鉴

    Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...

  7. (转)Java线程:线程的同步与锁

      Java线程:线程的同步与锁       一.同步问题提出   线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏. 例如:两个线程ThreadA.ThreadB都操作同一个对象Fo ...

  8. java 线程(1)

    Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...

  9. Java 线程池比较

    小结: 1. 高级面试题总结—线程池还能这么玩? - 这个时代,作为程序员可能要学习小程序 - CSDN博客https://blog.csdn.net/androidstarjack/article/ ...

随机推荐

  1. Button模板,样式

    一.button控件上的模板 <Button Content="Button" Height="25" HorizontalAlignment=" ...

  2. php多条件搜索

    PHP多条件查询 December : Tuesdayby 小屋 在我们的网站设计过程中,经常会用到多条件查询,本文的源码是一个二手房屋查询的例子.在本例中,我们要实现能够通过地理位置,物业类型,房屋 ...

  3. EBS 追前台最后一个执行的sql

    首先 从前台获取sid 然后 获取sql地址 ; 最后 获取sql文本 select * from v$sqltext_with_newlines t where t.ADDRESS = '07000 ...

  4. Android中webview和js之间的交互(转)

    http://www.cnblogs.com/leizhenzi/archive/2011/06/29/2093636.html 1.android中利用webview调用网页上的js代码. Andr ...

  5. JS---如何避免用户在请求时“猛击”

    var isAjax=false;//是否正在执行ajax请求,此处表示不在拿数据 var getInfoByTrainCode=function () { if(isAjax) return;//如 ...

  6. 十.oc内存管理

    引用百度百科图 栈(stack)又名堆栈. 栈定义:栈是限定仅在表头进行插入和删除操作的线性表(有序).(又称:后进先出表) (动态)数据展示存储的地方.(举例:升降电梯)特点:先进后出(FILO—F ...

  7. 转:fatal error: SDL/SDL.h: No such file or directory

    Ubuntu的新得立已经包含SDL库,所以通过几个简单的命令就可以安装,比windows还傻瓜! sudo apt-get install libsdl1.2-dev(比较大,10M左右) 附加包: ...

  8. Python基础之:List

    Python:List (列表) list 为Python内建类型,位于__builtin__模块中,元素类型可不同,元素可重复,以下通过实际操作来说明list的诸多功能,主要分为增.删.改.查 li ...

  9. java中的条件语句(if、if...else、多重if、嵌套if)

    Java条件语句之 if 生活中,我们经常需要先做判断,然后才决定是否要做某件事情.例如,如果考试成绩大于 90 分,则奖励一个 IPHONE 5S .对于这种"需要先判断条件,条件满足后才 ...

  10. 在ubuntu 16.04系统环境中搭建NAS(samba/iscsi/nfs)

    在ubuntu 16.04系统中搭建NAS环境 一.基本配置1:设置静态IPvi /etc/network/interfaces#iface ens32 inet dhcpiface ens32 in ...