多线程

进程跟线程的区别:

进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个进程.

多进程:操作系统中同时运行多个程序.一个程序至少有一个进程

 

线程:堆空间是共享的,栈空间是独立的,线程消耗的资源也比较小,互相之间可以影响的,又称之为轻型进程或进程元.

多线程:在同一个进程中运行的多个任务.一个进程至少有一个线程

Java操作进行

在java代码中如何去运行一个进程(简单讲解,获取进程中的数(IO))

方式一:Runtime类exec方法.

方式二:ProcessBuilder start方法 推荐使用

 

创建并启动线程

 

子线程一旦创建跟主线程有相同的级别.

线程类thread类和Runnable类的子类才能称之为线程类.

主线程(main方法运行,表示主线程)

方式一:继承Thread;

1):定义一个类,让类继承于java.lang.Thread类

2):在类中覆盖Thread中的run方法

3):在run方法中编写需要执行的操作--线程执行体(run方法里面的代码)

4):在main方法中,创建线程对象,并启动线程.

  1. 创建线程的格式: 线程类 a =  new 线程类()
  2. 调用线程对象的start方法 a.start;启动一个线程

 

注意:千万不要调用run方法.如果调用run方法,依然还是一个线程,并没有开启新的线程.

 

 

方式二:实现Runnable接口;

1):定义一个类,让类继承于java.lang.runnable接口

2):在类中覆盖runnable接口中的run方法

3):在run方法中编写需要执行的操作--线程执行体(run方法里面的代码)

4):在main方法中,创建线程对象,并启动线程.

public Thread(Runnable target)分配新的 Thread 对象.

  1. 创建线程类对象   

Thread  t  =  new  Thread(new 继承于runnable接口的对象)

  1. 调用线程对象的start方法  t.start();

使用匿名内部类来创建此线程

第一种:使用接口创建匿名内部类对象来创建线程(常用)

第二种:使用类来创建匿名内部类对象来创建线程

案例分析

public class AppleExtendsDome extends Thread {
public AppleExtendsDome(String name){
super(name);
}
private int apple = 50;
public void run() {
for(int i = 0; i <50;i++){
if(apple>0)
System.out.println(getName()+"吃了第" + apple-- + "个苹果");
}

}
public static void main(String[] args) {
new AppleExtendsDome("小杨").start();
new AppleExtendsDome("小张").start();
new AppleExtendsDome("小王").start();

}
}

 

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

//五个人同时吃50个苹果
public class AppleImplementsDome implements Runnable{
private int apple = 50;
public void run() {
for(int i = 0; i <50;i++){
if(apple>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"吃了第" + apple-- + "个苹果");
}
}

}
public static void main(String[] args) {
AppleImplementsDome a = new AppleImplementsDome();
new Thread(a,"杨贵武").start();
new Thread(a,"范晓").start();
new Thread(a,"周雨晨").start();

}

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

分析继承方式和实现方式的区别

继承方式:

 

1):java中类是单继承的,如果继承了Thread了,该类就不能再有其他的直接父类了

2):从操作上分析,继承方式更简单,获取线程名字也简单.getName()

3):从多线程贡共享同一个资源上分析,继承方式不能做到

 

实现方式:

 

1):java中可以实现多接口,此时该类还可以继承其他类,并且还可以实现其他接口.

2):从操作上分析,实现方式稍微复杂点,获取线程名字也比较复杂,得使用Thread.currentThread().getName()来获取当前线程的引用.

3):从多线程共享同一个资源上分析,实现方式可以做到

 

两种实现方式的区别和联系:

 

在程序开发中只要是多线程肯定永远以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类有如下好处:

1):避免点继承的局限,一个类可以继承多个接口。

2):适合于资源的共享

 

通过三个同学吃抢苹果的例子,说明使用实现方式才合理

 

线程不安全的问题分析

当多线程并发访问同一个对象的时候,可能出现线程不安全的问题.

 

解决方案:保证编号跟总数必须同步完成

   A线程进入操作的时候B,C只能在外面等着,A操作结束,B和C才有机会金融入代码区执行

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

线程同步(重要)

方式一:同步代码块:

/**
* 同步代码块
* @author Mr.bai
*
*/
class AppleImplementsDome implements Runnable {

private int apple = 50;

public void run() {
for (int i = 0; i < 50; i++) {
synchronized (this) {
if (apple > 0) {

System.out.println(Thread.currentThread().getName() + "吃了第" + apple + "个苹果");
//模拟网络延迟
try {
Thread.sleep(100);//sleep(long millis) 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
} catch (InterruptedException e) {
e.printStackTrace();
}
apple--;
}
}
}

}
}
//
public class SynchronizedDome {

public static void main(String[] args) {
AppleImplementsDome a = new AppleImplementsDome();
new Thread(a, "杨贵武").start();
new Thread(a, "范萧").start();
new Thread(a, "周雨晨").start();

}

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

语法:synchronized (同步锁){

//需要同步操作的代码块

   }

同步锁:此处存放引用数据类型/对象/class文件均可

为了保证每一线程都能正常执行原子操作,java引入了线程同步机制

同步监听对象/同步锁/同步监听器/互斥锁

对象的同步锁只是一个概念,可以想象成对象上标记一个锁。

Java程序运行使用任何对象作为同步监听对象,但是一般的,我们把当前并发访问的共同资源作为同步监听对象。

注意:在任何时候,最多允许一个线程拥有同步锁。

 

方式二:同步方法:使用 synchronized 修饰的方法,叫做同步方法,保证线程执行该方法的时候,其他线程只能在方法外等候

Synchronized public void play(){
         //TODO
         }      

 

同步锁是谁呢?

对于非static方法同步锁就是this;

对于static方法,我们使用当前方法所在类的字节码对象(xxx.class);

不要使用synchronized修饰run方法,修饰之后,某一个线程就执行完了所有的功能,好比是多个线程出现串行

解决方案:把需要同步操作的代码定义在一个新的方法中,并且该方法使用synchronized修饰,在用run方法调用该新的方法即可.

 

问题:假设现在有一筐苹果,数量为50个,现在分给三个人吃,直到吃完为止.

分析:

1):苹果的数量是固定的,逐步递减.所以只能有一个线程对象.

2):分给三个人吃,所以必须创建三个线程.

3):一个人吃完了才能下一个人吃,所以需要保证线程同步.

此时考虑该构造方法:public Thread(Runnable group, String name)分配新的 Thread 对象。
public static Thread currentThread()返回对当前正在执行的线程对象的引用。
public final String getName()返回该线程的名称。

/**
* 同步代码块
* @author Mr.bai
*
*/
class AppleImplementsDome implements Runnable {

private int apple = 50;

public void run() {
for (int i = 0; i < 50; i++) {
synchronized (this) {
if (apple > 0) {

System.out.println(Thread.currentThread().getName() + "吃了第" + apple + "个苹果");
//模拟网络延迟
try {
Thread.sleep(100);//sleep(long millis) 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
} catch (InterruptedException e) {
e.printStackTrace();
}
apple--;
}
}
}

}
}
//
public class SynchronizedDome {

public static void main(String[] args) {
AppleImplementsDome a = new AppleImplementsDome();
new Thread(a, "杨贵武").start();
new Thread(a, "范萧").start();
new Thread(a, "周雨晨").start();

}

}

































方式三:锁机制(Lock):

Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。同步方法和语句具有的功能Lock都有,除此之外更能体现面向对象. 此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。

详情见:

/**
* 同步锁(Lock)
* @author Mr.bai
*
*/
class AppleImplementsDome1 implements Runnable {

private int apple = 500;

public void run() {
for (int i = 0; i < 500; i++) {

eat();
}
}
//创建一个锁对象
Lock lock = new ReentrantLock();

public void eat() {
//进入方法,开启锁机制
lock.lock();
try {

if (apple > 0) {

System.out.println(Thread.currentThread().getName() + "吃了第" + apple + "个苹果");
Thread.sleep(10);//睡眠10毫秒
apple--;
}

} catch (Exception e) {
e.printStackTrace();

} finally {
//释放锁
lock.unlock();

}
}
}

//同步锁 (Lock)
public class LockDome {
public static void main(String[] args) {
AppleImplementsDome1 a = new AppleImplementsDome1();
new Thread(a, "杨贵武").start();
new Thread(a, "范萧").start();
new Thread(a, "周雨晨").start();
}
}

 

 

 




































同步锁池:

/**
* 消费者
* @author Mr.bai
*
*/
public class Consume implements Runnable {
private Intermediary up;

public Consume(Intermediary a) {
this.up = a;
}

public void run() {
for (int i = 0; i < 50; i++) {
if (i % 2 == 1) {
up.fetch();
} else {
up.fetch();
}

}
}

}

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

/**
* 中间商
* @author Mr.bai
*
*/
public class Intermediary {
private String name;
private String sex;
private boolean isEmpty = true;//表示共享资源是否为空的状态

/**
* 接收生产者输出的产品
* @param name 录入的名字
* @param sex 录入的性别
*/
synchronized public void entering(String name, String sex) {
try {
while(!isEmpty){//当前isEmpty为false的时候,不空等消费者来获取
//使用同步锁对象调用,表示当线程释放同步锁,进入等待池,只能被其他线程所唤醒
this.wait();
}
//生产开始........
this.name = name;
Thread.sleep(10);
this.sex = sex;
//生产结束........
isEmpty = false;//设置共享数据资源不能为空
this.notify();//唤醒一个消费者
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 消费者提取数据
*/
synchronized public void fetch() {

try {
while(isEmpty){ //当前isEmpty为false的时候,等生产者来生产
//使用同步锁对象调用,表示当线程释放同步锁,进入等待池,只能被其他线程所唤醒
this.wait();
}
System.out.println(name + "--" + sex);
this.notify();//唤醒一个生产者
isEmpty = true;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

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

/**
* 生产者
* @author Mr.bai
*
*/
public class Producer implements Runnable {
private Intermediary up;

public Producer(Intermediary a) {
this.up = a;
}

public void run() {

for (int i = 0; i < 50; i++) {
if (i % 2 == 1) {
up.entering("小赵", "男" );
} else {
up.entering("小杨", "女" );
}
}
}

}

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

public class Test {
public static void main(String[] args) {
Intermediary up = new Intermediary();
//生产者生产

new Thread(new Producer(up)).start();
//消费者消费
new Thread(new Consume(up)).start();
}

}





























































































问题一:出现性别紊乱的情况

使用同步锁方式解决(参照线程同步三种方式)

同步锁必须选择多个线程共同的资源对象

 

当前生产者在生产数据的时候(先拥有同步锁),其他线程就在锁池中等待获取锁匙

当线程执行完同步代码块的时候,就会释放同步锁,其他线程开始抢夺使用权.

 

问题二:应该生产一个数据消费一个数据

解决方案:使用等待和唤醒机制

 

线程通信wait和notify方法介绍

Object.long里面的方法

void wait( ) 导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法.

void notify() 唤醒在此对象监视器上等待的单个线程

Void notifyAll() 唤醒在此对象监视器上等待的所有线程

注意:上述方法只能被同步监听锁对象来调用,否则报错:IllegalMonitorStateException

多个线程只有使用相同的一个对象的时候,多线程之间才有互斥效果

我们把这个用来做互斥的对象称之为,同步监听对象/同步锁

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

同步锁对象可以选择任意类型的对象即可,只需要保证多个线程使用的是相同锁对象即可.

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

因为,只有同步监听锁对象才能调用wait和notify方法,所以wait和notify方法应该存在于Object类中,而不是Thread类中

 

案例分析:银行存取钱系统

银行管理系统类

 

存钱类(跟取钱类综合参照说明)

取钱类

测试类代码:

 

线程通信:使用LockCondition接口

Waitnotify方法只能被同步监听锁对象来调用,否则报错:IllegalMonitorStateException

Lock机制根本没有同步锁,也就没有自动获取锁和自动释放锁的概念(更加体现面向对象)

 

使用格式:

1):创建lock对象:           Lock lock = new ReentrantLock();

2):创建condition对象:  Condition condition = Lock.new condition();

3):需要同步执行的方法体

案例

线程的生命周期

有人又把阻塞状态, 等待状态, 计时等待状态综合称之为阻塞状态

 

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

线程对象的状态存放在Thread类的内部类state中:

 

注意:Thread.state类其实是一个枚举类.

 线程对象的状态是固定的,只有6种,此时使用枚举来表示最恰当的.

在给定的时间点上,一个线程只能处于一种状态.这些状态是虚拟机状态,他们并没有反映所有操作系统线程状态

 

 

1):新建状态使用new创建一个线程对象,仅仅在对重分配内存空间. 在调用start方法 之前

新建状态下,线程根本没有启动,仅仅是存在一个线程对象而已.

New Thread();就属于新建状态

 

2):可运行状态(runnale):分成两种状态,ready和running.分别表示就绪状态和运行状态

  1. i.         就绪状态:线程对象,调用start方法之后,等待JVM的调用
  2. ii.        运行状态:线程对象获得JVM调度,如果存在多个CPU,那么允许多个线程并运行.

线程对象的start方法,只能调用一次,否则报错.

3):堵塞(blocked由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。

此时JVM不会给线程分配CPU,知道线程重新进入就绪状态,才有机会转到运行状态.

阻塞状态只能先进入就绪状态,不能慧姐进入运行状态

 

正在睡眠:用sleep(long t) /wait(带参)方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。

 

正在等待:调用wait()方法。(调用notify()方法回到就绪状态)

 

被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)

4)死亡(dead

当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。

 

自然终止:正常运行run()方法后终止

 

异常终止:调用stop()方法让一个线程终止运行

 

线程控制操作

1):线程睡眠: 让执行的线程进入等待状态,睡眠一段时间.

方法:public static void sleep(long millis)  throws InterruptedException

在指定的毫秒数内让当前正在执行的线程休眠(暂停执行).调用sleep方法后,当前线程放弃CPU,在指定时间段类,sleep所在线程不会获得执行的机会.

该状态下的线程不会释放同步锁. 

2)联合线程:线程的join方法表示一个线程等待另一个线程完成后才执行,join方法被调用之后,线程对象处于阻塞状态.

 

 

 

4):后台线程:在后台运行的线程,其目的是为了其他线程提供服务,也称之为”守护线程”,JVM的垃圾会竖起就是一个典型的后台线程.

如果主线程结束了,那么后台线程也跟着结束了

方法:void setDaemon(boolean on)

将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。

参数:

on - 如果为 true,则将该线程标记为守护线程。

 

线程优先级:

每个线程都有优先级,优先级高低只和线程获得执行机会的次数多少有关,并非线程优先级越高的就一定先执行,哪个线程的先运行取决于CPU的调度.

MAX_PRIORITY = 10       最高优先级

MIN_PRIORITY = 1          最低优先级

NORM_PRIORITY = 5      默认优先级

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

int getPriority();  返回线程的优先级。 
void setPriority(int newPriority); 更改线程的优先级。
 
public static Thread currentThread();返回对当前正在执行的线程对象的引用。 
注意:不同的操作系统支持的线程优先级不同的,建议使用上述三个优先级,不要自定义.
 
 
线程礼让(一般不用)
public static void yield()

对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器。 调度程序可以自由地忽略这个提示。

产量是一种启发式尝试,以改善否则会过度利用CPU的线程之间的相对进度。 其使用应与详细的分析和基准相结合,以确保其具有预期的效果。

很少使用这种方法。 它可能对调试或测试有用,可能有助于根据种族条件重现错误。 在设计并发控制结构(例如java.util.concurrent.locks包中的并行控制结构)时也可能有用。

 

线程组:
 Thread(ThreadGroup group,Runnable target)分配新的 Thread 对象。这种构造方法与 Thread(group, target, gname) 具有相同的作用,其中的 gname 是一个新生成的名称。自动生成的名称的形式为 "Thread-"+n ,其中的 n 为整数。

类 ThreadGroup线程组表示一个线程的集合。此外,线程组也可以包含其他线程组。线程组构成一棵树,在树中,除了初始线程组外,每个线程组都有一个父线程组。

 

 



posted on 2017-12-21 17:06  听风醉  阅读(303)  评论(0编辑  收藏  举报

导航

因为不可能,所以才值得相信。