程序员学习 线程

来源:互联网 发布:山村诡事以知天命 编辑:程序博客网 时间:2024/04/30 09:28


 

多线程:

假如一个程序有多条执行流程,那么该程序就是多线程程序

进程:

正在运行的程序,是系统进行资源分配和调用的独立单位。

每一个进程都有它自己的内存空间和系统资源。

线程:

是进程中的单个顺序控制流,是一条执行路径

一个进程如果只有一条执行路径,则称为单线程程序。

一个进程如果有多条执行路径,则称为多线程程序。

举例

扫雷游戏,迅雷下载等

ava程序运行原理

java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。

多线程的实现方案1

    继承Thread类。

1.1 定义一个类继承Thread。

1.2 重写run方法。

1.3 创建子类对象,就是创建线程对象。

1.4 调用start方法,开启线程并让线程执行,同时还会告诉jvm去调用run方法。

 为什么要这么做?

继承Thread类:因为Thread类描述线程事物,具备线程应该有功能。

那为什么不只创建Thread类的对象呢?

Thread t1 = new Thread();

t1.start();//这么做没有错,但是该start调用的是Thread类中的run方法,

而这个run方法没有做什么事情,更重要的是这个run方法中并没有定义我们需要让线程执行的代码。

 

创建线程的目的是什么?

是为了建立单独的执行路径,让多部分代码实现同时执行。

也就是说线程创建并执行需要给定的代码(线程的任务)。

对于之前所讲的主线程,它的任务定义在main函数中。

自定义线程需要执行的任务都定义在run方法中。

Thread类中的run方法内部的任务并不是我们所需要,只要重写这个run方法,

既然Thread类已经定义了线程任务的位置,只要在位置中定义任务代码即可。

所以进行了重写run方法动作。

Thread类的基本获取和设置方法

public final String getName()

public final void setName(String name)

 

 

获取线程名称:

Thread:currentThread()获取当前线程对象。

获取当前线程对象的线程名称:

Thread.currentThread().getName();

 

主线程的名称: main

事例:

class Demo extends Thread

{

private String name;

Demo(String name)

{

this.name = name;

}

public void run()

{

int[] arr = new int[3];

System.out.println(arr[4]);

for(int x=1; x<=20; x++)

{

System.out.println("name="+name+"..."+Thread.currentThread().getName()+"..."+x);

}

}

}

 

 

class  ThreadDemo

{

public static void main(String[] args) 

{

//创建了两个线程对象。

Demo d1 = new Demo("小强");

Demo d2 = new Demo("旺财");

d2.start();//将d2这个线程开启。

d1.run();//由主线程负责。

}

}

/线程对象调用 run方法和调用start方法区

别?

调用run方法不开启线程。仅是对象调用方法。

调用start开启线程,并让jvm调用run方法在开启的线程中执行。

 

线程调度

假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?

线程有两种调度模型:

分时调度模型   所有线程轮流使用CPU的使用权,平均分配每个线程占用 CPU 的时间片

抢占式调度模型   优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。 

Java使用的是抢占式调度模型。

演示如何设置和获取线程优先级

public final int getPriority()

public final void setPriority(int newPriority)

 

使用如下方法对线程进行控制

线程休眠

public static void sleep(long millis)

线程加入

public final void join()

线程礼让

public static void yield()

后台线程

public final void setDaemon(Daemon(boolean on)

中断线程

public final void stop()

public void interrupt()

 

线程的实现方案2 实现Runnable接口

1,定义类实现Runnable接口:避免了继承Thread类的单继承局限性。

2,覆盖接口中的run方法。将线程任务代码定义到run方法中。

3,创建Thread类的对象:只有创建Thread类的对象才可以创建线程。

4,将Runnable接口的子类对象作为参数传递给Thread类的构造函数。

因为线程已被封装到Runnable接口的run方法中,而这个run方法所属于Runnable接口的子类对象,

所以将这个子类对象作为参数传递给Thread的构造函数,这样,线程对象创建时就可以明确要运行的线程的任务。

5,调用Thread类的start方法开启线程。

 

 

第二种方式实现Runnable接口避免了单继承的局限性,所以较为常用。

实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。

继承Thread类:线程对象和线程任务耦合在一起。一旦创建Thread类的子类对象,既是线程对象,有又有线程任务。

实现runnable接口:将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。

Runnable接口对线程对象和线程任务进行解耦。

事例:

class Demo implements Runnable

{

private String name;

Demo(String name)

{

this.name = name;

}

//覆盖了接口Runnable中的run方法。

public void run()

{

for(int x=1; x<=20; x++)

{

System.out.println("name="+name+"..."+Thread.currentThread().getName()+"..."+x);

}

}

}

 

class ThreadDemo2 

{

public static void main(String[] args) 

{

//创建Runnable子类的对象。注意它并不是线程对象。

Demo d = new Demo("Demo");

//创建Thread类的对象,将Runnable接口的子类对象作为参数传递给Thread类的构造函数。

Thread t1 = new Thread(d);

Thread t2 = new Thread(d);

//将线程启动。

t1.start();

t2.start();

System.out.println(Thread.currentThread().getName()+"----->");

 

 

}

}

 

线程安全问题:

下面我们看一个售票的多线程程序:

public class Ticket implements Runnable {

// 定义100张票

private int tickets = 100;

 

@Override

public void run() {

// 为了模拟次数足够多,用死循环

while (true) {

// 判断如果有票,就卖出票

if (tickets > 0) {

System.out.println(Thread.currentThread().getName() + "正在出售第"

+ (tickets--) + "张票");

}

}

}

 

}

public class TicketTest {

public static void main(String[] args) {

// 创建实现了Runnable接口的子类对象

Ticket t = new Ticket();

 

// 创建线程对象

Thread t1 = new Thread(t, "窗口1");

Thread t2 = new Thread(t, "窗口2");

Thread t3 = new Thread(t, "窗口3");

Thread t4 = new Thread(t, "窗口4");

 

t1.start();

t2.start();

t3.start();

t4.start();

}

}

运行结果中了出现了错误的数据 0 -1 -2 

问题产生的原因;

1,线程任务中在操作共享的数据。

2,线程任务操作共享数据的代码有多条(运算有多个)。

解决思路:

只要让一个线程在执行线程任务时将多条操作共享数据的代码执行完,在执行过程中,不要让其他线程参与运算。

 

如何代码体现:

Java中解决此问题通过代码块来完成的。

这个代码块就是同步代码块 synchronized

格式:

synchronized(对象)

{

//需要被同步的代码。

}

 

同步好处:

解决多线程安全问题。

同步弊端:

降低了程序的性能。

同步前提:

必须保证多个线程在同步中使用的是同一个锁。

当多线程安全问题发生时,加入了同步后,

问题依旧,就要通过这个同步的前提来判断同步是否写正确。

 

 

用同步代码块执行买票程序:

class Ticket implements Runnable

{

//1,描述票的数量。

private int tickets = 100;

//2,售票的动作,这个动作需要被多线程执行,那就是线程任务代码。需要定义run方法中。

//线程任务中通常都有循环结构。

private Object obj = new Object();

public void run()

{

while(true)

{

synchronized(obj)

{

if(tickets>0)

{

//要让线程在这里稍停,模拟问题的发生。sleep  看到了0 -1 -2 错误的数据,这就是传说中的多线程安全问题。

try{Thread.sleep(1);}catch(InterruptedException e){/*未写处理方式,后面讲*/}

 

System.out.println(Thread.currentThread().getName()+"....."+tickets--);//打印线程名称。

}

}

}

}

}

class ThreadDemo3 

{

public static void main(String[] args) 

{

//1,创建Runnable接口的子类对象。

Ticket t = new Ticket();

 

//2,创建四个线程对象。并将Runnable接口的子类对象作为参数传递给Thread的构造函数。

Thread t1 = new Thread(t);

Thread t2 = new Thread(t);

Thread t3 = new Thread(t);

Thread t4 = new Thread(t);

 

//3,开启四个线程。

t1.start();

t2.start();

t3.start();

t4.start();

}

}

同步代码块的对象可以是哪些呢?

static 同步函数,使用的锁不是this,而是字节码文件对象, 类名.class   

买票程序:

 

class Ticket implements Runnable

{

private static int tickets = 100;

private Object obj = new Object();

boolean flag = true;

public void run()

{

if(flag){

while(true){

synchronized(Ticket.class){

if(tickets>0){

try{Thread.sleep(10);}catch(InterruptedException e){}

System.out.println(Thread.currentThread().getName()+"...obj..."+tickets--);//打印线程名称。

}

}

}

}

else{

while(true){

this.sale();

}

}

}

 

public static synchronized void sale()//

{

if(tickets>0)

{

try{Thread.sleep(10);}catch(InterruptedException e){}

System.out.println(Thread.currentThread().getName()+"...sale..."+tickets--);//打印线程名称。

}

}

}

class ThreadDemo5 

{

public static void main(String[] args) 

{

Ticket t = new Ticket();

 

Thread t1 = new Thread(t);

Thread t2 = new Thread(t);

 

t1.start();

try{Thread.sleep(10);}catch(InterruptedException e){}

//切换标记,之前,让主线程停一会,这时就只有一个t1线程在,它就会执行同步代码块。

t.flag = false;

 

t2.start();

}

}

同步方法

就是把同步关键字加到方法上

 

 * 同步代码块的锁对象是谁? -- obj

  是任意象。

 

 同步方法:把同步关键字加在方法上。

 问题:同步方法的锁对象是谁?this对象

  

  静态方法:当前类的字节码文件对象。

 

 同步的好处:解决多线程中出现的数据安全问题

 同步的弊端:影响程序的效率

 

等待:wait();

告诉:notify();//唤醒

 

问题解决:实现生产一个消费一个。

 

=====================

等待/唤醒机制。

wait(): 会让线程处于等待状态,其实就是将线程临时存储到了线程池中。

notify():会唤醒线程池中任意一个等待的线程。

notifyAll():会唤醒线程池中所有的等待线程。

 

记住:这些方法必须使用在同步中,因为必须要标识wait,notify等方法所属的锁。

同一个锁上的notify,只能唤醒该锁上的被wait的线程。

 

为什么这些方法定义在Object类中呢?

因为这些方法必须标识所属的锁,而锁可以是任意对象,任意对象可以调用的方法必然时Object类中的方法。

 

//1,描述资源。属性:商品名称和编号,  行为:对商品名称赋值,获取商品。

class Resource

{

private String name;

private int count = 1;

 

//定义标记。

private boolean flag = false;

//1,提供设置的方法。

public synchronized void set(String name)

{

 

if(flag)

try{this.wait();}catch(InterruptedException e){}

//给成员变量赋值并加上编号。

this.name = name + count;

//编号自增。

count++;

//打印生产了哪个商品。

System.out.println(Thread.currentThread().getName()+"......生产者...."+this.name);

 

//将标记改为true。

flag = true;

//唤醒消费者。

this.notify();

}

public synchronized void out()

{

if(!flag)

try{this.wait();}catch(InterruptedException e){}

System.out.println(Thread.currentThread().getName()+"....消费者...."+this.name);

//将标记该为false。

flag = false;

//唤醒生产者。

this.notify();

}

}

 

//2,描述生产者。

class Producer implements Runnable

{

private Resource r ;

// 生产者一初始化就要有资源,需要将资源传递到构造函数中。

Producer(Resource r)

{

this.r = r;

}

public void run()

{

while(true)

{

r.set("面包");

}

}

}

 

//3,描述消费者。

class Consumer implements Runnable

{

private Resource r ;

// 消费者一初始化就要有资源,需要将资源传递到构造函数中。

Consumer(Resource r)

{

this.r = r;

}

public void run()

{

while(true)

{

r.out();

}

}

}

 

class ThreadDemo9

{

public static void main(String[] args) 

{

//1,创建资源对象。

Resource r = new Resource();

 

//2,创建线程任务。

Producer pro = new Producer(r);

Consumer con = new Consumer(r);

 

//3,创建线程。

Thread t1 = new Thread(pro);

Thread t2 = new Thread(con);

 

t1.start();

t2.start();

}

}

多生产多消费。

问题1;生产了商品没有被消费,同一个商品被消费多次。

Thread-0......生产者....面包2499//没有被消费。

Thread-1......生产者....面包2500

Thread-3....消费者....面包2500

 

被唤醒的线程没有判断标记,造成问题1的产生。

解决:只要让被唤醒的线程必须判断标记就可以了。将if判断标记的方式改为while判断标记。记住:多生产多消费,必须时while判断条件。

 

 

问题2:发现while判断后,死锁了。

原因:生产方唤醒了线程池中生产方的线程。本方唤醒了本方。

解决:希望本方要唤醒对方,没有对应的方法,所以只能唤醒所有。

*/

class Resource

{

private String name;

private int count = 1;

 

//定义标记。

private boolean flag = false;

//1,提供设置的方法。

public synchronized void set(String name)//   

{

 

while(flag)

try{this.wait();}catch(InterruptedException e){}// t1等  t2等

//给成员变量赋值并加上编号。

this.name = name + count;//商品1  商品2  商品3

//编号自增。

count++;//2 3  4

//打印生产了哪个商品。

System.out.println(Thread.currentThread().getName()+"......生产者...."+this.name);//生产 商品1  生产商品2  生产商品3

 

//将标记改为true。

flag = true;

//唤醒消费者。

this.notifyAll();

}

public synchronized void out()// 

{

while(!flag)

try{this.wait();}catch(InterruptedException e){}//t3等  //t4等

System.out.println(Thread.currentThread().getName()+"....消费者...."+this.name);//消费 商品1

//将标记该为false。

flag = false;

//唤醒生产者。

this.notifyAll();

}

}

 

//2,描述生产者。

class Producer implements Runnable

{

private Resource r ;

// 生产者一初始化就要有资源,需要将资源传递到构造函数中。

Producer(Resource r)

{

this.r = r;

}

public void run()

{

while(true)

{

r.set("面包");

}

}

}

 

//3,描述消费者。

class Consumer implements Runnable

{

private Resource r ;

// 消费者一初始化就要有资源,需要将资源传递到构造函数中。

Consumer(Resource r)

{

this.r = r;

}

public void run()

{

while(true)

{

r.out();

}

}

}

 

 

 

 

class ThreadDemo10

{

public static void main(String[] args) 

{

//1,创建资源对象。

Resource r = new Resource();

 

//2,创建线程任务。

Producer pro = new Producer(r);

Consumer con = new Consumer(r);

 

//3,创建线程。

Thread t1 = new Thread(pro);

Thread t2 = new Thread(pro);

Thread t3 = new Thread(con);

Thread t4 = new Thread(con);

 

t1.start();

t2.start();

t3.start();

t4.start();

}

}

 

 

 

JDK5中Lock锁的使用

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

买票程序案例

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


public class SellTicket implements Runnable {


// 定义票
private int tickets = 100;


// 定义锁对象
private Lock lock = new ReentrantLock();


@Override
public void run() {
while (true) {
try {
// 加锁
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票");
}
} finally {
// 释放锁
lock.unlock();
}
}
}


}

/*
 虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
 为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
 /*

 * 虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,

 * 为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

 * 

 * Lock

 *  void lock(): 获取锁。

 *  void unlock():释放锁。  

 * ReentrantLockLock的实现类.

 */

public class SellTicketDemo {

public static void main(String[] args) {

// 创建资源对象

SellTicket st = new SellTicket();

 

// 创建三个窗口

Thread t1 = new Thread(st, "窗口1");

Thread t2 = new Thread(st, "窗口2");

Thread t3 = new Thread(st, "窗口3");

 

// 启动线程

t1.start();

t2.start();

t3.start();

}

}

 

 

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

 

public class SellTicket implements Runnable {

 

// 定义票

private int tickets = 100;

 

// 定义锁对象

private Lock lock = new ReentrantLock();

 

@Override

public void run() {

while (true) {

try {

// 加锁

lock.lock();

if (tickets > 0) {

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName()

+ "正在出售第" + (tickets--) + "张票");

}

} finally {

// 释放锁

lock.unlock();

}

}

}

 

}

 

线程池

程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。

线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。

在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池

 

程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。

线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。

在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池

 

 

 定时器


Timer
public Timer(): 创建定时器

安排在指定延迟后执行指定的任务
public void schedule(TimerTask task, long delay): 在指定的时候到达后, 指定给定的任务

安排指定的任务从指定的延迟后开始进行重复的固定延迟执行
public void schedule(TimerTask task,long delay,long period):在指定的时候到达后,执行给定的任务, 再等待period时间后,再循环执行给定的任务

 

 案例:



  1:演示3秒后执行某个动作
  执行3秒后, 爆炸的动作
 
2:演示3秒后执行某个动作,然后每隔2秒执行某个动作
执行3秒后, 爆炸的动作然后,再每隔2秒执行 help动作

 

 

import java.util.TimerTask;


public class MyTimerTask extends TimerTask {


@Override
public void run() {
//System.out.println("嘣, 爆炸了,快跑");

System.out.println("help, 救命");
}


}

 public class TimerTest {
public static void main(String[] args) {
//创建定时器对象
Timer t = new Timer();

//public void schedule(TimerTask task, long delay): 在指定的时候到达后, 指定给定的任务
//执行3秒后, 爆炸的动作
//t.schedule(new MyTimerTask(), 3000);

//public void schedule(TimerTask task,long delay,long period)
t.schedule(new MyTimerTask(), 3000, 2000);

}

 import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
案例二:

/*
 * 练习: 定时删除指定的带内容目录
 * 
 * 在: 17:02:00秒  删除指定的文件夹[ E:\\resource  ]
 */
public class TimeTest {
public static void main(String[] args) throws ParseException {
//创建定时器对象
Timer t = new Timer();

//设置时间
String time = "2015-02-02 17:09:40";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = sdf.parse(time);

//需要执行的任务
//TimerTask: 由 Timer 安排为一次执行或重复执行的任务
TimerTask task = new TimerTask() {
@Override
public void run() {
//封装File对象 数据源
File path = new File("E:\\resource");
//删除文件夹
deleteFolder(path);
}


///删除文件夹
private void deleteFolder(File path) {

//获取到指定目录下所有的File对象
File[] files = path.listFiles();
//遍历,获取到每一个File对象
for (File file : files) {
//判断是否为文件夹
if (file.isDirectory()) {
//文件夹
deleteFolder(file);
} else {
//文件 
System.out.println(file.getAbsolutePath() + "---" + file.delete());
}
}
//删除空文件夹
System.out.println(path.getAbsolutePath() + "---" + path.delete()  );
}
};

//执行任务
t.schedule(task, date);
}
}

 

 

 


 

 

 

 

 

 

 

 

 

0 0
原创粉丝点击