---------------------- ASP.Net+Android+IO开发S、.Net培训、期待与您交流! ----------------------
(一)多线程概述进程:是一个正在执行中的程序。
每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
线程:就是进程中的一个独立的控制单元。
线程在控制着进程的执行。
一个进程中至少有一个线程。
Java VM启动的时候会有一个进程java.exe,该进程中至少有一个线程负责java程序的执行。而且这个线程的运行方法在main方法中。该线程称之为主线程。
扩展:其实更细节说明JVM启动不止一个线程,还有负责垃圾回收机制的线程。
多线程存在的意义:1.产生同时运行效果,提高效率 2.清除内存垃圾,解决内存不足。
(二)自定义线程
如何在自定义的代码中,自定义一个线程呢?线程存在于进程之中,而进程是系统创建的,JVM依赖并调用系统中的内容,就能完成执行动作。我们通过对api的查找,java已经为提供了对线程这类事物的描述。就是Thread类。创建线程有两种方法:
第一种方式:继承Thread类。
步骤:1.定义类继承Thread;
2.复写Thread类中的run方法。目的:将自定义的代码存储在run方法中,让线程运行。
3.调用线程的start方法,该方法有两个作用:启动线程和调用run方法。
/*创建两个线程,与主线程交替运行*/
class Demo extends Thread
{
private String name;
Demo(String name)//构造函数
{
//this.name = name;
super(name);
}
public void run()//复写run方法
{
for ( int i = 0 ; i < 60 ; i++ )
{
//System.out.println(name+"run="+i);
//System.out.println(this.getName()+"run="+i);
System.out.println(Thread.currentThread().getName()+"run="+i);
}
}
}
class ThreadDemo
{
public static void main(String[] args)
{
Demo t1 = new Demo("one");//创建一个对象t1
Demo t2 = new Demo("two");
t1.start();// 通过调用start方法,开启多线程
t2.start();
run();
}
public static void run()
{
for ( int i = 0 ; i < 60 ; i++ )
{
System.out.println("main="+i);
}
}
}
程序运行特点:
随即性:发现运行结果每次都不同。因为多个线程都获取cpu的执行权。我们可以把多线程的运行形象为抢夺cpu的资源,
这就是多线程的一个特点:随即性。谁抢到谁执行,至于执行多长,cpu说了算。
并发性:Cpu在做着快速的切换,以达到看上去是同时运行的效果。明确一点,在某一时刻,只能有一个程序在运行(多核除外)。
为什么要覆盖run方法呢?
这是因为Thread类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run 方法。
也就是说Thread 类中的run方法用于存储线程要运行的代码。
t1.start()开启线程并执行该线程的run方法。
t1.run()仅仅是对象调用方法。而线程创建了,并没有运行。还是在主线程的运行中。
线程运行流程图:
Thread类内部有个public的枚举Thread.State,里边将线程的状态分为:
NEW-------新建状态,至今尚未启动的线程处于这种状态。
RUNNABLE-------运行状态,正在 Java 虚拟机中执行的线程处于这种状态。
BLOCKED-------阻塞状态,受阻塞并等待某个监视器锁的线程处于这种状态。
WAITING-------冻结状态,无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
TIMED_WAITING-------等待状态,等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
TERMINATED------已退出的线程处于这种状态。
线程有自己默认的名称:Thread-编号,编号从零开始。
static Thread currentThread():获取当前线程的对象。
getName():获取线程的名称。
设置线程的名称:setName或者构造函数。
线程的第二种创建方式:声明实现Runnable接口类。
步骤:1.定义类实现Runnable接口。
2.覆盖Runnable接口中的run方法。将线程要运行的代码存放在该run方法中。
3.通过Thread类建立线程对象。
4.将Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数。
为什么要将Runnable接口的子类对象传递给Thread类的构造函数呢? 这是因为自定义的run方法所属的对象是Runnable接口的子类对象。
所以,要让线程去指定指定对象的run方法,就必须明确该run方法所属的对象。
5.调用Thread类中的start方法开启线程并调用Runnable接口子类的run方法。
/*
通过程序验证。
使用两个线程来买票。
一个线程在同步代码块中,‘
一个线程在同步函数中,
都执行买票动作
*/
class Ticket implements Runnable
{
private int ticket = 100;
Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag)
{
while(true)
{
synchronized(this) // this指向之后,根据运行结果,表示同步代码块跟同步函数是同一个锁。
{
if (ticket > 0)
{
try
{
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"runs="+ticket--);
}
catch (Exception e)
{
}
}
}
}
}
else
{
show();
}
}
public synchronized void show()//this
{
while(true)
if (ticket > 0)
{
try
{
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"show="+ticket--);
}
catch (Exception e)
{
}
}
}
}
class TicketDemo
{
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 (Exception e)
{
}
t.flag = false;
t2.start();
}
}
实现方式和继承方式的区别:
实现方式的好处:避免了单继承的局限性。
定义线程时,建议使用实现方式。
继承Thread:线程代码存放在Thread子类run方法中,实现Runnable:线程代码存放在接口的子类run方法中。
多线程的安全问题。
问题原因:当多条语句操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来,导致共享数据的错误。
解决办法就是:对多条操作共享数据的语句,只能让一个线程都执行完。在实行过程中,其他线程不能执行。
java对于多线程的安全问题提供了专业的方式:同步代码块。
synchronized (对象)
{
需要被同步的代码;//操作共享数据的代码
}
对象如同锁,持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
同步书写的前提:
1.必须要有两个或两个以上的线程。
2.必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程在运行。
好处:解决了多线程的安全问题。
弊端:多了一步锁的判断,消耗了更多的资源。
同步有两种方式:同步代码块,同步函数。
让函数具备同步性,只需在返回值修饰符前加:synchronized修饰符。
同步函数用哪一个锁呢?
函数需要被对象调用。那么函数都有一个所属对象引用。就是this。
所以同步函数使用的对象是this。
如果同步函数被静态修饰后,使用的锁是什么呢?
通过验证,发现不是this。因为静态方法中也不可以定义this。
静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。
类.class;该对象的类型是class
练习:
/*需求:
银行有一个金库,有两个储户分别存300元,每次存一百,存三次
如何找多线程操作是否有安全问题:
1.明确哪些代码是多线程运行代码
2.明确共享数据
3.明确多线程运行代码中哪些语句是操作共享数据的。
*/
class Bank
{
private int sum ;//共享数据
Object obj = new Object();
//public void add(int n)
public synchronized void add(int n)//同步函数
{
//synchronized(obj)//同步代码块
// {
try
{
Thread.sleep(10);
sum = sum + n;
System.out.println("sum ="+sum);
}
catch (Exception e)
{
}
// }
}
}
class Cus implements Runnable
{
Bank b = new Bank();//共享数据
public void run()
{
for ( int i = 0; i < 3 ; i++)
{
b.add(100);
}
}
}
class BankDemo
{
public static void main(String[] args)
{
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
线程间通信
1.线程间通信:
2.等待唤醒机制:
wait(),notify(),notify All(),都用在同步中,因为要对是有监视器(锁)的线程操作。都要使用在同步中,因为只有同步才具有锁。
为什么这些操作线程的方法要定义Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识他们所操作线程只有的锁。只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。不可以对不同锁中的线程进行唤醒。也就是说,等待和唤醒必须是同一锁。
而锁可以是任意对象,所以可以被任意对象调用的方法定义在Obect类中。
wait(),sleep()的区别:
wait():释放cpu执行权,释放锁。
sleep():释放cpu执行权,不释放锁。
停止线程
1. 定义循环结束标记 因为线程运行代码一般都是循环,只要控制了循环即可
2. 使用interrupt(中断)方法。该方法是结束线程的冻结状态,使线程回到运行状态中来。
注:stop方法已经过时不再使用。
如何停止线程?只有一种,run方法结束。开启多线程运行,运行代码通常都是循环结构。
只要控制住循环,就可以让run方法结束,也就是线程结束。
特殊情况:当线程处于冻结状态,就不会读取到标记,那么线程就不会结束。
当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结状态进行清除。强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。
Thread类中提供该方法interrupt();
JDK1.5中提供了多线程的升级解决方案:将同步synchronized替换成Lock操作;将Objec(监视器对象)的wait,notify,notifyAll,替换成Condition对象的相应操作,Condition对象可以通过Lock锁进行获取。这是一种显式的锁机制和显式的锁对象上的等待唤醒操作机制,它把等待唤醒动作封装成了Condition类对象,让一个锁可以对应多个Condition对象(监视器对象),即一个锁可以对应多组wait-notify。
class ProducerConsumerDemo
{
public static void main(String[] args)
{
Resource1 res = new Resource1();
Producer1 pro = new Producer1(res);
Consumer1 con = new Consumer1(res);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
class Resource1
{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name)
{
while(flag)//if(flag)
try{wait();}catch(Exception e){}
this.name = name + "--"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true;
this.notifyAll();
}
public synchronized void out()
{
while(!flag)//if(!flag)
try{wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);
flag =false;
this.notifyAll();
}
}
class Producer1 implements Runnable
{
private Resource1 res;
Producer1 (Resource1 res)
{
this.res = res;
}
public void run()
{
while(true)
{
res.set("商品");
}
}
}
class Consumer1 implements Runnable
{
private Resource1 res;
Consumer1(Resource1 res)
{
this.res = res;
}
public void run()
{
while(true)
{
res.out();
}
}
}
package com.it;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionDemo {
private final Lock lock = new ReentrantLock();
private final Condition isFull = lock.newCondition();
private final Condition isEmtry = lock.newCondition();
private String[] data = new String[10];
private int len, getStr,putStr;
private StringBuilder buff = new StringBuilder();
public void putData(){
lock.lock();
System.out.println("正在填充数据...");
try{
while(putStr<=data.length){
if(putStr==data.length){
putStr = 0;
break;
}
data[putStr] = String.valueOf(new Random().nextInt(100));
System.out.println("data["+putStr+"]="+data[putStr]);
++putStr;
++len;
}
if(len!=data.length){
this.putData();
}
else{
System.out.println(Thread.currentThread().getName()+" 填充完毕...");
putStr=buff.length();
try{
isFull.await();
}catch(InterruptedException e){
System.err.println(e.toString());
}
}
isEmtry.signal();
}
finally{
lock.unlock();
}
}
public void getData(){
lock.lock();
System.out.println("正在截获信息...");
try{
while(getStr<=data.length){
if(getStr==data.length){
getStr = 0;
break;
}
String info = data[getStr];
buff.append(info+",");
++getStr;
--len;
}
if(len==0){
System.out.println(Thread.currentThread().getName()+"截获数据完毕...");
System.out.println(buff.toString());
try {
isEmtry.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
this.getData();
}
isFull.signal();
}finally{
lock.unlock();
}
}
public static void main(String[] args) {
final ConditionDemo condition = new ConditionDemo();
// final CountDownLatch latch = new CountDownLatch(1);
for(int i=0;i<10;i++){
new Thread(new Runnable(){
@Override
public void run(){
condition.putData();
// latch.countDown();
}
}).start();
}
for(int i=0;i<100;i++){
new Thread(new Runnable(){
@Override
public void run(){
try{
Thread.sleep((long)Math.random()*10000);
condition.getData();
}catch(InterruptedException e){
System.err.println(e.toString());
}
}
}).start();
}
}
}
---------------------- ASP.Net+Android+IO开发S、.Net培训、期待与您交流! ----------------------