多线程
来源:互联网 发布:双色球选号过滤软件 编辑:程序博客网 时间:2024/06/16 02:24
多线程(知识点):
Run ()
重写Thread类的run()方法
Start ()
开启线程
currentThread ()
得到当前线程的实例对象
setDaemon (true)
将线程设置为后台程序
setPriority (int newPriority)
设置线程的优先级
Sleep (long millis)
线程休眠millis秒
Yield ()
线程让步
Join ()
线程插队
Synchronized
同步方法或代码块
Wait()
使当前线程放弃同步锁并进入等待,直到其他线程进入此同步锁,并调用notify()方法,或notifyAll()方法唤醒该线程为止。
Notify ()
唤醒此同步锁上等待的第一个调用wait()方法的线程。
notifyALL ()
唤醒此同步锁上调用wait()方法的所有线程
多线程:并发执行
指一个应用程序中有多条并发执行的线索,每条线索都被称作一个线程,它们会交替执行,彼此间可以进行通信。
线程概述:
进程:
在一个操作系统中,每个独立执行的程序都可称为一个进程,也就是“正在运行的程序”。
线程:
在一个程序中还可以有多个执行单元同时运行,这些执行单元可以看作程序执行的一条条线索,被称为线程。
线程的创建:
继承Thread类创建多线程:
通过继承Thread类,并重写Thread类中的run方法便可实现多线程。在Thread类中,提供了一个start()方法用于启动新线程。
public class Test01 {
public static void main(String[] args) {
MyThread my = new MyThread();
my.start();
while (true) {
System.out.println("main()!");
}
}
}
class MyThread extends Thread {
public void run() {
while (true) {
System.out.println("run()!");
}
}
}
实现Runnable:
为了一个类继承了某个父类就无法再继承Thread类,Thread类提供了另一个构造方法Thread(Runnable targer),其中Runnable是一个接口,它只有一个run()方法。当通过Thread(Runnable targer)构造方法创建线程对象时,只需为该方法传递一个实现了Runnable接口的实例对象,这样就不需要调用Thread类中的run()方法。
public class Test01 {
public static void main(String[] args) {
MyThread my = new MyThread();
//创建线程对象
Thread t = new Thread(my);
//开启线程,执行线程中的run()方法
t.start();
while (true) {
System.out.println("main()!");
}
}
}
class MyThread implements Runnable {
public void run() {
while (true) {
System.out.println("run()!");
}
}
}
同种实现多线程方式的对比分析:
public class Test01 {
public static void main(String[] args) {
MyThread my = new MyThread();
//创建线程对象
//开启线程,执行线程中的run()方法
new Thread(my, "窗口1").start();
new Thread(my, "窗口2").start();
new Thread(my, "窗口3").start();
new Thread(my, "窗口4").start();
}
}
class MyThread implements Runnable {
private int i = 100;
public void run() {
while (true) {
if(i > 0) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程名字
String t_name2 = t.getName();
System.out.println(t_name2+"正在发售第"+i--+"张票");
}
}
}
}
public class Test02 {
public static void main(String[] args) {
new MyThread2().start();
new MyThread2().start();
new MyThread2().start();
new MyThread2().start();
}
}
class MyThread2 extends Thread {
private int i = 100;
public void run() {
while (true) {
if(i > 0) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程名字
String t_name2 = t.getName();
System.out.println(t_name2+"正在发售第"+i--+"张票");
}
}
}
}
通过上面的两个例程可以看出,实现Runnable接口相对于继承Thread类来说,有如下显著的好处:
- 适合多个相同程序代码的线程去处理同一个资源的情况,把线程同程序代码、数据有效的分离,很好地体现了面向对象的设计思想。
- 可以避免由于Java的单继承带来的局限性。在开发中经常碰到这样一种情况,就是使用一个已经继承Thread类的方式,那么就只能采用实现Runnable接口的方式。
后台线程:
如果某个线程对象在启动之前调用了setDaemon(true)语句,这个线程就变成一个后台线程。
public class Test03 {
public static void main(String[] args) {
System.out.println("main线程是后台线程吗?"+Thread.currentThread().isDaemon());
//创建一个DamonThread对象
DamonThread damonThread = new DamonThread();
//创建线程共享damonThread对象
Thread thread = new Thread(damonThread, "后台线程");
//判断是否为后台线程
System.out.println("t 线程默认是后台线程吗?"+thread.isDaemon());
//将线程thread设置为后台线程
thread.setDaemon(true);
//调用start方法开启线程thread
thread.start();
for(int i=0; i<10; i++) {
System.out.println(i);
}
}
}
class DamonThread implements Runnable {
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName()+"---is running.");
}
}
}
注意:要将某个线程设置为后台线程,必须在该线程启动之前,也就是说setDaemon()方法必须在start()方法之前调用 ,否则会引发IllegalThreadStateException异常
线程的生命周期及状态转换:
新建状态——就绪状态——阻塞状态——运行状态——死亡状态
新建状态(New):
创建一个线程对象后,该线程就处于新建状态,此时它不能运行,和其他Java对象一样,仅仅由Java虚拟机为其分配了内存,没有表现出任何线程的动态特征。
就绪状态(Runnable):
当线程对象调用了start()方法后,该线程就进入就绪状态(也称可运行状态)。处于就绪状态的线程位于可运行池中,此时它只是具备了运行的条件,能否获得CPU的使用权开始运行,还需要等待系统的调度。
运行状态(Running):
如果处于就绪状态 的线程获得了CPU的使用权,开始执行run()方法中的线程执行体,则该线程处于运行状态。
阻塞状态(Blocked):
一个正在执行的线程在某些特殊情况下,如执行耗时的输入/输出操作时,会放弃CPU的使用权,进行阻塞状态。
死亡状态(Terminated):
线程的run()方法正常执行完毕或者线程抛出一个未捕获的异常(Exception)、错误(Error),线程就进入死亡状态。
线程的调度:
Java虚拟机执照特定的机制为程序中的每个线程分配CPU的使用权,这种机制被称作线程的调度。
线程调度有两种模型,分别是分时调度模型和抢占式调度模型。
分时调度:
指让所有的线程轮流获得CPU的使用权,并且平均分配每个线程占用的CPU的时间片。
抢占式调度:
指让可运行池中优先级高的线程优先占用CPU,而对于优先级相同的线程,随机选择一个线程使其占用CPU,当它失去了CPU的使用权后,再随机选择其他线程获取CPU使用权。
Java虚拟机默认采用抢占式调度模型。
线程的调度
如果要对线程进行调度,最直接的方式就是设置线程的优先级。优先级越高的线程获得CPU执行的机会越大。越小机会则越小。
Thread类的优先级常量
Thread类的静态常量
功能描述
Static int MAX_PRIORITY
static int MIN_PRIORITY
static int NORM_PRIORITY
表示线程的最高优先级,相当于值10
表示线程的最低优先级,相当于值1
表示线程的普通优先级,相当于值5
通过Thread类的setPriority(int new Priority)方法对其进行设置,该方法中的参数newPriority接收的是1~10之间的整数或者Thread类的三个静态常量
package org.zl.Test;
public class Test04 {
public static void main(String[] args) {
//创建两个线程
Thread min = new Thread(new MinPriority(), "优先级较低的线程");
Thread max = new Thread(new MaxPriority(), "优先级较高的线程");
//设置线程的优先级为1
min.setPriority(Thread.MIN_PRIORITY);
//设置线程的优先级为10
max.setPriority(10);
//开启两个线程
max.start();
min.start();
}
}
class MaxPriority implements Runnable {
public void run() {
for (int i=0; i<10; i++) {
System.out.println(Thread.currentThread().getName()+"正在输出:"+i);
}
}
}
class MinPriority implements Runnable {
public void run() {
for (int i=0; i<10; i++) {
System.out.println(Thread.currentThread().getName()+"正在输出:"+i);
}
}
}
线程休眠
如果希望人为地控制线程,使正在执行的线程暂停,将CPU让给别的线程,这时可以使用静态方法sleep(long millis),该方法可以让当前正在执行的线程暂停一段时间,进入休眠等待状态。当前线程调用sleep(long millis)方法后,在指定时间(参数 millis)内是不会执行的。这样其它线程就可以得到执行的机会了。
class SleepThread implements Runnable {
public void run() {
for (int i=0; i<10; i++) {
if (i == 3) {
try {
//当前线程休眠2秒
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("线程一正在输出:"+i);
try {
//当前线程休眠500毫秒
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class Test05 {
public static void main(String[] args) throws Exception {
//创建一个线程
new Thread(new SleepThread()).start();
for (int i=1; i<=10; i++) {
if (i == 5) {
//当前线程休眠2000秒
Thread.sleep(2000);
}
System.out.println("主线程正在输出:"+1);
//当前线程休眠500毫秒
Thread.sleep(500);
}
}
}
注意:sleep()是静态方法,只能控制当前正在运行的线程休眠,而不能控制其他线程休眠。
线程让步
线程让步可以通过yield()方法来实现,该方法和sleep()方法有点相似,都可以让当前正在运行的线程暂停,区别在于yield()方法不会阻塞该线程,它只是将线程转换成就绪状态,让系统的调度器重新调度一次。当某个线程调用yield()方法之后,只有与当前线程优先级相同或者更高的线程才能获得执行的机会。
class YieldThread extends Thread {
public YieldThread(String name) {
// TODO Auto-generated constructor stub
super(name);
}
public void run() {
for (int i=0; i<5; i++) {
System.out.println(Thread.currentThread().getName()+"----"+i);
if (i == 3) {
System.out.println("线程让步");
//线程运行到此,做出让步
Thread.yield();
}
}
}
}
public class Test06 {
public static void main(String[] args) throws Exception {
//创建两个线程
Thread t1 = new YieldThread("线程A");
Thread t2 = new YieldThread("线程B");
t1.start();
t2.start();
}
}
线程插队:
当在某个线程中调用其它线程的join()方法时,调用的线程将被阻塞,直到被join()方法加入的线程执行完成后才会继续运行.
package org.zl.Test;
class EmergencyThread implements Runnable {
public void run() {
for (int i=1; i<6; i++) {
System.out.println(Thread.currentThread().getName()+"输入:"+i);
try {
//线程休眠500毫秒
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class Test06 {
public static void main(String[] args) throws Exception {
//创建线程
Thread t = new Thread(new EmergencyThread(), "线程一");
t.start();
for (int i=1; i<6; i++) {
System.out.println(Thread.currentThread().getName()+"输入:"+i);
if (i == 2) {
//调用join()方法
t.join();
}
//线程休眠500毫秒
Thread.sleep(500);
}
}
}
多线程同步:
线程安全:
当多个线程去访问同一个资源时,会引发一些安全问题.为了解决这样的问题,需要实现多线程的同步,即限制某个资源在同一时刻只能被一个线程访问.
public class Test02 {
public static void main(String[] args) {
SaleThread saleThread = new SaleThread();
new Thread(saleThread,"线程一").start();
new Thread(saleThread,"线程二").start();
new Thread(saleThread,"线程三").start();
new Thread(saleThread,"线程四").start();
}
}
class SaleThread implements Runnable {
private int i = 10;
public void run() {
while (i>0) {
try {
//经过此处的线程休眠10毫秒
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖出的票"+i--);
}
}
}
在售票程序的while循环中添加了sleep()方法,导致结果出现了负号。要想解决这个问题请看下面同步代码块
同步代码块:
了解到线程安全问题其实就是由多个线程同时处理共享资源所导致的。要想解决上例线程的安全问题,必须得保证下面用于处理共享资源代码
在任何时刻只能有一个线程访问
当多个线程使用同一个共享资源时,可以将处理共享资源的代码放置在一个代码块中,使用synchronized关键字来修饰,被称作同步代码块
package org.zl.Test;
public class Test02 {
public static void main(String[] args) {
Ticketl ticketl = new Ticketl();
new Thread(ticketl,"线程一").start();
new Thread(ticketl,"线程二").start();
new Thread(ticketl,"线程三").start();
new Thread(ticketl,"线程四").start();
}
}
class Ticketl implements Runnable {
private int i = 10;
Object lock = new Object();
public void run() {
while (i>0) {
//定义同步代码块
synchronized (lock) {
try {
//经过此处的线程休眠10毫秒
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (i > 0) {
System.out.println(Thread.currentThread().getName()+"卖出的票"+i--);
}else{
break;
}
}
}
}
}
注意:线程在获得锁对象时有一定的随机性。
同步代码块中的锁对象可以是任意类型的对象,但多个线程共享的锁对象必须是唯一的。“任意”说的是共享锁对象的类型。所以,锁对象的创建代码不能放到run方法中,否则每个线程运行到run()方法都会创建一个新对象,这样每个线程都会有一个不同的锁,每个锁都有自己的标志位。线程之间便不能产生同步的效果。
同步方法:
当把共享资源的操作放在synchronized定义的区域内时,便为这些操作加了同步锁。在方法前面同样可以使用synchronzed关键字来修饰,被修饰的方法为同步方法,它能实现和同步代码块同样的功能。
语法:
Synchronized 返回值类型 方法名( [参数 1,……….] ) { }
被synchronized修饰的方法在某一时刻只允许一个线程访问,访问该方法的其他线程都会发生阻塞,直到当前线程访问完毕后,其他线程才有机会执行方法。
package org.zl.Test;
class Ticket1 implements Runnable {
private int i = 10;
public void run() {
while (true) {
//调用 售票方法
saleTicket();
if (i <= 0) {
break;
}
}
}
//定义一个同步方法saleTicket()
private synchronized void saleTicket() {
// TODO Auto-generated method stub
if (i > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"----卖出的票"+i--);
}
}
}
public class Test07 {
public static void main(String[] args) throws Exception {
//创建线程
Ticket1 t = new Ticket1();
new Thread(t, "线程一").start();
new Thread(t, "线程二").start();
new Thread(t, "线程三").start();
new Thread(t, "线程四").start();
}
}
死锁问题:
两个线程在运行时都在等待对方的锁,这样便造成了程序的停滞,这种现象称为死锁。
package org.zl.Test;
class DeadLockThread implements Runnable {
//定义Object类型的chopsticks锁对象
static Object chopsticks = new Object();
//定义Object类型的chopsticks锁对象
static Object knifeAndFork = new Object();
//定义boolean类型的变量flag;
private boolean flag;
//定义有参的构造方法
DeadLockThread(boolean flag){
this.flag = flag;
}
public void run() {
//调用 售票方法
if (flag) {
while (true) {
//chopsticks锁对象上的同步代码块
synchronized (chopsticks) {
System.out.println(Thread.currentThread().getName()+"---if---chopsticks");
}
//knifeAndFork锁对象上的同步代码块
synchronized (knifeAndFork) {
System.out.println(Thread.currentThread().getName()+"---if---knifeAndFork");
}
}
}else {
while(true) {
//chopsticks锁对象上的同步代码块
synchronized (chopsticks) {
System.out.println(Thread.currentThread().getName()+"---else---chopsticks");
}
//knifeAndFork锁对象上的同步代码块
synchronized (knifeAndFork) {
System.out.println(Thread.currentThread().getName()+"---else---knifeAndFork");
}
}
}
}
}
public class Test08 {
public static void main(String[] args) throws Exception {
DeadLockThread d1 = new DeadLockThread(true);
DeadLockThread d2 = new DeadLockThread(false);
new Thread(d1, "Chinese").start();
new Thread(d2, "American").start();
}
}
多线程通信:
在多线程的程序中,上下工序可以看作两个线程,这两个线程之间需要协同完成工作,就需要线程之间进行通信。
package org.zl.Test;
/**
* 输入线程类
* @author zl
*
*/
public class Input implements Runnable {
private Storage st;
private int num;
public Input(Storage st) {
// TODO Auto-generated constructor stub
this.st = st;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
//将num存入数组,每次存入后num自增
st.put(num++);
}
}
}
/**
* 输出线程类
* @author zl
*
*/
class Output implements Runnable {
private Storage st;
public Output(Storage st) {
// TODO Auto-generated constructor stub
this.st = st;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
//循环取出元素
st.get();
}
}
}
public class Storage {
//数据存储数组
private int[] cells = new int[10];
//inPos表示存入时数组下标,outPos表示取出时数组下标
private int inPos, outPos;
//定义一个put()方法向数组中存入数据
public void put(int num){
cells[inPos] = num;
System.out.println("在cells["+inPos+"]中放入数据----"+cells[inPos]);
//存完元素让位置加1
inPos++;
if (inPos == cells.length) {
//当inPos为数组长度时,将其置为0
inPos = 0;
}
}
//定义一个get()方法从数组中取出数据
public void get() {
int data = cells[outPos];
System.out.println("从cells["+outPos+"]中取出数据----"+data);
//取完元素让位置加1
outPos++;
if (outPos == cells.length){
outPos = 0;
}
}
}
public class InputStorageTest {
public static void main(String[] args) {
Storage st = new Storage();
Input input = new Input(st);
Output output = new Output(st);
new Thread(input).start();
new Thread(output).start();
}
}
运行出现取出的数据不是连续,出现断层!
解决上面问题
需要控制多个线程按照一定的顺序轮流执行,此时需要让线程间进行通信
唤醒线程的方法:
方法声明
功能描述
Void wait()
使当前线程放弃同步锁并进入等待,直到其他线程进入此同步锁,并调用notify()方法,或notifyAll()方法唤醒该线程为止。
Void notify()
唤醒此同步锁上等待的第一个调用wait()方法的线程。
Void notifyAll()
唤醒此同步锁上调用wait()方法的所有线程
注意:由于Java中所有类都是Object类的子类或间接子类,因此任何类的实例对象都可以直接使用这些方法。
package org.zl.Test;
public class Storage {
//数据存储数组
private int[] cells = new int[10];
//inPos表示存入时数组下标,outPos表示取出时数组下标
private int inPos, outPos;
//存入或者取出数据的数量
public int count;
//定义一个put()方法向数组中存入数据
public synchronized void put(int num){
try{
//如果放入数据等于cells的长度,此线程等待
while (count == cells.length) {
this.wait();
}
cells[inPos] = num;
System.out.println("在cells["+inPos+"]中放入数据----"+cells[inPos]);
//存完元素让位置加1
inPos++;
if (inPos == cells.length) {
//当inPos为数组长度时,将其置为0
inPos = 0;
}
count++;
this.notify();
}catch (Exception e) {
e.printStackTrace();
}
}
//定义一个get()方法从数组中取出数据
public synchronized void get() {
try{
//如果count为0,此线程等待
while (count == 0) {
this.wait();
}
int data = cells[outPos];
System.out.println("从cells["+outPos+"]中取出数据----"+data);
//取出后,当前位置的数据置0
cells[outPos] = 0;
//取完元素让位置加1
outPos++;
if (outPos == cells.length){
outPos = 0;
}
//每取出一个元素count减1
count--;
this.notify();
}catch (Exception e) {
e.printStackTrace();
}
}
}