--------------------- android培训、java培训、java学习型技术博客、期待与您交流! -------------------
多线程
一概述
1进程:正在进行中的程序。其实进程就是一个应用程序运行时的内存分配空间。
2线程:其实就是进程中一个程序执行控制单元,一条执行路径。进程负责的是应用程序的空间的标示。线程负责的是应用程序的执行顺序。
3一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是多线程应用程序,每个线程在栈区中都有自己的执行空间,自己的方法区、自己的变量。
Jvm就是一个多线程程序,在启动时,首先有一个主线程,负责程序的执行,调用的是main函数。主线程执行的代码都在main方法中。当产生垃圾时,收垃圾的动作,是不需要主线程来完成,因为这样,会出现主线程中的代码执行会停止,效率较低,所以由另外单独一个线程来负责垃圾回收。
3多线程的利弊
好处
多线程的存在解决了部分代码同时运行的问题,比如在网络编程中当多个客服端连接到服务端时,服务端需要同时处理这些客服端,不能一个处理完再处理另一个,这时利用多线程就可以进行同时处理
弊端
线程太多会导致效率降低,因为多线程的执行依靠的cpu的来回切换。
4cpu进行线程处理的特点
随机性:cpu处理哪个线程是随机的,毫无规律可言,哪个线程被CPU执行或者说抢到cpu执行权,哪个线程就可以执行,执行一段时间后,被另外一个线程抢去了执行权又会去执行另外一个线程。
二多线程的创建
多线程创建有两种方式,查阅API可知,在java中用Thread类来描述线程,因此第一种创建方式就是继承Thread类,通过复写Tread类中的run方法将需要多线程执行的代码放入run方法中,之所以需要将代码放入run方法中,是因为在Tread类中,描述线程时,就定义了一个方法,用于存放线程执行的代码,该方法就是run方法,所以要建立线程时需要将线程执行代码放入run方法中。
多线程创建步骤
1定义类继承Thread类成为Thread子类
2复写run方法,将需要多线程执行的代码放入run方法
3创建Thread子类,这时已经创建了一个线程
4调用start方法开启线程
代码示例
/*
创建两线程,和主线程交替运行。
*/
.//创建线程Test
class Test extends Thread
{
// private String name;
Test(String name)
{
super(name);
// this.name=name;
}
//复写run方法
public void run()
{
for(int x=0;x<60;x++)
System.out.println(Thread.currentThread().getName()+"..run..."+x);
// System.out.println(this.getName()+"..run..."+x);
}
}
class ThreadTest
{
public static void main(String[] args)
{
new Test("one+++").start();//开启一个线程
new Test("tow———").start();//开启第二线程
//主线程执行的代码
for(int x=0;x<170;x++)
System.out.println("Hello World!");
}
}
因为cpu的随机性,执行结果会是交替执行的
第二种创建方式:
因为继承的局限性,当该类继承了其他类时就无法再继承Tread类,所以Thread中提供了第二种方式,实现Runtable接口。通过复写接口中的run方法,在将该类在Thread构造时传入。
步骤:
1实现Runable接口,复写run方法。实现Runable是因为Tread类在创建时可以传入一个Runable对象,在传入Runable后,start方法调用的是Runable对象中的run方法,所以这时只要复写Runable中的run方法即可。
2创建该类对象。
3创建Thread对象,将Runable子类对象作为参数传入Thread对象构造函数
4Tread对象调用start方法
代码示例
/*
需求:简单的卖票程序。
多个窗口卖票。
*/
class Ticket implements Runnable//extends Thread
{
private int tick = 100;
public void run()
{
while(true)
{
if(tick>0)
{
//显示线程名及余票数
System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
}
}
}
}
class TicketDemo
{
public static void main(String[] args)
{
//创建Runnable接口子类的实例对象
Ticket t = new Ticket();
//有多个窗口在同时卖票,这里用四个线程表示
Thread t1 = new Thread(t);//创建了一个线程
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();//启动线程
t2.start();
t3.start();
t4.start();
}
}
注意:
run方法中存放的只是线程执行的代码,只有当start方法被调用时,start方法去调用run方法,线程才会去执行。
两种方式的区别
继承Thread:线程代码存放在Thread子类的run方法中
实现Runable:线程代码存放在Runable子类的run方法中
因为实现Runable方法避免了继承的局限性,因此实际开发中多用第二种方法创建线程。
三线程的几种状态
新建:new一个Thread对象或者其子类对象就是创建一个线程,当一个线程对象被创建,但是没有开启,这个时候,
只是对象线程对象开辟了内存空间和初始化数据。
准备运行:新建的对象调用start方法,就开启了线程,线程就到了准备运行状态。
在这个状态的线程对象,具有执行资格,没有执行权。
运行:当线程对象获取到了CPU的资源。
在这个状态的线程对象,既有执行资格,也有执行权。
冻结:运行过程中的线程由于某些原因(比如wait,sleep),释放了执行资格和执行权。
当冻结状态解除时,线程就回到了准备运行状态。
死亡:当线程对象调用的run方法结束,或者直接调用stop方法,就让线程对象死亡,在内存中变成垃圾
注意:在线程已经开启时在调用start方法,会产生线程状态异常。
图解
停止线程
stop()方法现已过时,
停止线程的方法就只有一种,就是run方法结束。如何让run方法结束呢?
开启多线程运行,运行代码通常是循环体,只要控制住循环,就可以让run方法结束,也就是结束线程。特殊情况:当线程属于冻结状态,就不会读取循环控制标记,则线程就不会结束。
为解决该特殊情况,可引入Thread类中的Interrupt方法结束线程的冻结状态;
当没有指定的方式让冻结线程恢复到运行状态时,需要对冻结进行清除,强制让线程恢复到运行状态。
sleep()和wait()的区别:
这两个方法来自不同的类,sleep()来Thread类中的特有方法,而wait()继承自Object类。
sleep()释放资源不释放锁,
wait()释放资源释放锁;
线程安全问题
当程序的多条语句在操作线程共享数据时(如买票例子中的票就是共享资源),由于线程的随机性导致一个线程对多条语句,执行了一部分还没执行完,另一个线程抢夺到cpu执行权参与进来执行,此时就导致共享数据发生错误。比如买票例子中打印重票和错票的情况
代码示例
class Ticket implements Runnable//extends Thread
{
private int tick = 100;
public void run()
{
while(true)
{
if(tick>0)
{
try{Thread.sleep(500);}
case(InterruptedException e){e.printStackTrace() }
//显示线程名及余票数
System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
}
}
}
}
class TicketDemo
{
public static void main(String[] args)
{
//创建Runnable接口子类的实例对象
Ticket t = new Ticket();
//有多个窗口在同时卖票,这里用四个线程表示
Thread t1 = new Thread(t);//创建了一个线程
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();//启动线程
t2.start();
t3.start();
t4.start();
}
}
运行结果中出现了0号票和-1号票和-2号票
四同步
线程安全一般情况下不容易出现,但一旦出现后果就是十分严重的。
解决办法-同步
将多个线程操作的代码进行同步,只有当一个线程执行完后,其他线程才可以执行同步中的代码。
锁:锁的作用是保证线程同步,解决线程安全问题。
持有锁的线程可以在同步中执行,没有锁的线程即使获得cpu执行权,也进不去
同步的前提:
必须有多个线程进行操作
必须多个线程使用同一个锁
同步代码块
格式:
synchronized(对象)
{
需要同步的代码
}
同步代码块中传入的对象参数就是同步代码块的锁
代码示例
Class
同步函数:用synchronized修饰的函数就是同步函数
代码示例
根据同步的前提,证明同步中所用的锁,就是本类对象。
静态同步函数:
当同步函数被静态修饰后,因为静态早与对象加载,所以同步函数使用的锁不可能还是本类对象,通过代码验证,静态同步函数所用的锁为字节码对象-类名.class
死锁:
当同步中嵌套同步时,程序可能会发生死锁问题,程序将会卡住,既不继续往下运行,也不停止运行。在开发中要尽量避免同步问题的发生。
死锁代码示例
class LockTest implements Runnable
{
//定义个标记
private boolean flag;
//定义两个锁
Object locka=new Object();
Object locka=new Object();
LockTest(boolean flag)
{
this.flag=flag;
}
public void run()
{
if(flag)
{
while(true)
{
synchronized(Locka)//a锁
{
System.out.println(Thread.currentThread().getName()+"------if_locka");
synchronized(Lockb)//b锁
{
System.out.println(Thread.currentThread().getName()+"------if_lockb");
}
}
}
}
else
{
while(true)
{
synchronized(Lockb)//b锁
{
System.out.println(Thread.currentThread().getName()+"------else_lockb");
synchronized(Locka)//a锁
{
System.out.println(Thread.currentThread().getName()+"------else_locka");
}
}
}
}
}
}
class SiLockDemo
{
public static void main(String[] args)
{
//创建两个线程并启动
New Thread(new LockTest(true)).start();
New Thread(new LockTest(false)).start();
}
}
同步的利弊:
好处:解决了多线程安全问题。
弊端:减低了程序效率。
如何寻找程序中需要被同步的代码
1明确被多线程运行的代码
2明确共享数据
3明确多线程运行代码中操作共享数据的代码
线程间通信:
其实就是多个线程在操作同一个资源,但是操作的动作却不一样。
代码示例
/*
有一个资源
一个线程如果里边没有就往里存东西。
一个线程如果里面有就往里取东西。
*/
//资源类
class Resource
{
private String name;
private String sex;
private boolean flag=false;
public synchronized void setInput(String name,String sex)
{
while(flag)
{
try{wait();}catch(Exception e){}//如果有资源时,等待资源取出
}
this.name=name;
this.sex=sex;
flag=true;//表示有资源
notify();//唤醒等待
}
public synchronized void getOutput()
{
while(!flag)
{
try{wait();}catch(Exception e){}//如果木有资源,等待存入资源
}
System.out.println("name="+name+"---sex="+sex);//这里用打印表示取出
flag=false;//资源已取出
notify();//唤醒等待
}
}
//生产线程
class Input implements Runnable
{
private Resource r;
Input(Resource r)
{
this.r=r;
}
public void run()//复写run方法
{
int x=0;
while(true)
{
if(x==0)//交替打印张三和王羲之
{
r.setInput("生产1代",".....爹");
}
else
{
r.setInput("生产2代","..son");
}
x=(x+1)%2;//控制交替打印
}
}
}
//消费线程
class Output implements Runnable
{
private Resource r;
Output(Resource r)
{
this.r=r;
}
public void run()//复写run方法
{
while(true)
{
r.getOutput();
}
}
}
class ResourceDemo2
{
public static void main(String[] args)
{
Resource r = new Resource();//表示操作的是同一个资源
new Thread(new Input(r)).start();//开启生产者线程
new Thread(new Output(r)).start();//开启消费者线程
}
}
线程活锁
线程活锁虽然不像死锁一样是常见的问题,但是对于并发编程的设计者来说就像一次邂逅一样。当所有线程阻塞,或者由于需要的资源无效而不能处理,不存在非阻塞线程使资源可用。
JavaAPI 中线程活锁可能发生在以下情形:
当所有线程在程序中执行 Object.wait (0),参数为 0 的 wait 方法。程序将发生活锁直到在相应的对象上有线程调用 Object.notify ()或者 Object.notifyAll ()。
当所有线程卡在无限循环中。
上面示例代码,当开启多个生产者线程或多个消费者线程时就会发生活锁。需要将代码中的notify方法改为notifyAll。
Jdk1.5之后出现了Lock对象替代同步,Coundition替代了wait notify notifyAll等方法
Coundition可以通过Lock对象获得,且Lock可以对应多个Coundition。
代码示例
/* 生产者生产商品,供消费者使用
有两个或者多个生产者,生产一次就等待消费一次
有两个或者多个消费者,等待生产者生产一次就消费掉
*/
import java.util.concurrent.locks.*;
class Resource
{
private String name;
private int count=1;
private boolean flag = false;
//多态
private Lock lock=new ReentrantLock();
//创建两Condition对象,分别来控制等待或唤醒本方和对方线程
Condition condition_pro=lock.newCondition();
Condition condition_con=lock.newCondition();
//p1、p2共享此方法
public void setProducer(String name)throws InterruptedException
{
lock.lock();//锁
try
{
while(flag)//重复判断标识,确认是否生产
condition_pro.await();//本方等待
this.name=name+"......"+count++;//生产
System.out.println(Thread.currentThread().getName()+"...生产..."+this.name);//打印生产
flag=true;//控制生产\消费标识
condition_con.signal();//唤醒对方
}
finally
{
lock.unlock();//解锁,这个动作一定执行
}
}
//c1、c2共享此方法
public void getConsumer()throws InterruptedException
{
lock.lock();
try
{
while(!flag)//重复判断标识,确认是否可以消费
condition_con.await();
System.out.println(Thread.currentThread().getName()+".消费."+this.name);//打印消费
flag=false;//控制生产\消费标识
condition_pro.signal();
}
finally
{
lock.unlock();
}
}
}
//生产者线程
class Producer implements Runnable
{
private Resource res;
Producer(Resource res)
{
this.res=res;
}
//复写run方法
public void run()
{
while(true)
{
try
{
res.setProducer("商品");
}
catch (InterruptedException e)
{
}
}
}
}
//消费者线程
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res=res;
}
//复写run
public void run()
{
while(true)
{
try
{
res.getConsumer();
}
catch (InterruptedException e)
{
}
}
}
}
class ProducerConsumer
{
public static void main(String[] args)
{
Resource res=new Resource();
new Thread(new Producer(res)).start();//第一个生产线程 p1
new Thread(new Consumer(res)).start();//第一个消费线程 c1
new Thread(new Producer(res)).start();//第二个生产线程 p2
new Thread(new Consumer(res)).start();//第二个消费线程 c2
}
}
Thread中的一些其他方法
join()方法
概述:当A线程执行到了B线程的.join()方法时,A线程就会等待,等B线程都执行完,A线程才会执行。(此时B和其他线程交替运行。)
作用:join可以用来临时加入线程执行。
代码示例
class JoinDemo
{
public static void main(String[] args) throws Exception
{
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
// t2.setPriority(Thread.MAX_PRIORITY);
// t1.join();//t1线程要申请加入进来,运行。临时加入一个线程运算时可以使用join方法。
for(int x=0; x<50; x++)
{
// System.out.println(Thread.currentThread()+"....."+x);
}
}
}
setDaemon()方法
概述:将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。又称后台程序
setPriorty()
概述:用于更改线程的优先级,从1-10,优先级越高,cpu执行的概率越大。
Thread中的静态成员变量:
MAX_PRIORITY 最高优先级10
MIN_PRIORITY 最低优先级1
NORM_PRIORITY系统默认优先级5
Yield()方法
概述:暂停当前正在执行的线程对象,并执行其他线程。
作用:可以使两个线程交替执行。
--------------------- android培训、java培训、java学习型技术博客、期待与您交流! -------------------
本文详细介绍了Java中多线程的基本概念、创建方法及其状态,包括进程与线程的区别、多线程的创建方式、线程的状态转换等内容,并通过具体示例讲解了线程安全问题与解决方案。

774

被折叠的 条评论
为什么被折叠?



